View Javadoc
1   package com.github.celldynamics.quimp.plugin.protanalysis;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Color;
5   import java.awt.Component;
6   import java.awt.Font;
7   import java.awt.Graphics;
8   import java.awt.Graphics2D;
9   import java.awt.GridBagConstraints;
10  import java.awt.GridBagLayout;
11  import java.awt.GridLayout;
12  import java.awt.Panel;
13  import java.awt.Point;
14  import java.awt.event.InputEvent;
15  import java.awt.event.MouseEvent;
16  import java.awt.geom.Rectangle2D;
17  import java.util.ArrayList;
18  import java.util.ListIterator;
19  
20  import javax.swing.BorderFactory;
21  import javax.swing.BoxLayout;
22  import javax.swing.ButtonGroup;
23  import javax.swing.JButton;
24  import javax.swing.JCheckBox;
25  import javax.swing.JComboBox;
26  import javax.swing.JLabel;
27  import javax.swing.JPanel;
28  import javax.swing.JRadioButton;
29  import javax.swing.JSeparator;
30  import javax.swing.JTextArea;
31  import javax.swing.JToggleButton;
32  import javax.swing.SwingConstants;
33  import javax.swing.SwingUtilities;
34  import javax.swing.border.EmptyBorder;
35  
36  import org.apache.commons.lang3.mutable.MutableBoolean;
37  import org.scijava.vecmath.Point2d;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  import com.github.celldynamics.quimp.Outline;
42  import com.github.celldynamics.quimp.OutlineHandler;
43  import com.github.celldynamics.quimp.QParamsQconf;
44  import com.github.celldynamics.quimp.QuimpException;
45  import com.github.celldynamics.quimp.Vert;
46  import com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions.GradientType;
47  import com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions.OutlinePlotTypes;
48  import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
49  import com.github.celldynamics.quimp.utils.UiTools;
50  import com.github.celldynamics.quimp.utils.graphics.GraphicsElements;
51  
52  import ij.ImagePlus;
53  import ij.gui.ImageCanvas;
54  import ij.gui.Overlay;
55  import ij.gui.PolygonRoi;
56  import ij.gui.Roi;
57  import ij.gui.StackWindow;
58  
59  /**
60   * Implement Prot Analysis UI based on IJ StackWindow.
61   * 
62   * @author baniu
63   *
64   */
65  @SuppressWarnings("serial")
66  class ProtAnalysisUi extends StackWindow {
67    private Prot_Analysis model; // main model with method to run on ui action
68    private Color overlayColor = Color.GREEN; // outline color
69  
70    private ArrayList<OutlineHandler> handlers; // extracted from loaded QCONF
71  
72    private Component cmp;
73    private Overlay overlay;
74    private ImagePlus imp; // local reference of main image from Prot_Analysis
75    JLabel pointsSelected = new JLabel("");
76    JLabel pointsSelectedPolar = new JLabel("");
77    JToggleButton bnPickPoint; // outside because it is set by CustomCanvas#mousePressed
78  
79    /**
80     * Construct the window.
81     * 
82     * @param model application logic module (with options)
83     * @param imp ImagePlus image to be displayed.
84     */
85    public ProtAnalysisUi(Prot_Analysis model, final ImagePlus imp) {
86      super(imp, new CustomCanvas(imp, model));
87      this.model = model;
88      try {
89        handlers = model.getQconfLoader().getEcmm().oHs;
90      } catch (QuimpException e) {
91        // we should never be here as ecmm is validated on load
92        throw new RuntimeException("ECMM can not be obtained");
93      }
94      cmp = this.getComponent(1);
95      remove(cmp); // FIXME Protect against single image
96      this.imp = imp;
97      buildWindow();
98    }
99  
100   void updateStaticFields() {
101     pointsSelected.setText(Integer.toString(model.selected.size()));
102     Point2d point = ((ProtAnalysisOptions) model.getOptions()).gradientPoint;
103     pointsSelectedPolar.setText(point.toString());
104   }
105 
106   /**
107    * Build the window.
108    */
109   public void buildWindow() {
110     ProtAnalysisOptions./../../com/github/celldynamics/quimp/plugin/protanalysis/ProtAnalysisOptions.html#ProtAnalysisOptions">ProtAnalysisOptions opt = (ProtAnalysisOptions) model.getOptions();
111     setLayout(new BorderLayout(10, 10));
112     add(ic, BorderLayout.CENTER); // IJ image
113     // panel for slidebar to make it more separated from window edges
114     JPanel cmpP = new JPanel();
115     cmpP.setLayout(new GridLayout());
116     cmpP.add(cmp);
117     cmpP.setBorder(new EmptyBorder(5, 5, 10, 10));
118     add(cmpP, BorderLayout.SOUTH); // slidebar
119     // right panel
120     Panel right = new Panel();
121     right.setLayout(new GridBagLayout());
122     GridBagConstraints c = new GridBagConstraints();
123     { // point selection panel
124       JPanel visualTrackingPanel = new JPanel();
125       // final int selectPointsPanelHeight = 160;
126       visualTrackingPanel.setLayout(new BoxLayout(visualTrackingPanel, BoxLayout.PAGE_AXIS));
127       visualTrackingPanel.setBorder(BorderFactory.createTitledBorder("Visual tracking"));
128       { // two text lines: help and selected points
129         JTextArea help = new JTextArea("Select points with CTRL key");
130         help.setLineWrap(true);
131         help.setWrapStyleWord(true);
132         help.setAlignmentX(Component.RIGHT_ALIGNMENT);
133 
134         // selected points: VAL
135         JPanel textPanel = new JPanel();
136         textPanel.setLayout(new GridLayout(1, 2));
137         JLabel selected = new JLabel("Selected: ");
138         textPanel.add(selected);
139         pointsSelected.setHorizontalAlignment(JLabel.RIGHT);
140         textPanel.add(pointsSelected);
141         visualTrackingPanel.add(buildSubPanel(2, 1, help, textPanel));
142       }
143       { // line with two buttons ROI
144         visualTrackingPanel.add(buildSubPanel(1, 2, getButton(new ActionToRoi("-> ROI",
145                 "Transfer selected points to ROI manager. ROI manager will be cleared", this)),
146                 getButton(new ActionFromRoi("<- ROI",
147                         "Import ROIs from Roi Manager. ROI name must have format: "
148                                 + ProtAnalysisOptions.roiPrefix
149                                 + "CELLNO, where CELLNO is cell index. Hold CTRL key if all points "
150                                 + "refer to the same cell (no naming pattern needed, active cell is"
151                                 + " the one selected in Maps section).",
152                         this))));
153       }
154       { // line with one button clear
155         visualTrackingPanel.add(buildSubPanel(1, 1, getButton(
156                 new ActionClearPoints("Remove all", "Remove all selected points", this))));
157       }
158       { // line with 2 radio buttons
159         JRadioButton rbnStatic = new JRadioButton();
160         rbnStatic.setAction(new ActionUpdateOptionsRadio("Static", "Static", this,
161                 opt.plotStaticDynamic, ProtAnalysisOptions.PLOT_STATIC));
162         JRadioButton rbnDynamic = new JRadioButton();
163         rbnDynamic.setAction(new ActionUpdateOptionsRadio("Dynamic", "Dynamic", this,
164                 opt.plotStaticDynamic, ProtAnalysisOptions.PLOT_DYNAMIC));
165         switch (opt.plotStaticDynamic.getValue()) {
166           case ProtAnalysisOptions.PLOT_STATIC:
167             rbnStatic.setSelected(true);
168             break;
169           case ProtAnalysisOptions.PLOT_DYNAMIC:
170             rbnDynamic.setSelected(true);
171             break;
172           default:
173             rbnStatic.setSelected(true);
174         }
175         ButtonGroup buttonGroup = new ButtonGroup();
176         buttonGroup.add(rbnStatic);
177         buttonGroup.add(rbnDynamic);
178         visualTrackingPanel.add(buildSubPanel(1, 2, rbnStatic, rbnDynamic));
179       }
180       { // line with 2 checkbox
181         visualTrackingPanel.add(buildSubPanel(2, 2,
182                 getCheckbox("Show point", "Show tracked point", opt.chbShowPoint),
183                 getCheckbox("Show track", "Show tracks", opt.chbShowTrack),
184                 getCheckbox("Smooth", "!Apply track smoothing in static and dynamic view",
185                         opt.chbSmoothTracks),
186                 getCheckbox("Show map", "Show tracks on motility map", opt.chbShowTrackMotility)));
187       }
188       { // line with outline selector
189         JComboBox<OutlinePlotTypes> cbOutlineColor =
190                 new JComboBox<OutlinePlotTypes>(OutlinePlotTypes.values());
191         cbOutlineColor.setSelectedItem(opt.selOutlineColoring.plotType);
192         cbOutlineColor.setAction(new ActionUpdateOptionsEnum("Outline color", "Set outline color",
193                 this, opt.selOutlineColoring));
194         visualTrackingPanel.add(buildSubPanel(1, 1, cbOutlineColor));
195       }
196       { // line with open new image check box
197         JCheckBox cb =
198                 getCheckbox("New image", "Always open new image with tracks", opt.chbNewImage);
199         visualTrackingPanel.add(buildSubPanel(1, 1, cb, getCheckbox("Flatten",
200                 "Flatten stack used for showing static tracks", opt.chbFlattenStaticTrackImage)));
201         // TODO decide if we need this. Refreshing overlay on org image will cause problems
202         cb.setEnabled(false);
203       }
204       { // two lines with buttons
205         JButton tmpButton = getButton(new ActionTrackPoints("Track", "Track points", this));
206         tmpButton.addActionListener(new ActionSaveTracks(this)); // second action to this button
207         visualTrackingPanel.add(buildSubPanel(2, 1, tmpButton,
208                 getButton(new ActionClearOverlay("Clear overlay", "Clear Overlay", this))));
209       }
210       c.anchor = GridBagConstraints.NORTH;
211       c.gridx = 0;
212       c.gridy = 0;
213       c.weighty = 0;
214       c.fill = GridBagConstraints.HORIZONTAL;
215       right.add(visualTrackingPanel, c);
216     }
217     { // Maps Panel
218       JPanel mapsPanel = new JPanel();
219       mapsPanel.setLayout(new BoxLayout(mapsPanel, BoxLayout.Y_AXIS));
220       mapsPanel.setBorder(BorderFactory.createTitledBorder("Maps"));
221       { // line with cell selector
222         JComboBox<Integer> cbMapCellNumber = new JComboBox<Integer>();
223         // get stmap but without checking, assume that there is q analysis
224         STmap[] gs = ((QParamsQconf) model.getQconfLoader().getQp()).getLoadedDataContainer()
225                 .getQState();
226         setComboBox(cbMapCellNumber, 0, gs.length - 1, opt.selActiveCellMap.getValue());
227         cbMapCellNumber.setAction(new ActionUpdateOptionsNumber("Cell number",
228                 "Which cell to generate map for.", this, opt.selActiveCellMap));
229         mapsPanel.add(buildSubPanel(1, 1, cbMapCellNumber));
230       }
231       { // map type line, 3 buttons
232         mapsPanel.add(buildSubPanel(1, 3, getButton(new ActionPlotMap("Mot",
233                 "Plot motility map for selected cell. Hold CTRL to get unscaled 32-bit map.", this,
234                 "MOT")),
235                 getButton(new ActionPlotMap("Con",
236                         "Plot convexity map for selected cell. Hold CTRL to get unscaled 32-bit "
237                                 + "map.",
238                         this, "CONV")),
239                 getButton(new ActionPlotMap("Flu",
240                         "Plot fluoresence maps for selected cell. Hold CTRL to get unscaled 32-bit"
241                                 + " map.",
242                         this, "FLU"))));
243       }
244       c.anchor = GridBagConstraints.NORTHWEST;
245       c.gridx = 0;
246       c.gridy = 1;
247       c.weighty = 0;
248       right.add(mapsPanel, c);
249     }
250     { // tables panel
251       JPanel tablePanel = new JPanel();
252       tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS));
253       tablePanel.setBorder(BorderFactory.createTitledBorder("Tables and plots"));
254       { // select cell
255         JComboBox<Integer> cbPlotCellNumber = new JComboBox<Integer>();
256         // get stmap but without checking, assume that there is q analysis
257         STmap[] gs = ((QParamsQconf) model.getQconfLoader().getQp()).getLoadedDataContainer()
258                 .getQState();
259         setComboBox(cbPlotCellNumber, 0, gs.length - 1, opt.selActiveCellPlot.getValue());
260         cbPlotCellNumber.setAction(new ActionUpdateOptionsNumber("Cell number",
261                 "Which cell to generate map for.", this, opt.selActiveCellPlot));
262         tablePanel.add(buildSubPanel(1, 1, cbPlotCellNumber));
263       }
264       { // line with channel selection (3 radios)
265         JRadioButton rbnCh1 = new JRadioButton();
266         rbnCh1.setAction(new ActionUpdateOptionsRadio("Ch1", "Set channel 1 active", this,
267                 opt.selActiveChannel, ProtAnalysisOptions.CH1));
268         JRadioButton rbnCh2 = new JRadioButton();
269         rbnCh2.setAction(new ActionUpdateOptionsRadio("Ch2", "Set channel 2 active", this,
270                 opt.selActiveChannel, ProtAnalysisOptions.CH2));
271         JRadioButton rbnCh3 = new JRadioButton();
272         rbnCh3.setAction(new ActionUpdateOptionsRadio("Ch3", "Set channel 3 active", this,
273                 opt.selActiveChannel, ProtAnalysisOptions.CH3));
274         switch (opt.selActiveChannel.getValue()) {
275           case ProtAnalysisOptions.CH2:
276             rbnCh2.setSelected(true);
277             break;
278           case ProtAnalysisOptions.CH3:
279             rbnCh3.setSelected(true);
280             break;
281           default:
282             rbnCh1.setSelected(true);
283         }
284         ButtonGroup buttonGroup = new ButtonGroup();
285         buttonGroup.add(rbnCh1);
286         buttonGroup.add(rbnCh2);
287         buttonGroup.add(rbnCh3);
288         tablePanel.add(buildSubPanel(1, 3, rbnCh1, rbnCh2, rbnCh3));
289       }
290       { // tables
291         tablePanel.add(buildSubPanel(1, 1, new JSeparator(SwingConstants.HORIZONTAL)));
292         tablePanel.add(buildSubPanel(1, 2,
293                 getButton(new ActionTableGeom("Geom", "Show geometric features in table.", this)),
294                 getButton(
295                         new ActionTableFluo("Fluo", "Show fluoresecne features in table.", this))));
296       }
297       { // separator
298         tablePanel.add(buildSubPanel(1, 1, new JSeparator(SwingConstants.HORIZONTAL)));
299       }
300       { // suff to plot checkboxes
301         tablePanel.add(buildSubPanel(5, 2,
302                 getCheckbox("X-Centr", "Centroid x-coordinate", opt.chbXcentrPlot),
303                 getCheckbox("Y-Centr", "Centroid y-coordinate", opt.chbYcentrPlot),
304                 getCheckbox("Displ", "Displacement", opt.chbDisplPlot),
305                 getCheckbox("Dist", "Distance", opt.chbDistPlot),
306                 getCheckbox("Persist", "Persistence", opt.chbPersistencePlot),
307                 getCheckbox("Speed", "Speed", opt.chbSpeedPlot),
308                 getCheckbox("Perim", "Perimeter", opt.chbPerimPlot),
309                 getCheckbox("Elong", "Elongation", opt.chbElongPlot),
310                 getCheckbox("Circ", "Circularity", opt.chbCircPlot),
311                 getCheckbox("Area", "Area", opt.chbAreaPlot)));
312       }
313       { // separator
314         tablePanel.add(buildSubPanel(1, 1, new JSeparator(SwingConstants.HORIZONTAL)));
315       }
316       { // suff to plot checkboxes
317         tablePanel.add(buildSubPanel(5, 2,
318                 // totalFluor
319                 getCheckbox("Total fl",
320                         "Total fluorescence. Sum of all pixel intensities within the cell outline.",
321                         opt.chbTotFluPlot),
322                 // meanFluor
323                 getCheckbox("Mean fl",
324                         "Mean fluorescence. Average intensity of pixels within the cell outline.",
325                         opt.chbMeanFluPlot),
326                 // cortexWidth
327                 getCheckbox("Cortex", "Width of the cortex, as specified by the user.",
328                         opt.chbCortexWidthPlot),
329                 // innerArea
330                 getCheckbox("Cyto",
331                         "Area of the cytoplasm (area of the whole cell minus the cortex area).",
332                         opt.chbCytoAreaPlot),
333                 // totalInnerFluor
334                 getCheckbox("Total ctf", "Sum of all pixel intensities within the cytoplasm.",
335                         opt.chbTotalCytoPlot),
336                 // meanInnerFluor
337                 getCheckbox("Mean ctf", "Average pixel intensity within the cytoplasm.",
338                         opt.chbMeanCytoPlot),
339                 // cortexArea
340                 getCheckbox("Cortex ar", "Area of the cortex.", opt.chbCortexAreaPlot),
341                 // totalCorFluo
342                 getCheckbox("Total ctf", "Sum of all pixel intensities within the cortex.",
343                         opt.chbTotalCtf2Plot),
344                 // meanCorFluo
345                 getCheckbox("Mean ctf", "Average pixel intensity within the cortex.",
346                         opt.chbManCtfPlot)));
347       }
348       { // button
349         tablePanel.add(buildSubPanel(1, 1,
350                 getButton(new ActionPlot2d("Plot", "Plot selected parameters.", this))));
351       }
352       c.anchor = GridBagConstraints.NORTHWEST;
353       c.gridx = 0;
354       c.gridy = 2;
355       c.weighty = 0;
356       right.add(tablePanel, c);
357     }
358     { // polarplot panel
359       JPanel polarPanel = new JPanel();
360       polarPanel.setLayout(new BoxLayout(polarPanel, BoxLayout.Y_AXIS));
361       polarPanel.setBorder(BorderFactory.createTitledBorder("Polar plots"));
362       polarPanel.setToolTipText(UiTools.getToolTipString(
363               "Use of any selection tool from those below overwrites current selected point."));
364       { // row with buttons
365         bnPickPoint = getToggleButton(new ActionClickPredefinedPoint("Click",
366                 "Select reference point on the screen.", this));
367         polarPanel.add(buildSubPanel(1, 2, bnPickPoint, getButton(
368                 new ActionRoiPredefinedPoint("ROI", "Select reference point from ROI.", this))));
369       }
370       { // relative to line
371         JComboBox<GradientType> cbRelativePolar =
372                 new JComboBox<GradientType>(GradientType.values());
373         cbRelativePolar.setSelectedItem(GradientType.LB_CORNER);
374         cbRelativePolar.setAction(
375                 new ActionGetPredefinedPoint("Relative to", "Select point relative to:", this));
376         polarPanel.add(buildSubPanel(1, 1, cbRelativePolar));
377       }
378       { // info
379         JLabel selected = new JLabel("Selected: ");
380         Font f = selected.getFont();
381         selected.setFont(f.deriveFont(f.getStyle() | Font.ITALIC | Font.BOLD));
382         pointsSelectedPolar.setHorizontalAlignment(JLabel.RIGHT);
383         polarPanel.add(buildSubPanel(1, 2, selected, pointsSelectedPolar));
384       }
385       { // generate
386         polarPanel.add(buildSubPanel(1, 1, getButton(new ActionPolarPlot("Generate",
387                 "Generate polar plot for reference point as shown above.", this))));
388       }
389       c.anchor = GridBagConstraints.NORTHWEST;
390       c.gridx = 0;
391       c.gridy = 3;
392       c.weighty = 1;
393       right.add(polarPanel, c);
394     }
395 
396     add(right, BorderLayout.EAST);
397     pack();
398     updateStaticFields();
399     // this.setSize(600, 600);
400     imp.setSlice(1);
401     updateOverlay(1);
402     setVisible(false); // to allow use showUI
403   }
404 
405   /**
406    * Helper to produce checkboxes.
407    * 
408    * @param name checkbox name
409    * @param desc tooltip, if starts with !, option is not implemented. For tests rather
410    * @param option option related
411    * @return checkbox
412    */
413   private JCheckBox getCheckbox(String name, String desc, MutableBoolean option) {
414     JCheckBox chb = new JCheckBox();
415     chb.setAction(new ActionUpdateOptionsBoolean(name, desc, this, option));
416     chb.setSelected(option.getValue());
417     if (desc.startsWith("!")) {
418       chb.setAction(new ActionNotSupported(name, desc, this));
419     }
420     return chb;
421   }
422 
423   /**
424    * Helper to produce buttons.
425    * 
426    * @param act action
427    * @return button
428    */
429   private JButton getButton(ProtAnalysisAbstractAction act) {
430     JButton chb = new JButton();
431     chb.setAction(act);
432     return chb;
433   }
434 
435   /**
436    * Helper to produce buttons.
437    * 
438    * @param act action
439    * @return toggle button
440    */
441   private JToggleButton getToggleButton(ProtAnalysisAbstractAction act) {
442     JToggleButton chb = new JToggleButton();
443     chb.setAction(act);
444     return chb;
445   }
446 
447   /**
448    * Helper for setting entries in JComboBox.
449    * 
450    * @param component component
451    * @param item item
452    * @param sel selection entry, should be in item list
453    */
454   private void setJComboBox(JComboBox<String> component, String[] item, String sel) {
455     for (String i : item) {
456       component.addItem(i);
457     }
458     if (item.length > 0) {
459       component.setSelectedItem(sel);
460     }
461   }
462 
463   /**
464    * Populate component with range of integers.
465    * 
466    * @param component component
467    * @param min first value
468    * @param max last value
469    * @param sel selected value
470    */
471   private void setComboBox(JComboBox<Integer> component, Integer min, Integer max, Integer sel) {
472     for (Integer i = min; i <= max; i++) {
473       component.addItem(i);
474     }
475     component.setSelectedItem(sel);
476   }
477 
478   /**
479    * Add components to GridLayout panel.
480    * 
481    * @param w number of rows
482    * @param h number of columns
483    * @param cmp components to add (vararg)
484    * @return Constructed panel
485    */
486   private JPanel buildSubPanel(int w, int h, Component... cmp) {
487     JPanel localPanel = new JPanel(new GridLayout(w, h));
488     for (Component c : cmp) {
489       localPanel.add(c);
490     }
491     return localPanel;
492 
493   }
494 
495   /*
496    * On each slice change.
497    * 
498    * Actions performed:
499    * - Clear outlines array (keep outlines only for current frame)
500    * - Clear selected points
501    * - Update overlay for new frame
502    * TODO Comply with new image checkbox
503    */
504   @Override
505   public void updateSliceSelector() {
506     super.updateSliceSelector();
507     model.currentFrame = imp.getCurrentSlice() - 1;
508     model.outlines.clear(); // remove old outlines for old frame
509     updateOverlay(model.currentFrame + 1);
510 
511   }
512 
513   /**
514    * Plot overlay (outline) at frame.
515    * 
516    * <p>Called on each slice selector action.
517    * 
518    * @param frame to plot in (1-based)
519    */
520   public void updateOverlay(int frame) {
521     overlay = new Overlay();
522     for (OutlineHandler oh : handlers) {
523       if (oh.isOutlineAt(frame)) {
524         Outline outline = oh.getStoredOutline(frame);
525         model.outlines.add(outline); // remember outline for proximity calculations
526         Roi r = outline.asFloatRoi();
527         r.setStrokeColor(overlayColor);
528         overlay.add(r);
529       }
530     }
531     imp.setOverlay(overlay);
532     updateOverlayPoints(frame);
533   }
534 
535   /**
536    * Add only {@link PointCoords} from {@link Prot_Analysis#selected} to overlay.
537    * 
538    * <p>Overlay must exist already in the image.
539    * 
540    * @param frame frame to update
541    */
542   void updateOverlayPoints(int frame) {
543     // name of all points selected by user
544     final String roiName = "USER_POINT";
545     overlay = imp.getOverlay();
546     if (overlay == null) {
547       return;
548     }
549     // remove all Rois with name USER_POINT. This trick is necessary to remove
550     // roi if it is clicked second time
551     overlay.remove(roiName);
552     // find points
553     for (PointCoords p : model.selected) {
554       if (p.frame == imp.getCurrentSlice() - 1) {
555         PolygonRoi or = GraphicsElements.getCircle(p.point.getX(), p.point.getY(),
556                 ProtAnalysisOptions.staticPointColor, ProtAnalysisOptions.staticPointSize);
557         or.setName(roiName);
558         overlay.add(or);
559       }
560     }
561   }
562 
563   /**
564    * Show UI.
565    * 
566    * @param val true or false to show or hide UI
567    */
568   public void showUI(boolean val) {
569     setVisible(val);
570   }
571 
572   /**
573    * Get model.
574    * 
575    * @return Reference to application model class
576    */
577   Prot_Analysis getModel() {
578     return model;
579   }
580 }
581 
582 /**
583  * Handle mouse events.
584  * 
585  * @author baniu
586  *
587  */
588 @SuppressWarnings("serial")
589 class CustomCanvas extends ImageCanvas {
590   static final Logger LOGGER = LoggerFactory.getLogger(CustomCanvas.class.getName());
591   // closest point on outline to mouse position (image coordinates) + index of outline
592   // updated on mouse move and copied to model on LMB
593   PointCoords pc = null;
594   private Prot_Analysis model; // main model with method to run on ui action
595   private ProtAnalysisOptions options; // helper
596   private int sensitivity = 10; // square of distance
597 
598   public CustomCanvas(ImagePlus imp, Prot_Analysis model) {
599     super(imp);
600     this.model = model;
601     this.options = ((ProtAnalysisOptions) model.getOptions());
602   }
603 
604   /*
605    * (non-Javadoc)
606    * 
607    * @see ij.gui.ImageCanvas#mousePressed(java.awt.event.MouseEvent)
608    */
609   @Override
610   public void mousePressed(MouseEvent e) {
611     // action - select outline point if CTRL is pressed and LMB. In this mode IJ handlers are
612     // suppressed. Second click on the point will remove it
613     if (SwingUtilities.isLeftMouseButton(e)
614             && ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK)) {
615       if (pc != null) {
616         // store in model after appending frame number
617         if (model.selected.add(pc) == false) { // already exists
618           model.selected.remove(pc); // so remove
619         }
620         model.getGui().updateStaticFields();
621         model.getGui().updateOverlayPoints(model.currentFrame);
622       }
623     } else {
624       if (SwingUtilities.isLeftMouseButton(e) && options.bnGradientPickActive.booleanValue()) {
625         options.gradientPoint = new Point2d(e.getX(), e.getY());
626         model.getGui().bnPickPoint.doClick();
627         model.getGui().updateStaticFields();
628       } else {
629         super.mousePressed(e);
630       }
631     }
632 
633   }
634 
635   /**
636    * Find closes point between outlines for current frame (outlines) and mouse position.
637    * 
638    * @param current current mouse position in the image coordinates
639    * @param dist max distance
640    * @return found point that belongs to outline (image coordinates, frame) and outline index in
641    *         {@link Prot_Analysis#outlines}
642    */
643   private PointCoords checkProximity(Point current, double dist) {
644     // Point current = new Point(screenXD(currentt.getX()), screenYD(currentt.getY()));
645     ListIterator<Outline> it = model.outlines.listIterator();
646     while (it.hasNext()) {
647       Integer io = it.nextIndex(); // order!
648       Outline o = it.next();
649       Rectangle2D.Double bounds = o.getDoubleBounds(); // FIXME cache
650       if (bounds.contains(current)) { // investigate deeper
651         for (Vert v : o) { // over vertices
652           if (current.distanceSq(v.getX(), v.getY()) < dist) {
653             return new PointCoords(
654                     new Point((int) Math.round(v.getX()), (int) Math.round(v.getY())), io);
655           }
656         }
657       }
658     }
659     return null;
660   }
661 
662   /*
663    * (non-Javadoc)
664    * 
665    * @see ij.gui.ImageCanvas#mouseMoved(java.awt.event.MouseEvent)
666    */
667   @Override
668   public void mouseMoved(MouseEvent e) {
669     super.mouseMoved(e);
670     // offscreen - coordinates of the image, regardless zoom. e - absolute coordinates of the window
671     Point p = new Point(offScreenX(e.getX()), offScreenY(e.getY()));
672     // LOGGER.trace("e: [" + e.getX() + "," + e.getY() + "] offScreenX: " + p.toString());
673     PointCoords ptmp = checkProximity(p, sensitivity);
674     if (ptmp != null) { // if there is point close
675       pc = ptmp; // set it to current under mouse
676       repaint(); // refresh
677     } else {
678       if (pc != null) {
679         pc = null; // otherwise clear current under mouse
680         repaint(); // and repaint
681       }
682     }
683   }
684 
685   /*
686    * (non-Javadoc)
687    * 
688    * @see ij.gui.ImageCanvas#paint(java.awt.Graphics)
689    */
690   @Override
691   public void paint(Graphics g) {
692     super.paint(g);
693     Graphics2D g2 = (Graphics2D) g;
694     double half = ProtAnalysisOptions.pointSize / 2;
695     if (pc != null) {
696       Rectangle2D e = new Rectangle2D.Double(screenXD(pc.point.getX()) - half,
697               screenYD(pc.point.getY()) - half, ProtAnalysisOptions.pointSize,
698               ProtAnalysisOptions.pointSize);
699       g2.setPaint(ProtAnalysisOptions.pointColor);
700       g2.draw(e);
701     }
702   }
703 
704 }