1 package ini
.trakem2
.display
;
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
;
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
) {
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 );
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
) {
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);
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
);
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
);
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. */
122 public MinMaxData() {}
123 public MinMaxData(double min
, double max
) {
126 synchronized public void set(double min
, double 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();
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();
190 c
.fill
= GridBagConstraints
.NONE
;
191 c
.anchor
= GridBagConstraints
.CENTER
;
192 c
.insets
= new Insets(10, 10, 0, 10);
193 gb
.setConstraints(plot
, c
);
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();
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
);
213 cm
.anchor
= GridBagConstraints
.WEST
;
214 gbm
.setConstraints(minLabel
, cm
);
218 cm
.anchor
= GridBagConstraints
.CENTER
;
219 cm
.fill
= GridBagConstraints
.HORIZONTAL
;
221 JPanel empty
= new JPanel();
222 empty
.setBackground(Color
.white
);
223 gbm
.setConstraints(empty
, cm
);
227 cm
.fill
= GridBagConstraints
.NONE
;
229 cm
.anchor
= GridBagConstraints
.EAST
;
230 gbm
.setConstraints(maxLabel
, cm
);
232 gbm
= null; // defensive programming
236 c
.insets
= new Insets(0, 10, 0, 10);
237 c
.fill
= GridBagConstraints
.HORIZONTAL
;
238 gb
.setConstraints(mm
, c
);
241 Utils
.log2("first min, max " + first
.getMin() + ", " + first
.getMax());
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);
271 minslider
.addChangeListener(adl
);
272 maxslider
.addChangeListener(adl
);
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();
298 cm
.anchor
= GridBagConstraints
.WEST
;
299 cm
.fill
= GridBagConstraints
.NONE
;
300 gbm
.setConstraints(cancel
, cm
);
303 JPanel space
= new JPanel();
304 space
.setBackground(Color
.white
);
307 cm
.anchor
= GridBagConstraints
.CENTER
;
308 cm
.fill
= GridBagConstraints
.HORIZONTAL
;
309 gbm
.setConstraints(space
, cm
);
314 cm
.anchor
= GridBagConstraints
.EAST
;
315 cm
.fill
= GridBagConstraints
.NONE
;
316 gbm
.setConstraints(apply
, cm
);
319 gbm
= null; // defensive programming
323 c
.fill
= GridBagConstraints
.HORIZONTAL
;
324 gb
.setConstraints(buttons
, c
);
327 frame
.getContentPane().add(panel
);
329 Utils
.invokeLater(new Runnable() { public void run() {
331 min_max
.set(minslider
.getValue(), maxslider
.getValue());
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();
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) {
358 } else if (newSliderRange
>=1280) {
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
);
375 c
.insets
= new Insets(2, 10, 0, 10);
376 gb
.setConstraints(s
, c
);
378 JLabel l
= new JLabel(title
);
379 l
.setBackground(Color
.white
);
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
);
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 */
405 Bureaucrat
.createAndStart( new Worker
.Task( "Applying transformations" ) {
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
) {
425 } catch (Throwable t
) {
430 // To reflect final state
433 }, layer
.getProject() );
441 public boolean cancel() {