/** * */ package icy.roi; import icy.type.collection.array.DynamicArray; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle3D; import icy.type.rectangle.Rectangle4D; import icy.type.rectangle.Rectangle5D; import java.awt.Rectangle; import java.util.Map.Entry; import java.util.TreeMap; /** * Class to define a 5D boolean mask region and make basic boolean operation between masks.<br> * The bounds property of this object represents the region defined by the boolean mask. * * @author Stephane */ public class BooleanMask5D { // Internal use only private static BooleanMask4D doUnion4D(BooleanMask4D m1, BooleanMask4D m2) { if (m1 == null) { // only use the 3D mask from second mask if (m2 != null) return (BooleanMask4D) m2.clone(); return null; } else if (m2 == null) // only use the 3D mask from first mask return (BooleanMask4D) m1.clone(); // process union of 3D mask return BooleanMask4D.getUnion(m1, m2); } // Internal use only private static BooleanMask4D doIntersection4D(BooleanMask4D m1, BooleanMask4D m2) { if ((m1 == null) || (m2 == null)) return null; // process intersection of 3D mask return BooleanMask4D.getIntersection(m1, m2); } // Internal use only private static BooleanMask4D doExclusiveUnion4D(BooleanMask4D m1, BooleanMask4D m2) { if (m1 == null) { // only use the 3D mask from second mask if (m2 != null) return (BooleanMask4D) m2.clone(); return null; } else if (m2 == null) // only use the 3D mask from first mask return (BooleanMask4D) m1.clone(); // process exclusive union of 3D mask return BooleanMask4D.getExclusiveUnion(m1, m2); } // Internal use only private static BooleanMask4D doSubtraction4D(BooleanMask4D m1, BooleanMask4D m2) { if (m1 == null) return null; // only use the 3D mask from first mask if (m2 == null) return (BooleanMask4D) m1.clone(); // process subtraction of 3D mask return BooleanMask4D.getSubtraction(m1, m2); } /** * Build resulting mask from union of the mask1 and mask2: * * <pre> * mask1 + mask2 = result * * ################ ################ ################ * ############## ############## ################ * ############ ############ ################ * ########## ########## ################ * ######## ######## ################ * ###### ###### ###### ###### * #### #### #### #### * ## ## ## ## * </pre> */ public static BooleanMask5D getUnion(BooleanMask5D mask1, BooleanMask5D mask2) { if ((mask1 == null) && (mask2 == null)) return new BooleanMask5D(); if ((mask1 == null) || mask1.isEmpty()) return (BooleanMask5D) mask2.clone(); if ((mask2 == null) || mask2.isEmpty()) return (BooleanMask5D) mask1.clone(); final Rectangle5D.Integer bounds = (Rectangle5D.Integer) mask1.bounds.createUnion(mask2.bounds); if (!bounds.isEmpty()) { final BooleanMask4D[] mask; // special case of infinite C dimension if (bounds.sizeC == Integer.MAX_VALUE) { // we can allow merge ROI only if they both has infinite C dimension if ((mask1.bounds.sizeC != Integer.MAX_VALUE) || (mask2.bounds.sizeC != Integer.MAX_VALUE)) throw new UnsupportedOperationException( "Cannot merge an infinite C dimension ROI with a finite Z dimension ROI"); mask = new BooleanMask4D[1]; final BooleanMask4D m2d1 = mask1.mask.firstEntry().getValue(); final BooleanMask4D m2d2 = mask2.mask.firstEntry().getValue(); mask[0] = doUnion4D(m2d1, m2d2); } else { mask = new BooleanMask4D[bounds.sizeT]; for (int c = 0; c < bounds.sizeC; c++) { final BooleanMask4D m2d1 = mask1.getMask4D(c + bounds.c); final BooleanMask4D m2d2 = mask2.getMask4D(c + bounds.c); mask[c] = doUnion4D(m2d1, m2d2); } } return new BooleanMask5D(bounds, mask); } return new BooleanMask5D(); } /** * Build resulting mask from intersection of the mask1 and mask2: * * <pre> * mask1 intersect mask2 = result * * ################ ################ ################ * ############## ############## ############ * ############ ############ ######## * ########## ########## #### * ######## ######## * ###### ###### * #### #### * ## ## * </pre> */ public static BooleanMask5D getIntersection(BooleanMask5D mask1, BooleanMask5D mask2) { if ((mask1 == null) || (mask2 == null)) return new BooleanMask5D(); final Rectangle5D.Integer bounds = (Rectangle5D.Integer) mask1.bounds.createIntersection(mask2.bounds); if (!bounds.isEmpty()) { final BooleanMask4D[] mask; // special case of infinite C dimension if (bounds.sizeC == Integer.MAX_VALUE) { // we can allow merge ROI only if they both has infinite C dimension if ((mask1.bounds.sizeC != Integer.MAX_VALUE) || (mask2.bounds.sizeC != Integer.MAX_VALUE)) throw new UnsupportedOperationException( "Cannot merge an infinite C dimension ROI with a finite Z dimension ROI"); mask = new BooleanMask4D[1]; final BooleanMask4D m2d1 = mask1.mask.firstEntry().getValue(); final BooleanMask4D m2d2 = mask2.mask.firstEntry().getValue(); mask[0] = doIntersection4D(m2d1, m2d2); } else { mask = new BooleanMask4D[bounds.sizeT]; for (int c = 0; c < bounds.sizeC; c++) { final BooleanMask4D m2d1 = mask1.getMask4D(c + bounds.c); final BooleanMask4D m2d2 = mask2.getMask4D(c + bounds.c); mask[c] = doIntersection4D(m2d1, m2d2); } } return new BooleanMask5D(bounds, mask); } return new BooleanMask5D(); } /** * Build resulting mask from exclusive union of the mask1 and mask2: * * <pre> * mask1 xor mask2 = result * * ################ ################ * ############## ############## ## ## * ############ ############ #### #### * ########## ########## ###### ###### * ######## ######## ################ * ###### ###### ###### ###### * #### #### #### #### * ## ## ## ## * </pre> */ public static BooleanMask5D getExclusiveUnion(BooleanMask5D mask1, BooleanMask5D mask2) { if ((mask1 == null) && (mask2 == null)) return new BooleanMask5D(); if ((mask1 == null) || mask1.isEmpty()) return (BooleanMask5D) mask2.clone(); if ((mask2 == null) || mask2.isEmpty()) return (BooleanMask5D) mask1.clone(); final Rectangle5D.Integer bounds = (Rectangle5D.Integer) mask1.bounds.createUnion(mask2.bounds); if (!bounds.isEmpty()) { final BooleanMask4D[] mask; // special case of infinite C dimension if (bounds.sizeC == Integer.MAX_VALUE) { // we can allow merge ROI only if they both has infinite C dimension if ((mask1.bounds.sizeC != Integer.MAX_VALUE) || (mask2.bounds.sizeC != Integer.MAX_VALUE)) throw new UnsupportedOperationException( "Cannot merge an infinite C dimension ROI with a finite Z dimension ROI"); mask = new BooleanMask4D[1]; final BooleanMask4D m2d1 = mask1.mask.firstEntry().getValue(); final BooleanMask4D m2d2 = mask2.mask.firstEntry().getValue(); mask[0] = doExclusiveUnion4D(m2d1, m2d2); } else { mask = new BooleanMask4D[bounds.sizeT]; for (int c = 0; c < bounds.sizeC; c++) { final BooleanMask4D m2d1 = mask1.getMask4D(c + bounds.c); final BooleanMask4D m2d2 = mask2.getMask4D(c + bounds.c); mask[c] = doExclusiveUnion4D(m2d1, m2d2); } } return new BooleanMask5D(bounds, mask); } return new BooleanMask5D(); } /** * Build resulting mask from the subtraction of mask2 from mask1: * * <pre> * mask1 - mask2 = result * * ################ ################ * ############## ############## ## * ############ ############ #### * ########## ########## ###### * ######## ######## ######## * ###### ###### ###### * #### #### #### * ## ## ## * </pre> */ public static BooleanMask5D getSubtraction(BooleanMask5D mask1, BooleanMask5D mask2) { if (mask1 == null) return new BooleanMask5D(); if (mask2 == null) return (BooleanMask5D) mask1.clone(); final Rectangle5D.Integer bounds = (Rectangle5D.Integer) mask1.bounds.createIntersection(mask2.bounds); // need to subtract something ? if (!bounds.isEmpty()) { final BooleanMask4D[] mask; // special case of infinite C dimension if (bounds.sizeC == Integer.MAX_VALUE) { // we can allow merge ROI only if they both has infinite C dimension if ((mask1.bounds.sizeC != Integer.MAX_VALUE) || (mask2.bounds.sizeC != Integer.MAX_VALUE)) throw new UnsupportedOperationException( "Cannot merge an infinite C dimension ROI with a finite Z dimension ROI"); mask = new BooleanMask4D[1]; final BooleanMask4D m2d1 = mask1.mask.firstEntry().getValue(); final BooleanMask4D m2d2 = mask2.mask.firstEntry().getValue(); mask[0] = doSubtraction4D(m2d1, m2d2); } else { mask = new BooleanMask4D[bounds.sizeC]; for (int c = 0; c < bounds.sizeC; c++) { final BooleanMask4D m2d1 = mask1.getMask4D(c + bounds.c); final BooleanMask4D m2d2 = mask2.getMask4D(c + bounds.c); mask[c] = doSubtraction4D(m2d1, m2d2); } } return new BooleanMask5D(bounds, mask); } return (BooleanMask5D) mask1.clone(); } /** * Region represented by the mask. */ public Rectangle5D.Integer bounds; /** * Boolean mask 4D array. */ public final TreeMap<Integer, BooleanMask4D> mask; /** * Build a new 4D boolean mask with specified bounds and 4D mask array.<br> * The 4D mask array length should be >= to <code>bounds.getSizeT()</code>. */ public BooleanMask5D(Rectangle5D.Integer bounds, BooleanMask4D[] mask) { super(); this.bounds = bounds; this.mask = new TreeMap<Integer, BooleanMask4D>(); // special case of infinite C dim if (bounds.sizeC == Integer.MAX_VALUE) this.mask.put(Integer.valueOf(Integer.MIN_VALUE), mask[0]); else { for (int c = 0; c < bounds.sizeC; c++) if (mask[c] != null) this.mask.put(Integer.valueOf(bounds.c + c), mask[c]); } } /** * Build a new 4D boolean mask from the specified array of {@link Point5D}.<br> */ public BooleanMask5D(Point5D.Integer[] points) { super(); mask = new TreeMap<Integer, BooleanMask4D>(); if ((points == null) || (points.length == 0)) bounds = new Rectangle5D.Integer(); else { int minX = Integer.MAX_VALUE; int minY = Integer.MAX_VALUE; int minZ = Integer.MAX_VALUE; int minT = Integer.MAX_VALUE; int minC = Integer.MAX_VALUE; int maxX = Integer.MIN_VALUE; int maxY = Integer.MIN_VALUE; int maxZ = Integer.MIN_VALUE; int maxT = Integer.MIN_VALUE; int maxC = Integer.MIN_VALUE; for (Point5D.Integer pt : points) { final int x = pt.x; final int y = pt.y; final int z = pt.z; final int t = pt.t; final int c = pt.c; if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; if (z < minZ) minZ = z; if (z > maxZ) maxZ = z; if (t < minT) minT = t; if (t > maxT) maxT = t; if (c < minC) minC = c; if (c > maxC) maxC = c; } // define bounds bounds = new Rectangle5D.Integer(minX, minY, minZ, minT, minC, (maxX - minX) + 1, (maxY - minY) + 1, (maxZ - minZ) + 1, (maxT - minT) + 1, (maxC - minC) + 1); // set mask for (Point5D.Integer pt : points) { BooleanMask4D m4d = mask.get(Integer.valueOf(pt.c)); // allocate 4D boolean mask if needed if (m4d == null) { m4d = new BooleanMask4D(new Rectangle4D.Integer(minX, minY, minZ, minT, bounds.sizeX, bounds.sizeY, bounds.sizeZ, bounds.sizeT), new BooleanMask3D[bounds.sizeT]); // set 4D mask for position C mask.put(Integer.valueOf(pt.c), m4d); } BooleanMask3D m3d = m4d.getMask3D(pt.t); // allocate 3D boolean mask if needed if (m3d == null) { m3d = new BooleanMask3D(new Rectangle3D.Integer(minX, minY, minZ, bounds.sizeX, bounds.sizeY, bounds.sizeZ), new BooleanMask2D[bounds.sizeZ]); // set 3D mask for position T m4d.mask.put(Integer.valueOf(pt.t), m3d); } BooleanMask2D m2d = m3d.getMask2D(pt.z); // allocate 2D boolean mask if needed if (m2d == null) { m2d = new BooleanMask2D(new Rectangle(minX, minY, bounds.sizeX, bounds.sizeY), new boolean[bounds.sizeX * bounds.sizeY]); // set 2D mask for position Z m3d.mask.put(Integer.valueOf(pt.z), m2d); } // set mask point m2d.mask[((pt.y - minY) * bounds.sizeX) + (pt.x - minX)] = true; } // optimize mask 4D bounds for (BooleanMask4D m : mask.values()) m.optimizeBounds(); } } /** * Build a new boolean mask from the specified array of {@link Point5D}.<br> */ public BooleanMask5D(Point5D[] points) { super(); mask = new TreeMap<Integer, BooleanMask4D>(); if ((points == null) || (points.length == 0)) bounds = new Rectangle5D.Integer(); else { int minX = Integer.MAX_VALUE; int minY = Integer.MAX_VALUE; int minZ = Integer.MAX_VALUE; int minT = Integer.MAX_VALUE; int minC = Integer.MAX_VALUE; int maxX = Integer.MIN_VALUE; int maxY = Integer.MIN_VALUE; int maxZ = Integer.MIN_VALUE; int maxT = Integer.MIN_VALUE; int maxC = Integer.MIN_VALUE; for (Point5D pt : points) { final int x = (int) pt.getX(); final int y = (int) pt.getY(); final int z = (int) pt.getZ(); final int t = (int) pt.getT(); final int c = (int) pt.getC(); if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; if (z < minZ) minZ = z; if (z > maxZ) maxZ = z; if (t < minT) minT = t; if (t > maxT) maxT = t; if (c < minC) minC = c; if (c > maxC) maxC = c; } // define bounds bounds = new Rectangle5D.Integer(minX, minY, minZ, minT, minC, (maxX - minX) + 1, (maxY - minY) + 1, (maxZ - minZ) + 1, (maxT - minT) + 1, (maxC - minC) + 1); // set mask for (Point5D pt : points) { BooleanMask4D m4d = mask.get(Integer.valueOf((int) pt.getC())); // allocate 4D boolean mask if needed if (m4d == null) { m4d = new BooleanMask4D(new Rectangle4D.Integer(minX, minY, minZ, minT, bounds.sizeX, bounds.sizeY, bounds.sizeZ, bounds.sizeT), new BooleanMask3D[bounds.sizeT]); // set 4D mask for position C mask.put(Integer.valueOf((int) pt.getC()), m4d); } BooleanMask3D m3d = m4d.getMask3D((int) pt.getT()); // allocate 3D boolean mask if needed if (m3d == null) { m3d = new BooleanMask3D(new Rectangle3D.Integer(minX, minY, minZ, bounds.sizeX, bounds.sizeY, bounds.sizeZ), new BooleanMask2D[bounds.sizeZ]); // set 3D mask for position T m4d.mask.put(Integer.valueOf((int) pt.getT()), m3d); } BooleanMask2D m2d = m3d.getMask2D((int) pt.getZ()); // allocate 2D boolean mask if needed if (m2d == null) { m2d = new BooleanMask2D(new Rectangle(minX, minY, bounds.sizeX, bounds.sizeY), new boolean[bounds.sizeX * bounds.sizeY]); // set 2D mask for position Z m3d.mask.put(Integer.valueOf((int) pt.getZ()), m2d); } // set mask point m2d.mask[(((int) pt.getY() - minY) * bounds.sizeX) + ((int) pt.getX() - minX)] = true; } // optimize mask 4D bounds for (BooleanMask4D m : mask.values()) m.optimizeBounds(); } } public BooleanMask5D() { this(new Rectangle5D.Integer(), new BooleanMask4D[0]); } /** * Returns the 4D boolean mask for the specified C position */ public BooleanMask4D getMask4D(int c) { // special case of infinite C dimension if (bounds.sizeC == Integer.MAX_VALUE) return mask.firstEntry().getValue(); return mask.get(Integer.valueOf(c)); } /** * Returns the 3D boolean mask for the specified T, C position */ public BooleanMask3D getMask3D(int t, int c) { final BooleanMask4D m = getMask4D(c); if (m != null) return m.getMask3D(t); return null; } /** * Returns the 2D boolean mask for the specified Z, T, C position */ public BooleanMask2D getMask2D(int z, int t, int c) { final BooleanMask3D m = getMask3D(t, c); if (m != null) return m.getMask2D(z); return null; } /** * Return <code>true</code> if boolean mask is empty */ public boolean isEmpty() { return bounds.isEmpty(); } /** * Return true if mask contains the specified point */ public boolean contains(int x, int y, int z, int t, int c) { if (bounds.contains(x, y, z, t, c)) { final BooleanMask4D m4d = getMask4D(c); if (m4d != null) return m4d.contains(x, y, z, t); } return false; } /** * Return true if mask contains the specified 2D mask at position Z, T, C */ public boolean contains(BooleanMask2D booleanMask, int z, int t, int c) { if (isEmpty()) return false; final BooleanMask2D mask2d = getMask2D(z, t, c); if (mask2d != null) return mask2d.contains(booleanMask); return false; } /** * Return true if mask contains the specified 3D mask at position T, C */ public boolean contains(BooleanMask3D booleanMask, int t, int c) { if (isEmpty()) return false; final BooleanMask3D mask3d = getMask3D(t, c); if (mask3d != null) return mask3d.contains(booleanMask); return false; } /** * Return true if mask contains the specified 4D mask at position C */ public boolean contains(BooleanMask4D booleanMask, int c) { if (isEmpty()) return false; final BooleanMask4D mask4d = getMask4D(c); if (mask4d != null) return mask4d.contains(booleanMask); return false; } /** * Return true if mask contains the specified 5D mask. */ public boolean contains(BooleanMask5D booleanMask) { if (isEmpty()) return false; final int sizeC = booleanMask.bounds.sizeC; // check for special MAX_INTEGER case (infinite C dim) if (sizeC == Integer.MAX_VALUE) { // we cannot contains it if we are not on infinite C dim too if (bounds.sizeC != Integer.MAX_VALUE) return false; return booleanMask.mask.firstEntry().getValue().contains(mask.firstEntry().getValue()); } final int offC = booleanMask.bounds.c; for (int c = offC; c < offC + sizeC; c++) if (!contains(booleanMask.getMask4D(c), c)) return false; return true; } /** * Return true if mask intersects (contains at least one point) the specified 2D mask at * position Z, T, C */ public boolean intersects(BooleanMask2D booleanMask, int z, int t, int c) { if (isEmpty()) return false; final BooleanMask2D mask2d = getMask2D(z, t, c); if (mask2d != null) return mask2d.intersects(booleanMask); return false; } /** * Return true if mask intersects (contains at least one point) the specified 3D mask at * position T, C */ public boolean intersects(BooleanMask3D booleanMask, int t, int c) { if (isEmpty()) return false; final BooleanMask3D mask3d = getMask3D(t, c); if (mask3d != null) return mask3d.intersects(booleanMask); return false; } /** * Return true if mask intersects (contains at least one point) the specified 4D mask at * position C */ public boolean intersects(BooleanMask4D booleanMask, int c) { if (isEmpty()) return false; final BooleanMask4D mask4d = getMask4D(c); if (mask4d != null) return mask4d.intersects(booleanMask); return false; } /** * Return true if mask intersects (contains at least one point) the specified 5D mask region */ public boolean intersects(BooleanMask5D booleanMask) { if (isEmpty()) return false; final int sizeC = booleanMask.bounds.sizeC; // check for special MAX_INTEGER case (infinite C dim) if (sizeC == Integer.MAX_VALUE) { // get the single T slice final BooleanMask4D mask4d = booleanMask.mask.firstEntry().getValue(); // test with every slice for (BooleanMask4D m : mask.values()) if (m.intersects(mask4d)) return true; return false; } // check for special MAX_INTEGER case (infinite C dim) if (bounds.sizeC == Integer.MAX_VALUE) { // get the single T slice final BooleanMask4D mask4d = mask.firstEntry().getValue(); // test with every slice for (BooleanMask4D m : booleanMask.mask.values()) if (m.intersects(mask4d)) return true; return false; } final int offC = booleanMask.bounds.c; for (int c = offC; c < offC + sizeC; c++) if (intersects(booleanMask.getMask4D(c), c)) return true; return false; } /** * Optimize mask bounds so it fits mask content. */ public Rectangle5D.Integer getOptimizedBounds(boolean compute4DBounds) { final Rectangle5D.Integer result = new Rectangle5D.Integer(); if (mask.isEmpty()) return result; Rectangle4D.Integer bounds4D = null; for (BooleanMask4D m4d : mask.values()) { // get optimized 4D bounds for each C final Rectangle4D.Integer optB4d; if (compute4DBounds) optB4d = m4d.getOptimizedBounds(); else optB4d = new Rectangle4D.Integer(m4d.bounds); // only add non empty bounds if (!optB4d.isEmpty()) { if (bounds4D == null) bounds4D = optB4d; else bounds4D.add(optB4d); } } // empty ? if ((bounds4D == null) || bounds4D.isEmpty()) return result; int minC = mask.firstKey().intValue(); int maxC = mask.lastKey().intValue(); // set 4D bounds to start with result.setX(bounds4D.x); result.setY(bounds4D.y); result.setZ(bounds4D.z); result.setT(bounds4D.t); result.setSizeX(bounds4D.sizeX); result.setSizeY(bounds4D.sizeY); result.setSizeZ(bounds4D.sizeZ); result.setSizeT(bounds4D.sizeT); // single C --> check for special MAX_INTEGER case if ((minC == maxC) && (bounds.sizeC == Integer.MAX_VALUE)) { result.setC(Integer.MIN_VALUE); result.setSizeC(Integer.MAX_VALUE); } else { result.setC(minC); result.setSizeC((maxC - minC) + 1); } return result; } /** * Optimize mask bounds so it fits mask content. */ public Rectangle5D.Integer getOptimizedBounds() { return getOptimizedBounds(true); } /** * Optimize mask bounds so it fits mask content. */ public void optimizeBounds() { // start by optimizing 4D bounds for (BooleanMask4D m : mask.values()) m.optimizeBounds(); moveBounds(getOptimizedBounds(false)); } /** * Change the bounds of BooleanMask.<br> * Keep mask data intersecting from old bounds. */ public void moveBounds(Rectangle5D.Integer value) { // bounds changed ? if (!bounds.equals(value)) { // changed to empty mask if (value.isEmpty()) { // clear bounds and mask bounds = new Rectangle5D.Integer(); mask.clear(); return; } final Rectangle4D.Integer bounds4D = new Rectangle4D.Integer(value.x, value.y, value.z, value.t, value.sizeX, value.sizeY, value.sizeZ, value.sizeT); // it was infinite C dim ? if (bounds.sizeC == Integer.MAX_VALUE) { // get the single 4D mask final BooleanMask4D m4d = mask.firstEntry().getValue(); // adjust 4D bounds if needed to the single 4D mask m4d.moveBounds(bounds4D); // we passed from infinite C to defined C range if (value.sizeC != Integer.MAX_VALUE) { // assign the same 4D mask for all C position mask.clear(); for (int c = 0; c <= value.sizeC; c++) mask.put(Integer.valueOf(c + value.c), (BooleanMask4D) m4d.clone()); } } // we pass to infinite C dim else if (value.sizeT == Integer.MAX_VALUE) { // try to use the 4D mask at C position BooleanMask4D mask4D = getMask4D(value.c); // otherwise we use the first found 2D mask if ((mask4D == null) && !mask.isEmpty()) mask4D = mask.firstEntry().getValue(); // set new mask mask.clear(); if (mask4D != null) mask.put(Integer.valueOf(Integer.MIN_VALUE), mask4D); } else { // create new mask array final BooleanMask4D[] newMask = new BooleanMask4D[value.sizeC]; for (int c = 0; c < value.sizeC; c++) { final BooleanMask4D mask4D = getMask4D(value.c + c); if (mask4D != null) // adjust 4D bounds mask4D.moveBounds(bounds4D); newMask[c] = mask4D; } // set new mask mask.clear(); for (int c = 0; c < value.sizeC; c++) mask.put(Integer.valueOf(value.c + c), newMask[c]); } bounds = value; } } /** * Transforms the specified 4D coordinates int array [x,y,z,t] in 5D coordinates int array [x,y,z,t,c] with the * specified C value. */ public static int[] toInt5D(int[] source4D, int c) { final int[] result = new int[(source4D.length * 5) / 4]; int pt = 0; for (int i = 0; i < source4D.length; i += 4) { result[pt++] = source4D[i + 0]; result[pt++] = source4D[i + 1]; result[pt++] = source4D[i + 2]; result[pt++] = source4D[i + 3]; result[pt++] = c; } return result; } /** * Return an array of {@link icy.type.point.Point5D.Integer} containing the contour/surface * points * of the 5D mask.<br> * Points are returned in ascending XYZTC order. <br> * <br> * WARNING: The basic implementation is not totally accurate.<br> * It returns all points from the first and the last C slices + contour points for intermediate * C * slices. * * @see #getContourPointsAsIntArray() */ public Point5D.Integer[] getContourPoints() { return Point5D.Integer.toPoint5D(getContourPointsAsIntArray()); } /** * Return an array of integer containing the contour/surface points of the 5D mask.<br> * <code>result.length</code> = number of point * 4<br> * <code>result[(pt * 4) + 0]</code> = X coordinate for point <i>pt</i>.<br> * <code>result[(pt * 4) + 1]</code> = Y coordinate for point <i>pt</i>.<br> * <code>result[(pt * 4) + 2]</code> = Z coordinate for point <i>pt</i>.<br> * <code>result[(pt * 4) + 3]</code> = T coordinate for point <i>pt</i>.<br> * <code>result[(pt * 5) + 4]</code> = C coordinate for point <i>pt</i>.<br> * Points are returned in ascending XYZTC order.<br> * <br> * WARNING: The basic implementation is not totally accurate.<br> * It returns all points from the first and the last C slices + contour points for intermediate * C * slices. * * @see #getContourPoints() */ public int[] getContourPointsAsIntArray() { final DynamicArray.Int result = new DynamicArray.Int(8); // perimeter = first slice volume + inter slices perimeter + last slice volume // TODO: fix this method and use real 5D contour point if (mask.size() <= 2) { for (Entry<Integer, BooleanMask4D> entry : mask.entrySet()) result.add(toInt5D(entry.getValue().getPointsAsIntArray(), entry.getKey().intValue())); } else { final Entry<Integer, BooleanMask4D> firstEntry = mask.firstEntry(); final Entry<Integer, BooleanMask4D> lastEntry = mask.lastEntry(); final Integer firstKey = firstEntry.getKey(); final Integer lastKey = lastEntry.getKey(); result.add(toInt5D(firstEntry.getValue().getPointsAsIntArray(), firstKey.intValue())); for (Entry<Integer, BooleanMask4D> entry : mask.subMap(firstKey, false, lastKey, false).entrySet()) result.add(toInt5D(entry.getValue().getContourPointsAsIntArray(), entry.getKey().intValue())); result.add(toInt5D(lastEntry.getValue().getPointsAsIntArray(), lastKey.intValue())); } return result.asArray(); } /** * Return the number of points contained in this boolean mask. */ public int getNumberOfPoints() { int result = 0; for (BooleanMask4D mask4d : mask.values()) result += mask4d.getNumberOfPoints(); return result; } /** * Return an array of {@link icy.type.point.Point5D.Integer} representing all points of the * current 5D mask.<br> * Points are returned in ascending XYZTC order. */ public Point5D.Integer[] getPoints() { return Point5D.Integer.toPoint5D(getPointsAsIntArray()); } /** * Return an array of integer representing all points of the current 5D mask.<br> * <code>result.length</code> = number of point * 5<br> * <code>result[(pt * 5) + 0]</code> = X coordinate for point <i>pt</i>.<br> * <code>result[(pt * 5) + 1]</code> = Y coordinate for point <i>pt</i>.<br> * <code>result[(pt * 5) + 2]</code> = Z coordinate for point <i>pt</i>.<br> * <code>result[(pt * 5) + 3]</code> = T coordinate for point <i>pt</i>.<br> * <code>result[(pt * 5) + 4]</code> = C coordinate for point <i>pt</i>.<br> * Points are returned in ascending XYZTC order. */ public int[] getPointsAsIntArray() { final DynamicArray.Int result = new DynamicArray.Int(8); for (Entry<Integer, BooleanMask4D> entry : mask.entrySet()) result.add(toInt5D(entry.getValue().getPointsAsIntArray(), entry.getKey().intValue())); return result.asArray(); } @Override public Object clone() { final BooleanMask5D result = new BooleanMask5D(); result.bounds = new Rectangle5D.Integer(bounds); for (Entry<Integer, BooleanMask4D> entry : mask.entrySet()) result.mask.put(entry.getKey(), (BooleanMask4D) entry.getValue().clone()); return result; } }