View Javadoc
1   package com.github.celldynamics.quimp.plugin.binaryseg;
2   
3   import java.awt.event.ActionEvent;
4   import java.awt.event.ActionListener;
5   import java.awt.event.ItemEvent;
6   import java.awt.event.ItemListener;
7   import java.io.FileNotFoundException;
8   import java.nio.file.Path;
9   import java.nio.file.Paths;
10  import java.util.ArrayList;
11  import java.util.List;
12  
13  import org.slf4j.Logger;
14  import org.slf4j.LoggerFactory;
15  
16  import com.github.celldynamics.quimp.BOAState;
17  import com.github.celldynamics.quimp.BOA_;
18  import com.github.celldynamics.quimp.CellStatsEval;
19  import com.github.celldynamics.quimp.Constrictor;
20  import com.github.celldynamics.quimp.Nest;
21  import com.github.celldynamics.quimp.QuimP;
22  import com.github.celldynamics.quimp.QuimpException;
23  import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
24  import com.github.celldynamics.quimp.Serializer;
25  import com.github.celldynamics.quimp.SnakeHandler;
26  import com.github.celldynamics.quimp.ViewUpdater;
27  import com.github.celldynamics.quimp.filesystem.DataContainer;
28  import com.github.celldynamics.quimp.filesystem.FileExtensions;
29  import com.github.celldynamics.quimp.filesystem.StatsCollection;
30  import com.github.celldynamics.quimp.geom.SegmentedShapeRoi;
31  import com.github.celldynamics.quimp.plugin.AbstractOptionsParser;
32  import com.github.celldynamics.quimp.plugin.AbstractPluginOptions;
33  import com.github.celldynamics.quimp.plugin.AbstractPluginTemplate;
34  import com.github.celldynamics.quimp.plugin.IQuimpPluginAttachImagePlus;
35  import com.github.celldynamics.quimp.plugin.IQuimpPluginAttachNest;
36  import com.github.celldynamics.quimp.plugin.IQuimpPluginExchangeData;
37  import com.github.celldynamics.quimp.plugin.IQuimpPluginSynchro;
38  import com.github.celldynamics.quimp.plugin.ParamList;
39  import com.github.celldynamics.quimp.plugin.QuimpPluginException;
40  import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
41  
42  import ij.IJ;
43  import ij.ImagePlus;
44  import ij.WindowManager;
45  import ij.io.FileInfo;
46  import ij.io.OpenDialog;
47  import ij.io.SaveDialog;
48  import ij.process.ImageProcessor;
49  
50  /**
51   * Binary segmentation plugin called from Fiji.
52   * 
53   * <p>Use {@link BinarySegmentation} for API calls or {@link BinarySegmentation_#run(String)}
54   * 
55   * <p>This is front-end of {@link BinarySegmentation} used as stand alone plugin and BOA component.
56   * 
57   * @author p.baniukiewicz
58   * @see BinarySegmentationView
59   * @See {@link BinarySegmentationOptions}
60   *
61   */
62  public class BinarySegmentation_ extends AbstractPluginTemplate implements IQuimpPluginSynchro,
63          IQuimpPluginAttachNest, IQuimpPluginExchangeData, IQuimpPluginAttachImagePlus {
64  
65    /**
66     * The Constant LOGGER.
67     */
68    static final Logger LOGGER = LoggerFactory.getLogger(BinarySegmentation_.class.getName());
69  
70    private static String thisPluginName = "Generate Qconf";
71  
72    private Nest nest = null; // reference to Nest object, can be null
73    private ViewUpdater vu = null; // BOA context for updating it
74    private BinarySegmentationViewn/binaryseg/BinarySegmentationView.html#BinarySegmentationView">BinarySegmentationView bsp = new BinarySegmentationView();
75    private boolean wasNest = false; // true if attached nest from outside, used to save file or not
76  
77    private ImagePlus ip = null;
78  
79    /**
80     * Default constructor.
81     */
82    public BinarySegmentation_() {
83      super(new BinarySegmentationOptions(), thisPluginName);
84      BinarySegmentationOptions/com/github/celldynamics/quimp/plugin/binaryseg/BinarySegmentationOptions.html#BinarySegmentationOptions">BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
85      bsp.addApplyListener(new ActionListener() {
86  
87        /**
88         * Apply button.
89         * 
90         * <p>Run plugin in GUI mode, handle exceptions.
91         * 
92         * @param e event
93         */
94        @Override
95        public void actionPerformed(ActionEvent e) {
96          BinarySegmentationOptions/com/github/celldynamics/quimp/plugin/binaryseg/BinarySegmentationOptions.html#BinarySegmentationOptions">BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
97          // update config for export, always handle current one
98          opts.options = bsp.getValues();
99          try {
100           runPlugin(); // run after apply
101           publishMacroString(thisPluginName);
102         } catch (QuimpException qe) {
103           qe.setMessageSinkType(errorSink);
104           qe.handleException(IJ.getInstance(), BinarySegmentation_.class.getSimpleName());
105         } catch (Exception ee) { // catch all exceptions here
106           logger.debug(ee.getMessage(), ee);
107           logger.error("Problem with running plugin: " + ee.getMessage());
108         }
109       }
110     });
111 
112     bsp.addLoadMaskListener(new ActionListener() {
113 
114       /**
115        * Load mask button.
116        * 
117        * <p>Grab path to image but technically does not load it yet.
118        * 
119        * @param e event
120        */
121       @Override
122       public void actionPerformed(ActionEvent e) {
123         opts.paramFile = ""; // clear path to ask again on new file
124         OpenDialog od = new OpenDialog("Load mask file", "");
125         if (od.getPath() != null) { // not canceled
126           opts.maskFileName = od.getPath(); // not part of UI, store separately
127         }
128       }
129     });
130 
131     bsp.addSelectImageListener(new ItemListener() {
132 
133       @Override
134       public void itemStateChanged(ItemEvent e) {
135         if (e.getStateChange() == ItemEvent.ITEM_STATE_CHANGED) {
136           opts.paramFile = ""; // clear path to ask again on new file
137         }
138       }
139     });
140 
141     opts.options = bsp.getValues(); // store initial values
142   }
143 
144   /**
145    * Constructor for API calls.
146    * 
147    * <p>Call {@link #runPlugin()} afterwards. Note that {@link AbstractOptionsParser#apiCall} is
148    * set to true and sink to Console.
149    * 
150    * @param options configuration options
151    */
152   public BinarySegmentation_(AbstractPluginOptions options) {
153     super(options, thisPluginName);
154     apiCall = true;
155     errorSink = MessageSinkTypes.CONSOLE;
156   }
157 
158   /**
159    * Main plugin logic.
160    * 
161    * @throws QuimpPluginException on any error, handled by {@link #run(String)}
162    */
163   @Override
164   protected void runPlugin() throws QuimpPluginException {
165     bsp.setValues(((BinarySegmentationOptions) options).options); // update GUI
166     DataContainer dt = null;
167     BinarySegmentationOptions/com/github/celldynamics/quimp/plugin/binaryseg/BinarySegmentationOptions.html#BinarySegmentationOptions">BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
168     LOGGER.debug(opts.toString());
169     // try to open images selected mask override loaded one (if both specified)
170     String selectedImage = opts.options.get(BinarySegmentationView.SELECT_MASK);
171     String selectedOriginalImage = opts.options.get(BinarySegmentationView.SELECT_ORIGINAL_IMAGE);
172 
173     ImagePlus maskFile = null;
174     ImagePlus orgFile = null;
175     Path orgFilePath = null; // depending on source will be read from fileinfo or provided path
176     if (selectedImage != null && !selectedImage.equals(BOA_.NONE)) {
177       maskFile = WindowManager.getImage(selectedImage);
178     } else { // try file if exists
179       if (opts.maskFileName != null && !opts.maskFileName.isEmpty()) {
180         maskFile = IJ.openImage(opts.maskFileName);
181       }
182     }
183     if (selectedOriginalImage != null && !selectedOriginalImage.equals(BOA_.NONE)) {
184       orgFile = WindowManager.getImage(selectedOriginalImage);
185       if (orgFile != null) { // if window failed try as path
186         FileInfo orgfileinfo = orgFile.getFileInfo();
187         orgFilePath = Paths.get(orgfileinfo.directory, orgFile.getTitle());
188       } else {
189         orgFile = IJ.openImage(selectedOriginalImage);
190         orgFilePath = Paths.get(selectedOriginalImage);
191       }
192     } else {
193       orgFilePath = Paths.get(""); // just to show something in exception
194     }
195     if (maskFile == null) {
196       throw new QuimpPluginException("Mask can not be loaded or found.");
197     }
198     // here we have maskFile filled
199     FileInfo maskfileinfo = maskFile.getFileInfo();
200 
201     // reconstruct BOA structures if called without it
202     if (wasNest == false) {
203       nest = new Nest(); // run as plugin outside BOA
204       // initialise static fields in BOAState, required for nest.addHandlers(ret)
205       // use mask file but replace to initialise sizes, etc but replace names to org
206       BOA_.qState = new BOAState(maskFile);
207       dt = new DataContainer();
208       dt.BOAState = BOA_.qState;
209       dt.BOAState.nest = nest;
210       dt.BOAState.binarySegmentationPlugin = this;
211       dt.Stats = new StatsCollection();
212 
213       // try if original image was provided and recalculate Stats, required for BOAfree mode
214       if (orgFile == null) {
215         throw new QuimpPluginException(
216                 "Original image " + orgFilePath.toString() + " can not be opened");
217       }
218       dt.BOAState.boap.setOrgFile(orgFilePath.toFile());
219       dt.BOAState.boap.setOutputFileCore(opts.paramFile);
220 
221     }
222     LOGGER.debug("Segmentation: " + (maskFile != null ? maskFile.toString() : "null") + "params: "
223             + opts.toString());
224     BinarySegmentationlugin/binaryseg/BinarySegmentation.html#BinarySegmentation">BinarySegmentation obj = new BinarySegmentation(maskFile); // create segmentation object
225     obj.trackObjects(); // run tracking
226     ArrayList<ArrayList<SegmentedShapeRoi>> ret = obj.getChains(); // get results
227     // set interpolation params for every tracker. They are used when converting from
228     // SegmentedShapeRoi to points in SnakeHandler
229     boolean smoothing = opts.options.getBooleanValue(BinarySegmentationView.SMOOTHING2);
230     int step = opts.options.getIntValue(BinarySegmentationView.STEP2);
231     LOGGER.debug("step: " + step + " smooth: " + smoothing);
232     for (ArrayList<SegmentedShapeRoi> asS : ret) {
233       for (SegmentedShapeRoi ss : asS) {
234         ss.setInterpolationParameters(step, smoothing);
235       }
236     }
237     if (opts.options.getBooleanValue(BinarySegmentationView.CLEAR_NEST)) {
238       nest.cleanNest(); // remove old stuff
239     }
240     nest.addHandlers(ret); // convert from array of SegmentedShapeRoi to SnakeHandlers
241 
242     // if run as plugin, original image is not available, use mask instead
243     if (ip == null) {
244       ip = maskFile;
245     }
246     if (opts.options.getBooleanValue(BinarySegmentationView.RESTORE_SNAKE)) {
247       Constrictornstrictor.html#Constrictor">Constrictor constrictor = new Constrictor();
248       for (SnakeHandler sh : nest.getHandlers()) {
249         for (int f = sh.getStartFrame(); f <= sh.getEndFrame(); f++) {
250           sh.getBackupSnake(f).calcCentroid(); // actually this is calculated in Snake constr.
251           sh.getBackupSnake(f).setPositions(); // actually this is calculated in Snake constr.
252           sh.getBackupSnake(f).updateNormals(true); // calculated in Snake constr. but for other
253           sh.getBackupSnake(f).getBounds(); // actually this is calculated in Snake constr.
254 
255           sh.getStoredSnake(f).calcCentroid();
256           sh.getStoredSnake(f).setPositions();
257           sh.getStoredSnake(f).updateNormals(true);
258           sh.getStoredSnake(f).getBounds();
259 
260           constrictor.constrict(sh.getStoredSnake(f), ip.getStack().getProcessor(f));
261           constrictor.constrict(sh.getBackupSnake(f), ip.getStack().getProcessor(f));
262         }
263         sh.getLiveSnake().calcCentroid();
264         sh.getLiveSnake().setPositions();
265         sh.getLiveSnake().updateNormals(true);
266         sh.getLiveSnake().getBounds();
267         constrictor.constrict(sh.getLiveSnake(), ip.getStack().getProcessor(sh.getStartFrame()));
268       }
269     }
270     if (vu != null) {
271       vu.updateView(); // update view if we can
272     }
273     // save file, assume if ViewUpdater is not attached we are in standalone mode
274     Serializer<DataContainer> n = new Serializer<>(dt, QuimP.TOOL_VERSION);
275     n.setPretty();
276     if (wasNest == false && vu == null) { // will not execute if run from BOA
277 
278       List<CellStatsEval> retstat = nest.analyse(orgFile, true);
279       dt.Stats.copyFromCellStat(retstat);
280 
281       if (opts.paramFile == null || opts.paramFile.isEmpty()) { // ask for path
282         String folder = maskfileinfo.directory;
283         if (folder == null || folder.isEmpty()) {
284           folder = IJ.getDirectory("current");
285         }
286         String name = QuimpToolsCollection.removeExtension(maskFile.getTitle());
287         SaveDialog sd = new SaveDialog("Save QCONF", folder, name, FileExtensions.newConfigFileExt);
288         String selPath = sd.getDirectory();
289         String selName = sd.getFileName();
290         if (selPath != null && selName != null && !selPath.isEmpty() && !selName.isEmpty()) {
291           opts.paramFile = Paths.get(selPath, selName).toString();
292           dt.BOAState.boap.setOutputFileCore(opts.paramFile); // update in BOA
293         }
294       }
295       // save
296       try {
297         n.save(opts.paramFile);
298       } catch (FileNotFoundException e) {
299         throw new QuimpPluginException(e);
300       }
301 
302     }
303   }
304 
305   /**
306    * Return true if window is visible.
307    * 
308    * @return true if window is visible
309    */
310   public boolean isWindowVisible() {
311     return bsp.isWindowVisible();
312   }
313 
314   /*
315    * (non-Javadoc)
316    * 
317    * @see
318    * com.github.celldynamics.quimp.plugin.IQuimpPluginAttachNest#attachNest(com.github.celldynamics.
319    * quimp.Nest)
320    */
321   @Override
322   public void attachNest(Nest data) {
323     this.nest = data;
324     wasNest = true;
325   }
326 
327   /*
328    * (non-Javadoc)
329    * 
330    * @see
331    * com.github.celldynamics.quimp.plugin.IQuimpPluginSynchro#attachContext(com.github.celldynamics.
332    * quimp.ViewUpdater)
333    */
334   @Override
335   public void attachContext(ViewUpdater b) {
336     vu = b;
337   }
338 
339   /*
340    * (non-Javadoc)
341    * 
342    * @see com.github.celldynamics.quimp.plugin.PluginTemplate#showUi(boolean)
343    */
344   @Override
345   public void showUi(boolean val) {
346     bsp.setValues(((BinarySegmentationOptions) options).options); // update GUI
347     bsp.showWindow(val);
348 
349   }
350 
351   /*
352    * (non-Javadoc)
353    * 
354    * @see com.github.celldynamics.quimp.plugin.IQuimpPluginExchangeData#getPluginConfig()
355    */
356   @Override
357   public ParamList getPluginConfig() {
358     BinarySegmentationOptions/com/github/celldynamics/quimp/plugin/binaryseg/BinarySegmentationOptions.html#BinarySegmentationOptions">BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
359     ParamList tmp = bsp.getValues();
360     if (opts.maskFileName != null) {
361       tmp.put(BinarySegmentationView.LOADED_FILE, opts.maskFileName);
362     }
363     return tmp;
364   }
365 
366   /*
367    * (non-Javadoc)
368    *
369    * @see IQuimpCorePlugin#setPluginConfig(com.github.celldynamics.quimp.plugin.ParamList)
370    */
371   @Override
372   public void setPluginConfig(ParamList par) throws QuimpPluginException {
373     bsp.setValues(par); // will not update values kept outside UI in ParamList
374   }
375 
376   /*
377    * (non-Javadoc)
378    * 
379    * @see com.github.celldynamics.quimp.plugin.IQuimpPluginAttachImage#attachImage(ij.process.
380    * ImageProcessor)
381    */
382   @Override
383   public void attachImage(ImageProcessor img) {
384     ip = new ImagePlus("", img);
385 
386   }
387 
388   /*
389    * (non-Javadoc)
390    * 
391    * @see
392    * com.github.celldynamics.quimp.plugin.IQuimpPluginAttachImagePlus#attachImagePlus(ij.ImagePlus)
393    */
394   @Override
395   public void attachImagePlus(ImagePlus img) {
396     ip = img;
397   }
398 
399   /*
400    * (non-Javadoc)
401    * 
402    * @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
403    */
404   @Override
405   public String about() {
406     return "Binary segmentation.\n" + "Author: Piotr Baniukiewicz\n"
407             + "mail: p.baniukiewicz@warwick.ac.uk";
408   }
409 }