/* * ------------------------------------------------------------------------ * * 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. * --------------------------------------------------------------------- * * Created on 07.11.2013 by Daniel */ package org.knime.knip.base.nodes.proc.thinning; import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.ops.operation.UnaryOperation; import net.imglib2.type.logic.BitType; import net.imglib2.view.Views; import org.knime.knip.base.nodes.proc.thinning.strategies.ThinningStrategy; /** * Thinning Operation * * @author Andreas Burger, University of Konstanz */ public class ThinningOp implements UnaryOperation<RandomAccessibleInterval<BitType>, RandomAccessibleInterval<BitType>> { private boolean m_foreground = true; private boolean m_background = false; private ThinningStrategy m_strategy; private ImgFactory<BitType> m_factory; /** * Instantiate a new ThinningOp using the given strategy and considering the given boolean value as foreground. * * @param strategy thinning strategy to use * @param foreground Boolean value of foreground pixels. * @param factory for temporary result image */ public ThinningOp(final ThinningStrategy strategy, final boolean foreground, final ImgFactory<BitType> factory) { m_strategy = strategy; m_foreground = foreground; m_background = !foreground; m_factory = factory; } /** * {@inheritDoc} */ @Override public RandomAccessibleInterval<BitType> compute(final RandomAccessibleInterval<BitType> input, final RandomAccessibleInterval<BitType> output) { // Create a new image as a buffer to store the thinning image in each iteration. // This image and output are swapped each iteration since we need to work on the image // without changing it. final Img<BitType> buffer = m_factory.create(input, new BitType()); final IterableInterval<BitType> it1 = Views.iterable(buffer); final IterableInterval<BitType> it2 = Views.iterable(output); // Extend the buffer in order to be able to iterate care-free later. final RandomAccessible<BitType> ra1 = Views.extendBorder(buffer); final RandomAccessible<BitType> ra2 = Views.extendBorder(output); RandomAccessible<BitType> currRa = Views.extendBorder(input); // Used only in first iteration. // Create cursors. final Cursor<BitType> firstCursor = it1.localizingCursor(); Cursor<BitType> currentCursor = Views.iterable(input).localizingCursor(); final Cursor<BitType> secondCursor = it2.localizingCursor(); // Create pointers to the current and next cursor and set them to Buffer and output respectively. Cursor<BitType> nextCursor; nextCursor = secondCursor; // The main loop. boolean changes = true; int i = 0; // Until no more changes, do: while (changes) { changes = false; // This For-Loop makes sure, that iterations only end on full cycles (as defined by the strategies). for (int j = 0; j < m_strategy.getIterationsPerCycle(); ++j) { // For each pixel in the image. while (currentCursor.hasNext()) { // Move both cursors currentCursor.fwd(); nextCursor.fwd(); // Get the position of the current cursor. long[] coordinates = new long[currentCursor.numDimensions()]; currentCursor.localize(coordinates); // Copy the value of the image currently operated upon. boolean curr = currentCursor.get().get(); nextCursor.get().set(curr); // Only foreground pixels may be thinned if (curr == m_foreground) { // Ask the strategy whether to flip the foreground pixel or not. boolean flip = m_strategy.removePixel(coordinates, currRa); // If yes - change and keep track of the change. if (flip) { nextCursor.get().set(m_background); changes = true; } } } // One step of the cycle is finished, notify the strategy. m_strategy.afterCycle(); // Reset the cursors to the beginning and assign pointers for the next iteration. currentCursor.reset(); nextCursor.reset(); // Keep track of the most recent image. Needed for output. if (currRa == ra2) { currRa = ra1; currentCursor = firstCursor; nextCursor = secondCursor; } else { currRa = ra2; currentCursor = secondCursor; nextCursor = firstCursor; } // Keep track of iterations. ++i; } } // Depending on the iteration count, the final image is either in ra1 or ra2. Copy it to output. if (i % 2 == 0) { //Ra1 points to img1, ra2 points to output. copy(buffer, output); } return output; } /** * {@inheritDoc} */ @Override public UnaryOperation<RandomAccessibleInterval<BitType>, RandomAccessibleInterval<BitType>> copy() { return new ThinningOp(m_strategy.copy(), m_foreground, m_factory); } private void copy(final RandomAccessibleInterval<BitType> source, final RandomAccessibleInterval<BitType> target) { IterableInterval<BitType> targetIt = Views.iterable(target); IterableInterval<BitType> sourceIt = Views.iterable(source); if (sourceIt.iterationOrder().equals(targetIt.iterationOrder())) { Cursor<BitType> targetCursor = targetIt.cursor(); Cursor<BitType> sourceCursor = sourceIt.cursor(); while (sourceCursor.hasNext()) { targetCursor.fwd(); sourceCursor.fwd(); targetCursor.get().set(sourceCursor.get().get()); } } else { // Fallback to random access RandomAccess<BitType> targetRA = target.randomAccess(); Cursor<BitType> sourceCursor = sourceIt.localizingCursor(); while (sourceCursor.hasNext()) { sourceCursor.fwd(); targetRA.setPosition(sourceCursor); targetRA.get().set(sourceCursor.get().get()); } } } }