View Javadoc
1   package com.github.celldynamics.quimp.plugin.randomwalk;
2   
3   import java.awt.Color;
4   import java.awt.FileDialog;
5   import java.awt.event.ActionEvent;
6   import java.awt.event.ActionListener;
7   import java.awt.event.WindowEvent;
8   import java.awt.event.WindowFocusListener;
9   import java.net.URI;
10  import java.nio.file.Paths;
11  import java.util.ArrayList;
12  import java.util.Arrays;
13  import java.util.List;
14  import java.util.concurrent.ExecutionException;
15  
16  import javax.swing.JOptionPane;
17  import javax.swing.SwingWorker;
18  
19  import org.scijava.vecmath.Point2d;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import com.github.celldynamics.quimp.BoaException;
24  import com.github.celldynamics.quimp.PropertyReader;
25  import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
26  import com.github.celldynamics.quimp.geom.SegmentedShapeRoi;
27  import com.github.celldynamics.quimp.geom.filters.HatSnakeFilter;
28  import com.github.celldynamics.quimp.plugin.AbstractOptionsParser;
29  import com.github.celldynamics.quimp.plugin.AbstractPluginOptions;
30  import com.github.celldynamics.quimp.plugin.AbstractPluginTemplate;
31  import com.github.celldynamics.quimp.plugin.QuimpPluginException;
32  import com.github.celldynamics.quimp.plugin.binaryseg.BinarySegmentation;
33  import com.github.celldynamics.quimp.plugin.generatemask.GenerateMask_;
34  import com.github.celldynamics.quimp.plugin.randomwalk.RandomWalkModel.SeedSource;
35  import com.github.celldynamics.quimp.plugin.randomwalk.RandomWalkSegmentation.SeedTypes;
36  import com.github.celldynamics.quimp.plugin.utils.QuimpDataConverter;
37  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
38  
39  import ch.qos.logback.core.status.OnConsoleStatusListener;
40  import ij.IJ;
41  import ij.ImagePlus;
42  import ij.ImageStack;
43  import ij.WindowManager;
44  import ij.gui.Roi;
45  import ij.io.OpenDialog;
46  import ij.plugin.ContrastEnhancer;
47  import ij.plugin.Converter;
48  import ij.plugin.tool.BrushTool;
49  import ij.process.AutoThresholder;
50  import ij.process.ByteProcessor;
51  import ij.process.ImageProcessor;
52  import ij.process.StackStatistics;
53  
54  /*
55   * !>
56   * 
57   * @startuml doc-files/RandomWalkSegmentationPlugin_3_UML.png
58   * actor "IJ plugin runner"
59   * actor User
60   * "IJ plugin runner" -> RandomWalkSegmentationPlugin_ : <<create>>
61   * activate RandomWalkSegmentationPlugin_
62   * RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ : <<model>>
63   * RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ : <<view>>
64   * RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ : writeUI()
65   * "IJ plugin runner" -> RandomWalkSegmentationPlugin_ : run()
66   * RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ :showUI(true)
67   * ...
68   * User -> Dialog : click Apply 
69   * Dialog -> Dialog : readUI()
70   * Dialog -> RWWorker : <<create>>
71   * activate RWWorker
72   * RWWorker -> Dialog : enableUI(false)
73   * RWWorker -> Dialog : rename button
74   * RWWorker -> RandomWalkSegmentationPlugin_ : runPlugin()
75   * RandomWalkSegmentationPlugin_ --> IJ : update progress
76   * RandomWalkSegmentationPlugin_ --> RWWorker : done
77   * RWWorker -> Dialog : enableUI(true)
78   * RWWorker -> Dialog : rename button
79   * RWWorker --> Dialog : <<end>>
80   * deactivate RWWorker
81   * @enduml
82   * !<
83   */
84  /**
85   * Run RandomWalkSegmentation in IJ environment.
86   * 
87   * <p>Implements common PlugIn interface as both images are provided after run. The seed can be one
88   * image - in this case seed propagation is used to generate seed for subsequent frames, or it can
89   * be stack of the same size as image. In latter case every slice from seed is used for seeding
90   * related slice from image.
91   * 
92   * <p>Principles of working:<br>
93   * <img src="doc-files/RandomWalkSegmentationPlugin_3_UML.png"/><br>
94   * 
95   * @author p.baniukiewicz
96   *
97   */
98  public class RandomWalkSegmentationPlugin_ extends AbstractPluginTemplate {
99  
100   /** The this plugin name. */
101   private static String thisPluginName = "RandomWalk";
102 
103   /**
104    * The Constant LOGGER.
105    */
106   static final Logger LOGGER =
107           LoggerFactory.getLogger(RandomWalkSegmentationPlugin_.class.getName());
108 
109   /** The view. */
110   RandomWalkView view;
111 
112   /** The seed picker wnd. */
113   private SeedPicker seedPickerWnd = null;
114 
115   /** The br. */
116   private BrushTool br = new BrushTool();
117 
118   /** The last tool. */
119   private String lastTool; // tool selected in IJ
120 
121   /** The is canceled. */
122   private boolean isCanceled; // true if user click Cancel, false if clicked Apply
123 
124   /** The is run. */
125   private boolean isRun; // true if segmentation is running
126 
127   /** The one slice. */
128   private boolean oneSlice = false; // true if only current slice is segmented
129 
130   /** The start slice. */
131   private int startSlice = 1; // number of slice to segment If oneSlice == false, segment all from 1
132   /**
133    * Result of {@link #runPlugin()}.
134    */
135   private ImagePlus segmented = null; // result of segmentation
136 
137   /**
138    * Default constructor.
139    */
140   public RandomWalkSegmentationPlugin_() {
141     super(new RandomWalkModel(), thisPluginName);
142     if (IJ.getInstance() != null) {
143       lastTool = IJ.getToolName(); // remember selected tool
144     }
145     isCanceled = false;
146     isRun = false;
147     view = new RandomWalkView();
148     seedPickerWnd = new SeedPicker(false);
149     writeUI();
150     view.addWindowController(new ActivateWindowController());
151     view.addImageController(new ImageController());
152     view.addSeedController(new SeedController());
153     view.addRunController(new RunBtnController());
154     view.addCancelController(new CancelBtnController());
155     view.addBgController(new BgController());
156     view.addFgController(new FgController());
157     view.addCloneController(new CloneController());
158     view.addLoadQconfController(new LoadQconfController());
159     view.addQconfShowSeedImageController(new QconfShowSeedImageController());
160     view.addRunActiveController(new RunActiveBtnController());
161     view.addHelpController(new HelpBtnController());
162     view.addSeedRoiController(new SeedRoiController());
163     seedPickerWnd.addFinishController(new FinishControllerSeedPicker());
164   }
165 
166   /**
167    * Constructor that allows to provide own configuration parameters.
168    * 
169    * @param paramString parameter string.
170    * @throws QuimpPluginException on error
171    */
172   public RandomWalkSegmentationPlugin_(String paramString) throws QuimpPluginException {
173     super(paramString, new RandomWalkModel(), thisPluginName);
174     view = new RandomWalkView();
175     writeUI(); // fill UI controls with default options
176   }
177 
178   /**
179    * This constructor allows to provide user configuration.
180    * 
181    * <p>Call {@link #runPlugin()} afterwards. Note that {@link AbstractOptionsParser#apiCall} is set
182    * to true and sink to Console.
183    * 
184    * @param options configuration object.
185    */
186   public RandomWalkSegmentationPlugin_(AbstractPluginOptions options) {
187     super(options, thisPluginName);
188     apiCall = true;
189     errorSink = MessageSinkTypes.CONSOLE;
190     view = new RandomWalkView();
191     writeUI(); // fill UI controls with default options
192   }
193 
194   /**
195    * Updates view from model.
196    */
197   public void writeUI() {
198     RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
199     if (model.getOriginalImage() != null) {
200       view.setCbOrginalImage(new String[] { model.getOriginalImage().getTitle() }, "");
201     }
202 
203     view.setSeedSource(model.getSeedSources(), model.getSelectedSeedSource().name());
204     if (model.getSeedImage() != null) {
205       view.setCbRgbSeedImage(new String[] { model.getSeedImage().getTitle() }, "");
206       view.setCbCreatedSeedImage(new String[] { model.getSeedImage().getTitle() }, "");
207       view.setCbMaskSeedImage(new String[] { model.getSeedImage().getTitle() }, "");
208     }
209 
210     view.setSrAlpha(model.algOptions.alpha);
211     view.setSrBeta(model.algOptions.beta);
212     view.setSrGamma0(model.algOptions.gamma[0]);
213     view.setSrGamma1(model.algOptions.gamma[1]);
214     view.setSrIter(model.algOptions.iter);
215     view.setSrRelerr(model.algOptions.relim[0]);
216 
217     view.setShrinkMethod(model.getShrinkMethods(), model.getselectedShrinkMethod().name());
218     view.setSrShrinkPower(model.shrinkPower);
219     view.setSrExpandPower(model.expandPower);
220     view.setSrScaleSigma(model.scaleSigma);
221     view.setSrScaleMagn(model.scaleMagn);
222     view.setSrScaleCurvDistDist(model.scaleCurvDistDist);
223     view.setSrScaleEqNormalsDist(model.scaleEqNormalsDist);
224     view.setFilteringMethod(model.getFilteringMethods(), model.getSelectedFilteringMethod().name());
225     view.setChLocalMean(model.algOptions.useLocalMean);
226     view.setSrLocalMeanWindow(model.algOptions.localMeanMaskSize);
227     view.setChTrueBackground(model.estimateBackground);
228     view.setChInterFrameFilter(model.interFrameFilter);
229 
230     view.setChHatFilter(model.hatFilter);
231     view.setSrAlev(model.alev);
232     view.setSrNum(model.num);
233     view.setSrWindow(model.window);
234     view.setFilteringPostMethod(model.getFilteringMethods(),
235             model.getSelectedFilteringPostMethod().name());
236     view.setChMaskCut(model.algOptions.maskLimit);
237 
238     view.setChShowSeed(model.showPreview);
239     view.setChShowPreview(model.showPreview);
240     view.setChShowProbMaps(model.showProbMaps);
241 
242   }
243 
244   /**
245    * Updates model from view.
246    * 
247    * @return updated model. It is reference of model stored in this class.
248    */
249   public RandomWalkModel readUI() {
250     RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
251     model.setOriginalImage(WindowManager.getImage(view.getCbOrginalImage()));
252     model.setSelectedSeedSource(view.getSeedSource());
253     switch (model.getSelectedSeedSource()) {
254       case RGBImage:
255         model.setSeedImage(WindowManager.getImage(view.getCbRgbSeedImage()));
256         break;
257       case CreatedImage:
258         model.setSeedImage(WindowManager.getImage(view.getCbCreatedSeedImage()));
259         break;
260       case MaskImage:
261         model.setSeedImage(WindowManager.getImage(view.getCbMaskSeedImage()));
262         break;
263       case QconfFile:
264         break; // no control to display or read from for this case
265       case Rois:
266         break;
267       default:
268         throw new IllegalArgumentException("Unknown seed source");
269     }
270 
271     model.algOptions.alpha = view.getSrAlpha();
272     model.algOptions.beta = view.getSrBeta();
273     model.algOptions.gamma[0] = view.getSrGamma0();
274     model.algOptions.gamma[1] = view.getSrGamma1();
275     model.algOptions.iter = view.getSrIter();
276     model.algOptions.relim[0] = view.getSrRelerr();
277     model.algOptions.relim[1] = model.algOptions.relim[0] * 10;
278 
279     model.setselectedShrinkMethod(view.getShrinkMethod());
280     model.shrinkPower = view.getSrShrinkPower();
281     model.expandPower = view.getSrExpandPower();
282     model.scaleSigma = view.getSrScaleSigma();
283     model.scaleMagn = view.getSrScaleMagn();
284     model.scaleCurvDistDist = view.getSrScaleCurvDistDist();
285     model.scaleEqNormalsDist = view.getSrScaleEqNormalsDist();
286     model.setSelectedFilteringMethod(view.getFilteringMethod());
287     model.algOptions.useLocalMean = view.getChLocalMean();
288     model.algOptions.localMeanMaskSize = view.getSrLocalMeanWindow();
289     model.estimateBackground = view.getChTrueBackground();
290 
291     model.hatFilter = view.getChHatFilter();
292     model.alev = view.getSrAlev();
293     model.num = view.getSrNum();
294     model.window = view.getSrWindow();
295     model.setSelectedFilteringPostMethod(view.getFilteringPostMethod());
296     model.algOptions.maskLimit = view.getChMaskCut();
297 
298     model.showSeeds = view.getChShowSeed();
299     model.showPreview = view.getChShowPreview();
300     model.showProbMaps = view.getChShowProbMaps();
301 
302     return model;
303   }
304 
305   /**
306    * Build main dialog.
307    * <br>
308    * <img src="doc-files/RandomWalkSegmentationPlugin_1_UML.png"/><br>
309    * State diagram <br>
310    * <img src="doc-files/RandomWalkSegmentationPlugin_2_UML.png"/><br>
311    *
312    * @param val the val
313    */
314   public void showUi(boolean val) {
315     view.show();
316   }
317 
318   /**
319    * Action {@link OnConsoleStatusListener} {@link SeedPicker}.
320    * 
321    * <p>Fill
322    * 
323    * @author p.baniukiewicz
324    *
325    */
326   class FinishControllerSeedPicker implements ActionListener {
327 
328     /*
329      * (non-Javadoc)
330      * 
331      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
332      */
333     @Override
334     public void actionPerformed(ActionEvent e) {
335       if (seedPickerWnd == null) {
336         return;
337       }
338       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
339       model.getOriginalImage().deleteRoi(); // just in case if ROI tool left something
340       List<Seeds> rois = seedPickerWnd.seedsRoi;
341       if (rois != null) {
342         String fgsize;
343         String bgsize;
344         if (rois.get(0).get(SeedTypes.FOREGROUNDS) == null) {
345           fgsize = "<no FG>";
346         } else {
347           fgsize = "" + rois.get(0).get(SeedTypes.FOREGROUNDS).size();
348         }
349         if (rois.get(0).get(SeedTypes.BACKGROUND) == null) {
350           bgsize = "<no BG>";
351         } else {
352           bgsize = "" + rois.get(0).get(SeedTypes.BACKGROUND).size();
353         }
354         view.setLroiSeedsInfo("Objects: " + fgsize + " FG and " + bgsize + " BG");
355       }
356     }
357 
358   }
359 
360   /**
361    * Updates list of images in selector on activation or deactivation of window.
362    * 
363    * @author p.baniukiewicz
364    *
365    */
366   class ActivateWindowController implements WindowFocusListener {
367 
368     /*
369      * (non-Javadoc)
370      * 
371      * @see java.awt.event.WindowFocusListener#windowGainedFocus(java.awt.event.WindowEvent)
372      */
373     @Override
374     public void windowGainedFocus(WindowEvent e) {
375       action();
376     }
377 
378     /*
379      * (non-Javadoc)
380      * 
381      * @see java.awt.event.WindowFocusListener#windowLostFocus(java.awt.event.WindowEvent)
382      */
383     @Override
384     public void windowLostFocus(WindowEvent e) {
385       action();
386     }
387 
388     /**
389      * Action.
390      */
391     private void action() {
392       if (isRun == true) {
393         return;
394       }
395       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
396       // list of open windows
397       String[] images = WindowManager.getImageTitles();
398       String selection = "";
399       // try to find that stored in model in list of opened windows and select it
400       if (model.getOriginalImage() != null) {
401         selection = model.getOriginalImage().getTitle();
402       }
403       // select that found (if any, first position otherwise)
404       view.setCbOrginalImage(images, selection);
405       selection = "";
406       // the same with seeds
407       if (model.getSeedImage() != null) {
408         selection = model.getSeedImage().getTitle();
409       }
410       // all seeds selectors share the same image in model
411       view.setCbCreatedSeedImage(images, selection);
412       view.setCbRgbSeedImage(images, selection);
413       view.setCbMaskSeedImage(images, selection);
414 
415     }
416   }
417 
418   /**
419    * Detect change on image JComboBox, this change can be due to user action adding new images to
420    * list by {@link ActivateWindowController}.
421    * 
422    * <p>Stores selected image in model.
423    * 
424    * @author p.baniukiewicz
425    *
426    */
427   class ImageController implements ActionListener {
428 
429     /*
430      * (non-Javadoc)
431      * 
432      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
433      */
434     @Override
435     public void actionPerformed(ActionEvent e) {
436       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
437       model.setOriginalImage(WindowManager.getImage(view.getCbOrginalImage()));
438     }
439   }
440 
441   /**
442    * Detect change on seeds selector, these change can be due to user action adding new images
443    * to list by {@link ActivateWindowController}.
444    * 
445    * <p>Stores selected image in model.
446    * 
447    * @author p.baniukiewicz
448    *
449    */
450   class SeedController implements ActionListener {
451 
452     /*
453      * (non-Javadoc)
454      * 
455      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
456      */
457     @Override
458     public void actionPerformed(ActionEvent e) {
459       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
460       SeedSource src = SeedSource.valueOf(model.getSeedSources()[view.getSeedSource()]);
461       switch (src) {
462         case RGBImage:
463           model.setSeedImage(WindowManager.getImage(view.getCbRgbSeedImage()));
464           break;
465         case MaskImage:
466           model.setSeedImage(WindowManager.getImage(view.getCbMaskSeedImage()));
467           break;
468         case CreatedImage:
469           model.setSeedImage(WindowManager.getImage(view.getCbCreatedSeedImage()));
470           break;
471         case QconfFile:
472           break;
473         case Rois:
474           break;
475         default:
476           throw new IllegalArgumentException("Unknown seed source");
477       }
478     }
479   }
480 
481   /**
482    * Handle Run button.
483    * 
484    * <p>Copy view status to model and start processing.
485    * 
486    * @author p.baniukiewicz
487    *
488    */
489   class RunBtnController implements ActionListener {
490 
491     /*
492      * (non-Javadoc)
493      * 
494      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
495      */
496     @Override
497     public void actionPerformed(ActionEvent e) {
498       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
499       startSlice = 1;// segment from first
500       oneSlice = false;
501       readUI();
502       RWWorker rww = new RWWorker();
503       rww.execute();
504       LOGGER.trace("model: " + model.toString());
505     }
506   }
507 
508   /**
509    * Handle Help button.
510    * 
511    * <p>Open browser.
512    * 
513    * @author p.baniukiewicz
514    *
515    */
516   class HelpBtnController implements ActionListener {
517 
518     /*
519      * (non-Javadoc)
520      * 
521      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
522      */
523     @Override
524     public void actionPerformed(ActionEvent e) {
525       String url = new PropertyReader().readProperty("quimpconfig.properties", "manualURL");
526       try {
527         java.awt.Desktop.getDesktop().browse(new URI(url));
528       } catch (Exception e1) {
529         LOGGER.error("Could not open help: " + e1.getMessage(), e1);
530       }
531     }
532   }
533 
534   /**
535    * Handle Run button from active.
536    * 
537    * <p>Copy view status to model and start processing.
538    * 
539    * @author p.baniukiewicz
540    *
541    */
542   class RunActiveBtnController implements ActionListener {
543 
544     /*
545      * (non-Javadoc)
546      * 
547      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
548      */
549     @Override
550     public void actionPerformed(ActionEvent e) {
551       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
552       ImagePlus orgImg = model.getOriginalImage();
553       if (orgImg == null) {
554         return;
555       }
556       readUI();
557       startSlice = orgImg.getCurrentSlice();
558       oneSlice = true;
559       RWWorker rww = new RWWorker();
560       rww.execute();
561       LOGGER.trace("model: " + model.toString());
562 
563     }
564   }
565 
566   /**
567    * Handle Cancel button.
568    * 
569    * @author p.baniukiewicz
570    *
571    */
572   public class CancelBtnController implements ActionListener {
573 
574     /*
575      * (non-Javadoc)
576      * 
577      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
578      */
579     @Override
580     public void actionPerformed(ActionEvent arg0) {
581       isCanceled = true;
582       if (isRun == false) {
583         view.getWnd().dispose();
584       }
585 
586     }
587   }
588 
589   /**
590    * Handle Clone button.
591    * 
592    * @author p.baniukiewicz
593    *
594    */
595   public class CloneController implements ActionListener {
596 
597     /*
598      * (non-Javadoc)
599      * 
600      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
601      */
602     @Override
603     public void actionPerformed(ActionEvent arg0) {
604       ImagePlus tmpImage = WindowManager.getImage(view.getCbOrginalImage());
605       if (tmpImage == null) {
606         return;
607       }
608       ImagePlus duplicatedImage;
609       Object[] options = { "Whole stack", "Current slice", "Cancel" };
610       int ret = JOptionPane.showOptionDialog(view.getWnd(),
611               QuimpToolsCollection
612                       .stringWrap("Do you want to duplicte the whole stack or only current slice?"),
613               "Duplicate stack", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
614               null, options, null);
615       switch (ret) {
616         case 0: // stack
617           duplicatedImage = tmpImage.duplicate();
618           break;
619         case 1: // slice
620           duplicatedImage = new ImagePlus("",
621                   tmpImage.getStack().getProcessor(tmpImage.getCurrentSlice()).duplicate());
622           break;
623         case 2: // cancel
624         default: // closed window
625           return;
626       }
627       duplicatedImage.show();
628       new Converter().run("RGB Color");
629       duplicatedImage.setTitle("SEED_" + tmpImage.getTitle());
630       view.setCbCreatedSeedImage(WindowManager.getImageTitles(), duplicatedImage.getTitle());
631     }
632   }
633 
634   /**
635    * Handle seed ROI button. Open {@link SeedPicker}.
636    * 
637    * @author p.baniukiewicz
638    *
639    */
640   public class SeedRoiController implements ActionListener {
641 
642     /*
643      * (non-Javadoc)
644      * 
645      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
646      */
647     @Override
648     public void actionPerformed(ActionEvent e) {
649       ImagePlus tmpImage = WindowManager.getCurrentImage();
650       if (tmpImage == null) {
651         return;
652       }
653       if (seedPickerWnd != null) {
654         seedPickerWnd.image = tmpImage;
655         seedPickerWnd.reset();
656         seedPickerWnd.setVisible(true);
657       }
658     }
659 
660   }
661 
662   /**
663    * Clone whole image or selected slice. Helper
664    * 
665    * @param tmpImage image to clone
666    * @return cloned image
667    */
668   private ImagePlus cloneImageAndAsk(ImagePlus tmpImage) {
669     Object[] options = { "Whole stack", "Current slice", "Cancel" };
670     int ret = JOptionPane.showOptionDialog(view.getWnd(),
671             QuimpToolsCollection
672                     .stringWrap("Do you want to duplicte the whole stack or only current slice?"),
673             "Duplicate stack", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
674             options, null);
675     ImagePlus duplicatedImage;
676     switch (ret) {
677       case 0: // stack
678         duplicatedImage = tmpImage.duplicate();
679         break;
680       case 1: // slice
681         duplicatedImage = new ImagePlus("",
682                 tmpImage.getStack().getProcessor(tmpImage.getCurrentSlice()).duplicate());
683         break;
684       case 2: // cancel
685       default: // closed window
686         return null;
687     }
688     return duplicatedImage;
689   }
690 
691   /**
692    * Handle FG button.
693    * 
694    * @author p.baniukiewicz
695    *
696    */
697   public class FgController implements ActionListener {
698 
699     /*
700      * (non-Javadoc)
701      * 
702      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
703      */
704     @Override
705     public void actionPerformed(ActionEvent arg0) {
706       if (view.getBnFore().isSelected()) {
707         IJ.setForegroundColor(Color.RED.getRed(), Color.RED.getGreen(), Color.RED.getBlue());
708         BrushTool.setBrushWidth(10); // set brush width
709         br.run(""); // run macro
710       } else {
711         IJ.setTool(lastTool); // if unselected just switch off BrushTool selecting other tool
712       }
713     }
714   }
715 
716   /**
717    * Handle BG button.
718    * 
719    * @author p.baniukiewicz
720    *
721    */
722   public class BgController implements ActionListener {
723 
724     /*
725      * (non-Javadoc)
726      * 
727      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
728      */
729     @Override
730     public void actionPerformed(ActionEvent arg0) {
731       if (view.getBnBack().isSelected()) {
732         IJ.setForegroundColor(Color.GREEN.getRed(), Color.GREEN.getGreen(), Color.GREEN.getBlue());
733         BrushTool.setBrushWidth(10); // set brush width
734         br.run(""); // run macro
735       } else {
736         IJ.setTool(lastTool); // if unselected just switch off BrushTool selecting other tool
737       }
738     }
739   }
740 
741   /**
742    * Handle Load qconf button.
743    * 
744    * @author p.baniukiewicz
745    *
746    */
747   public class LoadQconfController implements ActionListener {
748 
749     /*
750      * (non-Javadoc)
751      * 
752      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
753      */
754     @Override
755     public void actionPerformed(ActionEvent arg0) {
756       FileDialog od = new FileDialog(IJ.getInstance(), "Open Qconf file");
757       od.setFile("*.QCONF");
758       od.setDirectory(OpenDialog.getLastDirectory());
759       od.setMultipleMode(false);
760       od.setMode(FileDialog.LOAD);
761       od.setVisible(true);
762       if (od.getFile() == null) {
763         IJ.log("Cancelled - exiting...");
764         return;
765       }
766       String directory = od.getDirectory();
767       String filename = od.getFile();
768       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
769       model.setQconfFile(Paths.get(directory, filename).toString());
770       view.setLqconfFile(filename);
771     }
772   }
773 
774   /**
775    * Handle show loaded qconf file mask button.
776    * 
777    * @author p.baniukiewicz
778    *
779    */
780   public class QconfShowSeedImageController implements ActionListener {
781 
782     /*
783      * (non-Javadoc)
784      * 
785      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
786      */
787     @Override
788     public void actionPerformed(ActionEvent arg0) {
789       RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
790       if (model.getQconfFile() == null || model.getQconfFile().isEmpty()
791               || Paths.get(model.getQconfFile()).getFileName() == null) {
792         return;
793       }
794       try {
795         ImagePlus seedImage;
796         // temporary load, it is repeated in runPlugin
797         seedImage =
798                 new GenerateMask_("opts={paramFile:(" + model.getQconfFile() + "),binary:false}")
799                         .getRes();
800         seedImage.setTitle(
801                 "Mask-" + Paths.get(model.getQconfFile()).getFileName().toString() + ".tif");
802         new ContrastEnhancer().stretchHistogram(seedImage, 0.35);
803         seedImage.show();
804       } catch (QuimpPluginException e) {
805         LOGGER.debug("Can not load QCONF"); // not important, error handled in runPlugin
806       }
807 
808     }
809 
810   }
811 
812   /*
813    * (non-Javadoc)
814    * 
815    * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#run(java.lang.String)
816    */
817   @Override
818   public void run(String arg) {
819     super.run(arg);
820   }
821 
822   /**
823    * Run segmentation.
824    * 
825    * <p>Set {@link AbstractOptionsParser#apiCall} (this.apiCall) to true and
826    * {@link RandomWalkModel#showPreview} to false to block visual output.
827    * 
828    * @see RandomWalkModel
829    * @see RandomWalkSegmentationPlugin_#RandomWalkSegmentationPlugin_(String)
830    * @see #getResult()
831    */
832   @Override
833   public void runPlugin() {
834     RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
835     ImagePlus prev = null; // preview window, null if not opened
836     // local mean should not be applied for first slice if seeds are rgb - remember status here
837     // to temporarily disable it and enable before processing second slice
838     boolean useSeedStack; // true if seed has the same size as image, slices are seeds
839     isCanceled = false; // erase any previous state
840     Color foreColor; // color of seed image foreground pixels
841     Color backColor; // color of seed image background pixels
842     boolean localMeanUserStatus = model.algOptions.useLocalMean;
843     ImageStack ret; // all images treated as stacks
844     ImageStack is = null;
845     Seeds seeds;
846     PropagateSeeds propagateSeeds;
847     isRun = true; // segmentation started
848     // if preview selected - prepare image
849     if (model.showPreview) {
850       prev = new ImagePlus();
851     }
852     AutoThresholder.Method thresholdBackground = null;
853     if (model.estimateBackground == true) {
854       thresholdBackground = AutoThresholder.Method.Otsu;
855     }
856     try {
857       ImagePlus image = model.getOriginalImage();
858       ImagePlus seedImage;
859       // find if we have stack seeds or not
860       if (model.getSelectedSeedSource() == SeedSource.Rois) {
861         seedImage = seedPickerWnd.image; // stored image in seedpicker (at SeedRoiController)
862       } else {
863         seedImage = model.getSeedImage(); // stored in model (rgb, mask)
864       }
865       if (image == null || seedImage == null) {
866         throw new QuimpPluginException("Input image or seed image can not be opened.");
867       }
868       is = image.getStack(); // get current stack (size 1 for one image)
869       if (seedImage.getStackSize() == 1) {
870         useSeedStack = false; // use propagateSeed for generating next frame seed from prev
871       } else if (seedImage.getStackSize() == image.getStackSize()) {
872         useSeedStack = true; // use slices as seeds
873       } else {
874         throw new RandomWalkException("Seed stack and image stack must have the same z dimension");
875       }
876       // create seeding object with or without storing the history of configured type
877       propagateSeeds = PropagateSeeds.getPropagator(model.selectedShrinkMethod, model.showSeeds,
878               thresholdBackground);
879       if (propagateSeeds instanceof PropagateSeeds.Contour) {
880         ((PropagateSeeds.Contour) propagateSeeds).scaleMagn = model.scaleMagn;
881         ((PropagateSeeds.Contour) propagateSeeds).scaleSigma = model.scaleSigma;
882         ((PropagateSeeds.Contour) propagateSeeds).averageNormalsDist = model.scaleEqNormalsDist;
883         ((PropagateSeeds.Contour) propagateSeeds).averageCurvDist = model.scaleCurvDistDist;
884         ((PropagateSeeds.Contour) propagateSeeds).useFiltering = model.interFrameFilter;
885       }
886 
887       ret = new ImageStack(image.getWidth(), image.getHeight()); // output stack
888       // create segmentation engine
889       RandomWalkSegmentation obj =
890               new RandomWalkSegmentation(is.getProcessor(startSlice), model.algOptions);
891       // decode provided seeds depending on selected option
892       switch (model.getSelectedSeedSource()) {
893         case RGBImage: // use seeds as they are
894         case CreatedImage:
895           foreColor = Color.RED;
896           backColor = Color.GREEN;
897           if (seedImage != null && seedImage.equals(image)) {
898             throw new RandomWalkException("Seed image and segmented image are the same.");
899           }
900           if (seedImage.getNSlices() >= startSlice) {
901             seeds = SeedProcessor.decodeSeedsfromRgb(seedImage.getStack().getProcessor(startSlice),
902                     Arrays.asList(foreColor), backColor);
903           } else {
904             seeds = SeedProcessor.decodeSeedsfromRgb(seedImage.getStack().getProcessor(1),
905                     Arrays.asList(foreColor), backColor);
906           }
907           if (model.algOptions.useLocalMean) {
908             LOGGER.warn("LocalMean is not used for first frame when seed is RGB image");
909           }
910           model.algOptions.useLocalMean = false; // do not use LM on first frame (reenable it later)
911           break;
912         case Rois:
913           if (seedPickerWnd.seedsRoi.isEmpty()) {
914             throw new RandomWalkException("No ROIs processed, did you forget to click Finish?");
915           }
916           seeds = seedPickerWnd.seedsRoi.get(0); // TODO startSlice - 1 if it will support frames
917           if (model.algOptions.useLocalMean) {
918             LOGGER.warn("LocalMean is not used for first frame when seeds are ROIs.");
919           }
920           model.algOptions.useLocalMean = false; // do not use LM on first frame (reenable it later)
921           break;
922         case QconfFile:
923           seedImage =
924                   new GenerateMask_("opts={paramFile:(" + model.getQconfFile() + "),binary:false}")
925                           .getRes(); // it throws in case
926           // and continue to the next case
927         case MaskImage:
928           if (seedImage != null && seedImage.equals(image)) {
929             throw new RandomWalkException("Seed image and segmented image are the same.");
930           }
931           double max = new StackStatistics(seedImage).max;
932           if (max > 255) {
933             LOGGER.warn("There are more than 255 objects in loaded QCONF file. Only first"
934                     + " 255 will be segmented");
935           }
936           // get seeds split to FG and BG
937           // this is mask (bigger) so produce seeds, overwrite seeds
938           // do no scale here as seedImage is 16bit and it would remove some colors. Assume clipping
939           seeds = propagateSeeds.propagateSeed(
940                   seedImage.getStack().getProcessor(startSlice).duplicate().convertToByte(false),
941                   is.getProcessor(startSlice), model.shrinkPower, model.expandPower);
942           // mask to local mean
943           seeds.put(SeedTypes.ROUGHMASK,
944                   seedImage.getStack().getProcessor(startSlice).duplicate().convertToByte(false));
945           seeds.get(SeedTypes.ROUGHMASK, 1).threshold(0); // to have BW map in case
946           break;
947         default:
948           throw new IllegalArgumentException("Unsupported seed source");
949       }
950       // segment first slice (or image if it is not stack)
951       ImageProcessor retIp = obj.run(seeds);
952       if (retIp == null) { // segmentation failed, return empty image
953         LOGGER.error("Segmentation failed - no Foreground maps provided"); // not very important
954         retIp = new ByteProcessor(image.getWidth(), image.getHeight());
955       }
956       model.algOptions.useLocalMean = localMeanUserStatus; // restore status after 1st frame
957       if (model.hatFilter) {
958         retIp = applyHatSnakeFilter(retIp, is.getProcessor(startSlice));
959       }
960       ret.addSlice(retIp.convertToByte(true)); // store output in new stack
961       if (model.showPreview) { // display first slice
962         prev.setProcessor(retIp);
963         prev.setTitle("Previev - frame: " + 1);
964         prev.show();
965         prev.updateAndDraw();
966       }
967       // iterate over all slices after first (may not run for one image and for current image seg)
968       for (int s = 2; s <= is.getSize() && isCanceled == false && oneSlice == false; s++) {
969         LOGGER.info("----- Slice " + s + " -----");
970         Seeds/quimp/plugin/randomwalk/Seeds.html#Seeds">Seeds nextseed = new Seeds(); // just to remove null warning
971         obj = new RandomWalkSegmentation(is.getProcessor(s), model.algOptions);
972         // get seeds from previous result
973         if (useSeedStack) { // true - use slices
974           switch (model.getSelectedSeedSource()) {
975             case RGBImage:
976             case Rois:
977               // TODO add support for multislice seeds
978               throw new RandomWalkException(
979                       "This combination is not supported - for ROI seeds should be selected in "
980                               + "single image, " + "not a stack");
981             case QconfFile:
982             case MaskImage:
983               // do no scale here as seedImage is 16bit and it would remove some colors. Assume
984               // clipping
985               nextseed = propagateSeeds.propagateSeed(
986                       seedImage.getStack().getProcessor(s).duplicate().convertToByte(false),
987                       is.getProcessor(s), model.shrinkPower, model.expandPower);
988               nextseed.put(SeedTypes.ROUGHMASK,
989                       seedImage.getStack().getProcessor(s).duplicate().convertToByte(false));
990               nextseed.get(SeedTypes.ROUGHMASK, 1).threshold(0); // to have BW map in case
991               break;
992             default:
993           }
994         } else { // false - use previous frame
995           // modify masks and convert to lists
996           // retIp can be grayscale but it does not matter, return from propagateSeed is BW, each
997           // object separated
998           nextseed = propagateSeeds.propagateSeed(retIp, is.getProcessor(s), model.shrinkPower,
999                   model.expandPower);
1000           nextseed.put(SeedTypes.ROUGHMASK, retIp.duplicate());
1001           nextseed.get(SeedTypes.ROUGHMASK, 1).threshold(0); // to have BW map in case
1002         }
1003         // segmentation and results stored for next seeding
1004         retIp = obj.run(nextseed);
1005         if (retIp == null) { // segmentation failed, return empty image
1006           LOGGER.error("Segmentation failed - no Foreground maps provided"); // not very important
1007           retIp = new ByteProcessor(image.getWidth(), image.getHeight());
1008         }
1009         if (model.hatFilter) {
1010           retIp = applyHatSnakeFilter(retIp, is.getProcessor(s));
1011         }
1012         ret.addSlice(retIp); // add next slice
1013         if (model.showPreview) { // show preview remaining slices
1014           prev.setProcessor(retIp);
1015           prev.setTitle("Previev - frame: " + s);
1016           prev.setActivated();
1017           prev.updateAndDraw();
1018         }
1019         IJ.showProgress(s - 1, is.getSize());
1020       }
1021       // convert to ImagePlus and show
1022       segmented = new ImagePlus("Segmented_" + image.getTitle(), ret);
1023       if (!apiCall) {
1024         segmented.show();
1025         segmented.updateAndDraw();
1026       }
1027       // show maps (last used - not stack!)
1028       if (model.showProbMaps == true) {
1029         ProbabilityMaps pm = obj.getProbabilityMaps();
1030         if (pm != null) { // if seg run
1031           ImageStack pmstackfg = pm.convertToImageStack(SeedTypes.FOREGROUNDS);
1032           if (pmstackfg == null) { // if not successful seg
1033             logger.warn("showProbMaps is selected but segmentation returned empty FG maps.");
1034           } else { // if successful seg
1035             ImageStack pmstackbg = pm.convertToImageStack(SeedTypes.BACKGROUND);
1036             // add bg if exists
1037             if (pmstackbg != null) {
1038               for (int s = 1; s <= pmstackbg.size(); s++) {
1039                 pmstackfg.addSlice(pmstackbg.getProcessor(s));
1040               }
1041             }
1042             new ImagePlus("Last Probability maps", pmstackfg).show();
1043           } // Successful segm
1044         }
1045       }
1046       // show seeds if selected and not stack seeds (throw must be last
1047       if (model.showSeeds) {
1048         if (useSeedStack == true) {
1049           switch (model.getSelectedSeedSource()) {
1050             case QconfFile:
1051             case MaskImage:
1052               if (oneSlice) { // have stack but want only one slice
1053                 propagateSeeds.getCompositeSeed(image.duplicate(), startSlice).show();
1054               } else { // have stack and segmented stack
1055                 propagateSeeds.getCompositeSeed(image.duplicate(), 0).show();
1056               }
1057               break;
1058             default:
1059               LOGGER.warn("Effective seeds are not displayed if"
1060                       + " initial seeds are provided as stack");
1061           }
1062         } else {
1063           propagateSeeds.getCompositeSeed(image.duplicate(), 0).show();
1064         }
1065       }
1066     } catch (QuimpPluginException rwe) {
1067       // if (!(rwe instanceof RandomWalkException)) { // RandomWalkException has set proper sink
1068       rwe.setMessageSinkType(errorSink);
1069       // }
1070       rwe.handleException(view.getWnd(), "Segmentation problem.");
1071     } catch (Exception e) {
1072       LOGGER.debug(e.getMessage(), e);
1073       IJ.error("Random Walk Segmentation error",
1074               e.getClass().getSimpleName() + ": " + e.getMessage());
1075     } finally {
1076       isRun = false; // segmentation stopped
1077       IJ.showProgress(2, 1); // erase progress bar
1078       if (prev != null) {
1079         prev.close();
1080       }
1081       model.algOptions.useLocalMean = localMeanUserStatus; // restore status
1082     }
1083   }
1084 
1085   /**
1086    * Retrieve result of segmentation.
1087    * 
1088    * <p>Valid after {@link #runPlugin()}.
1089    * 
1090    * @return result of segmentation
1091    */
1092   public ImagePlus getResult() {
1093     return segmented;
1094   }
1095 
1096   /**
1097    * Helper method, applies HSF.
1098    * 
1099    * @param retIp image to filter (mask)
1100    * @param orIp original image
1101    * @return Filtered processor
1102    * @throws QuimpPluginException on problem with HatSnakeFilter
1103    */
1104   private ImageProcessor applyHatSnakeFilter(ImageProcessor retIp, ImageProcessor orIp)
1105           throws QuimpPluginException {
1106     RandomWalkModel/../../../com/github/celldynamics/quimp/plugin/randomwalk/RandomWalkModel.html#RandomWalkModel">RandomWalkModel model = (RandomWalkModel) options;
1107     BinarySegmentationlugin/binaryseg/BinarySegmentation.html#BinarySegmentation">BinarySegmentation obj = new BinarySegmentation(new ImagePlus("", retIp));
1108     obj.trackObjects(); // run tracking
1109     ArrayList<ArrayList<SegmentedShapeRoi>> ret = obj.getChains();
1110     for (ArrayList<SegmentedShapeRoi> asS : ret) {
1111       for (SegmentedShapeRoi ss : asS) {
1112         ss.setInterpolationParameters(1, false);
1113       }
1114     }
1115     SegmentedShapeRoi ssR = ret.get(0).get(0); // FIXME possible trap for multi object images
1116     HatSnakeFiltermp/geom/filters/HatSnakeFilter.html#HatSnakeFilter">HatSnakeFilter hsf = new HatSnakeFilter(model.window, model.num, model.alev);
1117     hsf.setMode(HatSnakeFilter.CAVITIES);
1118     // dont use interpolation - provide list of points as they are on image
1119     List<Point2d> retf = hsf.runPlugin(ssR.getOutlineasRawPoints(), orIp);
1120     Roi ssRF;
1121     try {
1122       ssRF = new QuimpDataConverter(retf).getSnake(0).asFloatRoi();
1123     } catch (BoaException e) { // lesss than 3 points
1124       e.setMessageSinkType(MessageSinkTypes.IJERROR);
1125       e.handleException(null, "HatSnake Filter failed");
1126       return retIp;
1127     }
1128     ImageProcessor retIptmp = new ByteProcessor(orIp.getWidth(), orIp.getHeight());
1129     retIptmp.setColor(Color.WHITE);
1130     retIptmp.fill(ssRF);
1131     return retIptmp;
1132   }
1133 
1134   /**
1135    * About string.
1136    * 
1137    * @return About string
1138    */
1139   @Override
1140   public String about() {
1141     return "Random Walk plugin.\n" + "Author: Piotr Baniukiewicz\n"
1142             + "mail: p.baniukiewicz@warwick.ac.uk\n"
1143             + "This plugin does support macro parameters\n";
1144   }
1145 
1146   /**
1147    * Swing worker class.
1148    * 
1149    * <p>Run segmentation and take care about renaming/blocking UI elements.
1150    * 
1151    * @author p.baniukiewicz
1152    *
1153    */
1154   class RWWorker extends SwingWorker<Object, Object> {
1155 
1156     /*
1157      * (non-Javadoc)
1158      * 
1159      * @see javax.swing.SwingWorker#doInBackground()
1160      */
1161     @Override
1162     protected Object doInBackground() throws Exception {
1163       view.setCancelLabel("STOP"); // use cancel to stopping
1164       view.enableUI(false);
1165       runPlugin(); // will update IJ progress bar
1166       view.enableUI(true);
1167       view.setCancelLabel("Cancel");
1168       return true;
1169     }
1170 
1171     /*
1172      * (non-Javadoc)
1173      * 
1174      * @see javax.swing.SwingWorker#done()
1175      */
1176     @Override
1177     protected void done() {
1178       try {
1179         get();
1180       } catch (ExecutionException e) {
1181         LOGGER.error(e.getMessage());
1182       } catch (InterruptedException e) {
1183         e.printStackTrace();
1184       } finally {
1185         view.enableUI(true);
1186         view.setCancelLabel("Cancel");
1187       }
1188     }
1189   }
1190 
1191 }