001/*
002 * Copyright 2010, 2011 Institut Pasteur.
003 * 
004 * This file is part of ICY.
005 * 
006 * ICY is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * ICY is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with ICY. If not, see <http://www.gnu.org/licenses/>.
018 */
019package plugins.tutorial.roi;
020
021import java.awt.Graphics2D;
022import java.awt.Rectangle;
023import java.awt.image.BufferedImage;
024import java.awt.image.DataBufferInt;
025import java.util.Arrays;
026
027import icy.canvas.IcyCanvas;
028import icy.canvas.IcyCanvas2D;
029import icy.gui.dialog.MessageDialog;
030import icy.gui.frame.progress.AnnounceFrame;
031import icy.gui.viewer.Viewer;
032import icy.gui.viewer.ViewerEvent;
033import icy.gui.viewer.ViewerEvent.ViewerEventType;
034import icy.gui.viewer.ViewerListener;
035import icy.image.IcyBufferedImage;
036import icy.painter.Overlay;
037import icy.plugin.abstract_.PluginActionable;
038import icy.roi.BooleanMask2D;
039import icy.roi.ROI2D;
040import icy.sequence.Sequence;
041import icy.sequence.SequenceEvent;
042import icy.sequence.SequenceEvent.SequenceEventSourceType;
043import icy.sequence.SequenceListener;
044
045/**
046 * This class show how we can use ROI to do localized operation on image
047 * 
048 * @author Stephane
049 */
050public class ProcessingFromROI extends PluginActionable implements SequenceListener, ViewerListener
051{
052    private Viewer viewer;
053    private Sequence sequence;
054    private Overlay overlay;
055    BufferedImage img;
056    private int[] imgData;
057
058    @Override
059    public void run()
060    {
061        viewer = getActiveViewer();
062
063        // no viewer has been found ?
064        if (viewer == null)
065        {
066            // display an information message as we need an opened sequence
067            MessageDialog.showDialog("This example needs a sequence to start. Please load an image file.",
068                    MessageDialog.INFORMATION_MESSAGE);
069            return;
070        }
071
072        // we should avoid direct sequence reference but it really help here
073        sequence = viewer.getSequence();
074
075        // display an announcement with Plugin description
076        new AnnounceFrame("This example show how do localized operation on image from ROI");
077        // no ROI2D in sequence
078        if (!sequence.hasROI(ROI2D.class))
079            new AnnounceFrame("Add a ROI to the sequence to see plugin action");
080
081        // define an overlay to just draw the image over the sequence
082        overlay = new Overlay("Mask")
083        {
084            @Override
085            public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
086            {
087                // check if we are dealing with a canvas 2D and we have a valid Graphics object
088                if ((canvas instanceof IcyCanvas2D) && (g != null))
089                {
090                    // just draw the image over the sequence
091                    g.drawImage(img, null, 0, 0);
092                }
093            }
094        };
095
096        // add the overlay to the sequence
097        sequence.addOverlay(overlay);
098
099        // build an ARGB image with same dimension than sequence
100        img = new BufferedImage(sequence.getSizeX(), sequence.getSizeY(), BufferedImage.TYPE_INT_ARGB);
101        // get internal image data reference
102        imgData = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
103
104        // listen viewer changes so we know when current displayed image change
105        viewer.addListener(this);
106        // listen sequence changes so we know when sequence rois are modified
107        sequence.addListener(this);
108
109        refresh();
110    }
111
112    // we call this method when we want to terminate plugin execution
113    private void close()
114    {
115        // remove viewer listener
116        viewer.removeListener(this);
117        // remove sequence listener
118        sequence.removeListener(this);
119        // remove the overlay from the sequence (same as painter.detachFromAll())
120        sequence.removeOverlay(overlay);
121
122        // free sequence reference
123        viewer = null;
124        sequence = null;
125    }
126
127    private void refresh()
128    {
129        // clear image
130        Arrays.fill(imgData, 0);
131
132        final IcyBufferedImage currentImage = getActiveImage();
133
134        if (currentImage != null)
135        {
136            // get image bounds (rectangle defining height and width of image)
137            final Rectangle imageBounds = currentImage.getBounds();
138
139            BooleanMask2D globalMask = null;
140
141            // compute global boolean mask of all ROI2D contained in the sequence
142            for (ROI2D roi : sequence.getROI2Ds())
143            {
144                // get intersection between image and roi bounds
145                final Rectangle intersect = roi.getBounds().intersection(imageBounds);
146                // get the boolean mask of roi (optimized from intersection bounds)
147                final boolean[] mask = roi.getBooleanMask(intersect, false);
148
149                // update global mask
150                if (globalMask == null)
151                    globalMask = new BooleanMask2D(intersect, mask);
152                else
153                    globalMask.getUnion(intersect, mask);
154            }
155
156            // process only if global mask is not empty
157            if ((globalMask != null) && (!globalMask.bounds.isEmpty()))
158            {
159                final Rectangle bounds = globalMask.bounds;
160                final boolean[] mask = globalMask.mask;
161
162                // calculate offset
163                int offMsk = 0;
164                int offImg = (bounds.y * imageBounds.width) + bounds.x;
165
166                // do process only on data contained in ROI
167                for (int y = 0; y < bounds.height; y++)
168                {
169                    // override all contained pixels with half transparent green
170                    for (int x = 0; x < bounds.width; x++)
171                        if (mask[offMsk + x])
172                            imgData[offImg + x] = 0x8000FF00;
173
174                    offMsk += bounds.width;
175                    offImg += imageBounds.width;
176                }
177            }
178        }
179
180        // notify that our overlay has changed (image data modified)
181        // this automatically refresh the display
182        overlay.painterChanged();
183    }
184
185    // called when sequence has changed
186    @Override
187    public void sequenceChanged(SequenceEvent sequenceEvent)
188    {
189        // sequence roi(s) changed --> refresh
190        if (sequenceEvent.getSourceType() == SequenceEventSourceType.SEQUENCE_ROI)
191            refresh();
192    }
193
194    // called when sequence is closed (last viewer containing sequence has changed
195    @Override
196    public void sequenceClosed(Sequence sequence)
197    {
198        //
199    }
200
201    @Override
202    public void viewerChanged(ViewerEvent event)
203    {
204        // we want to know about navigation change (current displayed image change)
205        if (event.getType() == ViewerEventType.POSITION_CHANGED)
206            // refresh
207            refresh();
208    }
209
210    @Override
211    public void viewerClosed(Viewer viewer)
212    {
213        // end plugin execution
214        close();
215    }
216}