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

net.algart.matrices.skeletons.Quadruple3x5ThinningSkeleton2D 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.

The 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 static net.algart.matrices.skeletons.ThinningTools.*;

/**
 * 

Algorithm of 2-dimensional skeletonization of binary matrices based on 4 thinning steps, * corresponding to 4 directions with the step 90 degree, based on analysis of 3x5 aperture.

* *

It is a stronger version of {@link OctupleThinningSkeleton2D} skeletonization, * the standard variant without diagonal thinning: * {@link OctupleThinningSkeleton2D#getInstance(ArrayContext, Matrix, boolean, boolean) * OctupleThinningSkeleton2D.getInstance(MemoryModel, Matrix, false, false)}. * This class implements almost the same algorithm as {@link OctupleThinningSkeleton2D}, * but the thinning method is more aggressive (and slow): * it can remove pixels even with breaking connectivity in 3x3 pixel aperture, * if the connectivity in 3x5 aperture is not broken. * This algorithm also guarantees that 8-connected "objects" * (areas filled by 1 elements) always stay 8-connected: * see {@link ThinningSkeleton} interface about the precise sense of this state.

* *

The typical "bad" cases for {@link OctupleThinningSkeleton2D} are successfully * skeletonized by this algorithm in the following or similar way:

* *
 * . . . . 1 . . . .                     . . . . 1 . . . .
 * . . . 1 . 1 . . .                     . . . 1 . 1 . . .
 * . . 1 . 1 . 1 . .                     . . 1 . 1 . 1 . .
 * . 1 . 1 1 1 . 1 .                     . 1 . . 1 . . 1 .
 * 1 . 1 1 1 1 1 . 1  is transformed to  1 . 1 1 1 1 1 . 1
 * . 1 . 1 1 1 . 1 .                     . 1 . . 1 . . 1 .
 * . . 1 . 1 . 1 . .                     . . 1 . 1 . 1 . .
 * . . . 1 . 1 . . .                     . . . 1 . 1 . . .
 * . . . . 1 . . . .                     . . . . 1 . . . .
 * 
* *

Examples of the result in the "bad" cases, when some areas cannot be "thinned" by this algorithm:

* *
 * . . . . . . . .     . . . . . . .      . . . 1 . . .     . . . . . . . .     1 . . 1 . . 1 .
 * . . . . . . . .     . 1 . . 1 . .      . 1 . 1 . 1 .     1 . . 1 . . 1 .     . 1 . 1 . 1 . .
 * . . 1 . . 1 . .     . . 1 1 . . .      . . 1 1 1 . .     . 1 . 1 . 1 . .     . . 1 1 1 . . .
 * . . . 1 1 . . .     . . 1 1 1 1 .      . . . 1 . . .     . . 1 1 1 . . .     1 1 1 1 1 1 1 .
 * . . . 1 1 . . .     . 1 . 1 . . .      . . 1 1 1 . .     . . 1 1 1 . . .     . . 1 1 1 . . .
 * . . 1 . . 1 . .     . . . 1 . . .      . 1 . 1 . 1 .     . 1 . 1 . 1 . .     . 1 . 1 . 1 . .
 * . . . . . . . .     . . . . . . .      . . . 1 . . .     1 . . 1 . . 1 .     1 . . 1 . . 1 .
 * 
* *

It is obvious that the left configuration cannot be thinned more without breaking connectivity.

* *

I recommend to run this algorithm after finishing {@link OctupleThinningSkeleton2D} * and {@link WeakOctupleThinningSkeleton2D} algorithms * (for example, with help of {@link IterativeArrayProcessor#chain(IterativeArrayProcessor, double)} method).

* *

This class is based on {@link Matrices#asShifted Matrices.asShifted} method * with some elementwise logical operations (AND, OR, NOT). * So, the matrix is supposed to be infinitely pseudo-cyclically continued, as well * {@link Matrices#asShifted Matrices.asShifted} method supposes it. * You can change this behavior by appending the source matrix with zero elements * by calling {@link Matrix#subMatrix(long[], long[], Matrix.ContinuationMode)} method, * where the dimensions of the "submatrix" are greater than dimensions of the source one by 1 * and the continuationMode argument is {@link net.algart.arrays.Matrix.ContinuationMode#ZERO_CONSTANT}.

* *

This class may be applied to a matrix with any number of dimensions, * but it is designed for 2-dimensional case: all other dimensions will be ignored.

* *

This class is not thread-safe, but is thread-compatible * and can be synchronized manually, if multithreading access is necessary.

* * @author Daniel Alievsky * @see StrongQuadruple3x5ThinningSkeleton2D */ public class Quadruple3x5ThinningSkeleton2D extends AbstractThinningSkeleton2D implements ThinningSkeleton { static final boolean TOP_3X5_WEAKENING = false; // should be false static final boolean BOTTOM_3X5_WEAKENING = true; // should be true // - These flags must not be true together to avoid breaking connectivity. private static final int[][] SKELETON_XP = { {1, 0}, // 0: -A2 {-1, 0}, // 1: -C2 {1, 1}, // 2: -A1 {0, 1}, // 3: -B1 {-1, 1, 0, 2}, // 4: -C1, -B0 (for debugging needs only, if TOP_3X5_WEAKENING = true) {1, -1}, // 5: -A3 {0, -1}, // 6: -B3 {-1, -1, 0, -2}, // 7: -C3, -B4 {0, 1, 0, -1, -1, 1, -1, -1}, // 8: -B1,-B3,-C1,-C3 }; private static final int[][] SKELETON_YP = rotate90(SKELETON_XP); private static final int[][] SKELETON_XM = rotate180(SKELETON_XP); private static final int[][] SKELETON_YM = rotate270(SKELETON_XP); private static final int[][][] SKELETON_3x5 = { SKELETON_XP, SKELETON_YP, SKELETON_XM, SKELETON_YM }; private Quadruple3x5ThinningSkeleton2D(ArrayContext context, Matrix matrix) { super(context, matrix, true, false); } /** * Creates new instance of this class. * * @param context the {@link #context() context} that will be used by this object; * can be {@code null}, then it will be ignored. * @param matrix the bit matrix that should be processed and returned by {@link #result()} method. * @return new instance of this class. * @throws NullPointerException if matrix argument is {@code null}. */ public static Quadruple3x5ThinningSkeleton2D getInstance(ArrayContext context, Matrix matrix) { return new Quadruple3x5ThinningSkeleton2D(context, matrix); } @Override public long estimatedNumberOfIterations() { return ThinningTools.estimatedNumberOfIterations(result, false); } // WARNING: SUN BUG IN javadoc UTILITY (1.6.0_04, 1.7.0-ea)! // Below we cannot write "{@link #result()}" - it leads to ClassCastException in javadoc. /** * Returns current {@link IterativeArrayProcessor#result() result()} matrix thinned along the given direction. * The result is "lazy": it is only a view of the current matrix. * *

The precise algorithm of thinning is not documented. * Generally speaking, the "thinning" means removing elements * from the boundary of any "object" (area of the matrix filled by 1). * directionIndex specifies the "eroded side" of objects, * or the direction of thinning:

    *
  • 0 means removing elements from the left, i.e. from the side (x−1,y),
  • *
  • 2 means removing elements from the side (x,y−1),
  • *
  • 4 means removing elements from the right, i.e. from the side (x+1,y),
  • *
  • 6 means removing elements from the side (x,y+1).
  • *
* Odd values (1, 3, 5, 7) are ignored by this method: for these values, the reference to the current * {@link IterativeArrayProcessor#result() result()} matrix is returned. * *

Though the algorithm is not documented, there are the following guarantees: *

    *
  • this algorithm never sets zero elements to unit: if the element of the current matrix * with some coordinates (x0, y0) is 0, * then the element with the same coordinates in the returned matrix is also 0;
  • * *
  • each element of the returned matrix with coordinates * (x0, y0) * depends only on the elements in 3x5 or 5x3 aperture of the current matrix: *
      *
    • x0−1≤xx0+1, * y0−2≤yy0+2, * if directionIndex is 0 or 2,
    • *
    • x0−2≤xx0+2, * y0−1≤yy0+1, * if directionIndex is 1 or 3;
    • *
  • * *
  • moreover, the element of the returned matrix with coordinates * (x0, y0) does not depend on isolated * unit elements in the aperture, specified above, i.e. * if there is no 8-connected series of unit elements, connecting the central * (x0, y0) unit element with another unit element * (x, y) in this aperture, then the result value of the central element * (will it be cleared or no) does not depend on * (x, y) element of the current matrix. * For example, in cases directionIndex=0 and directionIndex=2, if the central element is 1 * and yy0, the (x, y) is isolated * in the following three situations: * a) y=y0+2 and all 3 elements with y=y0+1 are zero; * b) y=y0+2, x=x0+1 and 3 elements * (x0, y0+2), * (x0, y0+1), * (x0+1, y0+1) are zero; * c) y=y0+2, x=x0−1 and 3 elements * (x0, y0+2), * (x0, y0+1), * (x0−1, y0+1) are zero; *
  • * *
  • if all elements of the current matrix in 3x3 (not 3x5!) aperture * x0−1≤xx0+1, * y0−1≤yy0+1 * are inside the matrix (i.e. 1≤x0dimX−2, * 1≤y0dimY−2, * dimX and dimY are dimensions of the matrix) * and all they are equal to 1, then the element (x0, y0) * in the returned matrix will be equal to 1.
  • *
