/* * ------------------------------------------------------------------------ * * 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.aggregation; import java.awt.Component; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.swing.BoxLayout; import javax.swing.JPanel; import org.knime.base.data.aggregation.AggregationOperator; import org.knime.base.data.aggregation.GlobalSettings; import org.knime.base.data.aggregation.OperatorColumnSettings; import org.knime.core.data.BooleanValue; import org.knime.core.data.DataCell; import org.knime.core.data.DataRow; import org.knime.core.data.DataTableSpec; import org.knime.core.data.DataType; import org.knime.core.data.DataValue; import org.knime.core.data.DoubleValue; import org.knime.core.data.IntValue; import org.knime.core.data.LongValue; import org.knime.core.node.InvalidSettingsException; import org.knime.core.node.NodeLogger; import org.knime.core.node.NodeSettingsRO; import org.knime.core.node.NodeSettingsWO; import org.knime.core.node.NotConfigurableException; import org.knime.core.node.defaultnodesettings.DialogComponent; import org.knime.core.node.defaultnodesettings.DialogComponentColumnNameSelection; import org.knime.core.node.defaultnodesettings.DialogComponentStringSelection; import org.knime.core.node.defaultnodesettings.SettingsModelString; import org.knime.knip.base.data.IntervalValue; import org.knime.knip.base.data.img.ImgPlusValue; import org.knime.knip.base.data.labeling.LabelingCell; import org.knime.knip.core.data.DefaultNamed; import org.knime.knip.core.data.DefaultSourced; import org.knime.knip.core.data.IntegerLabelGenerator; import org.knime.knip.core.data.LabelGenerator; import org.knime.knip.core.data.img.DefaultLabelingMetadata; import org.knime.knip.core.data.img.LabelingMetadata; import org.knime.knip.core.types.ImgFactoryTypes; import org.knime.knip.core.types.NativeTypes; import org.knime.knip.core.util.EnumUtils; import org.knime.knip.core.util.Triple; import net.imagej.axis.CalibratedAxis; import net.imagej.space.DefaultCalibratedSpace; import net.imglib2.Cursor; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.iterator.IntervalIterator; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelingType; import net.imglib2.type.NativeType; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.view.Views; /** * * @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 LabelingComposeOperator<T extends IntegerType<T> & NativeType<T>, L extends Comparable<L>> extends LabelingAggregrationOperation { private static final NodeLogger LOGGER = NodeLogger.getLogger(LabelingComposeOperator.class); private static SettingsModelString createAvoidOverlapOrderColModel() { return new SettingsModelString("avoid_overlap_column_order", ""); } private static SettingsModelString createIntervalColumnModel() { return new SettingsModelString("interval_column", ""); } private static SettingsModelString createLabelColumnModel() { return new SettingsModelString("label_column", ""); } private static SettingsModelString createLabelColorColumnModel() { return new SettingsModelString("label_color_col_model", ""); } private static SettingsModelString createLabelingFactoryModel() { return new SettingsModelString("labeling_factory", ImgFactoryTypes.ARRAY_IMG_FACTORY.toString()); } private static SettingsModelString createLabelingTypeModel() { return new SettingsModelString("labeling_type", NativeTypes.SHORTTYPE.toString()); } // fields needed, if the result interval is not known in advance, i.e. // if the default settings are used private List<Triple<ImgPlusValue<BitType>, L, Double>> m_bitMaskList = null; private DialogComponent m_dcAvoidOverlapCol; // dialog components private DialogComponent m_dcIntervalCol; private DialogComponent m_dcLabelCol; private DialogComponentStringSelection m_dcLabelingFactory; private DialogComponentStringSelection m_dcLabelingType; private ImgFactory<T> m_factory = null; // the default settings, i.e. if the respective globalsettings are not // set private String m_intervalCol = ""; private String m_labelCol = ""; private final LabelGenerator<Integer> m_labelGenerator = new IntegerLabelGenerator(); private long[] m_maxDims = null; private RandomAccess<LabelingType<L>> m_resAccess = null; private T m_resType; // field for the labeling generation private RandomAccessibleInterval<LabelingType<L>> m_resultLab = null; private LabelingMetadata m_resultMetadata = null; private final SettingsModelString m_smAvoidOverlapCol = createAvoidOverlapOrderColModel(); // settings models private final SettingsModelString m_smIntervalCol = createIntervalColumnModel(); private final SettingsModelString m_smLabelCol = createLabelColumnModel(); private final SettingsModelString m_smLabelingFactory = createLabelingFactoryModel(); private final SettingsModelString m_smLabelingType = createLabelingTypeModel(); public LabelingComposeOperator() { super("compose_labeling_2", "Compose Labeling", "Compose Labeling"); } /** * @param label * @param globalSettings */ public LabelingComposeOperator(final GlobalSettings globalSettings) { this(globalSettings, null, null, null, null, null); } @SuppressWarnings("unchecked") protected LabelingComposeOperator(final GlobalSettings globalSettings, final String intervalColName, final String labelColName, final String labelingType, final String labelingFactory, final String overlapCol) { super("compose_labeling_2", "Compose Labeling", globalSettings); if (intervalColName != null) { m_smIntervalCol.setStringValue(intervalColName); } if (labelColName != null) { m_smLabelCol.setStringValue(labelColName); } if (labelingType != null) { m_smLabelingType.setStringValue(labelingType); } if (labelingFactory != null) { m_smLabelingFactory.setStringValue(labelingFactory); } if (overlapCol != null) { m_smAvoidOverlapCol.setStringValue(overlapCol); } m_intervalCol = m_smIntervalCol.getStringValue(); m_labelCol = m_smLabelCol.getStringValue(); m_factory = ImgFactoryTypes.getImgFactory(m_smLabelingFactory.getStringValue(), null); m_resType = (T)NativeTypes.getTypeInstance(NativeTypes.valueOf(m_smLabelingType.getStringValue())); } /* * adds the bitmask to the labeling and return true, if it was * successfull */ private boolean addToLabeling(final ImgPlusValue<BitType> val, L label) { final Img<BitType> img = val.getImgPlus(); if (img.numDimensions() != m_resultLab.numDimensions()) { return false; } if (label == null) { label = (L)("" + m_labelGenerator.nextLabel()); } // Set segmented pixels to label final Cursor<BitType> cur = img.localizingCursor(); while (cur.hasNext()) { cur.fwd(); if (!cur.get().get()) { continue; } m_resAccess.setPosition(cur); m_resAccess.get().add(label); } return true; } /** * {@inheritDoc} */ @Override protected boolean computeInternal(final DataCell cell) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ @Override protected boolean computeInternal(final DataRow row, final DataCell cell) { if(cell.isMissing()) { return false; } // check correct pixel type if (!(((ImgPlusValue)cell).getImgPlus().firstElement() instanceof BitType)) { setSkipMessage("The Bitmask in row " + row.getKey() + " is not of pixel type BitType!"); return true; } L label = null; final int labelColIdx = getGlobalSettings().findColumnIndex(m_labelCol); if (labelColIdx != -1) { DataCell labelCell = row.getCell(labelColIdx); if (labelCell instanceof BooleanValue) { label = (L)new Boolean(((BooleanValue)labelCell).getBooleanValue()); } else if (labelCell instanceof IntValue) { label = (L)new Integer(((IntValue)labelCell).getIntValue()); } else if (labelCell instanceof LongValue) { label = (L)new Long(((LongValue)labelCell).getLongValue()); } else if (labelCell instanceof DoubleValue) { label = (L)new Double(((DoubleValue)labelCell).getDoubleValue()); } else { label = (L)labelCell.toString(); } } final int intervalColIdx = getGlobalSettings().findColumnIndex(m_intervalCol); final int avoidOverlapColIdx = getGlobalSettings().findColumnIndex(m_smAvoidOverlapCol.getStringValue()); // create result labeling, if the interval is known // if no column index is there, collect all tiles and put them // together to an image afterwards, if not, the result image can // be created in advance if ((intervalColIdx != -1) && (m_resultLab == null)) { final IntervalValue iv = (IntervalValue)row.getCell(intervalColIdx); // create labeling and metadata // todo make the generation of the result // labeling configurable final Interval i = new FinalInterval(iv.getMinimum(), iv.getMaximum()); m_resultLab = new ImgLabeling<L, T>(m_factory.create(i, m_resType)); m_resAccess = Views.extendValue(m_resultLab, Util.getTypeFromInterval(m_resultLab).createVariable()) .randomAccess(); m_resultMetadata = new DefaultLabelingMetadata(iv.getCalibratedSpace(), iv.getName(), iv.getSource(), null); } // add the bitmask either to the the bitmask list (if the // interval is not known or overlapping segments should be // filtered or add them directly to the labeling if ((intervalColIdx == -1) || (avoidOverlapColIdx != -1)) { if (m_bitMaskList == null) { m_bitMaskList = new ArrayList<Triple<ImgPlusValue<BitType>, L, Double>>(); } final ImgPlusValue<BitType> imgVal = (ImgPlusValue<BitType>)cell; final long[] dims = imgVal.getDimensions(); if (m_maxDims == null) { m_maxDims = new long[dims.length]; } if (m_maxDims.length != dims.length) { LOGGER.warn("Bitmask in row " + row.getKey() + " cannot be added to the result."); return true; } else { final long[] min = new long[dims.length]; imgVal.getImgPlus().min(min); for (int i = 0; i < m_maxDims.length; i++) { m_maxDims[i] = Math.max(m_maxDims[i], min[i] + dims[i]); } Double order = null; if (avoidOverlapColIdx != -1) { order = ((DoubleValue)row.getCell(avoidOverlapColIdx)).getDoubleValue(); } m_bitMaskList.add(new Triple<ImgPlusValue<BitType>, L, Double>(imgVal, label, order)); } } else { final boolean success = addToLabeling((ImgPlusValue<BitType>)cell, label); if (!success) { setSkipMessage("Bitmask in row " + row.getKey() + " cannot be added to the result."); return true; } } return false; } /** * {@inheritDoc} */ @Override public void configure(final DataTableSpec spec) throws InvalidSettingsException { if ((m_smIntervalCol.getStringValue().length() > 0) && (spec.findColumnIndex(m_smIntervalCol.getStringValue()) < 0)) { throw new InvalidSettingsException("Cannot find interval column."); } if ((m_smLabelCol.getStringValue().length() > 0) && (spec.findColumnIndex(m_smLabelCol.getStringValue()) < 0)) { throw new InvalidSettingsException("Cannot find label column."); } if ((m_smAvoidOverlapCol.getStringValue().length() > 0) && (spec.findColumnIndex(m_smAvoidOverlapCol.getStringValue()) < 0)) { throw new InvalidSettingsException("Cannot find overlap order column."); } if (m_smIntervalCol.getStringValue().length() == 0) { LOGGER.warn("Creating a composed labeling with no selected interval may be less efficient and may not meet the requirement."); } } private void createDCs() { if (m_dcIntervalCol == null) { m_dcIntervalCol = new DialogComponentColumnNameSelection(createIntervalColumnModel(), "Interval (the size of the result labeling)", 0, false, true, IntervalValue.class); m_dcLabelCol = new DialogComponentColumnNameSelection(createLabelColumnModel(), "Label", 0, false, true, DataValue.class); m_dcLabelingType = new DialogComponentStringSelection(createLabelingTypeModel(), "Result labeling type", EnumUtils.getStringListFromName(NativeTypes.SHORTTYPE, NativeTypes.BITTYPE, NativeTypes.BYTETYPE, NativeTypes.INTTYPE, NativeTypes.UNSIGNEDSHORTTYPE, NativeTypes.UNSIGNEDINTTYPE, NativeTypes.UNSIGNEDBYTETYPE)); m_dcLabelingFactory = new DialogComponentStringSelection(createLabelingFactoryModel(), "Result labeling factory", EnumUtils.getStringListFromName(ImgFactoryTypes.ARRAY_IMG_FACTORY, ImgFactoryTypes.CELL_IMG_FACTORY, ImgFactoryTypes.NTREE_IMG_FACTORY, ImgFactoryTypes.PLANAR_IMG_FACTORY)); m_dcAvoidOverlapCol = new DialogComponentColumnNameSelection(createAvoidOverlapOrderColModel(), "Order to avoid segment overlap", 0, false, true, DoubleValue.class); } } /** * {@inheritDoc} */ @Override public AggregationOperator createInstance(final GlobalSettings globalSettings, final OperatorColumnSettings opColSettings) { return new LabelingComposeOperator(globalSettings, m_smIntervalCol.getStringValue(), m_smLabelCol.getStringValue(), m_smLabelingType.getStringValue(), m_smLabelingFactory.getStringValue(), m_smAvoidOverlapCol.getStringValue()); } /* * filters overlapping segments */ private void filterBitMaskList() { // sort bitmask list Collections.sort(m_bitMaskList, new Comparator<Triple<ImgPlusValue<BitType>, L, Double>>() { @Override public int compare(final Triple<ImgPlusValue<BitType>, L, Double> o1, final Triple<ImgPlusValue<BitType>, L, Double> o2) { return o2.getThird().compareTo(o1.getThird()); } }); for (int i = 0; i < m_bitMaskList.size(); i++) { if (m_bitMaskList.get(i) == null) { continue; } for (int j = i + 1; j < m_bitMaskList.size(); j++) { if (m_bitMaskList.get(j) != null) { if (overlap(m_bitMaskList.get(i).getFirst(), m_bitMaskList.get(j).getFirst())) { m_bitMaskList.set(j, null); } } } } } @Override public Collection<String> getAdditionalColumnNames() { final ArrayList<String> cols = new ArrayList<String>(); if (m_smIntervalCol.getStringValue().length() > 0) { cols.add(m_smIntervalCol.getStringValue()); } if (m_smLabelCol.getStringValue().length() > 0) { cols.add(m_smLabelCol.getStringValue()); } if (m_smAvoidOverlapCol.getStringValue().length() > 0) { cols.add(m_smAvoidOverlapCol.getStringValue()); } return cols; } /** * {@inheritDoc} */ @Override protected DataType getDataType(final DataType origType) { return LabelingCell.TYPE; } /** * {@inheritDoc} */ @Override public String getDescription() { return "Composes labelings from binary images."; } /** * {@inheritDoc} */ @Override protected DataCell getResultInternal() { if (m_smAvoidOverlapCol.getStringValue().length() != 0) { filterBitMaskList(); } if (m_intervalCol.length() == 0) { // compose result and return m_resultLab = new ImgLabeling<L, T>(m_factory.create(m_maxDims, m_resType)); final Triple<ImgPlusValue<BitType>, L, Double> triple = m_bitMaskList.get(0); final CalibratedAxis[] axes = new CalibratedAxis[triple.getFirst().getDimensions().length]; triple.getFirst().getMetadata().axes(axes); m_resultMetadata = new DefaultLabelingMetadata(new DefaultCalibratedSpace(axes), new DefaultNamed("Unknown"), new DefaultSourced("Unknown"), null); m_resAccess = m_resultLab.randomAccess(); m_labelGenerator.reset(); } if ((m_intervalCol.length() == 0) || (m_smAvoidOverlapCol.getStringValue().length() != 0)) { for (final Triple<ImgPlusValue<BitType>, L, Double> p : m_bitMaskList) { if (p != null) { addToLabeling(p.getFirst(), p.getSecond()); } } } // return the already composed result try { return getLabelingCellFactory().createCell(m_resultLab, m_resultMetadata); } catch (final IOException e) { throw new RuntimeException(e); } } @Override public Component getSettingsPanel() { createDCs(); final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.add(m_dcIntervalCol.getComponentPanel()); panel.add(m_dcLabelCol.getComponentPanel()); panel.add(m_dcLabelingType.getComponentPanel()); panel.add(m_dcLabelingFactory.getComponentPanel()); panel.add(m_dcAvoidOverlapCol.getComponentPanel()); return panel; } @Override public boolean hasOptionalSettings() { return true; } /** * {@inheritDoc} */ @Override public void loadSettingsFrom(final NodeSettingsRO settings, final DataTableSpec spec) throws NotConfigurableException { createDCs(); m_dcIntervalCol.loadSettingsFrom(settings, new DataTableSpec[]{spec}); m_dcLabelingFactory.loadSettingsFrom(settings, new DataTableSpec[]{spec}); m_dcLabelingType.loadSettingsFrom(settings, new DataTableSpec[]{spec}); m_dcLabelCol.loadSettingsFrom(settings, new DataTableSpec[]{spec}); m_dcAvoidOverlapCol.loadSettingsFrom(settings, new DataTableSpec[]{spec}); } /** * {@inheritDoc} */ @Override public void loadValidatedSettings(final NodeSettingsRO settings) throws InvalidSettingsException { m_smIntervalCol.loadSettingsFrom(settings); m_smLabelCol.loadSettingsFrom(settings); if (m_smIntervalCol.getStringValue() == null) { m_smIntervalCol.setStringValue(""); } if (m_smLabelCol.getStringValue() == null) { m_smLabelCol.setStringValue(""); } m_smLabelingFactory.loadSettingsFrom(settings); m_smLabelingType.loadSettingsFrom(settings); try { m_smAvoidOverlapCol.loadSettingsFrom(settings); } catch (final InvalidSettingsException e) { // since knip 1.0.3 m_smAvoidOverlapCol.setStringValue(""); } if (m_smAvoidOverlapCol.getStringValue() == null) { m_smAvoidOverlapCol.setStringValue(""); } } private boolean overlap(final ImgPlusValue<BitType> val1, final ImgPlusValue<BitType> val2) { final Img<BitType> img1 = val1.getImgPlus(); final Img<BitType> img2 = val2.getImgPlus(); final RandomAccessibleInterval<BitType> iv1 = img1; final RandomAccessibleInterval<BitType> iv2 = img2; final Interval intersect = Intervals.intersect(iv1, iv2); for (int i = 0; i < intersect.numDimensions(); i++) { if (intersect.dimension(i) <= 0) { return false; } } final RandomAccess<BitType> ra1 = iv1.randomAccess(); final RandomAccess<BitType> ra2 = iv2.randomAccess(); final IntervalIterator ii = new IntervalIterator(intersect); while (ii.hasNext()) { ii.fwd(); ra1.setPosition(ii); if (ra1.get().get()) { ra2.setPosition(ii); if (ra2.get().get()) { return true; } } } return false; } /** * {@inheritDoc} */ @Override protected void resetInternal() { // labeling creation fields m_resultLab = null; m_resultMetadata = null; m_resAccess = null; // "lazy" labeling creation if (m_bitMaskList != null) { m_bitMaskList.clear(); } m_maxDims = null; // default settings m_intervalCol = ""; m_labelCol = ""; m_labelGenerator.reset(); } @Override public void saveSettingsTo(final NodeSettingsWO settings) { if (m_dcIntervalCol != null) { try { m_dcIntervalCol.saveSettingsTo(settings); m_dcLabelingFactory.saveSettingsTo(settings); m_dcLabelingType.saveSettingsTo(settings); m_dcLabelCol.saveSettingsTo(settings); m_dcAvoidOverlapCol.saveSettingsTo(settings); } catch (final InvalidSettingsException e) { throw new RuntimeException(e.getMessage()); } } else { m_smIntervalCol.saveSettingsTo(settings); m_smLabelingFactory.saveSettingsTo(settings); m_smLabelingType.saveSettingsTo(settings); m_smLabelCol.saveSettingsTo(settings); m_smAvoidOverlapCol.saveSettingsTo(settings); } } /** * {@inheritDoc} */ @Override public void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException { m_smIntervalCol.validateSettings(settings); m_smLabelingFactory.validateSettings(settings); m_smLabelingType.validateSettings(settings); m_smLabelCol.validateSettings(settings); try { m_smAvoidOverlapCol.validateSettings(settings); } catch (final InvalidSettingsException e) { } } }