/* * ------------------------------------------------------------------------ * * Copyright (C) 2003 - 2013 * University of Konstanz, Germany and * KNIME GmbH, Konstanz, Germany * Website: http://www.knime.org; Email: contact@knime.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 3, as * published by the Free Software Foundation. * * This program 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 this program; if not, see <http://www.gnu.org/licenses>. * * Additional permission under GNU GPL version 3 section 7: * * KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs. * Hence, KNIME and ECLIPSE are both independent programs and are not * derived from each other. Should, however, the interpretation of the * GNU GPL Version 3 ("License") under any applicable laws result in * KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants * you the additional permission to use and propagate KNIME together with * ECLIPSE with only the license terms in place for ECLIPSE applying to * ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the * license terms of ECLIPSE themselves allow for the respective use and * propagation of ECLIPSE together with KNIME. * * Additional permission relating to nodes for KNIME that extend the Node * Extension (and in particular that are based on subclasses of NodeModel, * NodeDialog, and NodeView) and that only interoperate with KNIME through * standard APIs ("Nodes"): * Nodes are deemed to be separate and independent programs and to not be * covered works. Notwithstanding anything to the contrary in the * License, the License does not apply to Nodes, you are not required to * license Nodes under the License, and you are granted a license to * prepare and propagate Nodes, in each case even if such Nodes are * propagated with or for interoperation with KNIME. The owner of a Node * may freely choose the license terms applicable to such Node, including * when such Node is propagated with or for interoperation with KNIME. * --------------------------------------------------------------------- * * */ package org.knime.knip.base.data.labeling; import java.awt.Image; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException; import org.knime.core.data.DataCell; import org.knime.core.data.DataType; import org.knime.core.data.StringValue; import org.knime.core.data.container.BlobDataCell; import org.knime.core.data.filestore.FileStore; import org.knime.core.data.filestore.FileStoreCell; import org.knime.knip.base.KNIMEKNIPPlugin; import org.knime.knip.base.data.CachedObjectAccess; import org.knime.knip.base.data.CachedObjectAccess.StreamSkipper; import org.knime.knip.base.data.FileStoreCellMetadata; import org.knime.knip.base.data.IntervalValue; import org.knime.knip.base.renderer.ThumbnailRenderer; import org.knime.knip.core.KNIPGateway; import org.knime.knip.core.awt.AWTImageTools; import org.knime.knip.core.awt.ColorLabelingRenderer; import org.knime.knip.core.awt.labelingcolortable.LabelingColorTableUtils; import org.knime.knip.core.awt.labelingcolortable.RandomMissingColorHandler; import org.knime.knip.core.data.LabelingView; import org.knime.knip.core.data.img.LabelingMetadata; import org.knime.knip.core.io.externalization.BufferedDataInputStream; import org.knime.knip.core.io.externalization.ExternalizerManager; import org.scijava.Named; import org.scijava.cache.CacheService; import net.imagej.Sourced; import net.imagej.axis.CalibratedAxis; import net.imagej.space.CalibratedSpace; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.ops.operation.SubsetOperations; import net.imglib2.roi.labeling.LabelingType; import net.imglib2.util.Intervals; import net.imglib2.view.Views; /** * * Cell holding a labeling and some metadata. * * @param <L> image type * @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a> * @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a> * @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael Zinsmaier</a> */ public class LabelingCell<L> extends FileStoreCell implements LabelingValue<L>, StringValue, IntervalValue { /** * ObjectRepository */ private static final CacheService CACHE = KNIPGateway.cache(); /** * UID */ private static final long serialVersionUID = 1L; /** * Convenience access method for DataType.getType(ImageCell.class). */ public static final DataType TYPE = DataType.getType(LabelingCell.class); /** * Image cells need not to be compressed. * * @see BlobDataCell#USE_COMPRESSION */ public static final boolean USE_COMPRESSION = false; /* information needed to store the data into a file */ private FileStoreCellMetadata m_fileMetadata; /* the labeling object */ private CachedObjectAccess<LabelingView<L>> m_labelingAccess; /* metadata of the labeling */ private CachedObjectAccess<LabelingCellMetadata> m_metadataAccess; /** * Constructor for Serialization * * @throws IOException */ protected LabelingCell() throws IOException { super(); } /** * Default Constructor * * @param labeling the {@link RandomAccessibleInterval} stored in this cell * @param metadata {@link LabelingMetadata} of the {@link RandomAccessibleInterval} stored in this cell * @param fileStore {@link FileStore} used to serialize/deserialize this cell */ protected LabelingCell(final RandomAccessibleInterval<LabelingType<L>> labeling, final LabelingMetadata metadata, final FileStore fileStore) { super(fileStore); final long[] dimensions = new long[labeling.numDimensions()]; labeling.dimensions(dimensions); m_metadataAccess = new CachedObjectAccess<LabelingCellMetadata>(fileStore, new LabelingCellMetadata(metadata, Views.iterable(labeling).size(), dimensions, null)); m_labelingAccess = new CachedObjectAccess<>(fileStore, new LabelingView<L>(labeling)); m_fileMetadata = new FileStoreCellMetadata(-1, false, null); CACHE.put(this, this); } private BufferedImage createThumbnail(final double factor) { // make sure that at least two dimensions exist RandomAccessibleInterval<LabelingType<L>> lab2d; if (getLabeling().numDimensions() > 1) { lab2d = getLabeling(); } else { lab2d = Views.addDimension(getLabeling(), 0, 0); } // set the labeling mapping final ColorLabelingRenderer<L> rend = new ColorLabelingRenderer<L>(); rend.setLabelMapping(lab2d.randomAccess().get().getMapping()); int i = 0; final long[] max = new long[lab2d.numDimensions()]; max[0] = lab2d.max(0); max[1] = lab2d.max(1); for (i = 2; i < lab2d.numDimensions(); i++) { if ((lab2d.dimension(i) == 2) || (lab2d.dimension(i) == 3)) { max[i] = lab2d.max(i); break; } } final RandomAccessibleInterval<LabelingType<L>> toRender; if (getLabeling() == lab2d) { toRender = getSubInterval(new FinalInterval(Intervals.minAsLongArray(lab2d), max)); } else { toRender = lab2d; } rend.setLabelingColorTable(LabelingColorTableUtils .extendLabelingColorTable(m_metadataAccess.get().getLabelingMetadata().getLabelingColorTable(), new RandomMissingColorHandler())); return AWTImageTools.renderScaledStandardColorImg(toRender, rend, factor, new long[max.length]); } /** * {@inheritDoc} */ @Override protected boolean equalsDataCell(final DataCell dc) { return dc.hashCode() == hashCode(); } /** * {@inheritDoc} */ @Override public synchronized CalibratedSpace<CalibratedAxis> getCalibratedSpace() { return m_metadataAccess.get().getLabelingMetadata(); } /** * {@inheritDoc} */ @Override public synchronized long[] getDimensions() { return m_metadataAccess.get().getDimensions(); } /** * {@inheritDoc} */ @Override public synchronized RandomAccessibleInterval<LabelingType<L>> getLabeling() { final Object o = m_labelingAccess.get(); if (o instanceof LabelingView) { return ((LabelingView)o).getSrc(); } else { return (RandomAccessibleInterval<LabelingType<L>>)o; } } /** * {@inheritDoc} */ @Override public synchronized LabelingMetadata getLabelingMetadata() { return m_metadataAccess.get().getLabelingMetadata(); } private LabelingCellMetadata getLabelingMetadataToWrite() { final LabelingCellMetadata tmp = m_metadataAccess.get(); int height = KNIMEKNIPPlugin.getMaximumImageCellHeight(); if (height == 0) { height = (int)tmp.getDimensions()[1]; } final int width = getThumbnailWidth(height); if (((height * width) / (double)tmp.getSize()) < KNIMEKNIPPlugin.getThumbnailImageRatio()) { getThumbnail(null); return tmp; } else { return new LabelingCellMetadata(tmp.getLabelingMetadata(), tmp.getSize(), tmp.getDimensions(), null); } } /** * {@inheritDoc} */ @Override public synchronized long[] getMaximum() { final LabelingCellMetadata labelingCellMetadata = m_metadataAccess.get(); final long[] max = new long[labelingCellMetadata.getDimensions().length]; for (int i = 0; i < max.length; i++) { max[i] = labelingCellMetadata.getDimensions()[i] - 1; } return max; } /** * {@inheritDoc} */ @Override public synchronized long[] getMinimum() { final LabelingCellMetadata labelingCellMetadata = m_metadataAccess.get(); final long[] min = new long[labelingCellMetadata.getDimensions().length]; return min; } /** * {@inheritDoc} */ @Override public synchronized Named getName() { return m_metadataAccess.get().getLabelingMetadata(); } /** * {@inheritDoc} */ @Override public synchronized Sourced getSource() { return m_metadataAccess.get().getLabelingMetadata(); } /** * {@inheritDoc} */ @Override public synchronized String getStringValue() { final LabelingCellMetadata tmp = m_metadataAccess.get(); final StringBuffer sb = new StringBuffer(); sb.append("Labeling[\nname="); sb.append(tmp.getLabelingMetadata().getName()); sb.append(";\nsource="); sb.append(tmp.getLabelingMetadata().getSource()); sb.append(";\ndimensions="); final int numDims = tmp.getDimensions().length; for (int i = 0; i < (numDims - 1); i++) { sb.append(tmp.getDimensions()[i]); sb.append(","); } sb.append(tmp.getDimensions()[numDims - 1]); sb.append(" ("); for (int i = 0; i < (numDims - 1); i++) { sb.append(tmp.getLabelingMetadata().axis(i).type().getLabel()); sb.append(","); } sb.append(tmp.getLabelingMetadata().axis(numDims - 1).type().getLabel()); sb.append(")]"); return sb.toString(); } private RandomAccessibleInterval<LabelingType<L>> getSubInterval(final Interval interval) { return SubsetOperations.subsetview(getLabeling(), interval); } /** * {@inheritDoc} */ @Override public synchronized Image getThumbnail(final RenderingHints renderingHints) { boolean renderMetadata; if ((renderingHints != null) && (renderMetadata = renderingHints.get(ThumbnailRenderer.RENDERING_HINT_KEY_METADATA) != null) && renderMetadata) { final int height = 200; return AWTImageTools.makeTextBufferedImage(getStringValue(), height * 3, height, "\n"); } else { double height = 1; // default for 1d images double fullHeight = 1; LabelingCellMetadata tmp = m_metadataAccess.get(); if (tmp.getDimensions().length > 1) { height = Math.min(tmp.getDimensions()[1], KNIMEKNIPPlugin.getMaximumImageCellHeight()); if (height == 0) { height = tmp.getDimensions()[1]; } fullHeight = tmp.getDimensions()[1]; } if ((tmp.getThumbnail() == null) || (tmp.getThumbnail().getHeight() != height)) { m_metadataAccess = new CachedObjectAccess<LabelingCellMetadata>(getFileStore(), tmp = new LabelingCellMetadata(tmp.getLabelingMetadata(), tmp.getSize(), tmp.getDimensions(), createThumbnail(height / fullHeight))); // update cached object CACHE.put(this, this); } return tmp.getThumbnail(); } } private int getThumbnailWidth(final int height) { final LabelingCellMetadata tmp = m_metadataAccess.get(); if (tmp.getDimensions().length == 1) { return (int)tmp.getDimensions()[0]; } else { return (int)(((double)tmp.getDimensions()[0] / tmp.getDimensions()[1]) * height); } } /** * {@inheritDoc} */ @Override public int hashCode() { return (int)(getFileStore().getFile().hashCode() + (31 * m_fileMetadata.getOffset())); } /** * Loads the cell content. To be called after the #FileImgPlusCell(DataCellDataInput) constructor. * * @param input * @throws IOException */ protected synchronized void load(final DataInput input) throws IOException { try { // work-around for bug#3578 // m_fileMetadata = // ExternalizerManager.read(stream); final int length = input.readInt(); for (int i = 0; i < length; i++) { input.readChar(); } m_fileMetadata = new FileStoreCellMetadata(input.readLong(), input.readBoolean(), null); } catch (final Exception e) { if (e instanceof IOException) { throw (IOException)e; } } } /** * {@inheritDoc} */ @Override protected void postConstruct() { @SuppressWarnings("unchecked") final LabelingCell<L> tmp = (LabelingCell<L>)CACHE.get(this); if (tmp == null) { // Creates empty CachedObjectAccesses which know how to reconstruct the managed objects. m_metadataAccess = new CachedObjectAccess<>(getFileStore(), null, m_fileMetadata.getOffset(), null); m_labelingAccess = new CachedObjectAccess<>(getFileStore(), null, m_fileMetadata.getOffset(), new StreamSkipper() { /** * {@inheritDoc} */ @Override public void skip(final BufferedDataInputStream in) { try { m_metadataAccess.setObject(ExternalizerManager.read(in)); } catch (Exception e) { throw new RuntimeException(e); } } }); CACHE.put(this, this); } else { m_labelingAccess = tmp.m_labelingAccess; m_metadataAccess = tmp.m_metadataAccess; } } /** * Stores the cell content. A few meta information to the data output, the heavy data to the file. * * @param output {@link DataOutput} to persist cell * @throws IOException can be thrown during serialization */ protected synchronized void save(final DataOutput output) throws IOException { long offset = m_fileMetadata.getOffset(); try { flushToFileStore(); // work-around for bug #3578: output.writeInt(0); // to be // backwards-compatible output.writeLong(offset); output.writeBoolean(true); // ExternalizerManager.write(stream, // m_fileMetadata); } catch (final Exception e) { if (e instanceof IOException) { throw (IOException)e; } } } /** * {@inheritDoc} */ @Override protected synchronized void flushToFileStore() throws IOException { long offset = m_fileMetadata.getOffset(); final boolean isPersistent = m_fileMetadata.isPersistent(); // if the image data wasn't made persitent yet ... if (!isPersistent) { final File file = getFileStore().getFile(); offset = file.length(); m_metadataAccess.setObject(getLabelingMetadataToWrite()); m_metadataAccess.serialize(); m_labelingAccess.serialize(); } m_fileMetadata = new FileStoreCellMetadata(offset, true, null); } /** * {@inheritDoc} */ @Override public String toString() { return getStringValue(); } }