View Javadoc
1   package com.github.celldynamics.quimp.plugin.randomwalk;
2   
3   import java.awt.Color;
4   import java.io.File;
5   import java.nio.file.Path;
6   import java.nio.file.Paths;
7   import java.security.InvalidParameterException;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.List;
11  import java.util.concurrent.atomic.AtomicInteger;
12  import java.util.stream.IntStream;
13  
14  import org.apache.commons.math3.linear.Array2DRowRealMatrix;
15  import org.apache.commons.math3.linear.ArrayRealVector;
16  import org.apache.commons.math3.linear.MatrixDimensionMismatchException;
17  import org.apache.commons.math3.linear.MatrixUtils;
18  import org.apache.commons.math3.linear.RealMatrix;
19  import org.apache.commons.math3.linear.RealMatrixChangingVisitor;
20  import org.apache.commons.math3.stat.StatUtils;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import com.github.celldynamics.quimp.QuimP;
25  import com.github.celldynamics.quimp.utils.QuimPArrayUtils;
26  
27  import ij.IJ;
28  import ij.ImagePlus;
29  import ij.ImageStack;
30  import ij.plugin.ImageCalculator;
31  import ij.process.BinaryProcessor;
32  import ij.process.ByteProcessor;
33  import ij.process.ImageProcessor;
34  
35  /*
36   * //!>
37   * @startuml doc-files/RandomWalkSegmentation_1_UML.png
38   * usecase IN as "Initialisation
39   * --
40   * Apply when main object is initialised.
41   * Object is always connected to the image
42   * that will be segmented."
43   * 
44   * usecase RUN as "Run
45   * --
46   * Related to running segmentation on
47   * the object. The result is segmented
48   * image"
49   * 
50   * usecase CON as "Convert
51   * --
52   * Related to various conversions
53   * that are available as static
54   * methods. They allow to change
55   * representation of objects among
56   * dataformats used by this object.
57   * "
58   * 
59   * usecase PAR as "Parameters
60   * --
61   * Related to creating object
62   * representing parameters
63   * of the segmentation process.
64   * "
65   * Dev -> IN
66   * Dev -> RUN
67   * Dev --> CON
68   * Dev --> PAR
69   * 
70   * RUN ..> IN : <<include>>
71   * PAR -- IN
72   * CON -- RUN
73   * @enduml
74   * //!<
75   */
76  /*
77   * //!>
78   * @startuml doc-files/RandomWalkSegmentation_2_UML.png
79   * start
80   * :Assign inputs
81   * to internal fields;
82   * note right
83   * Inputs are **referenced**
84   * end note
85   * :Validate input
86   * image;
87   * :Convert image to
88   * ""Matrix"";
89   * note right
90   * Or Matrix to image
91   * depending on constructor
92   * end note
93   * stop
94   * @enduml
95   * //!<
96   */
97  /*
98   * //!>
99   * @startuml doc-files/RandomWalkSegmentation_3_UML.png
100  * start
101  * :Compute gradients;
102  * note left
103  * Gradients depend
104  * on image structure
105  * and they are tabelarised
106  * for speed
107  * end note
108  * :solve problem;
109  * -> //Result of segmentation, FG and BG probability maps//;
110  * note right
111  * Main procedure for
112  * RW segmentation.
113  * Return segmented image.
114  * end note
115  * if (next sweep?) then (yes)
116  * :Run
117  * intermediate
118  * filter;
119  * note left
120  * Optional, run on
121  * previous results
122  * end note
123  * :Prepare to
124  * next sweep;
125  * note left
126  * Convert previous result
127  * into seed, increment
128  * ""currentSweep""
129  * end note
130  * :solve problem;
131  * endif
132  * :Compare FG and BG;
133  * note left
134  * Foreground and 
135  * background probabilities
136  * are compared to get 
137  * unique result
138  * end note
139  * -> //Binary image//;
140  * :Run
141  * post-filtering;
142  * note right 
143  * Optional
144  * end note
145  * stop
146  * @enduml
147  * //!<
148  */
149 /*
150  * //!>
151  * @startuml doc-files/RandomWalkSegmentation_4_UML.png
152  * actor User as user
153  * participant RandomWalkSegmentation as rw
154  * user -> rw : run(..)
155  * activate rw
156  * rw -> rw : precomputeGradients()
157  *  activate rw
158  *  rw --> rw : RealMatrix[]
159  *  deactivate rw
160  * rw -> rw : solver(..) 
161  *  note right: Solver always return\nprobability map for\nforeground unless input\nFG was empty
162  *  activate rw
163  *  rw --> rw : Map<Seeds, RealMatrix> solved
164  *  deactivate rw
165  * alt next sweep?
166  *  rw -> rw :  rollNextSweep(solved)
167  *   activate rw
168  *   rw --> rw : Map<Seeds, ImageProcessor> seedsNext
169  *   deactivate rw
170  *  rw -> rw : solver(seedsNext) 
171  *   activate rw
172  *   rw --> rw : Map<Seeds, RealMatrix> solved
173  *   deactivate rw
174  * end 
175  * rw -> rw : compare(solved)
176  *  note right: Compare FG and BG maps\nto get segmentation result
177  *  activate rw
178  *  rw --> rw : RealMatrix result
179  *  deactivate rw
180  * alt final filtering == yes
181  *  rw -> Converter : RealMatrix
182  *   activate Converter
183  *   Converter --> rw : ImageProcessor
184  *   deactivate Converter
185  *  rw -> BinaryFilters : filter(ImageProcessor) 
186  *   activate BinaryFilters
187  *   BinaryFilters --> rw : filtered ImageProcessor
188  *   deactivate BinaryFilters
189  * else No final filtering
190  *  rw -> Converter : RealMatrix
191  *   activate Converter
192  *   Converter --> rw : ImageProcessor
193  *   deactivate Converter  
194  * end 
195  * note right: Except filtering, output can be\ncut by raw mask here, See run method.
196  * rw --> user : ImageProcessor
197  * deactivate rw
198  * @enduml
199  * //!<
200  */
201 /**
202  * This is implementation of Matlab version of Random Walk segmentation algorithm.
203  * 
204  * <p>Available use cases are: <br>
205  * <img src="doc-files/RandomWalkSegmentation_1_UML.png"/><br>
206  * 
207  * <h1>Parameters</h1>
208  * In this Use Case user creates a set of segmentation parameters. They are hold in
209  * {@link RandomWalkOptions}
210  * class. This class also contains some pre- and post-processing settings used by
211  * {@link RandomWalkSegmentation} object. Default constructor sets recommended values to all numeric
212  * options skipping those related to pre- post-processing objects.
213  * 
214  * <h1>Initialisation</h1>
215  * In this step the image that is supposed to be segmented is assigned to the object as well as
216  * parameters of the process. There are two constructors available accepting <tt>ImageProcessor</tt>
217  * and <tt>RelaMatrix</tt> image. These formats can be converted to each other using static
218  * {@link QuimPArrayUtils#imageProcessor2RealMatrix(ImageProcessor)} and
219  * {@link QuimPArrayUtils#realMatrix2ImageProcessor(RealMatrix)}.
220  * <img src="doc-files/RandomWalkSegmentation_2_UML.png"/><br>
221  * 
222  * <h1>Convert</h1>
223  * This Use Case contains preparatory static methods used for converting data into required format.
224  * {@link RandomWalkSegmentation} uses {@link Seeds} structure to store information about foreground
225  * (FG) and background (BG) seeds. Initially these seeds can be provided in different formats such
226  * as:
227  * <ol>
228  * <li>RGB image where FG seeds are {@link Color#RED} and BG is {@link Color#GREEN}. In this mode
229  * only these two seeds can be stored.
230  * <li>Grayscale image, each object is labelled by different gray level. In this mode each object is
231  * segmented separately, setting other objects to background.
232  * <li>Binary image, works as grayscales.
233  * <li>ROIs - rois selected on image.
234  * </ol>
235  * All above formats are converted to {@link Seeds} (the only accepted input by
236  * {@link #run(Seeds)} by numerous methods from {@link SeedProcessor}. The
237  * {@link Seeds} contains two keys important for segmentation, {@link SeedTypes#FOREGROUNDS} and
238  * {@link SeedTypes#BACKGROUND}, {@link SeedTypes#FOREGROUNDS} is mandatory whereas
239  * {@link SeedTypes#BACKGROUND} can be empty. Along one key seeds are stored as separate
240  * <b>binary</b>
241  * images that contain only pixels that belong to specified seed (e.g. red color for RGB or
242  * specified grayscale for multi-cell segmentation). The optionl key {@link SeedTypes#ROUGHMASK}
243  * contains one binary image that stands for initial estimation of object larger than the object. It
244  * is used by local mean feature only ({@link RandomWalkOptions#useLocalMean} set <tt>true</tt>).
245  * 
246  * <h1>Run</h1>
247  * In this Use Case the image assigned in <b>Initialisation</b> step is segmented and processed
248  * according to parameters set in <b>Parameters</b>. The process is started by calling
249  * {@link #run(Seeds)} method. For RGB seeds FG and BG are always defined but if the input is
250  * converted from grayscale image, {@link SeedTypes#BACKGROUND} in {@link Seeds} is empty. The
251  * method returns probability maps {@link ProbabilityMaps} that are organised similarly to
252  * {@link Seeds}. Again for multi-cell segmentation returned {@link ProbabilityMaps} may not contain
253  * {@link SeedTypes#BACKGROUND} key. The algorithm assumes that background is on probability 0 and
254  * it is {@link Color#BLACK}. Probabilities are compared in {@link #compare(ProbabilityMaps)}
255  * method that returns 2d matrix being an output image.
256  * 
257  * <p>Here is brief look for {@link #run(Seeds)} method activity:
258  * <img src="doc-files/RandomWalkSegmentation_3_UML.png"/><br>
259  * 
260  * <p>Sequence diagram of {@link #run(Seeds)} giving the order of called methods:
261  * <img src="doc-files/RandomWalkSegmentation_4_UML.png"/><br>
262  * 
263  * <p>See: src/test/Resources-static/Matlab/rw_laplace4.m
264  * 
265  * @author p.baniukiewicz
266  */
267 public class RandomWalkSegmentation {
268 
269   /**
270    * How often we will compute relative error.
271    */
272   final int relErrStep = 20;
273 
274   /**
275    * Keep information about current sweep that {@link #solver(Seeds, RealMatrix[])} is doing.
276    * 
277    * <p>Affect indexing parameters arrays that keep different params for different sweeps. Used also
278    * in {@link #solver(Seeds, RealMatrix[]) for computing actual number of iterations (second sweep
279    * uses half of defined)}
280    * 
281    * @see RandomWalkOptions
282    */
283   private int currentSweep = 0;
284 
285   /**
286    * Squared maximal theoretical intensity value.
287    * 
288    * <p>For 8-bit images it is 255^2, for 16-bit 65535^2. Set in constructor.
289    */
290   private int maxTheoreticalIntSqr;
291 
292   /**
293    * Reasons of stopping diffusion process.
294    * 
295    * @author p.baniukiewicz
296    *
297    */
298   private enum StoppedBy {
299     /**
300      * Maximum number of iterations reached.
301      */
302     ITERATIONS(0),
303     /**
304      * Found NaN in solution.
305      */
306     NANS(1),
307     /**
308      * Found Inf in solution.
309      */
310     INFS(2),
311     /**
312      * Relative error smaller than limit.
313      */
314     RELERR(3);
315 
316     private final int value;
317 
318     private StoppedBy(int value) {
319       this.value = value;
320     }
321 
322     public int getValue() {
323       return value;
324     }
325   }
326 
327   /**
328    * Define foreground and background indexes enums with numerical indexes.
329    * 
330    * @author p.baniukiewicz
331    *
332    */
333   public enum SeedTypes {
334     /**
335      * Denote foreground related data. Usually on index 0.
336      * 
337      * <p>FG data can be grayscale images up to 8-bits.
338      */
339     FOREGROUNDS(0),
340     /**
341      * Denote background related data. Usually on index 1.
342      * 
343      * <p>BG data can be grayscale image up to 8-bits.
344      */
345     BACKGROUND(1),
346     /**
347      * Rough mask used for computing local mean. Used only if {@link RandomWalkOptions#useLocalMean}
348      * is true. This mask should be always binary.
349      * 
350      * @see RandomWalkSegmentation#solver(Seeds, RealMatrix[])
351      * @see RandomWalkSegmentation#getMeanSeedLocal(ImageProcessor, int)
352      */
353     ROUGHMASK(2);
354 
355     /** The index. */
356     private final int index;
357 
358     /**
359      * Instantiates a new seed types.
360      *
361      * @param index the index
362      */
363     private SeedTypes(int index) {
364       this.index = index;
365     }
366 
367     /**
368      * Convert enum to int. Used when addressing arrays.
369      * 
370      * @return index assigned to enum value.
371      */
372     public int getIndex() {
373       return index;
374     }
375   }
376 
377   /**
378    * The Constant LOGGER.
379    */
380   static final Logger LOGGER = LoggerFactory.getLogger(RandomWalkSegmentation.class.getName());
381 
382   /**
383    * Direction of circshift coded as in Matlab.
384    */
385   public static final int RIGHT = -10;
386   /**
387    * Direction of circshift coded as in Matlab.
388    */
389   public static final int LEFT = 10;
390   /**
391    * Direction of circshift coded as in Matlab.
392    */
393   public static final int TOP = -01;
394   /**
395    * Direction of circshift coded as in Matlab.
396    */
397   public static final int BOTTOM = 01;
398 
399   /**
400    * Image to process in 8bit greyscale. Converted to RealMatrix
401    * 
402    * @see #ip
403    */
404   private RealMatrix image;
405   /**
406    * Original image to process.
407    * 
408    * @see #image
409    */
410   private ImageProcessor ip;
411   /**
412    * User provided parameters.
413    */
414   private RandomWalkOptions params;
415   /**
416    * Probability map obtained in {@link #run(Seeds)}.
417    */
418   private ProbabilityMaps solved = null;
419 
420   /**
421    * Construct segmentation object from ImageProcessor.
422    * 
423    * @param ip image to segment
424    * @param params parameters
425    * @throws RandomWalkException on wrong image format
426    */
427   public RandomWalkSegmentation(ImageProcessor ip, RandomWalkOptions params)
428           throws RandomWalkException {
429     if (ip.getBitDepth() != 8 && ip.getBitDepth() != 16) {
430       throw new RandomWalkException("Only 8-bit or 16-bit images are supported");
431     }
432     this.ip = ip;
433     this.image = QuimPArrayUtils.imageProcessor2RealMatrix(ip);
434     this.params = params;
435     setMaxTheoreticalIntSqr(ip);
436   }
437 
438   /**
439    * Construct segmentation object from 2D RealMatrix representing image.
440    * 
441    * <p>It is assumed that this input image is 8-bit. Use
442    * {@link #RandomWalkSegmentation(ImageProcessor, RandomWalkOptions)} for support 8 and 16-bit
443    * images.
444    * Passing wrong image can have effect to results as {@link #solver(Seeds, RealMatrix[])}
445    * normalises
446    * image intensities to maximal theoretical intensity. See
447    * {@link #setMaxTheoreticalIntSqr(ImageProcessor)} and {@link #solver(Seeds, RealMatrix[])}
448    * 
449    * @param image image to segment
450    * @param params parameters
451    */
452   public RandomWalkSegmentation(RealMatrix image, RandomWalkOptions params) {
453     this.image = image;
454     this.ip = QuimPArrayUtils.realMatrix2ImageProcessor(image);
455     this.params = params;
456     setMaxTheoreticalIntSqr(ip);
457   }
458 
459   /**
460    * Main runner, does segmentation.
461    * 
462    * <p>Requires defined FOREGROUNDS seeds and one BACKGROUND. If background is not given it assumes
463    * during weighting ({@link #compare(ProbabilityMaps)}) background probability map with
464    * probability 0. IT should wor even if there is only one seed FG and no BG because most arrays
465    * are 0-filled by default.
466    * 
467    * @param seeds Seed arrays from {@link SeedProcessor}
468    * @return Segmented image as ByteProcessor or null if segmentation failed due to e.g. empty seeds
469    * @throws RandomWalkException On wrong seeds
470    */
471   public ImageProcessor run(Seeds seeds) throws RandomWalkException {
472     LOGGER.debug("Running with options: " + params.toString());
473     if (seeds.get(SeedTypes.FOREGROUNDS) == null) {
474       return null; // no FG maps - no segmentation
475     }
476     RealMatrix[] precomputed = precomputeGradients(); // precompute gradients
477     solved = solver(seeds, precomputed);
478     if (params.intermediateFilter != null && params.gamma[1] != 0) { // do second sweep
479       LOGGER.debug("Running next sweep: " + params.intermediateFilter.getClass().getName());
480       Seeds seedsNext = rollNextSweep(solved);
481       if (seeds.get(SeedTypes.ROUGHMASK) != null) {
482         seedsNext.put(SeedTypes.ROUGHMASK, seeds.get(SeedTypes.ROUGHMASK, 1));
483       }
484       solved = solver(seedsNext, precomputed);
485     }
486     RealMatrix result = compare(solved); // result as matrix
487     ImageProcessor resultim =
488             QuimPArrayUtils.realMatrix2ImageProcessor(result).convertToByteProcessor(true);
489     // cut mask - cut segmentation result by initial ROUGHMASK if present
490     if (params.maskLimit == true && seeds.get(SeedTypes.ROUGHMASK) != null) {
491       ImageCalculator ic = new ImageCalculator();
492       ImagePlus retc = ic.run("and create",
493               new ImagePlus("", new BinaryProcessor(
494                       (ByteProcessor) seeds.get(SeedTypes.ROUGHMASK, 1).convertToByte(true))),
495               new ImagePlus("", new BinaryProcessor((ByteProcessor) resultim)));
496       resultim = retc.getProcessor();
497     }
498     // do final filtering
499     if (params.finalFilter != null) {
500       return params.finalFilter.filter(resultim.convertToByteProcessor(true));
501     } else {
502       return resultim.convertToByteProcessor(true);
503     }
504 
505   }
506 
507   /**
508    * Prepare seeds from results of previous solver.
509    * 
510    * @param solved results from {@link #solver(Seeds, RealMatrix[])}
511    * @return new seed taken from previous solution.
512    * @throws RandomWalkException on unsupported image or empty seed list after decoding
513    */
514   private Seeds rollNextSweep(ProbabilityMaps solved) throws RandomWalkException {
515     final double weight = 1e20;
516     Seedsamics/quimp/plugin/randomwalk/Seeds.html#Seeds">Seeds ret = new Seeds(2);
517     RealMatrix solvedWeighted;
518     // make copy of input results to weight them
519     for (int m = 0; m < solved.get(SeedTypes.FOREGROUNDS).size(); m++) {
520       solvedWeighted = solved.get(SeedTypes.FOREGROUNDS).get(m).copy();
521 
522       // seed_fg = FGl>1e20*BGl
523       solvedWeighted.walkInOptimizedOrder(
524               new MatrixCompareWeighted(solved.get(SeedTypes.BACKGROUND).get(0), weight));
525 
526       // convert weighted results to images
527       ImageProcessor fg1 =
528               QuimPArrayUtils.realMatrix2ImageProcessor(solvedWeighted).convertToByte(true);
529 
530       if (QuimP.SUPER_DEBUG) { // save intermediate results
531         LOGGER.debug("Saving intermediate results");
532         String tmpdir = System.getProperty("java.io.tmpdir") + File.separator;
533         IJ.saveAsTiff(new ImagePlus("", fg1), tmpdir + "fg1_" + m + "_QuimP.tif");
534       }
535       // filter them (if we are here intermediate filter can't be null)
536       fg1 = params.intermediateFilter.filter(fg1);
537       ret.put(SeedTypes.FOREGROUNDS, fg1);
538     }
539     // increment sweep pointer to point correct parameters (if they are different for next sweep)
540     currentSweep++;
541     solvedWeighted = solved.get(SeedTypes.BACKGROUND).get(0).copy();
542     // flatten foregrounds to weight background
543     double[][] fl = flatten(solved, SeedTypes.FOREGROUNDS);
544     // seed_bg = BGl>1e20*FGl;
545     solvedWeighted.walkInOptimizedOrder(
546             new MatrixCompareWeighted(MatrixUtils.createRealMatrix(fl), weight));
547     ImageProcessor bg1 =
548             QuimPArrayUtils.realMatrix2ImageProcessor(solvedWeighted).convertToByte(true);
549     if (QuimP.SUPER_DEBUG) { // save intermediate results
550       String tmpdir = System.getProperty("java.io.tmpdir") + File.separator;
551       IJ.saveAsTiff(new ImagePlus("", bg1), tmpdir + "bg1_\"+m+\"_QuimP.tif");
552     }
553     bg1.invert();
554     bg1 = params.intermediateFilter.filter(bg1);
555     bg1.invert();
556     ret.put(SeedTypes.BACKGROUND, bg1);
557 
558     return ret;
559   }
560 
561   /**
562    * Compare probabilities from and create segmentation matrix depending on winner.
563    * 
564    * @param solved foreground and background seeds. If there is no background seed this procedure
565    *        assume probability map of size of foreground filled with 0s
566    * @return matrix of size of input image where objects are labelled with constitutive numbers
567    *         starting from 1
568    */
569   RealMatrix compare(ProbabilityMaps solved) {
570     double backgroundColorValue = 0.0; // value for fill background
571     double[][][] fgmaps3d = solved.convertTo3dMatrix(SeedTypes.FOREGROUNDS);
572     double[][][] bgmaps3d = solved.convertTo3dMatrix(SeedTypes.BACKGROUND);
573 
574     if (fgmaps3d == null) {
575       return null;
576     }
577     int rows = fgmaps3d[0].length;
578     int cols = fgmaps3d[0][0].length;
579     // assume probability of 0 for BG. If there are only FG objects and probability of being FG1 is
580     // 0 as well as for FG2, this will impose background
581     if (bgmaps3d == null) {
582       bgmaps3d = new double[1][rows][cols];
583     }
584     if (rows == 0 || cols == 0 || rows != bgmaps3d[0].length || cols != bgmaps3d[0][0].length) {
585       return null;
586     }
587 
588     RealMatrix ret = MatrixUtils.createRealMatrix(rows, cols);
589 
590     int[][] fgMaxMap = flattenInd(fgmaps3d);
591     int[][] bgMaxMap = flattenInd(bgmaps3d);
592 
593     for (int r = 0; r < rows; r++) {
594       for (int c = 0; c < cols; c++) {
595         int fi = fgMaxMap[r][c]; // z-index of max element in FG
596         int bi = bgMaxMap[r][c]; // z-index of max element in bg
597         if (fgmaps3d[fi][r][c] > bgmaps3d[bi][r][c]) { // FG>BG - object
598           ret.setEntry(r, c, fi + 1); // set object coded at index z+1 (1-based)
599         } else {
600           ret.setEntry(r, c, backgroundColorValue); // background
601         }
602       }
603     }
604     return ret;
605 
606   }
607 
608   /**
609    * Compare values along z dimension for each x,y and return index of max value.
610    * 
611    * @param in matrix to flatten
612    * @return 2d matrix of indexes (0-based) of maximal values for each x,y along z
613    */
614   private int[][] flattenInd(double[][][] in) {
615     int rows = in[0].length;
616     int cols = in[0][0].length;
617     int[][] ret = new int[rows][cols];
618 
619     if (in.length == 1) {
620       return ret; // just return 0 indexes
621     }
622     for (int r = 0; r < rows; r++) {
623       for (int c = 0; c < cols; c++) {
624         double max = in[0][r][c];
625         for (int z = 0; z < in.length; z++) {
626           if (in[z][r][c] > max) {
627             max = in[z][r][c];
628             ret[r][c] = z;
629           }
630         }
631       }
632     }
633     return ret;
634   }
635 
636   /**
637    * Produce 2d matrix of max values taken along z.
638    * 
639    * @param maps maps to flatten to flatten
640    * @param key seed key
641    * 
642    * @return 2d matrix of max values (0-based) of maximal values for each x,y along z
643    */
644   double[][] flatten(ProbabilityMaps maps, SeedTypes key) {
645     double[][][] in = maps.convertTo3dMatrix(key);
646     if (in == null) {
647       return null;
648     }
649     int rows = in[0].length;
650     int cols = in[0][0].length;
651     double[][] ret = new double[rows][cols];
652 
653     if (in.length == 1) {
654       return in[0]; // just return input
655     }
656     for (int r = 0; r < rows; r++) {
657       for (int c = 0; c < cols; c++) {
658         double max = in[0][r][c];
659         for (int z = 0; z < in.length; z++) {
660           if (in[z][r][c] > max) {
661             max = in[z][r][c];
662           }
663           ret[r][c] = max;
664         }
665       }
666     }
667     return ret;
668   }
669 
670   /**
671    * Shift image in given direction with step of one and circular boundary conditions.
672    *
673    * @param input Image to be shifted
674    * @param direction Shift direction. This method is adjusted to be compatible with Matlab code
675    *        and to keep Matlab naming (rw_laplace4.m) thus the shift direction names are not
676    *        adequate to shift direction.
677    * @return Copy of input shifted by one pixel in direction.
678    */
679   RealMatrix circshift(RealMatrix input, int direction) {
680     // Routine cuts part of matrix that does not change, paste it into new matrix shifting in given
681     // direction and then add missing looped row/column (last or first depending on shift
682     // direction).
683     double[][] sub; // part of matrix that does no change but is shifted
684     int rows = input.getRowDimension(); // cache sizes
685     int cols = input.getColumnDimension();
686     Array2DRowRealMatrix out = new Array2DRowRealMatrix(rows, cols); // output matrix, shifted
687     switch (direction) {
688       case BOTTOM:
689         // rotated right - last column become first
690         // cut submatrix from first column to before last
691         sub = new double[rows][cols - 1];
692         input.copySubMatrix(0, rows - 1, 0, cols - 2, sub); // cols-2 - cols is size not last ind
693         // create new matrix - paste submatrix but shifted right
694         out.setSubMatrix(sub, 0, 1);
695         // copy last column to first
696         out.setColumnVector(0, input.getColumnVector(cols - 1));
697         break;
698       case TOP:
699         // rotated left - first column become last
700         // cut submatrix from second column to last
701         sub = new double[rows][cols - 1];
702         input.copySubMatrix(0, rows - 1, 1, cols - 1, sub);
703         // create new matrix - paste submatrix but shifted right
704         out.setSubMatrix(sub, 0, 0);
705         // copy first column to last
706         out.setColumnVector(cols - 1, input.getColumnVector(0));
707         break;
708       case RIGHT:
709         // rotated top - first row become last
710         // cut submatrix from second row to last
711         sub = new double[rows - 1][cols];
712         input.copySubMatrix(1, rows - 1, 0, cols - 1, sub);
713         // create new matrix - paste submatrix but shifted up
714         out.setSubMatrix(sub, 0, 0);
715         // copy first row to last
716         out.setRowVector(rows - 1, input.getRowVector(0));
717         break;
718       case LEFT:
719         // rotated bottom - last row become first
720         // cut submatrix from first row to before last
721         sub = new double[rows - 1][cols];
722         input.copySubMatrix(0, rows - 2, 0, cols - 1, sub);
723         // create new matrix - paste submatrix but shifted up
724         out.setSubMatrix(sub, 1, 0);
725         // copy last row to first
726         out.setRowVector(0, input.getRowVector(rows - 1));
727         break;
728       default:
729         throw new IllegalArgumentException("circshift: Unknown direction");
730     }
731     return out;
732   }
733 
734   /**
735    * Compute (a-b)^2.
736    * 
737    * @param a left operand
738    * @param b right operand
739    * @return Image (a-b).^2
740    */
741   RealMatrix getSqrdDiffIntensity(RealMatrix a, RealMatrix b) {
742     RealMatrix s = a.subtract(b);
743     s.walkInOptimizedOrder(new MatrixElementPower());
744     return s;
745   }
746 
747   /**
748    * Pre-compute normalised gradient matrices.
749    * 
750    * <p>This computes squared intensity differences.These correspond to horizontal and vertical
751    * gradients assuming distance of one between pixels.
752    * 
753    * @return Array of precomputed data in the following order: [0] - gRight2 [1] - gTop2 [2] -
754    *         gLeft2 [3] - gBottom2
755    */
756   private RealMatrix[] precomputeGradients() {
757     // setup shifted images assuming shift of one pixel and periodic boundary conditions.
758     RealMatrix right = circshift(image, RIGHT);
759     RealMatrix top = circshift(image, TOP);
760     // compute squared intensity differences (a-b).^2
761     RealMatrix gradRight2 = getSqrdDiffIntensity(image, right);
762     RealMatrix gradTop2 = getSqrdDiffIntensity(image, top);
763     // compute maximum of horizontal and vertical intensity gradients (for normalization)
764     double maxGright2 = QuimPArrayUtils.getMax(gradRight2);
765     LOGGER.debug("maxGright2 " + maxGright2);
766     double maxGtop2 = QuimPArrayUtils.getMax(gradTop2);
767     LOGGER.debug("maxGtop2 " + maxGtop2);
768     // find maximum of horizontal and vertical maxima
769     double maxGrad2 = maxGright2 > maxGtop2 ? maxGright2 : maxGtop2;
770     LOGGER.debug("maxGrad2max " + maxGrad2);
771     if (maxGrad2 == 0) {
772       maxGrad2 = 1.0;
773     }
774     // Normalize squared gradients to maxGrad
775     gradRight2.walkInOptimizedOrder(new MatrixElementDivide(maxGrad2));
776     gradTop2.walkInOptimizedOrder(new MatrixElementDivide(maxGrad2));
777     // assign outputs
778     RealMatrix[] out = new RealMatrix[4];
779     // get remaining directions - use already normalized squared gradients
780     RealMatrix gradLeft2 = circshift(gradRight2, LEFT);
781     out[2] = gradLeft2;
782     RealMatrix gradBottom2 = circshift(gradTop2, BOTTOM);
783     out[3] = gradBottom2;
784     out[0] = gradRight2;
785     out[1] = gradTop2;
786 
787     return out;
788   }
789 
790   /**
791    * Compute mean value from image only seeded pixels.
792    * 
793    * @param seeds coordinates of points used to calculate their mean intensity
794    * @return mean value for points
795    * @see Seeds#convertToList(SeedTypes)
796    */
797   protected double getMeanSeedGlobal(List<Point> seeds) {
798     return StatUtils.mean(getValues(image, seeds).getDataRef());
799   }
800 
801   /**
802    * Calculate local mean intensity for input image but only for areas masked by specified mask.
803    * 
804    * <p>The mean over segmented image intensity is evaluated within square window of configurable
805    * size and only for those pixels that are masked by binary mask given to this method. Mean is
806    * calculated respectfully to the number of masked pixels within window. Window is moved over the
807    * whole image.
808    * 
809    * <p>This method works similarly to the convolution with the difference that the kernel is
810    * normalised for each position of the window to the number of masked pixels (within it). If for
811    * any position of the window there are no masked pixels inside, value 0.0 is set as result.
812    * 
813    * @param mask Binary mask of segmented image. Mask must contain only pixels with intensity 0 or
814    *        255 (according to definition of binary image in IJ)
815    * @param localMeanMaskSize Odd size of kernel
816    * @return Averaged image. Average is computed for every location of the kernel for its center
817    *         utlising only masked pixels. For not masked pixels the mean is set to 0.0
818    */
819   protected RealMatrix getMeanSeedLocal(ImageProcessor mask, int localMeanMaskSize) {
820     // IJ convolution is utilised. Computations are done twice, first time only on mask to get the
821     // number of masked pixels covered by it for each image pixel
822     // second time for segmented image to get sum of intensities within the kernel.
823     // in both cases kernels are normalised (default behaviour of ImageProcessor.convolve) but then
824     // "denormalised" by multiplying by their size. Kernels are 1-filled.
825     if (localMeanMaskSize % 2 == 0) {
826       throw new IllegalArgumentException("Kernel sie must be odd");
827     }
828     // must be binary as we later assume that mx intensity is 255 (like in IJ binary images)
829     if (!mask.isBinary()) {
830       throw new IllegalArgumentException("Mask must be binary");
831     }
832     if (mask.getWidth() != ip.getWidth() || mask.getHeight() != ip.getHeight()) {
833       throw new IllegalArgumentException("Mask must have size of processed image");
834     }
835     ImageProcessor maskc = mask.duplicate(); // local copy of mask to not modify it
836     maskc.subtract(254); // if it is IJ binary it contains [0,255], scale to 0 - 1.0
837     // make mask copy for getting number of pixels in mask for each position of kernel
838     ImageProcessor numofpix = maskc.duplicate();
839 
840     // generate 1-filled kernel of given size
841     float[] kernel = new float[localMeanMaskSize * localMeanMaskSize];
842     Arrays.fill(kernel, 1.0f);
843     // get number of mask pixesl for ach position of kernel
844     numofpix = maskc.convertToFloat(); // must convert here
845     numofpix.convolve(kernel, localMeanMaskSize, localMeanMaskSize);
846     numofpix.multiply(kernel.length); // convolution normalises kernel - revert it 1)
847     // cut to mask - mulitply by 1.0 mask - reject pixels that are not masked
848     numofpix = new ImageCalculator()
849             .run("mul create", new ImagePlus("", numofpix), new ImagePlus("", maskc))
850             .getProcessor();
851 
852     // get sum of intensities within the kernel for segmented image
853     // cut image to input mask - set all other pixels to 0. Must be done here to not count nonmasked
854     // pixels
855     ImageProcessor cutImage = new ImageCalculator()
856             .run("mul create float", new ImagePlus("", ip), new ImagePlus("", maskc))
857             .getProcessor();
858     // convolve cut image
859     cutImage.setCalibrationTable(null);
860     cutImage.convolve(kernel, localMeanMaskSize, localMeanMaskSize);
861     cutImage.multiply(kernel.length); // denormalise result
862     // deal with edges of image after convolution - remove them
863     cutImage = new ImageCalculator()
864             .run("mul create float", new ImagePlus("", cutImage), new ImagePlus("", maskc))
865             .getProcessor();
866     // rounding floats to integer values - convolution works for float images only
867     float[] cutImageRaw = (float[]) cutImage.getPixels();
868     for (int i = 0; i < cutImage.getPixelCount(); i++) {
869       cutImageRaw[i] = Math.round(cutImageRaw[i]);
870     }
871     float[] numofpixRaw = (float[]) numofpix.getPixels();
872     for (int i = 0; i < numofpix.getPixelCount(); i++) {
873       numofpixRaw[i] = Math.round(numofpixRaw[i]);
874     }
875     // proper mean - use only pixels inside mask. we use cutImageRaw that contains sums of
876     // intensities for each location of kernel and numofpixRaw that contains number of masked pixels
877     RealMatrix meanseedFg = new Array2DRowRealMatrix(cutImage.getHeight(), cutImage.getWidth());
878     int count = 0;
879     for (int r = 0; r < cutImage.getHeight(); r++) {
880       for (int c = 0; c < cutImage.getWidth(); c++) {
881         if (numofpix.getPixelValue(c, r) != 0) { // skip 0 pixels to not divide by 0
882           meanseedFg.setEntry(r, c, ((double) cutImageRaw[count] / numofpixRaw[count]));
883         } else {
884           meanseedFg.setEntry(r, c, 0.0); // if no pixels in mask for current r,c - set mean to 0
885         }
886         count++;
887       }
888     }
889 
890     if (QuimP.SUPER_DEBUG) { // save intermediate results
891       LOGGER.debug("Saving intermediate results");
892       String tmpdir = System.getProperty("java.io.tmpdir") + File.separator;
893       IJ.saveAsTiff(new ImagePlus("", numofpix), tmpdir + "maskc_QuimP.tif");
894       IJ.saveAsTiff(new ImagePlus("", cutImage), tmpdir + "imagecc_QuimP.tif");
895       IJ.saveAsTiff(new ImagePlus("", QuimPArrayUtils.realMatrix2ImageProcessor(meanseedFg)),
896               tmpdir + "meanseedFg_QuimP.tif");
897       LOGGER.trace("meanseedFg M[183;289] " + meanseedFg.getEntry(183 - 1, 289 - 1));
898       LOGGER.trace("meanseedFg M[242;392] " + meanseedFg.getEntry(242 - 1, 392 - 1));
899       LOGGER.trace("cutImage M[192;279] " + cutImage.getPixelValue(279 - 1, 192 - 1));
900     }
901     return meanseedFg;
902   }
903 
904   /*
905    * //!>
906    * @startuml doc-files/RandomWalkSegmentation_5_UML.png
907    * start
908    * :Convert seeds to coordinates;
909    * :Combine FG and BG maps together;
910    * repeat :solve for object **i**
911    * :Set object **i** as FG;
912    * :Set all other objects as BG;
913    * if (local mean?) then (yes)
914    *  :compute **local** mean for **FG**;
915    *  note left
916    *  Result is an image
917    *  end note
918    *  :compute **global** mean for **BG**;
919    *  note left
920    *  Result is a number
921    *  end note
922    *  :subtract FG and BG means\nfrom image;
923    * else (no)
924    *  :compute **global** mean for **FG**;
925    *  :compute **global** mean for **BG**;
926    *  :subtract FG and BG means\nfrom image;
927    * endif
928    * -> Here we have Image-meanseed;
929    * :compute normalised\n""(Image-meanseed).^2"";
930    * note left
931    * Square ""Image-meanseed"" and
932    * normalise to maximal theoretical
933    * intensity value
934    * end note
935    * :compute weights;
936    * note right
937    * Based on intensity of
938    * pixels in stencil and 
939    * intensity gradient
940    * end note
941    * :compute average of weights;
942    * note left
943    * Separately for horizontal
944    * and vertical differences.
945    * Used later for equalising
946    * grid
947    * end note
948    * :compute diffusion constant;
949    * note left
950    * To obey stability
951    * criterion
952    * end note
953    * :compute averaged weights;
954    * note right
955    * Equalising differentiation
956    * grid.
957    * end note
958    * :compute FG;
959    * note left
960    * Solving Laplace equation
961    * by Euler method (loop, number of iter
962    * depends on sweep number)
963    * end note
964    * :Store map;
965    * note right
966    * Generally algorithm assumes that 
967    * last map is BG and can detect it 
968    * and store under proper key in Seeds
969    * end note
970    * repeat while (more objects?)
971    * stop
972    * @enduml
973    * //!<
974    */
975   /**
976    * Run Random Walk segmentation.
977    * 
978    * <p>The activity diagram for solver is as follows:
979    * <img src="doc-files/RandomWalkSegmentation_5_UML.png"/><br>
980    * 
981    * <p>Note 1: that solver treats FG and BG seeds like equal objects. There is no difference
982    * between foreground and background seeds.
983    * 
984    * <p>Note 2:number of iterations for BG object is limited to maximum number of iterations that
985    * occurred for FG objects.
986    * 
987    * @param seeds seed array returned from {@link SeedProcessor}
988    * @param gradients pre-computed gradients returned from {@link #precomputeGradients()}
989    * @return Computed probabilities for background and foreground. Returned structure can be also
990    *         empty if there are not FG seeds provided on input (no FG map in {@link Seeds}) or may
991    *         not contain BG map.
992    */
993   protected ProbabilityMaps solver(Seeds seeds, RealMatrix[] gradients) {
994     ImageStack debugPm = null;
995     RealMatrix diffIfg = null; // normalised squared differences to mean seed intensities for FG
996     // store number of iterations performed for each object. These numbers are used for stopping
997     // iterations earlier for background object
998     ArrayList<Integer> iterations = new ArrayList<>();
999 
1000     ProbabilityMapsp/plugin/randomwalk/ProbabilityMaps.html#ProbabilityMaps">ProbabilityMaps ret = new ProbabilityMaps(); // keep output probab map for each object
1001 
1002     if (seeds.get(SeedTypes.FOREGROUNDS) == null) { // if no FG maps (e.g. all disappeared)
1003       return ret;
1004     }
1005     // seed images as list of points
1006     List<List<Point>> seedsPointsFg = seeds.convertToList(SeedTypes.FOREGROUNDS);
1007     // user selected background (it is solved like other objects but e.g. local mean does not apply
1008     // for it so we need to know what was selected by user as background). There is possible
1009     // that there will be no BCK key, if input is given as GraySclale image
1010     List<List<Point>> userBckPoints = seeds.convertToList(SeedTypes.BACKGROUND);
1011     // add background at the end - it will be solved as regular object
1012     seedsPointsFg.addAll(userBckPoints);
1013     // background points used in conjunction with current foreground. Background points are all
1014     // other points which are not current foreground (e.g. other cells + user background, or all
1015     // cells if we solve for user background)
1016     List<List<Point>> seedsPointsBg = new ArrayList<>();
1017 
1018     // solve problem for each label in FOREGROUNDS, other labels (if any) are merged with BACKGROUND
1019     for (int cell = 0; cell < seedsPointsFg.size(); cell++) { // over each seed label
1020       // some maps for FOREGROUNDS key can be empty, so lists will be too. Note that decodeSeeds
1021       // throw exception when all maps for specified key are empty. Other situations are allowed.
1022       if (seedsPointsFg.get(cell).isEmpty()) {
1023         continue;
1024       }
1025       // make copy of objects seeds - need of removing current one and integrate remaining with bck
1026       ArrayList<List<Point>> tmpSeedsPointsFg = new ArrayList<List<Point>>(seedsPointsFg);
1027       tmpSeedsPointsFg.remove(cell); // remove current object seed
1028       // clear Bg, it always contains all objects except current (seedsPointsFg[cell])
1029       seedsPointsBg.clear();
1030       seedsPointsBg.addAll(tmpSeedsPointsFg); // add all remaining foregrounds to current bck
1031 
1032       // decide whether to use local mean or global mean. Local mean is computed within square mask
1033       // of configurable size whereas the global mean is a mean intensity of all seeded pixels.
1034       // Local mean evaluated only for FG objects.
1035       if (params.useLocalMean && seeds.get(SeedTypes.ROUGHMASK) != null
1036               && !userBckPoints.contains(seedsPointsFg.get(cell))) { // skip BG object
1037         RealMatrix localMeanFg =
1038                 getMeanSeedLocal(seeds.get(SeedTypes.ROUGHMASK, 1), params.localMeanMaskSize);
1039         diffIfg = image.subtract(localMeanFg);
1040       } else { // global for whole seeds
1041         // compute intensity means for image points labelled by seeds
1042         double meanseed = getMeanSeedGlobal(seedsPointsFg.get(cell));
1043         LOGGER.debug("meanseed_fg=" + meanseed);
1044         // compute normalised squared differences to mean seed intensities (Image-meanseed).^2
1045         diffIfg = image.scalarAdd(-meanseed);
1046       }
1047       // normalize (Image-meanseed).^2 to maximal (theoretical) value which is 255^2 for 8-bit
1048       // images. Have it as private field as we support 16 images as well
1049       diffIfg.walkInOptimizedOrder(new MatrixElementPowerDiv(maxTheoreticalIntSqr));
1050       LOGGER.trace("fseeds size: " + seedsPointsFg.get(cell).size());
1051       LOGGER.trace("bseeds size: " + seedsPointsBg.stream().mapToInt(p -> p.size()).sum());
1052 
1053       // compute weights for diffusion in all four directions, dependent on local gradients and
1054       // differences to mean intensities of seeds, for FG maps
1055       Array2DRowRealMatrix wrfg = (Array2DRowRealMatrix) computeweights(diffIfg, gradients[0]);
1056       Array2DRowRealMatrix wlfg = (Array2DRowRealMatrix) computeweights(diffIfg, gradients[2]);
1057       Array2DRowRealMatrix wtfg = (Array2DRowRealMatrix) computeweights(diffIfg, gradients[1]);
1058       Array2DRowRealMatrix wbfg = (Array2DRowRealMatrix) computeweights(diffIfg, gradients[3]);
1059 
1060       // compute averaged weights, left/right and top/bottom used when computing second spatial
1061       // derivative from first one, avgwx_fg = 0.5*(wl_fg+wr_fg) - for FG
1062       RealMatrix avgwxfg = wlfg.add(wrfg); // wl_fg+wr_fg - (left+right)
1063       avgwxfg.walkInOptimizedOrder(new MatrixElementMultiply(0.5)); // 0.5*(wl_fg+wr_fg)
1064       RealMatrix avgwyfg = wtfg.add(wbfg); // wt_fg+wb_fg - (top+bottom)
1065       avgwyfg.walkInOptimizedOrder(new MatrixElementMultiply(0.5)); // 0.5*(wt_fg+wb_fg)
1066 
1067       // Compute diffusion coefficient that will obey stability criterion
1068       double diffusion = getDiffusionConst(wrfg, wlfg, wtfg, wbfg, avgwxfg, avgwyfg);
1069       LOGGER.debug("D=" + diffusion);
1070 
1071       // get average "distance" between weights multiplying w = w.*avgw, this is only for
1072       // optimisation purposes.
1073       // does not apply for FG if we use local mean, applied for BG always (better results)
1074       if (params.useLocalMean == false || userBckPoints.contains(seedsPointsFg.get(cell))) {
1075         wrfg.walkInOptimizedOrder(new MatrixDotProduct(avgwxfg));
1076         wlfg.walkInOptimizedOrder(new MatrixDotProduct(avgwxfg));
1077         wtfg.walkInOptimizedOrder(new MatrixDotProduct(avgwyfg));
1078         wbfg.walkInOptimizedOrder(new MatrixDotProduct(avgwyfg));
1079       }
1080 
1081       // initialize FG probability maps, they are outputs from this routine
1082       Array2DRowRealMatrix fg =
1083               new Array2DRowRealMatrix(image.getRowDimension(), image.getColumnDimension());
1084       // this temporary array will keep solution from n-1 iteration used for computing rel error
1085       double[][] tmpFglast2d = new double[image.getRowDimension()][image.getColumnDimension()];
1086 
1087       // dereferencing arrays, all computations are done on underlying [][] arrays but not on
1088       // RelaMatrix objects, optimisation again
1089       double[][] wrfg2d = wrfg.getDataRef(); // weight to right for FG
1090       double[][] wlfg2d = wlfg.getDataRef(); // weight to left for FG
1091       double[][] wtfg2d = wtfg.getDataRef(); // weight to top for FG
1092       double[][] wbfg2d = wbfg.getDataRef(); // weight to bottom for FG
1093 
1094       double[][] fg2d = fg.getDataRef(); // reference to FG probabilities
1095 
1096       StoppedBy stoppedReason = StoppedBy.ITERATIONS; // default assumption
1097       int i; // iteration counter
1098       // compute correct number of iterations. Second sweep uses 0.5*user
1099       int iter;
1100       // use less iterations when diffuse background. Background needs much more iterations to reach
1101       // specified relError and after weighting it dominates leaving only original object seed as
1102       // segmented object. Here we stop segmenting background after certain number of iterations but
1103       // not relErr. This is how we have it solved in MAtlab
1104       if (true && (userBckPoints.contains(seedsPointsFg.get(cell)) && iterations.size() > 0)) {
1105         // just use average of iters for BCK
1106         iter = iterations.stream().mapToInt(Integer::intValue).max().getAsInt();
1107         // FIXME This can be disabled, then BCK will need more iterations but sometimes results are
1108         // better
1109         // potential pitfall is if user mark BG far from cell, then small number of iters is not
1110         // enough to flood whole background (but it will work because during comparison bck is on 0
1111         // and FG segmentation rather does not leave object
1112         iter /= (currentSweep + 1);
1113       } else { // object - use specified number of iters
1114         iter = params.iter / (currentSweep + 1);
1115       }
1116       // main loop here we simulate diffusion process in time
1117       outerloop: for (i = 0; i < iter; i++) {
1118         if (i % relErrStep == 0) {
1119           LOGGER.info("Iter: " + i);
1120         } else {
1121           LOGGER.trace("Iter: " + i);
1122         }
1123         // fill seed pixels explicitly with probability 1 for FG and BG
1124         ArrayRealVector tmp = new ArrayRealVector(1); // filled with 0, setValues() needs that input
1125         double[] tmpref = tmp.getDataRef(); // just get reference to underlying array
1126         // set probability to 0 of being FG for BG seeds and vice versa
1127         for (List<Point> b : seedsPointsBg) {
1128           setValues(fg, b, tmp); // set 0 all seed pixel currently considered as BG
1129         }
1130         // set probability to 1 for of being FG for FG seeds and vice versa
1131         tmpref[0] = 1;
1132         setValues(fg, seedsPointsFg.get(cell), tmp); // set 1
1133         tmp = null;
1134 
1135         // ------------------- Computation of FG map ----------------------------------------------
1136         // groups for long term for FG. Get all four neighbours to current pixel
1137         Array2DRowRealMatrix fgcircright = (Array2DRowRealMatrix) circshift(fg, RIGHT);
1138         Array2DRowRealMatrix fgcircleft = (Array2DRowRealMatrix) circshift(fg, LEFT);
1139         Array2DRowRealMatrix fgcirctop = (Array2DRowRealMatrix) circshift(fg, TOP);
1140         Array2DRowRealMatrix fgcircbottom = (Array2DRowRealMatrix) circshift(fg, BOTTOM);
1141 
1142         // extract required references from RealMatrix objects.
1143         double[][] fgcircright2d = fgcircright.getDataRef(); // reference to FG prob shifted right
1144         double[][] fgcircleft2d = fgcircleft.getDataRef(); // reference to FG probab. shifted left
1145         double[][] fgcirctop2d = fgcirctop.getDataRef(); // reference to FG probab. shifted top
1146         double[][] fgcircbottom2d = fgcircbottom.getDataRef(); // reference to FG prob. shifted bot
1147 
1148         AtomicInteger at = new AtomicInteger(stoppedReason.getValue());
1149         // Traverse all pixels in FG map and update them according to diffusion from 4 neighbours of
1150         // each pixel
1151         int nrows = fg.getRowDimension();
1152         int ncols = fg.getColumnDimension();
1153         IntStream.range(0, nrows * ncols).parallel().forEach(ii -> {
1154           int c = ii % ncols;
1155           int r = ii / ncols;
1156           fg2d[r][c] += params.dt * (diffusion * (((fgcircright2d[r][c] - fg2d[r][c]) / wrfg2d[r][c]
1157                   - (fg2d[r][c] - fgcircleft2d[r][c]) / wlfg2d[r][c])
1158                   + ((fgcirctop2d[r][c] - fg2d[r][c]) / wtfg2d[r][c]
1159                           - (fg2d[r][c] - fgcircbottom2d[r][c]) / wbfg2d[r][c])));
1160           // - params.gamma[currentSweep] * fg2d[r][c] * bg2d[r][c] - disabled
1161           // validate numerical quality. This flags will stop iterations (break outerloop) but
1162           // after updating all pixels in FG maps
1163           if (Double.isNaN(fg2d[r][c])) { // if at least one NaN in solution
1164             at.set(StoppedBy.NANS.getValue());
1165           }
1166           if (Double.isInfinite(fg2d[r][c])) { // or Inf (will overwrite previous flag of course)
1167             at.set(StoppedBy.INFS.getValue());
1168           }
1169         });
1170 
1171         // Test state of the flag. Stop iteration if there is NaN or Inf. Iterations are stopped
1172         // after full looping over FG maps.
1173         if (stoppedReason == StoppedBy.NANS || stoppedReason == StoppedBy.INFS) {
1174           break outerloop;
1175         }
1176         // check error every relErrStep number of iterations
1177         if ((i + 1) % relErrStep == 0) {
1178           double rele = computeRelErr(tmpFglast2d, fg2d);
1179           LOGGER.info("Relative error for object " + cell + " = " + rele);
1180           if (rele < params.relim[currentSweep]) {
1181             stoppedReason = StoppedBy.RELERR;
1182             // store number of iters for object, required for limiting iterations for BCK
1183             if (!userBckPoints.contains(seedsPointsFg.get(cell))) {
1184               iterations.add(i);
1185             }
1186             break outerloop;
1187           }
1188         }
1189         // store probabilities over iterations
1190         if (QuimP.SUPER_DEBUG) {
1191           debugPm =
1192                   (debugPm == null) ? new ImageStack(fg.getColumnDimension(), fg.getRowDimension())
1193                           : debugPm;
1194           if (i > 1000) {
1195             if (i % 50 == 0) {
1196               debugPm.addSlice(QuimPArrayUtils.realMatrix2ImageProcessor(fg));
1197             }
1198           } else {
1199             debugPm.addSlice(QuimPArrayUtils.realMatrix2ImageProcessor(fg));
1200           }
1201         }
1202         // remember FG map for this iteration to use it to compute relative error in next iteration
1203         QuimPArrayUtils.copy2darray(fg2d, tmpFglast2d);
1204       } // iter
1205       LOGGER.info("Sweep " + currentSweep + " for object " + cell + " stopped by " + stoppedReason
1206               + " after " + i + " iteration from " + iter);
1207       if (userBckPoints.contains(seedsPointsFg.get(cell))) { // we processed background seeds
1208         ret.put(SeedTypes.BACKGROUND, fg); // store it in separate key - needed for proper compar.
1209       } else {
1210         ret.put(SeedTypes.FOREGROUNDS, fg);
1211       }
1212       // save stack of probability maps (over iterations) for each processed object separately
1213       if (QuimP.SUPER_DEBUG) {
1214         if (debugPm != null) {
1215           ImagePlus debugIm = new ImagePlus("debug", debugPm);
1216           String tmp = System.getProperty("java.io.tmpdir");
1217           Path p = Paths.get(tmp, "Rw_ProbMap-cell_" + cell);
1218           IJ.saveAsTiff(debugIm, p.toString());
1219           debugPm = null; // next object
1220         }
1221       }
1222     } // cell
1223     return ret;
1224   }
1225 
1226   /**
1227    * Compute relative error between current foreground and foreground from previous iteration.
1228    * 
1229    * <p>Assumes that both matrixes have the same size and they are regular arrays.
1230    * 
1231    * @param fglast foreground matrix from previous iteration
1232    * @param fg current foreground
1233    * @return relative mean error sum[2* |fg - fglast|/(fg + fglast)]/numofel
1234    */
1235   double computeRelErr(double[][] fglast, double[][] fg) {
1236     int rows = fglast.length;
1237     int cols = fglast[0].length;
1238     double rel = 0; // result to sum up
1239     double tmp = 0; // temporary - current element
1240     for (int r = 0; r < rows; r++) { // iterate over every element
1241       for (int c = 0; c < cols; c++) {
1242         double denominator = fg[r][c] + fglast[r][c]; // compute denominator
1243         if (denominator == 0.0) { // not divide by 0
1244           tmp = 0.0;
1245         } else { // get relative error
1246           tmp = 2 * Math.abs(fg[r][c] - fglast[r][c]) / denominator;
1247         }
1248         rel += tmp; // sum it up to get mean at the end
1249       }
1250     }
1251     double rele = rel / (rows * cols);
1252     return rele; // return mean error
1253   }
1254 
1255   /**
1256    * Compute diffusion weights using difference to mean intensity and gradient.
1257    * 
1258    * @param diffI2 normalized squared differences to mean seed intensities
1259    * @param grad2 normalized the squared gradients by the maximum gradient
1260    * @return wr_fg = exp(alpha*diffI2 + beta*grad2);
1261    */
1262   private RealMatrix computeweights(RealMatrix diffI2, RealMatrix grad2) {
1263     double alpha = params.alpha; // user provided segmentation parameter
1264     double beta = params.beta; // user provided segmentation parameter
1265     double[][] diffI2d; // temporary references to intensity to mean values
1266     double[][] grad22d; // and gradient
1267     // convert RealMatrix to 2D array, approach depends on the RealMatrix type (see RealMatrix doc)
1268     if (diffI2 instanceof Array2DRowRealMatrix) {
1269       diffI2d = ((Array2DRowRealMatrix) diffI2).getDataRef();
1270     } else {
1271       diffI2d = diffI2.getData();
1272     }
1273     if (grad2 instanceof Array2DRowRealMatrix) {
1274       grad22d = ((Array2DRowRealMatrix) grad2).getDataRef();
1275     } else {
1276       grad22d = grad2.getData();
1277     }
1278     Array2DRowRealMatrix w =
1279             new Array2DRowRealMatrix(diffI2.getRowDimension(), diffI2.getColumnDimension()); // out
1280     double[][] w2d = w.getDataRef(); // reference of w to skip get/set element from RealMatrix
1281     // calculate weights
1282     for (int r = 0; r < diffI2.getRowDimension(); r++) {
1283       for (int c = 0; c < diffI2.getColumnDimension(); c++) {
1284         w2d[r][c] = Math.exp(diffI2d[r][c] * alpha + grad22d[r][c] * beta);
1285       }
1286     }
1287 
1288     return w;
1289   }
1290 
1291   /**
1292    * Multiply two matrices (element by element) and return minimum value from result.
1293    * 
1294    * @param a left operand (matrix)
1295    * @param b right operand (matrix of size of left operand)
1296    * @return min(a.*b)
1297    * @see #getDiffusionConst(RealMatrix, RealMatrix, RealMatrix, RealMatrix, RealMatrix, RealMatrix)
1298    */
1299   private double getMinofDotProduct(RealMatrix a, RealMatrix b) {
1300     RealMatrix cp = a.copy();
1301     cp.walkInOptimizedOrder(new MatrixDotProduct(b));
1302     return QuimPArrayUtils.getMin(cp);
1303   }
1304 
1305   /**
1306    * Compute diffusion constant that obeys stability criterion.
1307    * 
1308    * <p>Diffusion constant that meets CFL stability criterion (when solving Euler scheme):
1309    * D*dt/(dx^2) should be <<1/4
1310    * It is computed in the following way:
1311    * 
1312    * <pre>
1313    * <code>
1314    * drl2 = min ( min(min (wr_fg.*avgwx_fg)) , min(min (wl_fg.*avgwx_fg)) );
1315    * dtb2 = min ( min(min (wt_fg.*avgwy_fg)) , min(min (wb_fg.*avgwy_fg)) );
1316    * 
1317    * D=0.25*min(drl2,dtb2);
1318    * </code>
1319    * </pre>
1320    * 
1321    * @param wrfg weights for diffusion in right direction
1322    * @param wlfg weights for diffusion in left direction
1323    * @param wtfg weights for diffusion in top direction
1324    * @param wbfg weights for diffusion in bottom direction
1325    * @param avgwxfg averaged left -- right weights
1326    * @param avgwyfg averaged bottom -- top weights
1327    * @return diffusion constant that obeys stability criterion
1328    */
1329   private double getDiffusionConst(RealMatrix wrfg, RealMatrix wlfg, RealMatrix wtfg,
1330           RealMatrix wbfg, RealMatrix avgwxfg, RealMatrix avgwyfg) {
1331     // diffusion constant along x
1332     double tmp1 = getMinofDotProduct(wrfg, avgwxfg); // min(wrfg .* avgwxfg)
1333     double tmp2 = getMinofDotProduct(wlfg, avgwxfg); // min(wlfg .* avgwxfg)
1334     double drl2 = tmp1 < tmp2 ? tmp1 : tmp2; // min(tmp1,tmp2)
1335     // diffusion constant along y
1336     tmp1 = getMinofDotProduct(wtfg, avgwyfg); // min(wtfg .* avgwyfg)
1337     tmp2 = getMinofDotProduct(wbfg, avgwyfg); // min(wbfg .* avgwyfg)
1338     double dtb2 = tmp1 < tmp2 ? tmp1 : tmp2; // min(tmp1,tmp2)
1339     double diffusion = drl2 < dtb2 ? drl2 : dtb2; // D = min(drl2,dtb2)
1340     diffusion *= 0.25; // D = 0.25*min(drl2,dtb2)
1341     LOGGER.debug("drl2=" + drl2 + " dtb2=" + dtb2);
1342     return diffusion;
1343   }
1344 
1345   /**
1346    * Set maximum theoretical squared intensity depending on image type.
1347    * 
1348    * @param ip segmented image.
1349    */
1350   private void setMaxTheoreticalIntSqr(ImageProcessor ip) {
1351     switch (ip.getBitDepth()) {
1352       case 16:
1353         maxTheoreticalIntSqr = 65535 * 65535;
1354         break;
1355       case 8:
1356       default:
1357         maxTheoreticalIntSqr = 255 * 255; // default in case of RealMatrix on input
1358     }
1359   }
1360 
1361   /**
1362    * Return values from in matrix that are on indexes ind.
1363    * 
1364    * @param in Input matrix 2D
1365    * @param ind List of indexes
1366    * @return Values that are on indexes ind
1367    */
1368   ArrayRealVector getValues(RealMatrix in, List<Point> ind) {
1369     ArrayRealVector out = new ArrayRealVector(ind.size());
1370     int l = 0;
1371     for (Point p : ind) {
1372       out.setEntry(l++, in.getEntry(p.row, p.col));
1373     }
1374     return out;
1375   }
1376 
1377   /**
1378    * Set values from val on indexes ind in array in.
1379    * 
1380    * @param in Input matrix. Will be modified
1381    * @param ind List of indexes
1382    * @param val List of values, length must be the same as ind or 1
1383    */
1384   void setValues(RealMatrix in, List<Point> ind, ArrayRealVector val) {
1385     if (ind.size() != val.getDimension() && val.getDimension() != 1) {
1386       throw new InvalidParameterException(
1387               "Vector with data must contain 1 element or the same as indexes");
1388     }
1389     int delta; // step we move in val across elements, can be 1 or 0
1390     int l = 0; // will point current element in val
1391     if (val.getDimension() == 1) {
1392       delta = 0; // still point to the same element (because it is only one)
1393     } else {
1394       delta = 1; // move to the next one
1395     }
1396     for (Point p : ind) { // iterate over points to fill with values val
1397       in.setEntry(p.row, p.col, val.getDataRef()[l]);
1398       l += delta; // skip to next value (points are iterated in for)
1399     }
1400   }
1401 
1402   /**
1403    * Return probability maps for each object (foreground is last). Valid after running the plugin.
1404    * 
1405    * @return probability maps
1406    */
1407   public ProbabilityMaps getProbabilityMaps() {
1408     return solved;
1409   }
1410 
1411   /**
1412    * Sub and then div in-place this matrix and another.
1413    * 
1414    * @author p.baniukiewicz
1415    *
1416    */
1417   static class MatrixDotSubDiv implements RealMatrixChangingVisitor {
1418 
1419     /** The sub. */
1420     RealMatrix sub;
1421 
1422     /** The div. */
1423     RealMatrix div;
1424 
1425     /**
1426      * Instantiates a new matrix dot sub div.
1427      *
1428      * @param sub the sub
1429      * @param div the div
1430      */
1431     public MatrixDotSubDiv(RealMatrix sub, RealMatrix div) {
1432       this.sub = sub;
1433       this.div = div;
1434     }
1435 
1436     /*
1437      * (non-Javadoc)
1438      * 
1439      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1440      */
1441     @Override
1442     public double end() {
1443       return 0;
1444     }
1445 
1446     /*
1447      * (non-Javadoc)
1448      * 
1449      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1450      * int)
1451      */
1452     @Override
1453     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1454 
1455     }
1456 
1457     /*
1458      * (non-Javadoc)
1459      * 
1460      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1461      */
1462     @Override
1463     public double visit(int arg0, int arg1, double arg2) {
1464 
1465       return (arg2 - sub.getEntry(arg0, arg1)) / div.getEntry(arg0, arg1);
1466     }
1467 
1468   }
1469 
1470   /**
1471    * Perform element-wise multiplication by value (.*val in Matlab). Done in-place.
1472    * 
1473    * @author p.baniukiewicz
1474    */
1475   static class MatrixElementMultiply implements RealMatrixChangingVisitor {
1476 
1477     /** The multiplier. */
1478     private double multiplier;
1479 
1480     /**
1481      * Assign multiplier.
1482      * 
1483      * @param d multiplier
1484      */
1485     MatrixElementMultiply(double d) {
1486       this.multiplier = d;
1487     }
1488 
1489     /*
1490      * (non-Javadoc)
1491      * 
1492      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1493      */
1494     @Override
1495     public double end() {
1496       return 0;
1497     }
1498 
1499     /*
1500      * (non-Javadoc)
1501      * 
1502      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1503      * int)
1504      */
1505     @Override
1506     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1507     }
1508 
1509     /**
1510      * Multiply entry by value.
1511      *
1512      * @param arg0 the arg 0
1513      * @param arg1 the arg 1
1514      * @param arg2 the arg 2
1515      * @return the double
1516      */
1517     @Override
1518     public double visit(int arg0, int arg1, double arg2) {
1519       return arg2 * multiplier;
1520     }
1521 
1522   }
1523 
1524   /**
1525    * Perform element-wise division by value (./val in Matlab). Done in-place.
1526    * 
1527    * @author p.baniukiewicz
1528    */
1529   static class MatrixElementDivide implements RealMatrixChangingVisitor {
1530 
1531     /** The div. */
1532     private double div;
1533 
1534     /**
1535      * Assign multiplier.
1536      * 
1537      * @param d multiplier
1538      */
1539     MatrixElementDivide(double d) {
1540       this.div = d;
1541     }
1542 
1543     /*
1544      * (non-Javadoc)
1545      * 
1546      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1547      */
1548     @Override
1549     public double end() {
1550       return 0;
1551     }
1552 
1553     /*
1554      * (non-Javadoc)
1555      * 
1556      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1557      * int)
1558      */
1559     @Override
1560     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1561     }
1562 
1563     /**
1564      * Multiply entry by value.
1565      *
1566      * @param arg0 the arg 0
1567      * @param arg1 the arg 1
1568      * @param arg2 the arg 2
1569      * @return the double
1570      */
1571     @Override
1572     public double visit(int arg0, int arg1, double arg2) {
1573       return arg2 / div;
1574     }
1575 
1576   }
1577 
1578   /**
1579    * Perform element-wise exp. Done in-place.
1580    * 
1581    * @author p.baniukiewicz
1582    */
1583   static class MatrixElementExp implements RealMatrixChangingVisitor {
1584 
1585     /*
1586      * (non-Javadoc)
1587      * 
1588      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1589      */
1590     @Override
1591     public double end() {
1592       return 0;
1593     }
1594 
1595     /*
1596      * (non-Javadoc)
1597      * 
1598      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1599      * int)
1600      */
1601     @Override
1602     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1603 
1604     }
1605 
1606     /*
1607      * (non-Javadoc)
1608      * 
1609      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1610      */
1611     @Override
1612     public double visit(int arg0, int arg1, double arg2) {
1613       return Math.exp(arg2);
1614     }
1615 
1616   }
1617 
1618   /**
1619    * Perform element-wise power (.^2 in Matlab). Done in-place.
1620    * 
1621    * @author p.baniukiewicz
1622    */
1623   static class MatrixElementPower implements RealMatrixChangingVisitor {
1624 
1625     /*
1626      * (non-Javadoc)
1627      * 
1628      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1629      */
1630     @Override
1631     public double end() {
1632       return 0;
1633     }
1634 
1635     /*
1636      * (non-Javadoc)
1637      * 
1638      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1639      * int)
1640      */
1641     @Override
1642     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1643     }
1644 
1645     /**
1646      * Multiply entry by itself.
1647      *
1648      * @param arg0 the arg 0
1649      * @param arg1 the arg 1
1650      * @param arg2 the arg 2
1651      * @return the double
1652      */
1653     @Override
1654     public double visit(int arg0, int arg1, double arg2) {
1655       return arg2 * arg2;
1656     }
1657   }
1658 
1659   /**
1660    * Multiply in-place this matrix by another.
1661    * 
1662    * @author p.baniukiewicz
1663    *
1664    */
1665   static class MatrixDotProduct implements RealMatrixChangingVisitor {
1666 
1667     /** The matrix. */
1668     RealMatrix matrix;
1669 
1670     /**
1671      * Instantiates a new matrix dot product.
1672      *
1673      * @param m the m
1674      */
1675     public MatrixDotProduct(RealMatrix m) {
1676       this.matrix = m;
1677     }
1678 
1679     /*
1680      * (non-Javadoc)
1681      * 
1682      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1683      */
1684     @Override
1685     public double end() {
1686       return 0;
1687     }
1688 
1689     /*
1690      * (non-Javadoc)
1691      * 
1692      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1693      * int)
1694      */
1695     @Override
1696     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1697       if (matrix.getColumnDimension() != arg1 || matrix.getRowDimension() != arg0) {
1698         throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
1699                 matrix.getColumnDimension(), arg0, arg1);
1700       }
1701 
1702     }
1703 
1704     /*
1705      * (non-Javadoc)
1706      * 
1707      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1708      */
1709     @Override
1710     public double visit(int arg0, int arg1, double arg2) {
1711 
1712       return arg2 * matrix.getEntry(arg0, arg1);
1713     }
1714 
1715   }
1716 
1717   /**
1718    * Divide in-place this matrix by another.
1719    * 
1720    * @author p.baniukiewicz
1721    *
1722    */
1723   static class MatrixDotDiv implements RealMatrixChangingVisitor {
1724 
1725     /** The matrix. */
1726     RealMatrix matrix;
1727 
1728     /**
1729      * Instantiates a new matrix dot div.
1730      *
1731      * @param m the m
1732      */
1733     public MatrixDotDiv(RealMatrix m) {
1734       this.matrix = m;
1735     }
1736 
1737     /*
1738      * (non-Javadoc)
1739      * 
1740      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1741      */
1742     @Override
1743     public double end() {
1744       return 0;
1745     }
1746 
1747     /*
1748      * (non-Javadoc)
1749      * 
1750      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1751      * int)
1752      */
1753     @Override
1754     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1755       if (matrix.getColumnDimension() != arg1 || matrix.getRowDimension() != arg0) {
1756         throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
1757                 matrix.getColumnDimension(), arg0, arg1);
1758       }
1759 
1760     }
1761 
1762     /*
1763      * (non-Javadoc)
1764      * 
1765      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1766      */
1767     @Override
1768     public double visit(int arg0, int arg1, double arg2) {
1769       return arg2 / matrix.getEntry(arg0, arg1);
1770     }
1771 
1772   }
1773 
1774   /**
1775    * Divide matrix by matrix (element by element) setting 0 to result if divider is 0. Prevent NaNs.
1776    * 
1777    * <p>It performs the operation:
1778    * 
1779    * <pre>
1780    * <code>
1781    * A = [....];
1782    * B = [....];
1783    * R = A./B;
1784    * R(isnan(R)) = 0;
1785    * </code>
1786    * </pre>
1787    * 
1788    * @author p.baniukiewicz
1789    *
1790    */
1791   static class MatrixDotDivN extends MatrixDotDiv {
1792 
1793     /**
1794      * Instantiates a new matrix dot div N.
1795      *
1796      * @param m the m
1797      */
1798     public MatrixDotDivN(RealMatrix m) {
1799       super(m);
1800     }
1801 
1802     /*
1803      * (non-Javadoc)
1804      * 
1805      * @see
1806      * com.github.celldynamics.quimp.plugin.randomwalk.RandomWalkSegmentation.MatrixDotDiv#visit(
1807      * int, int, double)
1808      */
1809     @Override
1810     public double visit(int arg0, int arg1, double arg2) {
1811       double entry = matrix.getEntry(arg0, arg1);
1812       if (entry != 0.0) {
1813         return arg2 / matrix.getEntry(arg0, arg1);
1814       } else {
1815         return 0.0;
1816       }
1817     }
1818   }
1819 
1820   /**
1821    * Perform operation this = this>d*matrix?1:0.
1822    * 
1823    * @author p.baniukiewicz
1824    *
1825    */
1826   static class MatrixCompareWeighted implements RealMatrixChangingVisitor {
1827 
1828     /** The matrix. */
1829     RealMatrix matrix;
1830 
1831     /** The weight. */
1832     double weight;
1833 
1834     /**
1835      * Instantiates a new matrix compare weighted.
1836      *
1837      * @param matrix the matrix
1838      * @param weight the weight
1839      */
1840     public MatrixCompareWeighted(RealMatrix matrix, double weight) {
1841       this.matrix = matrix;
1842       this.weight = weight;
1843     }
1844 
1845     /*
1846      * (non-Javadoc)
1847      * 
1848      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1849      */
1850     @Override
1851     public double end() {
1852       return 0;
1853     }
1854 
1855     /*
1856      * (non-Javadoc)
1857      * 
1858      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1859      * int)
1860      */
1861     @Override
1862     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1863       if (matrix.getColumnDimension() != arg1 || matrix.getRowDimension() != arg0) {
1864         throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
1865                 matrix.getColumnDimension(), arg0, arg1);
1866       }
1867     }
1868 
1869     /*
1870      * (non-Javadoc)
1871      * 
1872      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1873      */
1874     @Override
1875     public double visit(int arg0, int arg1, double arg2) {
1876       if (arg2 > matrix.getEntry(arg0, arg1) * weight) {
1877         return 1.0;
1878       } else {
1879         return 0.0;
1880       }
1881     }
1882 
1883   }
1884 
1885   /**
1886    * Add in-place this matrix to another.
1887    * 
1888    * <p>src/test/Resources-static/Matlab/rw_laplace4_java_base.m This is source file of segmentation
1889    * in Matlab that was a base for RandomWalkSegmentation Implementation
1890    * 
1891    * @author p.baniukiewicz
1892    *
1893    */
1894   static class MatrixDotAdd implements RealMatrixChangingVisitor {
1895 
1896     /** The matrix. */
1897     RealMatrix matrix;
1898 
1899     /**
1900      * Instantiates a new matrix dot add.
1901      *
1902      * @param m the m
1903      */
1904     public MatrixDotAdd(RealMatrix m) {
1905       this.matrix = m;
1906     }
1907 
1908     /*
1909      * (non-Javadoc)
1910      * 
1911      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1912      */
1913     @Override
1914     public double end() {
1915       return 0;
1916     }
1917 
1918     /*
1919      * (non-Javadoc)
1920      * 
1921      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1922      * int)
1923      */
1924     @Override
1925     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1926       if (matrix.getColumnDimension() != arg1 || matrix.getRowDimension() != arg0) {
1927         throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
1928                 matrix.getColumnDimension(), arg0, arg1);
1929       }
1930 
1931     }
1932 
1933     /*
1934      * (non-Javadoc)
1935      * 
1936      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1937      */
1938     @Override
1939     public double visit(int arg0, int arg1, double arg2) {
1940       return arg2 + matrix.getEntry(arg0, arg1);
1941     }
1942 
1943   }
1944 
1945   /**
1946    * Sub in-place this matrix to another.
1947    * 
1948    * @author p.baniukiewicz
1949    *
1950    */
1951   static class MatrixDotSub implements RealMatrixChangingVisitor {
1952 
1953     /**
1954      * Example src/test/Resources-static/Matlab/rw_laplace4_java_base.m This is source file of
1955      * segmentation in Matlab that was a base for RandomWalkSegmentation Implementation.
1956      */
1957     RealMatrix matrix;
1958 
1959     /**
1960      * Instantiates a new matrix dot sub.
1961      *
1962      * @param m the m
1963      */
1964     public MatrixDotSub(RealMatrix m) {
1965       this.matrix = m;
1966     }
1967 
1968     /*
1969      * (non-Javadoc)
1970      * 
1971      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
1972      */
1973     @Override
1974     public double end() {
1975       return 0;
1976     }
1977 
1978     /*
1979      * (non-Javadoc)
1980      * 
1981      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
1982      * int)
1983      */
1984     @Override
1985     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
1986       if (matrix.getColumnDimension() != arg1 || matrix.getRowDimension() != arg0) {
1987         throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
1988                 matrix.getColumnDimension(), arg0, arg1);
1989       }
1990 
1991     }
1992 
1993     /*
1994      * (non-Javadoc)
1995      * 
1996      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#visit(int, int, double)
1997      */
1998     @Override
1999     public double visit(int arg0, int arg1, double arg2) {
2000       return arg2 - matrix.getEntry(arg0, arg1);
2001     }
2002 
2003   }
2004 
2005   /**
2006    * Perform element-wise power (.^2 in Matlab) and then divide by val. Done in-place.
2007    * 
2008    * @author p.baniukiewicz
2009    */
2010   static class MatrixElementPowerDiv implements RealMatrixChangingVisitor {
2011 
2012     /** The val. */
2013     double val;
2014 
2015     /**
2016      * Instantiates a new matrix element power div.
2017      *
2018      * @param val the val
2019      */
2020     public MatrixElementPowerDiv(double val) {
2021       this.val = val;
2022     }
2023 
2024     /*
2025      * (non-Javadoc)
2026      * 
2027      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#end()
2028      */
2029     @Override
2030     public double end() {
2031       return 0;
2032     }
2033 
2034     /*
2035      * (non-Javadoc)
2036      * 
2037      * @see org.apache.commons.math3.linear.RealMatrixChangingVisitor#start(int, int, int, int, int,
2038      * int)
2039      */
2040     @Override
2041     public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
2042     }
2043 
2044     /**
2045      * Multiply entry by itself.
2046      *
2047      * @param arg0 the arg 0
2048      * @param arg1 the arg 1
2049      * @param arg2 the arg 2
2050      * @return the double
2051      */
2052     @Override
2053     public double visit(int arg0, int arg1, double arg2) {
2054       return (arg2 * arg2) / val;
2055     }
2056   }
2057 
2058 }