Use internal SNAPSHOT couplings again
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / ContrastAdjustmentMode.java
blob34dcf12e1e25d0e8ff9547c142b0fa4a69a52b83
1 package ini.trakem2.display;
3 import ij.IJ;
4 import ij.measure.Measurements;
5 import ij.process.FloatProcessor;
6 import ij.process.ImageProcessor;
7 import ij.process.ImageStatistics;
8 import ini.trakem2.imaging.ContrastPlot;
9 import ini.trakem2.utils.Bureaucrat;
10 import ini.trakem2.utils.IJError;
11 import ini.trakem2.utils.Utils;
12 import ini.trakem2.utils.Worker;
14 import java.awt.Color;
15 import java.awt.Dimension;
16 import java.awt.FlowLayout;
17 import java.awt.Font;
18 import java.awt.Graphics2D;
19 import java.awt.GridBagConstraints;
20 import java.awt.GridBagLayout;
21 import java.awt.Insets;
22 import java.awt.Rectangle;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.awt.event.MouseEvent;
26 import java.awt.event.WindowAdapter;
27 import java.awt.event.WindowEvent;
28 import java.awt.image.BufferedImage;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.concurrent.Future;
36 import javax.swing.JButton;
37 import javax.swing.JFrame;
38 import javax.swing.JLabel;
39 import javax.swing.JPanel;
40 import javax.swing.JSlider;
41 import javax.swing.event.ChangeEvent;
42 import javax.swing.event.ChangeListener;
44 public class ContrastAdjustmentMode extends GroupingMode {
46 private MinMaxData min_max = new MinMaxData();
48 protected void doPainterUpdate( final Rectangle r, final double m ) {
49 try {
50 MinMaxData md = min_max.clone();
51 final HashMap<Paintable, GroupingMode.ScreenPatchRange<?>> screenPatchRanges = this.screenPatchRanges; // keep a pointer to the current list
52 for ( final GroupingMode.ScreenPatchRange<?> spr : screenPatchRanges.values()) {
53 if (screenPatchRanges != this.screenPatchRanges) {
54 // List has been updated; restart painting
55 // TODO should it call itself: doPainterUpdate( r, m );
56 break;
58 ((ScreenPatchRange)spr).update( md );
60 } catch (Exception e) {}
63 private class ContrastAdjustmentSource extends GroupingMode.GroupedGraphicsSource {
64 public void paintOnTop(final Graphics2D g, final Display display, final Rectangle srcRect, final double magnification) {
65 // do nothing
69 protected GroupingMode.GroupedGraphicsSource createGroupedGraphicSource() {
70 return new ContrastAdjustmentSource();
73 protected ScreenPatchRange createScreenPathRange(final PatchRange range, final Rectangle srcRect, final double magnification) {
74 return new ScreenPatchRange(range, srcRect, magnification);
77 private class ScreenPatchRange extends GroupingMode.ScreenPatchRange<MinMaxData> {
79 ScreenPatchRange( final PatchRange range, final Rectangle srcRect, final double magnification )
81 // The super constructor creates an appropriate alpha mask
82 super(range, srcRect, magnification);
83 super.transformedImage = makeImage(null, null);
85 @Override
86 protected BufferedImage makeImage( final ImageProcessor ignored, final FloatProcessor mask )
88 if (null == transformed) return null; // not yet ready
90 // Use the super.mask, which never changes. super.transformedMask is empty, don't use it!
91 return super.makeImage(transformed, super.mask);
93 @Override
94 public void update(MinMaxData m) {
95 // Transform min and max from slider values to image values
96 double[] mm = toImage(m.min, m.max);
97 transformed.reset();
98 transformed.setMinAndMax(mm[0], mm[1]);
99 super.transformedImage = makeImage(null, super.mask);
103 private final double[] toImage(double slider_min, double slider_max) {
104 double imin = initial.getMin();
105 double imax = initial.getMax();
106 double ratio = (imax-imin) / sliderRange;
107 return new double[]{imin + slider_min * ratio, slider_max * ratio};
110 /** Expected min,max in slider values, which may be considerably smaller than the proper image min and max. */
111 private final void updateLabelsAndPlot(double min, double max) {
112 double[] m = toImage(min, max);
113 minLabel.setText(Utils.cutNumber(m[0], 1));
114 maxLabel.setText(Utils.cutNumber(m[1], 1));
115 plot.update(m[0], m[1]);
118 static private class MinMaxData {
119 /** Min and max in slider values, not in image values. */
120 double min = 0,
121 max = 0;
122 public MinMaxData() {}
123 public MinMaxData(double min, double max) {
124 set(min, max);
126 synchronized public void set(double min, double max) {
127 this.min = min;
128 this.max = max;
130 synchronized public MinMaxData clone() {
131 return new MinMaxData(min ,max);
135 private ImageProcessor initial, transformed;
136 private final JFrame frame;
137 private final ContrastPlot plot;
138 private final JLabel minLabel, maxLabel;
139 private int sliderRange;
141 public ContrastAdjustmentMode(final Display display, final List<Displayable> selected) throws Exception {
142 super(display, selected);
144 // Check that all images are of the same type
145 int type = originalPatches.get(0).getType();
146 for (Patch p : originalPatches)
147 if (p.getType() != type)
148 throw new Exception("All images must be of the same type!\nFirst offending image: " + p);
150 // Create an ImageProcessor of the correct type (Short, Float, etc.)
151 ArrayList<Patch> patches = new ArrayList<Patch>(originalPatches);
152 Patch first = patches.get(0);
153 int pad = (int)(ScreenPatchRange.pad / magnification);
154 Rectangle box = new Rectangle(srcRect.x - pad, srcRect.y - pad, srcRect.width + 2*pad, srcRect.height + 2*pad);
155 initial = Patch.makeFlatImage(first.getType(), layer, box, magnification, patches, Color.black);
156 initial.resetMinAndMax();
157 transformed = initial.duplicate();
158 transformed.setMinAndMax(first.getMin(), first.getMax());
159 transformed.snapshot();
161 Utils.log2("transformed min, max: " + transformed.getMin() + ", " + transformed.getMax());
162 ImageStatistics stats = ImageStatistics.getStatistics(transformed, Measurements.AREA + Measurements.MEAN + Measurements.MODE + Measurements.MIN_MAX, layer.getParent().getCalibrationCopy());
163 Utils.log2("stats.min " + stats.min + ", stats.max " + stats.max);
164 // correct min and max, which for some reason can be wrong (the min can be zero, for example):
165 if (stats.min < initial.getMin()) stats.min = initial.getMin();
166 if (stats.max > initial.getMax()) stats.max = initial.getMax();
168 // stats is giving a minimum of zero even if its wrong
169 plot = new ContrastPlot(initial.getMin(), initial.getMax(), first.getMin(), first.getMax());
170 plot.setHistogram(stats, Color.black);
172 this.sliderRange = computeSliderRange();
174 // Create GUI
175 this.frame = new JFrame("Contrast adjustment");
176 frame.addWindowListener(new WindowAdapter() {
177 public void windowClosing(WindowEvent we) {
178 display.getCanvas().cancelTransform();
181 final JPanel panel = new JPanel();
182 panel.setBackground(Color.white);
183 final GridBagLayout gb = new GridBagLayout();
184 final GridBagConstraints c = new GridBagConstraints();
185 panel.setLayout(gb);
187 // 1. Plot
188 c.gridx = 0;
189 c.gridy = 0;
190 c.fill = GridBagConstraints.NONE;
191 c.anchor = GridBagConstraints.CENTER;
192 c.insets = new Insets(10, 10, 0, 10);
193 gb.setConstraints(plot, c);
194 panel.add(plot);
196 // 2. min,max labels
197 final JPanel mm = new JPanel();
198 mm.setMinimumSize(new Dimension(plot.getWidth(), 15));
199 mm.setBackground(Color.white);
200 final Font monoFont = new Font("Monospaced", Font.PLAIN, 12);
202 GridBagLayout gbm = new GridBagLayout();
203 GridBagConstraints cm = new GridBagConstraints();
204 mm.setLayout(gbm);
205 minLabel = new JLabel(" ");
206 minLabel.setFont(monoFont);
207 minLabel.setBackground(Color.white);
208 maxLabel = new JLabel(" ");
209 maxLabel.setFont(monoFont);
210 maxLabel.setBackground(Color.white);
211 cm.gridx = 0;
212 cm.gridy = 0;
213 cm.anchor = GridBagConstraints.WEST;
214 gbm.setConstraints(minLabel, cm);
215 mm.add(minLabel);
217 cm.gridx = 1;
218 cm.anchor = GridBagConstraints.CENTER;
219 cm.fill = GridBagConstraints.HORIZONTAL;
220 cm.weightx = 1;
221 JPanel empty = new JPanel();
222 empty.setBackground(Color.white);
223 gbm.setConstraints(empty, cm);
224 mm.add(empty);
226 cm.weightx = 0;
227 cm.fill = GridBagConstraints.NONE;
228 cm.gridx = 2;
229 cm.anchor = GridBagConstraints.EAST;
230 gbm.setConstraints(maxLabel, cm);
231 mm.add(maxLabel);
232 gbm = null; // defensive programming
233 cm = null;
235 c.gridy = 1;
236 c.insets = new Insets(0, 10, 0, 10);
237 c.fill = GridBagConstraints.HORIZONTAL;
238 gb.setConstraints(mm, c);
239 panel.add(mm);
241 Utils.log2("first min, max " + first.getMin() + ", " + first.getMax());
243 // 3. Sliders
244 double ratio = sliderRange / (initial.getMax() - initial.getMin());
245 double firstMin = (first.getMin() - initial.getMin()) * ratio;
246 double firstMax = (first.getMax() - initial.getMin()) * ratio;
247 plot.update(first.getMin(), first.getMax());
250 int sliderMin = (int)firstMin;
251 int sliderMax = (int)firstMax;
252 // Prevent potential errors
253 if (sliderMin < 0) sliderMin = 0;
254 if (sliderMax > sliderRange -1) sliderMax = sliderRange -1;
255 if (sliderMin > sliderMax) sliderMin = sliderMax;
256 Utils.log2("After checking, slider min and max values are: " + sliderMin + ", " + sliderMax + " for range " + sliderRange);
258 final JSlider minslider = createSlider(panel, gb, c, "Minimum", monoFont, sliderRange, sliderMin);
259 final JSlider maxslider = createSlider(panel, gb, c, "Maximum", monoFont, sliderRange, sliderMax);
260 ChangeListener adl = new ChangeListener() {
261 public void stateChanged(ChangeEvent ce) {
262 double smin = minslider.getValue();
263 double smax = maxslider.getValue();
264 Utils.log2("smin, smax: " + smin + ", " + smax);
265 min_max.set(smin, smax);
266 updateLabelsAndPlot(smin, smax);
267 //doPainterUpdate(srcRect, magnification);
268 painter.update();
271 minslider.addChangeListener(adl);
272 maxslider.addChangeListener(adl);
274 // 4. Buttons
275 final JButton cancel = new JButton("Cancel");
276 final JButton apply = new JButton("Apply");
277 ActionListener actlis = new ActionListener() {
278 public void actionPerformed(ActionEvent ae) {
279 Object source = ae.getSource();
280 if (cancel == source) {
281 display.getCanvas().cancelTransform();
282 } else if (apply == source) {
283 display.getCanvas().applyTransform();
287 cancel.addActionListener(actlis);
288 apply.addActionListener(actlis);
290 JPanel buttons = new JPanel();
291 buttons.setBackground(Color.white);
292 gbm = new GridBagLayout();
293 buttons.setLayout(gbm);
294 cm = new GridBagConstraints();
295 cm.gridx = 0;
296 cm.gridy = 0;
297 cm.weightx = 0;
298 cm.anchor = GridBagConstraints.WEST;
299 cm.fill = GridBagConstraints.NONE;
300 gbm.setConstraints(cancel, cm);
301 buttons.add(cancel);
303 JPanel space = new JPanel();
304 space.setBackground(Color.white);
305 cm.gridx = 1;
306 cm.weightx = 1;
307 cm.anchor = GridBagConstraints.CENTER;
308 cm.fill = GridBagConstraints.HORIZONTAL;
309 gbm.setConstraints(space, cm);
310 buttons.add(space);
312 cm.gridx = 2;
313 cm.weightx = 0;
314 cm.anchor = GridBagConstraints.EAST;
315 cm.fill = GridBagConstraints.NONE;
316 gbm.setConstraints(apply, cm);
317 buttons.add(apply);
319 gbm = null; // defensive programming
320 cm = null;
322 c.gridy += 1;
323 c.fill = GridBagConstraints.HORIZONTAL;
324 gb.setConstraints(buttons, c);
325 panel.add(buttons);
327 frame.getContentPane().add(panel);
329 Utils.invokeLater(new Runnable() { public void run() {
331 min_max.set(minslider.getValue(), maxslider.getValue());
333 frame.pack();
335 // after calling pack
336 Dimension dim = new Dimension(plot.getWidth(), 15);
337 minslider.setMinimumSize(dim);
338 maxslider.setMinimumSize(dim);
340 frame.pack(); // again
342 ij.gui.GUI.center(frame);
343 frame.setAlwaysOnTop(true);
345 frame.setVisible(true);
347 ContrastAdjustmentMode.super.initThreads();
348 }});
351 private int computeSliderRange() {
352 double defaultMin = initial.getMin();
353 double defaultMax = initial.getMax();
354 int valueRange = (int)(defaultMax - defaultMin);
355 int newSliderRange = valueRange;
356 if (newSliderRange>640 && newSliderRange<1280) {
357 newSliderRange /= 2;
358 } else if (newSliderRange>=1280) {
359 newSliderRange /= 5;
361 if (newSliderRange < 256) newSliderRange = 256;
362 if (newSliderRange > 1024) newSliderRange = 1024;
363 return newSliderRange;
366 private JSlider createSlider(JPanel panel, GridBagLayout gb, GridBagConstraints c, String title, Font font, int sliderRange, int start) {
368 Utils.log2("createSlider range: " + sliderRange + ", start: " + start);
370 JSlider s = new JSlider(JSlider.HORIZONTAL, 0, sliderRange, start);
371 s.setPaintLabels(false);
372 s.setPaintTicks(false);
373 s.setBackground(Color.white);
374 c.gridy++;
375 c.insets = new Insets(2, 10, 0, 10);
376 gb.setConstraints(s, c);
377 panel.add(s);
378 JLabel l = new JLabel(title);
379 l.setBackground(Color.white);
380 l.setFont(font);
381 c.gridy++;
382 c.insets = new Insets(0, 10, IJ.isMacOSX() ? 4 : 0, 0);
383 JPanel p = new JPanel();
384 p.setBackground(Color.white);
385 p.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
386 gb.setConstraints(p, c);
387 p.add(l);
388 panel.add(p);
389 return s;
392 public void mousePressed( MouseEvent me, int x_p, int y_p, double magnification ) {}
393 public void mouseDragged( MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_d_old, int y_d_old ) {}
394 public void mouseReleased( MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_r, int y_r ) {}
395 public boolean isDragging() { return false; }
397 private final void setUndoState() {
398 layer.getParent().addEditStep(new Displayable.DoEdits(new HashSet<Displayable>(originalPatches)).init(new String[]{"data"}));
401 public boolean apply() {
402 /* Set undo step to reflect initial state before any transformations */
403 setUndoState();
405 Bureaucrat.createAndStart( new Worker.Task( "Applying transformations" ) {
406 public void exec() {
407 // 1. Close dialog
408 frame.dispose();
410 // 2. Set min and max
411 final double[] m = toImage(min_max.min, min_max.max);
413 final Collection<Future<?>> fus = new ArrayList<Future<?>>();
415 // Submit all for regeneration
416 for (Patch p : originalPatches) {
417 p.setMinAndMax(m[0], m[1]);
418 fus.add(p.getProject().getLoader().regenerateMipMaps(p));
421 // Wait until all done
422 for (Future<?> fu : fus) {
423 try {
424 fu.get();
425 } catch (Throwable t) {
426 IJError.print(t);
430 // To reflect final state
431 setUndoState();
433 }, layer.getProject() );
435 super.quitThreads();
437 return true;
440 @Override
441 public boolean cancel() {
442 super.cancel();
443 frame.dispose();
444 return true;