/* * ------------------------------------------------------------------------ * * 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.nodes.view.segmentoverlay; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.border.BevelBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.knime.core.data.DataCell; import org.knime.core.node.NodeLogger; import org.knime.core.node.NodeView; import org.knime.core.node.property.hilite.HiLiteHandler; import org.knime.core.node.tableview.TableContentView; import org.knime.core.node.tableview.TableView; import org.knime.knip.base.data.img.ImgPlusValue; import org.knime.knip.base.data.labeling.LabelingValue; import org.knime.knip.base.nodes.view.PlainCellView; import org.knime.knip.base.nodes.view.segmentoverlay.SegmentOverlayNodeModel.LabelTransformVariables; import org.knime.knip.core.data.img.DefaultLabelingMetadata; import org.knime.knip.core.data.img.LabelingMetadata; import org.knime.knip.core.ui.event.EventListener; import org.knime.knip.core.ui.event.EventService; import org.knime.knip.core.ui.imgviewer.ExpandingPanel; import org.knime.knip.core.ui.imgviewer.ImgCanvas; import org.knime.knip.core.ui.imgviewer.ImgViewer; import org.knime.knip.core.ui.imgviewer.ViewerComponents; import org.knime.knip.core.ui.imgviewer.events.ImgAndLabelingChgEvent; import org.knime.knip.core.ui.imgviewer.events.ImgRedrawEvent; import org.knime.knip.core.ui.imgviewer.events.LabelingWithMetadataChgEvent; import org.knime.knip.core.ui.imgviewer.events.TableOverviewDisableEvent; import org.knime.knip.core.ui.imgviewer.events.TablePositionEvent; import org.knime.knip.core.ui.imgviewer.events.ViewClosedEvent; import org.knime.knip.core.ui.imgviewer.panels.LabelFilterPanel; import org.knime.knip.core.ui.imgviewer.panels.RendererSelectionPanel; import org.knime.knip.core.ui.imgviewer.panels.TransparencyColorSelectionPanel; import org.knime.knip.core.ui.imgviewer.panels.ViewerControlEvent; import org.knime.knip.core.ui.imgviewer.panels.ViewerScrollEvent; import org.knime.knip.core.ui.imgviewer.panels.ViewerScrollEvent.Direction; import org.knime.knip.core.ui.imgviewer.panels.infobars.ImgLabelingViewInfoPanel; import org.knime.knip.core.ui.imgviewer.panels.providers.AWTImageProvider; import org.knime.knip.core.ui.imgviewer.panels.providers.CombinedRU; import org.knime.knip.core.ui.imgviewer.panels.providers.ImageRU; import org.knime.knip.core.ui.imgviewer.panels.providers.LabelingRU; import org.knime.knip.core.util.MiscViews; import net.imagej.ImgPlus; import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.Converter; import net.imglib2.converter.Converters; import net.imglib2.img.Img; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelingMapping; import net.imglib2.roi.labeling.LabelingType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.ByteType; import net.imglib2.util.ConstantUtils; import net.imglib2.util.Intervals; import net.imglib2.util.Util; /** * * @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 SegmentOverlayNodeView<T extends RealType<T>, L extends Comparable<L>, I extends IntegerType<I>> extends NodeView<SegmentOverlayNodeModel<T, L>> implements ListSelectionListener { /* A node logger */ static NodeLogger LOGGER = NodeLogger.getLogger(SegmentOverlayNodeView.class); private LabelHiliteProvider<L, T> m_hiliteProvider; /* Image cell view pane */ private ImgViewer m_imgView = null; private EventService m_eventService; /* Current row */ private int m_row; /* Table for the images */ private TableContentView m_tableContentView; /* The Table view */ private TableView m_tableView; private final ExecutorService UPDATE_EXECUTOR = Executors.newCachedThreadPool(new ThreadFactory() { private final AtomicInteger m_counter = new AtomicInteger(); @Override public Thread newThread(final Runnable r) { final Thread t = new Thread(r, "Segment Overlay Viewer-Updater-" + m_counter.incrementAndGet()); t.setDaemon(true); return t; } }); private PlainCellView m_cellView; private Container m_tableViewPanel; private JLabel m_statusLabel; private static final String DEFAULT_STATUS_BAR = "Click on a cell or drag and select multiple cells to continue ..."; /** * Constructor * * @param model */ public SegmentOverlayNodeView(final SegmentOverlayNodeModel<T, L> model) { super(model); m_row = -1; initTableView(); m_eventService = new EventService(); m_eventService.subscribe(this); setComponent(m_tableViewPanel); loadPortContent(); } private void loadPortContent() { m_tableContentView.setModel(getNodeModel().getTableContentModel()); // Scale to thumbnail size m_tableView.validate(); m_tableView.repaint(); } /*Initializes the table view (left side of the split pane)*/ private void initTableView() { m_tableContentView = new TableContentView(); m_tableContentView.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); m_tableContentView.getSelectionModel().addListSelectionListener(this); m_tableContentView.getColumnModel().getSelectionModel().addListSelectionListener(this); m_tableView = new TableView(m_tableContentView); m_tableViewPanel = new JPanel(new BorderLayout()); m_tableViewPanel.add(m_tableView, BorderLayout.CENTER); JPanel statusBar = new JPanel(); statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED)); m_tableViewPanel.add(statusBar, BorderLayout.SOUTH); statusBar.setPreferredSize(new Dimension(m_tableViewPanel.getWidth(), 16)); statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.X_AXIS)); m_statusLabel = new JLabel(DEFAULT_STATUS_BAR); m_statusLabel.setHorizontalAlignment(SwingConstants.LEFT); statusBar.add(m_statusLabel); } /* Initializes the img view (right side of the split pane)*/ private void initImgView() { m_imgView = new ImgViewer(); AWTImageProvider prov = new AWTImageProvider(20, new CombinedRU(new ImageRU<T>(true), new LabelingRU<L>())); prov.setEventService(m_imgView.getEventService()); m_imgView.addViewerComponent(prov); m_imgView.addViewerComponent(new ImgLabelingViewInfoPanel<T, L>()); m_imgView.addViewerComponent(new ImgCanvas<T, Img<T>>()); m_imgView.addViewerComponent(ViewerComponents.MINIMAP_PLANE_SELECTION.createInstance()); m_imgView.addViewerComponent(new ExpandingPanel("Brightness and Contrast", ViewerComponents.IMAGE_ENHANCE.createInstance(), true)); m_imgView.addViewerComponent(new ExpandingPanel("Renderer Selection", new RendererSelectionPanel<T>(), true)); m_imgView.addViewerComponent(new ExpandingPanel("Transparency", new TransparencyColorSelectionPanel(), true)); m_imgView.addViewerComponent( new ExpandingPanel("Label Filter", new LabelFilterPanel<L>(getNodeModel() .getInternalTables().length > SegmentOverlayNodeModel.PORT_SEG), true)); m_imgView.doneAdding(); if (getNodeModel().getInternalTables().length > SegmentOverlayNodeModel.PORT_SEG) { m_hiliteProvider = new LabelHiliteProvider<L, T>(); m_imgView.addViewerComponent(m_hiliteProvider); getNodeModel(); final HiLiteHandler handler = getNodeModel().getInHiLiteHandler(SegmentOverlayNodeModel.PORT_SEG); m_hiliteProvider.updateInHandler(handler); } else { m_hiliteProvider = null; } } /** * * {@inheritDoc} */ @Override protected void modelChanged() { m_tableContentView.setModel(getNodeModel().getTableContentModel()); m_imgView = null; //TODO // m_sp.setRightComponent(new JPanel()); if (m_hiliteProvider != null) { getNodeModel(); final HiLiteHandler handler = getNodeModel().getInHiLiteHandler(SegmentOverlayNodeModel.PORT_SEG); m_hiliteProvider.updateInHandler(handler); } m_row = -1; } /** * * {@inheritDoc} */ @Override protected void onClose() { UPDATE_EXECUTOR.shutdownNow(); if (m_hiliteProvider != null) { m_hiliteProvider.onClose(); } m_tableView.removeAll(); m_hiliteProvider = null; m_tableContentView.removeAll(); if (m_imgView != null) { m_imgView.getEventService().publish(new ViewClosedEvent()); m_imgView.removeAll(); } m_imgView = null; m_tableContentView = null; m_tableView = null; m_row = -1; } /** * * {@inheritDoc} */ @Override protected void onOpen() { // Scale to thumbnail size m_tableView.validate(); m_tableView.repaint(); initializeListeners(); } @SuppressWarnings({"unchecked", "rawtypes"}) private void rowChanged(final int row) { //if the imgView isn't visible yet, add it to the spit pane -> happens when a cell is selected the first time if (m_imgView == null) { initImgView(); m_cellView = new PlainCellView(m_tableView, m_imgView); m_cellView.setEventService(m_eventService); } m_row = row; try { boolean labelingOnly = (m_tableContentView.getModel().getColumnCount() == 1); RandomAccessibleInterval<T> underlyingInterval; String imgName = ""; String imgSource = ""; LabelingValue<L> currentLabelingCell; if (labelingOnly) { currentLabelingCell = (LabelingValue<L>)m_tableContentView.getContentModel() .getValueAt(row, SegmentOverlayNodeModel.COL_IDX_SINGLE_LABELING); final T max = (T)new ByteType(); max.setReal(max.getMaxValue()); underlyingInterval = ConstantUtils .constantRandomAccessibleInterval(max, currentLabelingCell.getDimensions().length, new FinalInterval(currentLabelingCell.getLabeling())); } else { currentLabelingCell = (LabelingValue<L>)m_tableContentView.getContentModel() .getValueAt(row, SegmentOverlayNodeModel.COL_IDX_LABELING); // Set image final DataCell currentImgCell = m_tableContentView.getContentModel().getValueAt(row, SegmentOverlayNodeModel.COL_IDX_IMAGE); final ImgPlus<T> imgPlus = ((ImgPlusValue<T>)currentImgCell).getImgPlus(); imgName = imgPlus.getName(); imgSource = imgPlus.getSource(); underlyingInterval = imgPlus; } // Update Labeling Mapping for Hiliting final RandomAccessibleInterval<LabelingType<L>> labeling = currentLabelingCell.getLabeling(); LabelingMetadata labelingMetadata = currentLabelingCell.getLabelingMetadata(); final Map<String, Object> transformationInputMap = new HashMap<String, Object>(); transformationInputMap.put(LabelTransformVariables.LabelingName.toString(), labelingMetadata.getName()); transformationInputMap.put(LabelTransformVariables.LabelingSource.toString(), labelingMetadata.getSource()); transformationInputMap.put(LabelTransformVariables.ImgName.toString(), imgName); transformationInputMap.put(LabelTransformVariables.ImgSource.toString(), imgSource); transformationInputMap.put(LabelTransformVariables.RowID.toString(), m_tableContentView.getContentModel().getRowKey(row)); RandomAccessibleInterval<LabelingType<String>> displayedLabeling = null; if (labeling instanceof ImgLabeling) { displayedLabeling = new ImgLabeling<>(((ImgLabeling)labeling).getIndexImg()); new LabelingMappingAccess(Util.getTypeFromInterval(displayedLabeling).getMapping(), Util.getTypeFromInterval(labeling).getMapping(), transformationInputMap, getNodeModel()); } else { final LabelingType<String> type = (LabelingType<String>)Util.getTypeFromInterval(labeling).createVariable(); // access and set new LabelingMappingAccess(type.getMapping(), Util.getTypeFromInterval(labeling).getMapping(), transformationInputMap, getNodeModel()); new LabelingMappingAccess(Util.getTypeFromInterval(displayedLabeling).getMapping(), Util.getTypeFromInterval(labeling).getMapping(), transformationInputMap, getNodeModel()); // read only converter displayedLabeling = Converters.convert(labeling, new Converter<LabelingType<L>, LabelingType<String>>() { @Override public void convert(final LabelingType<L> arg0, final LabelingType<String> arg1) { arg1.clear(); for (final L label : arg0) { transformationInputMap.put(LabelTransformVariables.Label.toString(), label.toString()); arg1.add(getNodeModel().getTransformer().transform(transformationInputMap)); } } }, type); } // TODO here copy source labeling into new labeling if (!Intervals.equalDimensions(underlyingInterval, displayedLabeling)) { displayedLabeling = MiscViews.synchronizeDimensionality(displayedLabeling, currentLabelingCell.getLabelingMetadata(), underlyingInterval, (ImgPlus<T>)underlyingInterval); labelingMetadata = new DefaultLabelingMetadata((ImgPlus<T>)underlyingInterval, labelingMetadata, labelingMetadata, labelingMetadata.getLabelingColorTable()); } m_imgView.getEventService().publish(new ImgAndLabelingChgEvent<T, String>(underlyingInterval, displayedLabeling, labelingMetadata, labelingMetadata, labelingMetadata)); m_imgView.getEventService() .publish(new LabelingWithMetadataChgEvent<String>(displayedLabeling, labelingMetadata)); m_imgView.getEventService().publish(new ImgRedrawEvent()); m_eventService.publish(new TableOverviewDisableEvent(false, true)); m_eventService.publish(new TablePositionEvent(-1, m_tableContentView.getRowCount(), -1, m_row + 1, "", m_tableContentView.getContentModel().getRowKey(m_row).toString())); if (getComponent() == m_tableViewPanel) { setComponent(m_cellView); } } catch (final IndexOutOfBoundsException e2) { return; } } /** * Updates the ViewPane with the selected image and labeling * * * {@inheritDoc} */ @Override public void valueChanged(final ListSelectionEvent e) { final int row = m_tableContentView.getSelectionModel().getLeadSelectionIndex(); if ((row == m_row) || e.getValueIsAdjusting()) { if (getComponent() == m_tableViewPanel && m_cellView != null) { setComponent(m_cellView); } return; } rowChanged(row); } /** * Creates the Listeners used in the displayed views. */ private void initializeListeners() { } @EventListener public void onViewerScrollEvent(final ViewerScrollEvent e) { if (e.getDirection() == Direction.NORTH) { int r = m_row - 1; rowChanged(r); } if (e.getDirection() == Direction.SOUTH) { int r = m_row + 1; rowChanged(r); } } @EventListener public void onViewerOverviewToggle(final ViewerControlEvent e) { if (getComponent() == m_cellView) { if (m_cellView.isTableViewVisible()) { m_cellView.hideTableView(); } m_tableContentView.clearSelection(); setComponent(m_tableViewPanel); } else { // Should not happen. } } static class LabelingMappingAccess extends LabelingMapping.SerialisationAccess<String> { private LabelingMapping<String> mapping; /** * @param mapping */ protected LabelingMappingAccess(final LabelingMapping<String> newMapping, final LabelingMapping<?> oldMapping, final Map<String, Object> map, final SegmentOverlayNodeModel model) { super(newMapping); final List<Set<String>> newLabels = new ArrayList<>(); for (int i = 0; i < oldMapping.numSets(); i++) { final HashSet<String> labels = new HashSet<>(); for (Object o : oldMapping.labelsAtIndex(i)) { map.put(LabelTransformVariables.Label.toString(), o.toString()); labels.add(model.getTransformer().transform(map)); } newLabels.add(labels); } super.setLabelSets(newLabels); } } }