/* * Copyright 2010, 2011 Institut Pasteur. * * This file is part of NHerve Main Toolbox, which is an ICY plugin. * * NHerve Main Toolbox is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NHerve Main Toolbox is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NHerve Main Toolbox. If not, see <http://www.gnu.org/licenses/>. */ package plugins.nherve.toolbox.image.mask; import icy.image.IcyBufferedImage; import icy.roi.ROI2D; import icy.roi.ROI2DArea; import icy.sequence.Sequence; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Area; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.io.Serializable; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import plugins.nherve.toolbox.Algorithm; import plugins.nherve.toolbox.image.BinaryIcyBufferedImage; import plugins.nherve.toolbox.image.feature.region.IcyPixel; import plugins.nherve.toolbox.image.toolboxes.ColorSpaceTools; import plugins.nherve.toolbox.image.toolboxes.MorphologyToolbox; /** * The Class Mask. * * @author Nicolas HERVE - nicolas.herve@pasteur.fr */ public class Mask implements Serializable, Iterable<String> { /** The Constant serialVersionUID. */ private static final long serialVersionUID = -8351680455776644549L; /** * Gets the surface. * * @param ibi * the ibi * @return the surface */ public static int getSurface(BinaryIcyBufferedImage ibi) { int s = 0; byte[] raw = ibi.getRawData(); for (int d = 0; d < raw.length; d++) { if (raw[d] == BinaryIcyBufferedImage.TRUE) { s++; } } return s; } /** The need redraw. */ private transient boolean needRedraw; /** The raw binary data. */ private transient byte[] rawBinaryData; /** The cache. */ private transient BufferedImage cache; /** The draw only contours. */ private transient boolean drawOnlyContours; /** The binary data. */ private BinaryIcyBufferedImage binaryData; /** The color. */ private Color color; /** The height. */ private int height; /** The id. */ private int id; /** The label. */ private String label; /** The need automatic label. */ private boolean needAutomaticLabel; /** The opacity. */ private float opacity; /** The visible layer. */ private boolean visibleLayer; /** The width. */ private int width; /** The tags. */ private Set<String> tags; /** * Instantiates a new mask. */ private Mask() { super(); visibleLayer = true; setColor(Color.WHITE); setOpacity(1.0f); setNeedAutomaticLabel(false); tags = new HashSet<String>(); forceRedraw(); } /** * Instantiates a new mask. * * @param width * the width * @param height * the height */ public Mask(int width, int height) { this(width, height, false); } /** * Instantiates a new mask. * * @param width * the width * @param height * the height * @param defaultValue * the default value */ public Mask(int width, int height, boolean defaultValue) { this(); this.width = width; this.height = height; setBinaryData(new BinaryIcyBufferedImage(width, height, defaultValue)); } /** * Instantiates a new mask. * * @param width * the width * @param height * the height * @param label * the label * @param defaultValue * the default value */ public Mask(int width, int height, String label, boolean defaultValue) { this(width, height, defaultValue); this.label = label; } public static Mask copy(Mask m) throws MaskException { Mask r = new Mask(m.getWidth(), m.getHeight()); if (m.hasBinaryData()) { r.setBinaryData(m.getBinaryData().getCopy()); } else { throw new MaskException("No internal mask representation available for " + m); } return r; } /** * Adds the. * * @param rhs * the rhs * @throws MaskException * the mask exception */ public void add(Area rhs) throws MaskException { if (hasBinaryData()) { manageArea(rhs, BinaryIcyBufferedImage.TRUE); } else { throw new MaskException("No internal mask representation available"); } forceRedraw(); } /** * Adds the. * * @param m * the m * @throws MaskException * the mask exception */ public void add(Mask m) throws MaskException { if (hasBinaryData() && m.hasBinaryData()) { binaryData.add(m.getBinaryData()); } else { throw new MaskException("No internal mask representation available"); } forceRedraw(); } public void add(ROI2D roi) throws MaskException { if (hasBinaryData()) { manageROI(roi, BinaryIcyBufferedImage.TRUE); } else { throw new MaskException("No internal mask representation available"); } forceRedraw(); } /** * Adds the tag. * * @param arg0 * the arg0 * @return true, if successful */ public boolean addTag(String arg0) { return tags.add(arg0); } /** * As ro i2 d area. * * @param seq * the seq * @return the rO i2 d area */ public ROI2DArea asROI2DArea(Sequence seq) { return binaryData.asROI2DArea(seq); } /** * Clear tags. */ public void clearTags() { tags.clear(); } /** * Contains. * * @param x * the x * @param y * the y * @return true, if successful */ public boolean contains(int x, int y) { return binaryData.contains(x, y); } /** * Contains. * * @param px * the px * @return true, if successful */ public boolean contains(IcyPixel px) { return binaryData.contains(px); } /** * Contains. * * @param shp * the shp * @return true, if successful */ public boolean contains(Shape shp) { throw new RuntimeException("Not yet implemented"); } /** * Contains tag. * * @param arg0 * the arg0 * @return true, if successful */ public boolean containsTag(String arg0) { return tags.contains(arg0); } /** * Creates the cache. * * @param c * the c * @return the buffered image */ public BufferedImage createCache(Color c) { return createCache(c.getRGB()); } /** * Creates the cache. * * @param rgb * the rgb * @return the buffered image */ public BufferedImage createCache(int rgb) { BufferedImage localCache = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); fillCache(rgb, localCache); return localCache; } /** * Dilate. * * @throws MaskException * the mask exception */ public void dilate() throws MaskException { if (hasBinaryData()) { binaryData.dilate(); forceRedraw(); } else { throw new MaskException("No internal mask representation available"); } } /** * Erode. * * @throws MaskException * the mask exception */ public void erode() throws MaskException { if (hasBinaryData()) { binaryData.erode(); forceRedraw(); } else { throw new MaskException("No internal mask representation available"); } } private void fillCache(Color c, BufferedImage localCache) { fillCache(c.getRGB(), localCache); } private void fillCache(int rgb, BufferedImage localCache) { int[] localCacheData = ((DataBufferInt) localCache.getRaster().getDataBuffer()).getData(); Arrays.fill(localCacheData, 0); if (hasBinaryData()) { BinaryIcyBufferedImage bin = binaryData; if (drawOnlyContours) { bin = MorphologyToolbox.computeBorder(binaryData); } final int limit = localCacheData.length; final byte[] data = bin.getRawData(); for (int i = 0; i < limit; i++) { if (data[i] == BinaryIcyBufferedImage.TRUE) { localCacheData[i] = rgb; } } } } /** * Fill hole. * * @param x * the x * @param y * the y * @throws MaskException * the mask exception */ public void fillHole(int x, int y) throws MaskException { if (hasBinaryData()) { try { binaryData.fillHole(x, y); } catch (StackOverflowError e1) { Algorithm.err(e1.getClass().getName() + " : hole is too big for me !"); } forceRedraw(); } else { throw new MaskException("Operation only available"); } } /** * Fill holes. * * @throws MaskException * the mask exception */ public void fillHoles() throws MaskException { if (hasBinaryData()) { binaryData.fillHoles(); forceRedraw(); } else { throw new MaskException("No internal mask representation available"); } } public void fill(boolean value) { Arrays.fill(binaryData.getRawData(), value ? BinaryIcyBufferedImage.TRUE : BinaryIcyBufferedImage.FALSE); } /** * Filter size. * * @param size * the size * @throws MaskException * the mask exception */ public void filterSize(int size) throws MaskException { if (hasBinaryData()) { binaryData.filterSize(size); forceRedraw(); } else { throw new MaskException("Operation only available"); } } /** * Force redraw. */ public void forceRedraw() { needRedraw = true; } /** * Gets the average color. * * @param img * the img * @return the average color */ public Color getAverageColor(IcyBufferedImage img) { double[] col = getAverageColor(img, ColorSpaceTools.RGB); double r = col[0] / 255d; double g = col[1] / 255d; double b = col[2] / 255d; return new Color((float) r, (float) g, (float) b); } /** * Gets the average color. * * @param img * the img * @param colorSpace * the color space * @return the average color */ public double[] getAverageColor(IcyBufferedImage img, int colorSpace) { double[] c = new double[ColorSpaceTools.NB_COLOR_CHANNELS]; Arrays.fill(c, 0); int i = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (rawBinaryData[i] == BinaryIcyBufferedImage.TRUE) { double[] l = ColorSpaceTools.getColorComponentsD_0_255(img, colorSpace, x, y); for (int d = 0; d < ColorSpaceTools.NB_COLOR_CHANNELS; d++) { c[d] += l[d]; } } i++; } } double nrm = getSurface(); if (nrm == 0) { // System.out.println("Empty mask : " + getLabel()); } else { for (int d = 0; d < ColorSpaceTools.NB_COLOR_CHANNELS; d++) { c[d] /= nrm; } } return c; } /** * Gets the binary data. * * @return the binary data */ public BinaryIcyBufferedImage getBinaryData() { return binaryData; } /** * Gets the color. * * @return the color */ public Color getColor() { return color; } /** * Gets the height. * * @return the height */ public int getHeight() { return height; } /** * Gets the id. * * @return the id */ public int getId() { return id; } /** * Gets the label. * * @return the label */ public String getLabel() { return label; } /** * Gets the nb tags. * * @return the nb tags */ public int getNbTags() { return tags.size(); } /** * Gets the opacity. * * @return the opacity */ public float getOpacity() { return opacity; } /** * Gets the surface. * * @return the surface */ public int getSurface() { return getSurface(getBinaryData()); } /** * Gets the width. * * @return the width */ public int getWidth() { return width; } /** * Checks for binary data. * * @return true, if successful */ public boolean hasBinaryData() { return (binaryData != null) && (rawBinaryData != null); } /** * Intersects. * * @param shp * the shp * @return true, if successful */ public boolean intersects(Shape shp) { throw new RuntimeException("Not yet implemented"); } /** * Invert. * * @throws MaskException * the mask exception */ public void invert() throws MaskException { if (hasBinaryData()) { binaryData.invert(); forceRedraw(); } else { throw new MaskException("Operation only available for binary masks"); } } /** * Checks if is draw only contours. * * @return true, if is draw only contours */ public boolean isDrawOnlyContours() { return drawOnlyContours; } /** * Checks if is need automatic label. * * @return true, if is need automatic label */ public boolean isNeedAutomaticLabel() { return needAutomaticLabel; } /** * Checks if is visible layer. * * @return true, if is visible layer */ public boolean isVisibleLayer() { return visibleLayer; } /* (non-Javadoc) * @see java.lang.Iterable#iterator() */ @Override public Iterator<String> iterator() { return tags.iterator(); } /** * Manage area. * * @param rhs * the rhs * @param val * the val */ private void manageArea(Area rhs, byte val) { Rectangle r = rhs.getBounds(); int x1 = (int) Math.max(Math.floor(r.getMinX()), 0); int x2 = (int) Math.min(x1 + Math.ceil(r.getWidth()), width); int y1 = (int) Math.max(Math.floor(r.getMinY()), 0); int y2 = (int) Math.min(y1 + Math.ceil(r.getHeight()), height); for (int x = x1; x < x2; x++) { for (int y = y1; y < y2; y++) { if (rhs.contains(x, y)) { rawBinaryData[x + width * y] = val; } } } } private void manageROI(ROI2D roi, byte val) { Rectangle r = roi.getBounds(); int x1 = (int) Math.max(Math.floor(r.getMinX()), 0); int x2 = (int) Math.min(x1 + Math.ceil(r.getWidth()), width); int y1 = (int) Math.max(Math.floor(r.getMinY()), 0); int y2 = (int) Math.min(y1 + Math.ceil(r.getHeight()), height); for (int x = x1; x < x2; x++) { for (int y = y1; y < y2; y++) { if (roi.contains(x, y)) { rawBinaryData[x + width * y] = val; } } } } /** * Paint. * * @param g * the g */ public void paint(Graphics2D g) { if (needRedraw) { if (cache == null) { cache = createCache(getColor()); } else { fillCache(getColor(), cache); } needRedraw = false; } Composite bck = g.getComposite(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getOpacity())); g.drawImage(cache, 0, 0, null); g.setComposite(bck); } /** * Removes the. * * @param rhs * the rhs * @throws MaskException * the mask exception */ public void remove(Area rhs) throws MaskException { if (hasBinaryData()) { manageArea(rhs, BinaryIcyBufferedImage.FALSE); } else { throw new MaskException("No internal mask representation available"); } forceRedraw(); } /** * Removes the. * * @param m * the m * @throws MaskException * the mask exception */ public void remove(Mask m) throws MaskException { if (hasBinaryData() && m.hasBinaryData()) { binaryData.remove(m.getBinaryData()); } else { throw new MaskException("No internal mask representation available"); } forceRedraw(); } public void remove(ROI2D roi) throws MaskException { if (hasBinaryData()) { manageROI(roi, BinaryIcyBufferedImage.FALSE); } else { throw new MaskException("No internal mask representation available"); } forceRedraw(); } /** * Removes the tag. * * @param arg0 * the arg0 * @return true, if successful */ public boolean removeTag(String arg0) { return tags.remove(arg0); } /** * Sets the binary data. * * @param data * the new binary data */ public void setBinaryData(BinaryIcyBufferedImage data) { this.binaryData = data; this.rawBinaryData = data.getRawData(); forceRedraw(); } /** * Sets the color. * * @param color * the new color */ public void setColor(Color color) { this.color = color; forceRedraw(); } /** * Sets the draw only contours. * * @param drawOnlyContours * the new draw only contours */ public void setDrawOnlyContours(boolean drawOnlyContours) { this.drawOnlyContours = drawOnlyContours; forceRedraw(); } /** * Sets the height. * * @param height * the new height */ public void setHeight(int height) { this.height = height; } /** * Sets the id. * * @param id * the new id */ public void setId(int id) { this.id = id; } /** * Sets the label. * * @param label * the new label */ public void setLabel(String label) { this.label = label; } /** * Sets the need automatic label. * * @param needAutomaticLabel * the new need automatic label */ public void setNeedAutomaticLabel(boolean needAutomaticLabel) { this.needAutomaticLabel = needAutomaticLabel; } /** * Sets the opacity. * * @param opacity * the new opacity */ public void setOpacity(float opacity) { this.opacity = opacity; } /** * Sets the visible layer. * * @param visibleLayer * the new visible layer */ public void setVisibleLayer(boolean visibleLayer) { this.visibleLayer = visibleLayer; } /** * Sets the width. * * @param width * the new width */ public void setWidth(int width) { this.width = width; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { String strTags = ""; if (getNbTags() > 0) { strTags += " ("; boolean first = true; for (String t : this) { if (first) { first = false; } else { strTags += ", "; } strTags += t; } strTags += ")"; } return getId() + " - " + getLabel() + strTags; } }