View Javadoc
1   package com.github.celldynamics.quimp.plugin.randomwalk;
2   
3   import java.awt.FlowLayout;
4   import java.awt.event.ActionEvent;
5   import java.awt.event.ActionListener;
6   import java.awt.event.ComponentAdapter;
7   import java.awt.event.ComponentEvent;
8   import java.awt.event.WindowAdapter;
9   import java.awt.event.WindowEvent;
10  import java.util.ArrayList;
11  import java.util.Arrays;
12  import java.util.List;
13  
14  import javax.swing.JButton;
15  import javax.swing.JFrame;
16  import javax.swing.JOptionPane;
17  import javax.swing.JPanel;
18  import javax.swing.SwingUtilities;
19  
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
24  import com.github.celldynamics.quimp.utils.UiTools;
25  
26  import ij.IJ;
27  import ij.ImagePlus;
28  import ij.gui.Line;
29  import ij.gui.Roi;
30  import ij.plugin.frame.RoiManager;
31  
32  /**
33   * Simple seed picker by means of ROIs. Allow to select multiple foreground objects and one
34   * background. Produces {@link Seeds} structure at output.
35   * 
36   * <p>Add current ROI to ROI list, if nothing selected look through ROI manager and renames
37   * un-renamed ROIs according to requested type (FG or BG).
38   * 
39   * @author p.baniukiewicz
40   *
41   */
42  @SuppressWarnings("serial")
43  public class SeedPicker extends JFrame {
44  
45    /**
46     * The Constant LOGGER.
47     */
48    static final Logger LOGGER = LoggerFactory.getLogger(SeedPicker.class.getName());
49  
50    /**
51     * Prefix for foreground ROIs.
52     */
53    public final String fgName = "fg";
54    /**
55     * Prefix for background ROI.
56     */
57    public final String bgName = "bg";
58  
59    /** The content pane. */
60    private JPanel contentPane;
61    /**
62     * Image used for seeding. It is not modified, only its dimensions are used.
63     */
64    public ImagePlus image;
65  
66    /** The rm. */
67    RoiManager rm;
68  
69    /** The last tool. */
70    private String lastTool;
71  
72    /** The last line width. */
73    private int lastLineWidth;
74  
75    /** The last fg num. */
76    private int lastFgNum = 0; // cell number
77  
78    /** The last bg num. */
79    private int lastBgNum = 0; // but this is index, cell is always 0 for BG
80  
81    /** The bn finish listeners. */
82    // For Finish button we need specific order of ActionListeners
83    private ActionListener[] bnFinishListeners = new ActionListener[2];
84    /**
85     * Converted seeds available after Finish.
86     */
87    public List<Seeds> seedsRoi = new ArrayList<>();
88  
89    /** The btn finish. */
90    private JButton btnFinish;
91  
92    /**
93     * Default constructor. Does not show window. Require {@link #image} to be set.
94     */
95    public SeedPicker() {
96      this(false);
97  
98    }
99  
100   /**
101    * Allow to provide image to process and disable UI. Recommended to API calls.
102    * 
103    * @param image image to seed, actually only size of it is required
104    * @param show true to show the UI
105    */
106   public SeedPicker(ImagePlus image, boolean show) {
107     this(show);
108     this.image = image;
109   }
110 
111   /**
112    * Allow to provide image to work with.
113    * 
114    * @param image image to seed, actually only size of it is required
115    */
116   public SeedPicker(ImagePlus image) {
117     this(true);
118     this.image = image;
119   }
120 
121   /**
122    * Create the frame.
123    * 
124    * @param show true to show the UI
125    */
126   public SeedPicker(boolean show) {
127     setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
128     setResizable(false);
129     setAlwaysOnTop(true);
130     setType(Type.NORMAL);
131     setTitle("Select seeds");
132     setVisible(show);
133     // setPreferredSize(new Dimension(300, 100));
134     // setBounds(100, 100, 295, 66);
135     contentPane = new JPanel();
136     setContentPane(contentPane);
137     contentPane.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
138 
139     JButton tglbtnNewFg = new JButton("New FG");
140     UiTools.setToolTip(tglbtnNewFg,
141             "Create next FG label from current selection or from unassigned ROIs from Roi Manager"
142                     + ". For creating one label from separated ROIs,"
143                     + " add them first to Roi Manager and then use this button.");
144     tglbtnNewFg.addActionListener(new ActionListener() {
145       public void actionPerformed(ActionEvent e) {
146         if (!increaseRois(fgName)) {
147           // nothing added - add current selection
148           Roi current = image.getRoi();
149           if (current != null) {
150             rm.add(image, current, 0);
151             increaseRois(fgName);
152           }
153         }
154       }
155     });
156     contentPane.add(tglbtnNewFg);
157 
158     JButton btnBackground = new JButton("New BG");
159     UiTools.setToolTip(btnBackground,
160             "Create next BG label from current selection or from unassigned ROIs from Roi Manager"
161                     + ". For creating one label from separated ROIs,"
162                     + " add them first to Roi Manager and then use this button.");
163     btnBackground.addActionListener(new ActionListener() {
164       public void actionPerformed(ActionEvent e) {
165         if (!increaseRois(bgName)) {
166           // nothing added - add current selection
167           Roi current = image.getRoi();
168           if (current != null) {
169             rm.add(image, current, 0);
170             increaseRois(bgName);
171           }
172         }
173       }
174     });
175     contentPane.add(btnBackground);
176 
177     btnFinish = new JButton("Finish");
178     // this listener should go first, before that from RandomWalkView
179     bnFinishListeners[0] = new ActionListener() {
180 
181       @Override
182       public void actionPerformed(ActionEvent e) {
183         try {
184           if (image == null) {
185             throw new RandomWalkException("No image opened with SeedPicker.");
186           }
187           seedsRoi = SeedProcessor.decodeSeedsfromRoiStack(Arrays.asList(rm.getRoisAsArray()),
188                   fgName, bgName, image.getWidth(), image.getHeight(), image.getImageStackSize());
189           dispose();
190         } catch (RandomWalkException ex) {
191           ex.setMessageSinkType(MessageSinkTypes.GUI);
192           ex.handleException((JFrame) SwingUtilities.getRoot(contentPane),
193                   "Problem with converting seeds from ROI");
194         }
195 
196       }
197     };
198     btnFinish.addActionListener(new ActionListener() {
199 
200       @Override
201       public void actionPerformed(ActionEvent e) {
202         bnFinishListeners[0].actionPerformed(e);
203         bnFinishListeners[1].actionPerformed(e);
204       }
205     });
206 
207     contentPane.add(btnFinish);
208 
209     addComponentListener(new ComponentAdapter() {
210       @Override
211       public void componentShown(ComponentEvent arg0) {
212         openRoiManager();
213         selectTools();
214       }
215 
216       @Override
217       public void componentHidden(ComponentEvent e) {
218         IJ.setTool(lastTool);
219         Line.setWidth(lastLineWidth);
220       }
221     });
222     addWindowListener(new WindowAdapter() {
223       @Override
224       public void windowClosing(WindowEvent arg0) {
225         IJ.setTool(lastTool);
226         Line.setWidth(lastLineWidth);
227       }
228 
229       @Override
230       public void windowClosed(WindowEvent e) {
231         IJ.setTool(lastTool);
232         Line.setWidth(lastLineWidth);
233       }
234     });
235     pack();
236   }
237 
238   /**
239    * Add Listener to Finish button that is executed as second.
240    * 
241    * <p>First listener prepares {@link SeedPicker#seedsRoi} structure, so second one can use it.
242    * 
243    * @param list listener to add as second
244    */
245   void addFinishController(ActionListener list) {
246     bnFinishListeners[1] = list;
247   }
248 
249   /**
250    * Rename ROIs in RoiManager and increases counters.
251    * 
252    * @param coreName ROI to rename
253    * @return true if something added
254    */
255   private boolean increaseRois(String coreName) {
256     boolean ret = false;
257     if (coreName.equals(fgName)) {
258       ret = renameRois(fgName);
259       if (ret) { // rename FG
260         lastFgNum++; // and increase counter if anything renamed
261       }
262     }
263     if (coreName.equals(bgName)) {
264       ret = renameRois(bgName);
265       if (ret) {
266         lastBgNum++;
267       }
268     }
269     return ret;
270   }
271 
272   /**
273    * Open roi manager.
274    */
275   private void openRoiManager() {
276     rm = RoiManager.getRoiManager();
277     // rm.reset();
278   }
279 
280   /**
281    * Select tools.
282    */
283   private void selectTools() {
284     lastTool = IJ.getToolName();
285     lastLineWidth = Line.getWidth();
286     IJ.setTool("freeline");
287     Line.setWidth(10);
288 
289   }
290 
291   /**
292    * Rename ROIs in RoiManager.
293    * 
294    * <p>Any nonrenamed ROIS will be renamed to format corenameCell_no, where Cell is cell number and
295    * no
296    * is ROI number for the cell. There can be many ROIs for one cell. For background there is only
297    * one object but again it cn be scribbled by many ROIs.
298    * 
299    * @param coreName corename ROI will be renamed to
300    * @return true if anything was added.
301    */
302   private boolean renameRois(String coreName) {
303     boolean added = false; // nothing renamed in manager
304     int localIndex; // for FG will progress and match global Index, for BG always 0
305     int localNum;
306     if (rm == null) {
307       return false;
308     }
309     if (coreName.equals(bgName)) {
310       localIndex = 0; // do not count backgrounds ROIs
311       localNum = lastBgNum;
312     } else {
313       localIndex = lastFgNum; // but count foreground
314       localNum = 0;
315     }
316     List<Roi> rois = Arrays.asList(rm.getRoisAsArray());
317     for (Roi roi : rois) {
318       String n = roi.getName();
319       if (n.startsWith(fgName) || n.startsWith(bgName)) {
320         continue;
321       }
322       int roiIndex = rm.getRoiIndex(roi);
323       rm.rename(roiIndex, coreName + localIndex + "_" + localNum);
324       roi.setName(coreName + localIndex + "_" + localNum);
325       localNum++;
326       added = true;
327     }
328     if (coreName.equals(bgName)) {
329       lastBgNum = localNum;
330     }
331     return added;
332   }
333 
334   /**
335    * Prepare for new ROI selection. Clears all counters, roi manager and Seed list.
336    */
337   public void reset() {
338     lastFgNum = 0;
339     lastBgNum = 0;
340     seedsRoi.clear();
341     if (rm != null) {
342       if (rm.getRoisAsArray().length != 0) {
343         int dialogButton = JOptionPane.YES_NO_OPTION;
344         int dialogResult =
345                 JOptionPane.showConfirmDialog(null, "Clear ROI manager??", "Warning", dialogButton);
346         if (dialogResult == JOptionPane.YES_OPTION) {
347           rm.reset();
348         }
349       }
350     }
351   }
352 
353 }