001/* 002 * Copyright 2010-2015 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.kernel.roi.roi4d; 020 021import icy.roi.BooleanMask3D; 022import icy.roi.BooleanMask4D; 023import icy.roi.ROI; 024import icy.roi.ROI3D; 025import icy.type.point.Point4D; 026import icy.type.point.Point5D; 027import icy.type.rectangle.Rectangle4D; 028 029import java.awt.geom.Point2D; 030import java.util.Map.Entry; 031 032import plugins.kernel.roi.roi3d.ROI3DArea; 033 034/** 035 * 4D Area ROI. 036 * 037 * @author Stephane 038 */ 039public class ROI4DArea extends ROI4DStack<ROI3DArea> 040{ 041 public ROI4DArea() 042 { 043 super(ROI3DArea.class); 044 } 045 046 public ROI4DArea(Point4D pt) 047 { 048 this(); 049 050 addBrush(pt.toPoint2D(), (int) pt.getZ(), (int) pt.getT()); 051 } 052 053 public ROI4DArea(Point5D pt) 054 { 055 this(pt.toPoint4D()); 056 } 057 058 /** 059 * Create a 3D Area ROI type from the specified {@link BooleanMask4D}. 060 */ 061 public ROI4DArea(BooleanMask4D mask) 062 { 063 this(); 064 065 setAsBooleanMask(mask); 066 } 067 068 /** 069 * Create a copy of the specified 4D Area ROI. 070 */ 071 public ROI4DArea(ROI4DArea area) 072 { 073 this(); 074 075 // copy the source 4D area ROI 076 for (Entry<Integer, ROI3DArea> entry : area.slices.entrySet()) 077 slices.put(entry.getKey(), new ROI3DArea(entry.getValue())); 078 079 roiChanged(true); 080 } 081 082 @Override 083 public String getDefaultName() 084 { 085 return "Area4D"; 086 } 087 088 /** 089 * Adds the specified point to this ROI 090 */ 091 public void addPoint(int x, int y, int z, int t) 092 { 093 setPoint(x, y, z, t, true); 094 } 095 096 /** 097 * Remove a point from the mask.<br> 098 * Don't forget to call optimizeBounds() after consecutive remove operation 099 * to refresh the mask bounds. 100 */ 101 public void removePoint(int x, int y, int z, int t) 102 { 103 setPoint(x, y, z, t, false); 104 } 105 106 /** 107 * Set the value for the specified point in the mask. 108 * Don't forget to call optimizeBounds() after consecutive remove point operation 109 * to refresh the mask bounds. 110 */ 111 public void setPoint(int x, int y, int z, int t, boolean value) 112 { 113 final ROI3DArea slice = getSlice(t, value); 114 115 if (slice != null) 116 slice.setPoint(x, y, z, value); 117 } 118 119 /** 120 * Add brush point at specified position and for specified Z,T slice. 121 */ 122 public void addBrush(Point2D pos, int z, int t) 123 { 124 getSlice(t, true).addBrush(pos, z); 125 } 126 127 /** 128 * Remove brush point from the mask at specified position and for specified Z,T slice.<br> 129 * Don't forget to call optimizeBounds() after consecutive remove operation 130 * to refresh the mask bounds. 131 */ 132 public void removeBrush(Point2D pos, int z, int t) 133 { 134 final ROI3DArea slice = getSlice(t, false); 135 136 if (slice != null) 137 slice.removeBrush(pos, z); 138 } 139 140 /** 141 * Sets the ROI slice at given T position to this 4D ROI 142 * 143 * @param t 144 * the position where the slice must be set 145 * @param roiSlice 146 * the 3D ROI to set 147 * @param merge 148 * <code>true</code> if the given slice should be merged with the existing slice, or 149 * <code>false</code> to 150 * replace the existing slice. 151 */ 152 public void setSlice(int t, ROI3D roiSlice, boolean merge) 153 { 154 if (roiSlice == null) 155 throw new IllegalArgumentException("Cannot add an empty slice in a 4D ROI"); 156 157 final ROI3DArea currentSlice = getSlice(t); 158 final ROI newSlice; 159 160 // merge both slice 161 if ((currentSlice != null) && merge) 162 { 163 // we need to modify the T and C position so we do the merge correctly 164 roiSlice.setT(t); 165 roiSlice.setC(getC()); 166 // do ROI union 167 newSlice = currentSlice.getUnion(roiSlice); 168 } 169 else 170 newSlice = roiSlice; 171 172 if (newSlice instanceof ROI3DArea) 173 setSlice(t, (ROI3DArea) newSlice); 174 else if (newSlice instanceof ROI3D) 175 setSlice(t, new ROI3DArea(((ROI3D) newSlice).getBooleanMask(true))); 176 else 177 throw new IllegalArgumentException( 178 "Can't add the result of the merge operation on 3D slice " + t + ": " + newSlice.getClassName()); 179 } 180 181 /** 182 * Returns true if the ROI is empty (the mask does not contains any point). 183 */ 184 @Override 185 public boolean isEmpty() 186 { 187 for (ROI3DArea area : slices.values()) 188 if (!area.isEmpty()) 189 return false; 190 191 return true; 192 } 193 194 /** 195 * Set the mask from a BooleanMask4D object<br> 196 * If specified mask is <i>null</i> then ROI is cleared. 197 */ 198 public void setAsBooleanMask(BooleanMask4D mask) 199 { 200 // mask empty ? --> just clear the ROI 201 if ((mask == null) || mask.isEmpty()) 202 clear(); 203 else 204 { 205 final Rectangle4D.Integer bounds4d = mask.bounds; 206 final int startT = bounds4d.t; 207 final int sizeT = bounds4d.sizeT; 208 final BooleanMask3D masks3d[] = new BooleanMask3D[sizeT]; 209 210 for (int t = 0; t < sizeT; t++) 211 masks3d[t] = mask.getMask3D(startT + t); 212 213 setAsBooleanMask(bounds4d, masks3d); 214 } 215 } 216 217 /** 218 * Set the 4D mask from a 3D boolean mask array 219 * 220 * @param rect 221 * the 4D region defined by 3D boolean mask array 222 * @param mask 223 * the 4D mask data (array length should be equals to rect.sizeT) 224 */ 225 public void setAsBooleanMask(Rectangle4D.Integer rect, BooleanMask3D[] mask) 226 { 227 if (rect.isInfiniteT()) 228 throw new IllegalArgumentException("Cannot set infinite T dimension on the 4D Area ROI."); 229 230 beginUpdate(); 231 try 232 { 233 clear(); 234 235 for (int t = 0; t < rect.sizeT; t++) 236 setSlice(t + rect.t, new ROI3DArea(mask[t])); 237 } 238 finally 239 { 240 endUpdate(); 241 } 242 } 243 244 /** 245 * Optimize the bounds size to the minimum surface which still include all mask<br> 246 * You should call it after consecutive remove operations. 247 */ 248 public void optimizeBounds() 249 { 250 final Rectangle4D.Integer bounds = getBounds(); 251 252 beginUpdate(); 253 try 254 { 255 for (int t = bounds.t; t < bounds.t + bounds.sizeT; t++) 256 { 257 final ROI3DArea roi = getSlice(t); 258 259 if (roi != null) 260 { 261 if (roi.isEmpty()) 262 removeSlice(t); 263 else 264 roi.optimizeBounds(); 265 } 266 } 267 } 268 finally 269 { 270 endUpdate(); 271 } 272 } 273}