* * @param directionIndex the direction of thinning, from 0 to 7. * @return the thinned view if the current {@link #result()} matrix. * @throws IllegalArgumentException if directionIndex is not in 0..7 range. */ @Override public Matrix asThinning(int directionIndex) { if (directionIndex < 0 || directionIndex > 7) throw new IllegalArgumentException("Illegal directionIndex = " + directionIndex + " (must be 0..7)"); if (directionIndex % 2 == 0) { return asQuadrupleThinning(result, directionIndex / 2); } else { return result.cast(BitArray.class); } } /** * Returns a brief string description of this object. * * @return a brief string description of this object. */ @Override public String toString() { return "quadruple thinning 2D skeletonizer with 3x5 aperture"; } private static Matrix asQuadrupleThinning(Matrix matrix, int directionIndex) { int[][] points = SKELETON_3x5[directionIndex]; // Comments below suppose the case SKELETON_XP (directionIndex = 0) // We consider the 3x5 aperture of bit (0 or 1) elements: // (A0 B0 C0) // A1 B1 C1 // A2 1 C2 // A3 B3 C3 // A4 B4 C4 // The central element can be supposed to be 1, because the last operation of the method is "and" // (if the central element is 0, all other calculations have no effect). // We need to clear the central element to 0 in some "thinning" situation. Matrix internal = shift(matrix, points[0]); // -A2 Matrix internalOrThin = or(internal, not(shift(matrix, points[1]))); // -C2 // internalOrThin is 0 if ~A2 & C2 (it is a left boundary and not both A2 and C2 are 0): candidate for removal // If internalOrThin is 1, the central element must never be cleared. // From this moment, we may suppose that it is 0, i.e. A2=0 and C2=1: // if not, other calculations have no effect due to the last "or" operation. So, the aperture is: // A0 B0 C0 // A1 B1 C1 // 0 1 1 // A3 B3 C3 // A4 B4 C4 Matrix topArticulation = andNot(shift(matrix, points[2]), TOP_3X5_WEAKENING ? or(shift(matrix, points[3]), and(shifts(matrix, points[4]))) : shift(matrix, points[3])); // points[2] = -A1, points[3] = -B1, points[4] = {-C1, -B0} // TOP_3X5_WEAKENING=true: topArticulation = 1 if A1 & ~(B1 | (B0 & C1)): a local 3x5 articulation point // TOP_3X5_WEAKENING=false: topArticulation = 1 if A1 & ~B1: a local 3x3 articulation point Matrix bottomArticulation = andNot(shift(matrix, points[5]), BOTTOM_3X5_WEAKENING ? or(shift(matrix, points[6]), and(shifts(matrix, points[7]))) : shift(matrix, points[6])); // points[5] = -A3, points[6] = -B3, points[7] = {-C3, -B4} // BOTTOM_3X5_WEAKENING=true: bottomArticulation = 1 if A3 & ~(B3 | (B4 & C3)): a local 3x5 articulation point // BOTTOM_3X5_WEAKENING=false: bottomArticulation = 1 if A3 & ~B3: a local 3x3 articulation point Matrix dilation4Points = or(shifts(matrix, points[8])); // points[8] = {-B1,-B3,-C1,-C3} // dilation4Points is 0 if B1=B3=C1=C3=0: we have a "free end" of a horizontal line. // Note: such algorithm removes the central point in the situation // (1) . 1 . (1) // (1) 1 . 1 (1) // (1) . 1 1 (1) // (1) 1 . 1 (1) // (1) . 1 . (1) Matrix notRemoved = or(internalOrThin, topArticulation, bottomArticulation, not(dilation4Points)); return and(matrix, notRemoved); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy