DistanceTransformOp.h
1 /*
2 
3 MIT License
4 
5 Copyright (c) 2017 FMI Open Development / Markus Peura, first.last@fmi.fi
6 
7 Permission is hereby granted, free of charge, to any person obtaining a copy
8 of this software and associated documentation files (the "Software"), to deal
9 in the Software without restriction, including without limitation the rights
10 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 copies of the Software, and to permit persons to whom the Software is
12 furnished to do so, subject to the following conditions:
13 
14 The above copyright notice and this permission notice shall be included in all
15 copies or substantial portions of the Software.
16 
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 SOFTWARE.
24 
25 */
26 /*
27 Part of Rack development has been done in the BALTRAD projects part-financed
28 by the European Union (European Regional Development Fund and European
29 Neighbourhood Partnership Instrument, Baltic Sea Region Programme 2007-2013)
30 */
31 #ifndef DISTANCETRANSFORMOP_H_
32 #define DISTANCETRANSFORMOP_H_
33 
34 
35 
36 #include <math.h>
37 
38 #include "drain/image/CoordinateHandler.h"
39 #include "drain/image/DistanceModelLinear.h"
40 #include "drain/image/DistanceModelExponential.h"
41 
42 #include "ImageOp.h"
43 
44 
45 namespace drain
46 {
47 
48 namespace image
49 {
50 
51 
52 
56 template <class T>
58 {
59 
60 public:
61 
62 
63  typedef float dist_t;
64 
65  virtual ~DistanceTransformOp(){};
66 
67  inline
68  void setRadius(dist_t width, dist_t height=DistanceModel::nan_f, dist_t width2=DistanceModel::nan_f, dist_t height2=DistanceModel::nan_f){
69  distanceModel.setRadius(width, height, width2, height2);
70  };
71 
72 
73 
74  virtual
75  void traverseChannels(const ImageTray<const Channel> & src, ImageTray<Channel> & dst) const {
76  this->traverseChannelsSeparately(src, dst);
77  };
78 
79  virtual
80  void traverseChannel(const Channel & src, Channel &dst) const ;
81 
83  virtual
84  inline
85  void traverseChannel(const Channel &src, const Channel &srcAlpha, Channel &dst, Channel &dstAlpha) const {
86  drain::Logger mout(getImgLog(), __FILE__, __FUNCTION__);
87  mout.note("discarding alpha channels, redirecting to traverseChannel(src, dst)");
88  traverseChannel(src, dst);
89  };
90 
92  /*
93  */
94  virtual inline
95  void getDstConf(const ImageConf &src, ImageConf & dst) const {
96 
97  dst.setGeometry(src.getGeometry());
98 
99  if (src.isPhysical()){
100  dst.setPhysicalRange(src.getPhysicalRange(), true);
101  }
102  // dst.setPhysicalRange(0.0, 1.0);
103 
104  dst.coordinatePolicy.set(src.coordinatePolicy);
105 
106 
107  }
108 
109  /*
110  virtual // TODO: non-virtual, ie, final!
111  void makeCompatible(const ImageConf & src, Image & dst) const {
112  drain::Logger mout(getImgLog(), __FILE__, __FUNCTION__);
113  mout.warn("derived " );
114  ImageOp::makeCompatible(src, dst);
115  }
116  */
117 
118  /*
119  virtual
120  inline
121  void make Compatible(const ImageFrame &src, Image &dst) const {
122 
123  drain::Logger mout(getImgLog(), __FILE__, __FUNCTION__);
124  mout.debug3("src: " , src );
125 
126  //if (!dst.typeIsSet())
127  //dst.setType(src.getType());
128  if (dst.getType() != src.getType()){
129  mout.note(" changing dst image type: " , dst.getType().name() , '>' , src.getType().name() );
130  }
131 
132  dst.copyShallow(src);
133  // dst.initialize(src.getType(), src.getGeometry());
134  // dst.adoptScaling(src);
135 
136  mout .debug3() << "dst: " << dst << mout.endl;
137 
138  };
139  */
140 
141  // Debugging
142 
143  virtual
144  const DistanceModel & getDistanceModel() const {
145  return distanceModel;
146  };
147 
148  virtual
149  DistanceModel & getDistanceModel() {
150  return distanceModel;
151  };
152 
153 
154 protected:
155 
156  mutable T distanceModel;
157 
158  //int topology;
159 
160  DistanceTransformOp(const std::string &name, const std::string &description, float width, float height,
161  //DistanceTransformOp(const std::string &name, float width, float height,
162  DistanceModel::topol_t topology=DistanceModel::KNIGHT) : // PIX_ADJACENCY_
163  ImageOp(name, description) { // TODO: from model
164  // drain::Logger mout(getImgLog(), __FILE__, __FUNCTION__);
165  // mout.attention("model:", distanceModel);
166  parameters.append(distanceModel.getParameters());
167  distanceModel.setTopology(topology);
168  // mout.attention("width:", width, ", height:", height);
169  //distanceModel.setRadius(width, height, width, height);
170  distanceModel.setRadiusVerbatim(width, height, width, height); // NEW 2024: does not "expand" defaults.
171  // mout.attention("model:", distanceModel);
172  };
173 
174  DistanceTransformOp(const DistanceTransformOp & op) : ImageOp(op) {
175  parameters.append(this->distanceModel.getParameters());
176  //setParameters(op.getParameters());
177  this->distanceModel.setParameters(op.distanceModel.getParameters());
178  this->distanceModel.update(); // Important: handles nan_f's. Is it desired?
179  };
180 
181  // Extend default range (of src) when coord policy is WRAP
182  Range<int> getHorzRange(const CoordinateHandler2D & coordinateHandler) const ;
183 
184  // Extend default range (of src) when coord policy is WRAP
185  Range<int> getVertRange(const CoordinateHandler2D & coordinateHandler) const;
186 
187 
189 
192  virtual
193  void initializeParameters(const ImageFrame &src, const ImageFrame &dst) const {
194 
195  drain::Logger mout(getImgLog(), __FILE__, __FUNCTION__);
196 
197  // WAS: src (all) // .getEncoding()
198  const double physMax = dst.getConf().requestPhysicalMax(100.0);
199  const double codeMax = dst.getScaling().inv(physMax);
200 
201  mout.debug("dst of type=", Type::getTypeChar(dst.getType()), ", scaling: ", dst.getScaling());
202 
203  if (dst.getScaling().isPhysical()){
204  mout.info("ok, physical scaling [", dst.getConf().getPhysicalRange(), "] : ");
205  }
206  else if (Type::call<typeIsSmallInt>(dst.getType())){
207  mout.note("no physical scaling, but small int, guessing: ");
208  }
209  else {
210  mout.warn("no physical scaling, no small int: default ");
211  }
212  mout.note("physMax=", physMax, " => ", codeMax);
213 
214  distanceModel.setMax(codeMax);
215  // distModel.setMax(dst.getMax<double>()); // why not dst
216  distanceModel.update(); // radii
217  }
218 
219 
220 
221  void traverseDownRight(const Channel & src, Channel & dst) const ;
222 
223  void traverseUpLeft(const Channel & src, Channel & dst) const ;
224 
225 
226 
227 };
228 
229 template <class T>
231 
232  Range<int> xRange = coordinateHandler.getXRange();
233  const Bidirectional<float> & radiusHorz = getDistanceModel().getRadiusHorz();
234 
235  if (coordinateHandler.policy.xUnderFlowPolicy == EdgePolicy::WRAP)
236  xRange.min -= radiusHorz.backward;
237 
238  if (coordinateHandler.policy.xOverFlowPolicy == EdgePolicy::WRAP)
239  xRange.max += radiusHorz.forward;
240 
241  return xRange;
242 }
243 
244 template <class T>
246 
247  Logger mout(getImgLog(), __FILE__, __FUNCTION__);
248 
249  Range<int> yRange = coordinateHandler.getYRange();
250 
251  //mout.warn("yRange: " , yRange );
252 
253  const Bidirectional<float> & radiusVert = getDistanceModel().getRadiusVert();
254 
255  // mout.warn("radiusVert: " , radiusVert );
256 
257 
258  if (coordinateHandler.policy.yUnderFlowPolicy == EdgePolicy::WRAP)
259  yRange.min -= radiusVert.backward;
260 
261  if (coordinateHandler.policy.yOverFlowPolicy == EdgePolicy::WRAP)
262  yRange.max += radiusVert.forward;
263 
264  // mout.warn("yRange: " , yRange );
265 
266  return yRange;
267 
268 }
269 
270 template <class T>
272 {
273 
274  Logger mout(getImgLog(), __FILE__, __FUNCTION__);
275 
276  //mout.warn("start" );
277  mout.debug("model max: ", getDistanceModel().getMax());
278 
279  //File::write(dst,"DistanceTransformOp-dst0.png");
280  traverseDownRight(src,dst);
281  //File::write(dst,"DistanceTransformOp-dst1.png");
282  traverseUpLeft(dst,dst);
283  //File::write(dst,"DistanceTransformOp-dst2.png");
284 
285 }
286 
287 
288 template <class T>
289 void DistanceTransformOp<T>::traverseDownRight(const Channel &src, Channel &dst) const
290 {
291 
292  Logger mout(getImgLog(), __FILE__, __FUNCTION__);
293  //mout.debug("start" );
294 
295  //const DistanceModel & distModel = getDistanceModel();
296  mout.debug2("distModel:", this->distanceModel);
297 
298  DistanceNeighbourhood chain;
299  this->distanceModel.createChain(chain, true);
300 
301  mout.debug2("coordPolicy: ", src.getCoordinatePolicy());
302 
303  CoordinateHandler2D coordinateHandler(src); // TODO: change param to ImageConf
304  mout.debug2("coordHandler:", coordinateHandler);
305 
306 
307  const ValueScaling src2dst(dst.getConf().getTypeMax<double>()/src.getConf().getTypeMax<double>(), 0);
308  //const ValueScaling src2dst(src.getScaling(), dst.getScaling());
309 
310  /*
311  for (double d: {0.0, 0.5, 1.0, 255.0, 65535.0}){
312  mout.special(d, " =>\t", src2dst.fwd(d), " inv:", src2dst.inv(d));
313  }
314  */
315 
316  // proximity (inverted distance)
317  dist_t d;
318  dist_t dTest;
319 
320  // Point in the source image
321  Point2D<int> pTest;
322 
323  // Point in the target image
324  Point2D<int> p;
325  Point2D<int> pSafe;
326 
327  Range<int> xRange = getHorzRange(coordinateHandler);
328  Range<int> yRange = getVertRange(coordinateHandler);
329  mout.debug2(xRange, " - ", yRange);
330 
331  // Experimental (element horz/vert topology not yet implemented)
332  // Possibly wrong... not interchangible due to to scanning element geometry?
333  /*
334  bool HORZ = true;
335  const Range<int> & inner = HORZ ? xRange : yRange;
336  const Range<int> & outer = HORZ ? yRange : xRange;
337  int & innerValue = HORZ ? p.x : p.y;
338  int & outerValue = HORZ ? p.y : p.x;
339  mout.debug2("outer range:" , outer );
340  mout.debug2("inner range:" , inner );
341  */
342  //Range<int>
343 
344  // Todo: extended area, needs coordinateHandler.handle(p);?
345  for (p.y=yRange.min; p.y<=yRange.max; ++p.y){
346  for (p.x=xRange.min; p.x<=xRange.max; ++p.x){
347 
348  pSafe.setLocation(p);
349  if (coordinateHandler.handle(pSafe) == CoordinateHandler2D::IRREVERSIBLE)
350  continue;
351 
352  // Take (converted) source value as default
353  d = src2dst.fwd(src.get<dist_t>(pSafe));
354 
355  // Compare to previous value
356  dTest = dst.get<dist_t>(pSafe);
357  if (dTest > d){
358  d = dTest;
359  }
360 
361  for (const DistanceElement & elem: chain){
362  pTest.setLocation(pSafe.x + elem.diff.x, pSafe.y + elem.diff.y);
363  coordinateHandler.handle(pTest);
364  dTest = this->distanceModel.decrease(dst.get<dist_t>(pTest), elem.coeff);
365  if (dTest > d){
366  d = dTest;
367  }
368  }
369 
370  if (d > 0){
371  dst.put(pSafe,d);
372  }
373 
374  };
375  };
376 
377 }
378 
379 
380 
381 template <class T>
383 
384  Logger mout(getImgLog(), __FILE__, __FUNCTION__);
385  mout.debug("start");
386 
387  //const DistanceModel & distModel = getDistanceModel();
388  mout.debug2("distModel:", this->distanceModel);
389 
390  DistanceNeighbourhood chain;
391  this->distanceModel.createChain(chain, false);
392 
393  CoordinateHandler2D coordinateHandler(src);
394  mout.debug2("coordHandler:", coordinateHandler);
395 
396  //const ValueScaling src2dst(src.scaling.scale, src.scaling.offset, dst.scaling.scale, dst.scaling.offset);
397  // const ValueScaling src2dst(src.getScaling(), dst.getScaling());
398  const ValueScaling src2dst(dst.getConf().getTypeMax<double>()/src.getConf().getTypeMax<double>(), 0);
399 
400  // proximity (inverted distance)
401  dist_t d;
402  dist_t dTest;
403 
405  Point2D<int> pTest(0,0);
406 
407  // Point in the target image
408  Point2D<int> p;
409  Point2D<int> pSafe;
410 
411  //const Range<int> & xRange = coordinateHandler.getXRange();
412  //const Range<int> & yRange = coordinateHandler.getYRange();
413  Range<int> xRange = getHorzRange(coordinateHandler); //coordinateHandler.getXRange();
414  Range<int> yRange = getVertRange(coordinateHandler); // coordinateHandler.getYRange();
415  mout.special(xRange, ',', yRange);
416 
417  for (p.y=yRange.max; p.y>=yRange.min; --p.y){
418  for (p.x=xRange.max; p.x>=xRange.min; --p.x){
419 
420  pSafe.setLocation(p);
421  if (coordinateHandler.handle(pSafe) == CoordinateHandler2D::IRREVERSIBLE)
422  continue;
423 
424  // Take (converted) source value as default
425  d = src2dst.fwd(src.get<dist_t>(pSafe));
426 
427  // Compare to previous value
428  dTest = dst.get<dist_t>(pSafe);
429  if (dTest > d){
430  d = dTest;
431  }
432 
433  for (const DistanceElement & elem: chain){
434  pTest.setLocation(pSafe.x + elem.diff.x, pSafe.y + elem.diff.y);
435  coordinateHandler.handle(pTest);
436  dTest = this->distanceModel.decrease(dst.get<dist_t>(pTest), elem.coeff);
437  if (dTest > d){
438  d = dTest;
439  }
440  }
441 
442  if (d>0)
443  dst.put(pSafe,d);
444 
445  }
446  }
447 
448 
449 }
450 
451 
452 
453 
454 
455 
457 
481 class DistanceTransformLinearOp : public DistanceTransformOp<DistanceModelLinear>
482 {
483 public:
484 
485  inline
486  DistanceTransformLinearOp(float horz = 10.0, float vert = DistanceModel::nan_f, DistanceModel::topol_t topology = DistanceModel::KNIGHT): // PIX_ADJACENCY_
487  DistanceTransformOp<DistanceModelLinear>(__FUNCTION__, "Linearly decreasing intensities - applies decrements.", horz, vert, topology) {
488  };
489 
490 
491 };
492 
512 class DistanceTransformExponentialOp : public DistanceTransformOp<DistanceModelExponential>
513 {
514 public:
515 
517 
520  inline
521  DistanceTransformExponentialOp(dist_t horz = 10.0, dist_t vert = DistanceModel::nan_f, DistanceModel::topol_t topology = DistanceModel::KNIGHT): // PIX_ADJACENCY_
522  DistanceTransformOp<DistanceModelExponential>(__FUNCTION__, "Exponentially decreasing intensities. Set half-decay radii.", horz, vert, topology) {
523  };
524 
525 };
526 
527 } // image::
528 } // drain::
529 
530 #endif /*DISTANCETRANSFORMOP_H_*/
531 
LogSourc e is the means for a function or any program segment to "connect" to a Log.
Definition: Log.h:308
Logger & special(const TT &... args)
Other useful information.
Definition: Log.h:527
Logger & note(const TT &... args)
For top-level information.
Definition: Log.h:485
Logger & warn(const TT &... args)
Possible error, but execution can continue.
Definition: Log.h:426
Logger & debug(const TT &... args)
Public, yet typically used "internally", when TIMING=true.
Definition: Log.h:676
Logger & debug2(const TT &... args)
Debug information.
Definition: Log.h:686
void append(ReferenceMap &rMap, bool replace=true)
Adopts the references of r. If replace==false, only new entries are appended.
Definition: ReferenceMap.h:320
Linear scaling and physical range for image intensities.
Definition: ValueScaling.h:64
bool isPhysical() const
Returns true, physical intensity range has been set.
Definition: ValueScaling.h:281
double fwd(double x) const
Forward scaling: given encoded value x, returns corresponding value (possibly physically meaningful).
Definition: ValueScaling.h:295
const Range< double > & getPhysicalRange() const
Returns a typical or supported range for physical values.
Definition: ValueScaling.h:221
double inv(double y) const
Inverse scaling: given physically meaningful value y, returns the corresponding code value.
Definition: ValueScaling.h:301
Image with static geometry.
Definition: ImageChannel.h:60
Definition: CoordinateHandler.h:62
static const coord_overflow_t IRREVERSIBLE
Equal move in inverse direction would not result original position.
Definition: CoordinateHandler.h:74
virtual coord_overflow_t handle(int &x, int &y) const
Ensures the validity of the coordinates. If inside limits, arguments (x,y) remain intact and 0 is ret...
Definition: CoordinateHandler.h:190
Definition: DistanceModel.h:76
Definition: DistanceModelExponential.h:55
Base class for linear and exponential distances in rectangular pixel images.
Definition: DistanceModel.h:123
Definition: DistanceTransformOp.h:513
DistanceTransformExponentialOp(dist_t horz=10.0, dist_t vert=DistanceModel::nan_f, DistanceModel::topol_t topology=DistanceModel::KNIGHT)
Constructor with default dimensions.
Definition: DistanceTransformOp.h:521
Computes inverse linear distance to bright points.
Definition: DistanceTransformOp.h:482
Definition: DistanceTransformOp.h:58
void traverseUpLeft(const Channel &src, Channel &dst) const
Definition: DistanceTransformOp.h:382
virtual void getDstConf(const ImageConf &src, ImageConf &dst) const
Ensures dst the same geometry and type with src.
Definition: DistanceTransformOp.h:95
virtual void traverseChannel(const Channel &src, Channel &dst) const
Apply to single channel.
Definition: DistanceTransformOp.h:271
virtual void initializeParameters(const ImageFrame &src, const ImageFrame &dst) const
Sets internal parameters.
Definition: DistanceTransformOp.h:193
virtual void traverseChannel(const Channel &src, const Channel &srcAlpha, Channel &dst, Channel &dstAlpha) const
Apply to single channel with alpha.
Definition: DistanceTransformOp.h:85
double requestPhysicalMax(double defaultMax=static_cast< double >(std::numeric_limits< short int >::max())) const
Returns the actual or guessed maximum physical value,.
Definition: ImageConf.h:293
T getTypeMax() const
Returns the maximum value supported by the current storage type.
Definition: ImageConf.h:281
void setPhysicalRange(const Range< double > &range, bool rescale=false)
Sets channel specific scaling instead of shared (image-level) scaling.
Definition: ImageConf.h:229
Struct for image (excluding data)
Definition: ImageConf.h:333
CoordinatePolicy coordinatePolicy
Rules to handle under- and overflows of horizontal and vertical coordinates.
Definition: ImageConf.h:383
Image with static geometry.
Definition: ImageFrame.h:67
void put(size_t i, T x)
Sets the intensity in location i to x. See \address.
Definition: ImageFrame.h:192
T get(size_t i) const
Gets the intensity at location i. See address().
Definition: ImageFrame.h:254
const std::type_info & getType() const
Get the storage type.
Definition: ImageLike.h:100
const CoordinatePolicy & getCoordinatePolicy() const
Coord policy.
Definition: ImageLike.h:167
virtual int srcAlpha() const
Tell if alpha channel(s) is required in input.
Definition: ImageMod.h:66
Base class for image processing functions.
Definition: ImageOp.h:49
ImageOp(const std::string &name=__FUNCTION__, const std::string &description="")
Definition: ImageOp.h:156
void traverseChannelsSeparately(const ImageTray< const Channel > &src, ImageTray< Channel > &dst) const
Process each (src,dst) channel pair independently. Raise error if their counts differ.
Definition: ImageOp.cpp:340
Container applicable for Channels and Images, with alpha support.
Definition: ImageTray.h:267
Definition: DataSelector.cpp:1277