/* * 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 java.awt.Color; import java.awt.image.BufferedImage; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import plugins.nherve.toolbox.image.BinaryIcyBufferedImage; import plugins.nherve.toolbox.image.DifferentColorsMap; import plugins.nherve.toolbox.image.segmentation.Segmentation; /** * The Class MaskStack. * * @author Nicolas HERVE - nicolas.herve@pasteur.fr */ public class MaskStack implements Iterable<Mask>, Serializable { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 332436724094094775L; /** The Constant MASK_DEFAULT_LABEL. */ public final static String MASK_DEFAULT_LABEL = "Default mask label"; /** The height. */ private int height; /** The width. */ private int width; /** The new layer id. */ private transient int newLayerId; /** The active id. */ private int activeIndex; /** The masks. */ private ArrayList<Mask> masks; private transient Map<MaskListener, MaskListener> listeners; private boolean updating; /** * Instantiates a new mask stack. */ public MaskStack() { super(); clear(); listeners = new HashMap<MaskListener, MaskListener>(); updating = false; } /** * Instantiates a new mask stack. * * @param width * the width * @param height * the height */ public MaskStack(int width, int height) { this(); this.width = width; this.height = height; } /** * Adds the. * * @param e * the e * @return true, if successful */ public boolean add(Mask e) { boolean r = masks.add(e); fireChangeEvent(); return r; } /** * Adds the external mask. * * @param m * the m * @throws MaskException * the mask exception */ public void addExternalMask(Mask m) throws MaskException { if (newLayerId < (Integer.MAX_VALUE - 1)) { m.setId(newLayerId++); setActiveIndex(masks.size()); add(m); } else { throw new MaskException("Maximum mask capacity reached ()"); } } public void addExternalMask(Mask m, DifferentColorsMap colorMap) throws MaskException { if (newLayerId < (Integer.MAX_VALUE - 1)) { m.setId(newLayerId++); setActiveIndex(masks.size()); m.setColor(colorMap.get(m.getId())); add(m); } else { throw new MaskException("Maximum mask capacity reached ()"); } } public void addListener(MaskListener l) { listeners.put(l, l); } /** * Adds the previous in stack. * * @param m * the m * @throws MaskException * the mask exception */ public void addPreviousInStack(Mask m) throws MaskException { int idx1 = masks.indexOf(m); int idx2 = idx1 - 1; if (idx2 >= 0) { Mask m2 = masks.get(idx2); m.add(m2); remove(m2); } fireChangeEvent(); } /** * As segmentation. * * @return the segmentation * @throws MaskException * the mask exception */ public Segmentation asSegmentation() throws MaskException { int w = getWidth(); int h = getHeight(); Segmentation seg = new Segmentation(w, h); for (Mask m : this) { Mask sm = seg.createNewMask(m.getLabel(), m.isNeedAutomaticLabel(), m.getColor(), m.getOpacity()); sm.setBinaryData(m.getBinaryData().getCopy()); } seg.createBackgroundMask("Background", Color.BLACK); seg.createIndex(); return seg; } public void beginUpdate() { updating = true; } /** * Check after load. * * @param opacity * the opacity * @param img * the img */ public void checkAfterLoad(float opacity, BufferedImage img) { updateNewLayerId(); for (Mask m : masks) { m.setOpacity(opacity); m.forceRedraw(); } } /** * Clear. */ public void clear() { masks = new ArrayList<Mask>(); newLayerId = 0; setActiveIndex(-1); } /** * Copy current mask. * * @return the mask * @throws MaskException * the mask exception */ public Mask copyCurrentMask() throws MaskException { Mask m = getActiveMask(); m = copyMask(m); return m; } public Mask copyCurrentMask(DifferentColorsMap colorMap) throws MaskException { Mask m = getActiveMask(); m = copyMask(m, colorMap); return m; } /** * Copy mask. * * @param m * the m * @return the mask * @throws MaskException * the mask exception */ public Mask copyMask(Mask m) throws MaskException { Mask m2 = createNewMask("Copy of (" + m.getId() + ") " + m.getLabel(), m.isNeedAutomaticLabel(), m.getColor(), m.getOpacity()); if (m.hasBinaryData()) { m2.setBinaryData(m.getBinaryData().getCopy()); } else { throw new MaskException("No internal mask representation available for current mask"); } return m2; } public Mask copyMask(Mask m, DifferentColorsMap colorMap) throws MaskException { Mask m2 = createNewMask("Copy of (" + m.getId() + ") " + m.getLabel(), m.isNeedAutomaticLabel(), colorMap, m.getOpacity()); if (m.hasBinaryData()) { m2.setBinaryData(m.getBinaryData().getCopy()); } else { throw new MaskException("No internal mask representation available for current mask"); } return m2; } /** * Creates the background mask. * * @param label * the label * @param clr * the clr * @return the mask * @throws MaskException * the mask exception */ public Mask createBackgroundMask(String label, Color clr) throws MaskException { BinaryIcyBufferedImage sum = null; for (Mask o : this) { if (sum == null) { sum = o.getBinaryData().getCopy(); } else { sum.add(o.getBinaryData()); } } sum.invert(); Mask m = createNewMask(label, false, clr, 1.0f); m.setBinaryData(sum); // moveTop(m); return m; } /** * Creates the new mask. * * @param label * the label * @param needAutomaticLabel * the need automatic label * @param c * the c * @param opacity * the opacity * @return the mask * @throws MaskException * the mask exception */ public Mask createNewMask(String label, boolean needAutomaticLabel, Color c, float opacity) throws MaskException { Mask m = new Mask(width, height, label, false); m.setColor(c); m.setOpacity(opacity); m.setNeedAutomaticLabel(needAutomaticLabel); addExternalMask(m); return m; } public Mask createNewMask(String label, boolean needAutomaticLabel, DifferentColorsMap colorMap, float opacity) throws MaskException { Mask m = new Mask(width, height, label, false); m.setOpacity(opacity); m.setNeedAutomaticLabel(needAutomaticLabel); addExternalMask(m, colorMap); return m; } public void endUpdate() { updating = false; fireChangeEvent(); } public void fireChangeEvent() { if (!updating) { for (MaskListener l : listeners.keySet()) { l.stackChanged(this); } } } /** * Gets the active id. * * @return the active id */ public int getActiveIndex() { return activeIndex; } /** * Gets the active mask. * * @return the active mask */ public Mask getActiveMask() { return getByIndex(getActiveIndex()); } /** * Gets the mask by id. * * @param id * the id * @return the mask by id */ public Mask getById(int id) { for (Mask m : this) { if (m.getId() == id) { return m; } } return null; } /** * Gets the. * * @param index * the index * @return the mask */ public Mask getByIndex(int index) { if ((index < 0) || (index >= masks.size())) { return null; } return masks.get(index); } /** * Gets the mask. * * @param label * the label * @return the mask */ public Mask getByLabel(String label) { for (Mask m : this) { if (label.equalsIgnoreCase(m.getLabel())) { return m; } } return null; } /** * Gets the height. * * @return the height */ public int getHeight() { return height; } /** * Gets the masks. * * @return the masks */ public ArrayList<Mask> getMasks() { return masks; } /** * Gets the masks by tag. * * @param tag * the tag * @return the masks by tag */ public ArrayList<Mask> getMasksByTag(String tag) { ArrayList<Mask> res = new ArrayList<Mask>(); for (Mask m : this) { if (m.containsTag(tag)) { res.add(m); } } return res; } /** * Gets the masks. * * @param labelStratingWith * the label strating with * @return the masks */ public ArrayList<Mask> getMasksStartingWithLabel(String labelStratingWith) { ArrayList<Mask> res = new ArrayList<Mask>(); for (Mask m : this) { if (m.getLabel().toUpperCase().startsWith(labelStratingWith.toUpperCase())) { res.add(m); } } return res; } /** * Gets the max id. * * @return the max id */ public int getMaxId() { return newLayerId - 1; } /** * Gets the width. * * @return the width */ public int getWidth() { return width; } public int indexOf(Mask o) { return masks.indexOf(o); } /** * Intersect previous in stack. * * @param m * the m * @throws MaskException * the mask exception */ public void intersectPreviousInStack(Mask m) throws MaskException { int idx1 = masks.indexOf(m); int idx2 = idx1 - 1; if (idx2 >= 0) { Mask m2 = masks.get(idx2); m.remove(m2); remove(m2); } fireChangeEvent(); } /* * (non-Javadoc) * * @see java.lang.Iterable#iterator() */ @Override public Iterator<Mask> iterator() { return masks.iterator(); } public void moveAt(Mask m, int newIdx) { int idx = masks.indexOf(m); if (idx == newIdx) { return; } if (idx > newIdx) { while (idx > newIdx) { swap(idx, --idx); } } else if (idx < newIdx) { while (idx < newIdx) { swap(idx, ++idx); } } fireChangeEvent(); } public void moveBottom(Mask m) { moveAt(m, 0); } /** * Move down. * * @param m * the m */ public void moveDown(Mask m) { int idx1 = masks.indexOf(m); int idx2 = idx1 + 1; if (idx2 < masks.size()) { swap(idx1, idx2); } fireChangeEvent(); } /** * Move top. * * @param m * the m */ public void moveTop(Mask m) { moveAt(m, masks.size() - 1); } /** * Move up. * * @param m * the m */ public void moveUp(Mask m) { int idx1 = masks.indexOf(m); int idx2 = idx1 - 1; if (idx2 >= 0) { swap(idx1, idx2); } fireChangeEvent(); } public void reInitColors(DifferentColorsMap colorMap) { for (Mask m : masks) { m.setColor(colorMap.get(m.getId())); } fireChangeEvent(); } /** * Re init colors. * * @param img * the img */ public void reInitColors(IcyBufferedImage img) { for (Mask m : masks) { m.setColor(m.getAverageColor(img)); } fireChangeEvent(); } /** * Removes the. * * @param m * the m */ public void remove(Mask m) { int idx1 = masks.indexOf(m); masks.remove(idx1); if (idx1 <= getActiveIndex()) { setActiveIndex(getActiveIndex() - 1); } if ((getActiveIndex() < 0) && (masks.size() > 0)) { setActiveIndex(0); } updateNewLayerId(); fireChangeEvent(); } public void removeListener(MaskListener l) { listeners.remove(l); } /** * Removes the mask with tag. * * @param tag * the tag */ public void removeMaskWithTag(String tag) { ArrayList<Mask> masks = getMasksByTag(tag); for (Mask m : masks) { remove(m); } } /** * Sets the active id. * * @param activeId * the new active id */ public void setActiveIndex(int active) { this.activeIndex = active; } /** * Sets the active mask. * * @param active * the new active mask */ public void setActiveMask(Mask active) { setActiveIndex(masks.indexOf(active)); } /** * Sets the height. * * @param height * the new height */ public void setHeight(int height) { this.height = height; } /** * Sets the masks. * * @param masks * the new masks */ public void setMasks(ArrayList<Mask> masks) { this.masks = masks; fireChangeEvent(); } /** * Sets the width. * * @param width * the new width */ public void setWidth(int width) { this.width = width; } /** * Size. * * @return the int */ public int size() { return masks.size(); } /** * Swap. * * @param idx1 * the idx1 * @param idx2 * the idx2 */ private void swap(int idx1, int idx2) { Mask t = masks.get(idx2); masks.set(idx2, masks.get(idx1)); masks.set(idx1, t); if (getActiveIndex() == idx1) { setActiveIndex(idx2); } else if (getActiveIndex() == idx2) { setActiveIndex(idx1); } } /** * Update new layer id. */ private void updateNewLayerId() { newLayerId = 0; for (Mask m : masks) { if (m.getId() >= newLayerId) { newLayerId = m.getId() + 1; } } } @Override public String toString() { String str = ""; for (Mask m : this) { if (str.length() > 0) { str += ", "; } str += m.toString(); } return str; } }