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}