All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.matrices.skeletons.ApertureBasedSkeletonPixelClassifier Maven / Gradle / Ivy

Go to download

Open-source Java libraries, supporting generalized smart arrays and matrices with elements of any types, including a wide set of 2D-, 3D- and multidimensional image processing and other algorithms, working with arrays and matrices.

There is a newer version: 1.4.23
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.matrices.skeletons;

import net.algart.arrays.*;
import net.algart.math.functions.AbstractFunc;
import net.algart.math.functions.Func;
import net.algart.math.functions.LinearFunc;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 

A skeletal implementation of the {@link SkeletonPixelClassifier} abstract class, * minimizing the effort required to implement its abstract methods.

* *

Namely, the main {@link #asPixelTypes asPixelTypes} method is implemented in this class * via the following 2 abstract methods:

* *
    *
  • {@link #pixelTypeOrAttachingBranch(int)},
  • *
  • {@link #pixelTypeOrAttachedNode(int)}.
  • *
* *

The methods {@link #neighbourOffset(long[], int)} and {@link #reverseNeighbourIndex(int)} * are implemented on the base of the array of offsets of all neighbours of a random element. * This array should be passed to the {@link #ApertureBasedSkeletonPixelClassifier(int, long[][]) constructor}. * The constructor analyses the passed array, checks that it really contains * offsets of all neighbours, copies this array into an internal field, which is used * by {@link #neighbourOffset(long[], int)}, and also automatically finds, * for every neighbour, the reverse neighbour index, which will be returned by {@link #reverseNeighbourIndex(int)}. * *

The method {@link #markNeighbouringNodesNotConnectedViaDegeneratedBranches(int[])} is implemented here * in some reasonable way for 2-dimensional case, as specified in comments to this method. * For other number of dimensions, this method does nothing. * It is a good solution for the degenerated case {@link #dimCount() dimCount()}=1; * for 3-dimensional case, this method probably should be overridden.

* *

So, it is enough to implement {@link #pixelTypeOrAttachingBranch(int)} and {@link #pixelTypeOrAttachedNode(int)} * methods and, maybe, override {@link #markNeighbouringNodesNotConnectedViaDegeneratedBranches(int[])} method * to create a full implementation of the skeleton pixel classifier on the base of this class.

* *

This class can be used in 1-, 2- and 3-dimensional cases only. * One of the reasons of this restriction is that the argument of {@link #pixelTypeOrAttachingBranch(int)} * and {@link #pixelTypeOrAttachedNode(int)} (int type) can represent the values of, maximally, 32 * neighbours. It is enough for 3-dimensional case, where the number of neighbours is 33−1=26<32, * but not enough already for 4-dimensional case, where the number of neighbours is 34−1=80 * (and even 64-bit long type would have been insufficient).

* *

This class is immutable and thread-safe: * there are no ways to modify settings of the created instance.

* * @author Daniel Alievsky */ public abstract class ApertureBasedSkeletonPixelClassifier extends SkeletonPixelClassifier { private final long[][] neighbourOffsets; private final int[] reverseNeighbourIndexes; /** * Creates new instance of this class, allowing to process skeletons with the given number of dimensions, * with the order of neighbours, specified in the second argument. * The number of dimensions must be 1, 2 or 3. * *

The argument neighbourOffsets must contain offsets of all neighbours, * in terms of the {@link net.algart.matrices.scanning.ConnectivityType#STRAIGHT_AND_DIAGONAL * straight-and-diagonal connectivity kind}, of any matrix element, in some order. * More precisely, this array must contain * {@link #numberOfNeighbours() numberOfNeighbours()}=3dimCount-1 elements * (2, 8 or 26 for 1-, 2-, 3-dimensional cases) in 3/3x3/3x3x3-aperture, and each its element * neighbourOffsets[k] must be equal to the result of * {@link #neighbourOffset(int) neighbourOffset(k)} call. * *

The passed neighbourOffsets array is deeply cloned by the constructor: no references to it * or its elements are maintained by the created object. * * @param dimCount the number of dimensions, which will be returned by * {@link #dimCount() dimCount()} method. * @param neighbourOffsets offsets of all neighbours of any matrix element, * in terms of {@link #neighbourOffset(int) neighbourOffset(int)} method. * @throws NullPointerException if neighbourOffsets or one of its elements is {@code null}. * @throws IllegalArgumentException if dimCount is not in 1..3 range, * or if neighbourOffsets.length!=3dimCount-1, * or if neighbourOffsets[k].length!=dimCount * for some k, * or if neighbourOffsets does not contain, in some order, * the offsets of all 3dimCount-1 neighbours * (in particular, if some elements neighbourOffsets[k][j] are * not in -1..+1 range or if offsets of some neighbours are equal). */ protected ApertureBasedSkeletonPixelClassifier(int dimCount, long[][] neighbourOffsets) { super(dimCount); Objects.requireNonNull(neighbourOffsets, "Null neighbourOffsets array"); if (dimCount > 3) { throw new IllegalArgumentException("This class " + getClass().getName() + " cannot process " + dimCount + "-dimensional apertures (maximum 3-dimensional ones are allowed)"); } if (this.numberOfNeighbours != neighbourOffsets.length) { throw new IllegalArgumentException("Number of passed neighbour offsets " + neighbourOffsets.length + " does not match the number of neighbours in 3x3x... aperture " + this.numberOfNeighbours); } if (this.numberOfNeighbours > 30) // bit #31 can be used for the central element of the aperture { throw new AssertionError("This class " + getClass().getName() + " cannot process more than 30 elements in the aperture (besides the central element)"); } // We cannot use full 63-bit precision here, because double values cannot precisely store all long values this.neighbourOffsets = new long[neighbourOffsets.length][dimCount]; for (int k = 0; k < neighbourOffsets.length; k++) { long[] neighbourOffset = neighbourOffsets[k]; // creating a copy: necessary if another thread is modifying the argument now Objects.requireNonNull(neighbourOffset, "Null neighbourOffsets[" + k + "]"); if (neighbourOffset.length != dimCount) { throw new IllegalArgumentException("Illegal neighbourOffsets[" + k + "].length = " + neighbourOffset.length + ": does not match to the number of dimensions " + dimCount); } System.arraycopy(neighbourOffset, 0, this.neighbourOffsets[k], 0, dimCount); } // now this.neighbourOffsets is a deep copy of the argument, which cannot be destroyed by another thread this.reverseNeighbourIndexes = new int[this.neighbourOffsets.length]; for (int k = 0; k < this.neighbourOffsets.length; k++) { int reverseIndex = -1; boolean allZero = true; for (int j = 0; j < dimCount; j++) { if (Math.abs(this.neighbourOffsets[k][j]) > 1) { throw new IllegalArgumentException("Illegal neighbourOffsets: the offset #" + k + " (" + JArrays.toString(this.neighbourOffsets[k], ",", 1000) + " describes not a neighbour, because some of its components is not in -1..1 range"); } allZero &= this.neighbourOffsets[k][j] == 0; } if (allZero) { throw new IllegalArgumentException("Illegal neighbourOffsets: the offset #" + k + " is zero"); } for (int i = 0; i < this.neighbourOffsets.length; i++) { if (i == k) { continue; } boolean matchThis = true; for (int j = 0; j < dimCount; j++) { matchThis &= this.neighbourOffsets[i][j] == this.neighbourOffsets[k][j]; } if (matchThis) { throw new IllegalArgumentException("Illegal neighbourOffsets: the offsets #" + k + " and # " + i + " are equal"); } boolean matchNegative = true; for (int j = 0; j < dimCount; j++) { matchNegative &= this.neighbourOffsets[i][j] == -this.neighbourOffsets[k][j]; } if (matchNegative) { reverseIndex = i; break; } } if (reverseIndex == -1) { throw new IllegalArgumentException("Illegal neighbourOffsets: the offset #" + k + " (" + JArrays.toString(this.neighbourOffsets[k], ",", 1000) + ") has no corresponding reverse offset (the same but with negative sign)"); } this.reverseNeighbourIndexes[k] = reverseIndex; } // We've checked 3^dimCount-1 offsets and all they are different non-zero vectors with -1..1 components, // so, we can be sure that they are really the offsets of all elements of 3x3x... aperture, in some order. } @Override public void neighbourOffset(long[] coordinateIncrements, int neighbourIndex) { Objects.requireNonNull(coordinateIncrements, "Null list of coordinates"); if (coordinateIncrements.length != dimCount) { throw new IllegalArgumentException("Number of coordinates " + coordinateIncrements.length + " is not equal to the number of matrix dimensions " + dimCount()); } if (neighbourIndex < 0 || neighbourIndex >= numberOfNeighbours) { throw new IndexOutOfBoundsException("Illegal neighbourIndex = " + neighbourIndex + ": must be in 0.." + (numberOfNeighbours - 1) + " range"); } System.arraycopy(neighbourOffsets[neighbourIndex], 0, coordinateIncrements, 0, dimCount); } @Override public int reverseNeighbourIndex(int neighbourIndex) { return reverseNeighbourIndexes[neighbourIndex]; } @Override public Matrix asPixelTypes( Matrix skeleton, AttachmentInformation attachmentInformation) { Objects.requireNonNull(attachmentInformation, "Null attachmentInformation"); Matrix packed = asNeighbourhoodBitMaps(skeleton); switch (attachmentInformation) { case NEIGHBOUR_INDEX_OF_ATTACHING_BRANCH: return Matrices.asFuncMatrix(false, new AbstractFunc() { @Override public double get(double... x) { return get(x[0]); } @Override public double get(double x0) { int apertureBits = (int) x0; // precise operations, because x0 is "int" 31-bit value if ((apertureBits & 1) == 0) { return TYPE_ZERO; } return pixelTypeOrAttachingBranch(apertureBits >>> 1); } }, IntArray.class, packed); case NEIGHBOUR_INDEX_OF_ATTACHED_NODE: return Matrices.asFuncMatrix(false, new AbstractFunc() { @Override public double get(double... x) { return get(x[0]); } @Override public double get(double x0) { int apertureBits = (int) x0; // precise operations, because x0 is "int" 31-bit value if ((apertureBits & 1) == 0) { return TYPE_ZERO; } return pixelTypeOrAttachedNode(apertureBits >>> 1); } }, IntArray.class, packed); default: throw new AssertionError("Unknown attachmentInformation: " + attachmentInformation); } } @Override public void markNeighbouringNodesNotConnectedViaDegeneratedBranches(int[] pixelTypesOfAllNeighbours) { if (pixelTypesOfAllNeighbours.length < numberOfNeighbours) { throw new IllegalArgumentException("Too short pixelTypesOfAllNeighbours array"); } if (dimCount != 2) { return; // should be overridden for another number of dimensions } for (int neighbourIndex = 0; neighbourIndex < numberOfNeighbours; neighbourIndex++) { if (neighbourIndex % 2 == 0) { // diagonal degenerated branch if (pixelTypesOfAllNeighbours[(neighbourIndex + 1) & 7] == TYPE_USUAL_NODE || pixelTypesOfAllNeighbours[(neighbourIndex + 7) & 7] == TYPE_USUAL_NODE) { pixelTypesOfAllNeighbours[neighbourIndex] = Integer.MIN_VALUE; } } } } /** * Returns an immutable view of the passed skeleton matrix, where each element is an integer, * containing, in its low bits, the bit values of the corresponding element * C of the source skeleton and of all its neighbours (in terms of the * {@link net.algart.matrices.scanning.ConnectivityType#STRAIGHT_AND_DIAGONAL * straight-and-diagonal connectivity kind}). * *

More precisely, each integer element w of the resulting matrix will contain: *

    *
  • in the bit #0 (in other words, w&1): * the value of the corresponding element * C of the source skeleton bit matrix;
  • *
  • in the bit #k+1, 0≤k<{@link #numberOfNeighbours() numberOfNeighbours()} * (in other words, (w>>>(k+1))&1): * the value of the neighbour #k of the central element C, * in terms of {@link #neighbourOffset(int) neighbourOffset(int)} method;
  • *
  • all other bits of the elements if the resulting matrix will be zero.
  • *
* *

In particular, in {@link BasicSkeletonPixelClassifier2D} implementation, * the lower 9 bits in the elements of the returned matrix correspond to elements of 3x3 aperture * of the source skeleton according the following diagram: *

     * 1 2 3
     * 8 0 4
     * 7 6 5
*

(the x-axis is directed rightward, the y-axis is directed downward).

* *

The implementation of {@link #asPixelTypes asPixelTypes} method in this class is based on * this method and {@link #pixelTypeOrAttachingBranch(int)} and {@link #pixelTypeOrAttachedNode(int)} methods: * the results w, returned by this method for unit central elements C * of the source skeleton, are shifted to the right and passed as * apertureBits=w>>>1 argument to * {@link #pixelTypeOrAttachingBranch(int)} or {@link #pixelTypeOrAttachedNode(int)} to form the elements * of the resulting matrix. * *

Note, that the situation, when the neighbouring elements are out of ranges of the matrix coordinates, * is processed according to the model of infinite pseudo-cyclical continuation — * see the end of the {@link SkeletonPixelClassifier comments to SkeletonPixelClassifier}. * * @param skeleton the skeleton matrix that should be processed. * @return the matrix of integer values with the same sizes, containing the bit maps * of the neighbourhoods of all skeleton pixels. * @throws NullPointerException if skeleton is {@code null}. * @throws IllegalArgumentException if skeleton.dimCount()!={@link #dimCount()}. */ public final Matrix asNeighbourhoodBitMaps( Matrix skeleton) { Objects.requireNonNull(skeleton, "Null skeleton"); if (skeleton.dimCount() != dimCount) { throw new IllegalArgumentException("This object (" + this + ") can process " + dimCount + "-dimensional matrices only"); } List> shifted = new ArrayList>(); shifted.add(skeleton); long[] shift = new long[dimCount]; for (long[] apertureOffset : this.neighbourOffsets) { for (int k = 0; k < dimCount; k++) { shift[k] = -apertureOffset[k]; } shifted.add(Matrices.asShifted(skeleton, shift).cast(PArray.class)); } double[] weights = new double[shifted.size()]; assert weights.length <= 31; for (int k = 0; k < weights.length; k++) { weights[k] = 1L << k; } Func packingShiftedBits = LinearFunc.getInstance(0.0, weights); return Matrices.asFuncMatrix(packingShiftedBits, IntArray.class, shifted); } /*Repeat() NEIGHBOUR_INDEX_OF_ATTACHING_BRANCH ==> NEIGHBOUR_INDEX_OF_ATTACHED_NODE;; OrAttachingBranch ==> OrAttachedNode */ /** * Calculates and returns the value of an element C' * in the resulting matrix, produced by * {@link #asPixelTypes asPixelTypes} method with * {@link SkeletonPixelClassifier.AttachmentInformation#NEIGHBOUR_INDEX_OF_ATTACHING_BRANCH * NEIGHBOUR_INDEX_OF_ATTACHING_BRANCH} value of attachmentInformation argument, * on the base of bit values of all neighbours (in terms of the * {@link net.algart.matrices.scanning.ConnectivityType#STRAIGHT_AND_DIAGONAL * straight-and-diagonal connectivity kind}) * of the corresponding unit element C in the source skeleton bit matrix. * *

More precisely, the bit values of the neighbours of this skeleton element C * are passed via the low * m={@link #numberOfNeighbours() numberOfNeighbours()} bits of apertureBits argument. * The bit #k of this argument, 0≤k<m (its value is * (apertureBits>>>k)&1), is equal to the value * of the neighbour #k in terms of {@link #neighbourOffset(int) neighbourOffset(int)} method. * In particular, in {@link BasicSkeletonPixelClassifier2D} implementation, * the order of neighbours is described by the following diagram: *

     * 0 1 2
     * 7 C 3
     * 6 5 4
*

So, 8 low bits of apertureBits contain the values of the corresponding neighbouring elements * in anticlockwise order (the x-axis is directed rightward, the y-axis is directed downward). * *

It is supposed that the central element (C) of the skeleton is unit * (for zero elements ot the skeleton matrix, {@link #asPixelTypes asPixelTypes} method of * this class returns {@link #TYPE_ZERO} without calling this method). * *

Note, that the situation, when the neighbouring elements are out of ranges of the matrix coordinates, * is processed according to the model of infinite pseudo-cyclical continuation — * see the end of the {@link SkeletonPixelClassifier comments to SkeletonPixelClassifier}. * * @param apertureBits the values of all 8 neighbours of the current unit element of the source skeleton * bit matrix. * @return the type of this pixel of the skeleton. */ protected abstract int pixelTypeOrAttachingBranch(int apertureBits); /*Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! */ /** * Calculates and returns the value of an element C' * in the resulting matrix, produced by * {@link #asPixelTypes asPixelTypes} method with * {@link SkeletonPixelClassifier.AttachmentInformation#NEIGHBOUR_INDEX_OF_ATTACHED_NODE * NEIGHBOUR_INDEX_OF_ATTACHED_NODE} value of attachmentInformation argument, * on the base of bit values of all neighbours (in terms of the * {@link net.algart.matrices.scanning.ConnectivityType#STRAIGHT_AND_DIAGONAL * straight-and-diagonal connectivity kind}) * of the corresponding unit element C in the source skeleton bit matrix. * *

More precisely, the bit values of the neighbours of this skeleton element C * are passed via the low * m={@link #numberOfNeighbours() numberOfNeighbours()} bits of apertureBits argument. * The bit #k of this argument, 0≤k<m (its value is * (apertureBits>>>k)&1), is equal to the value * of the neighbour #k in terms of {@link #neighbourOffset(int) neighbourOffset(int)} method. * In particular, in {@link BasicSkeletonPixelClassifier2D} implementation, * the order of neighbours is described by the following diagram: *

     * 0 1 2
     * 7 C 3
     * 6 5 4
*

So, 8 low bits of apertureBits contain the values of the corresponding neighbouring elements * in anticlockwise order (the x-axis is directed rightward, the y-axis is directed downward). * *

It is supposed that the central element (C) of the skeleton is unit * (for zero elements ot the skeleton matrix, {@link #asPixelTypes asPixelTypes} method of * this class returns {@link #TYPE_ZERO} without calling this method). * *

Note, that the situation, when the neighbouring elements are out of ranges of the matrix coordinates, * is processed according to the model of infinite pseudo-cyclical continuation — * see the end of the {@link SkeletonPixelClassifier comments to SkeletonPixelClassifier}. * * @param apertureBits the values of all 8 neighbours of the current unit element of the source skeleton * bit matrix. * @return the type of this pixel of the skeleton. */ protected abstract int pixelTypeOrAttachedNode(int apertureBits); /*Repeat.AutoGeneratedEnd*/ }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy