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