Rank operations over {@link Matrix n-dimensional matrices}: percentile, rank, mean between given percentiles or values, etc., calculated on all matrix elements in an aperture with the fixed shape, represented by {@link Pattern} class.It is supposed that the type of matrix elements is one of primitive Java types (boolean, char, byte, short, int, long, float, double) and, so, represents an integer or a real number, according to comments to {@link PFixedArray#getLong(long)} and {@link PArray#getDouble(long)} methods.In 2-dimensional case, these operations can be used for processing grayscale digital images.
The simplest rank operation is a percentile, for example, minimum, maximum or median. In the case of maximum, the percentile is strictly equivalent to dilation — the basic operation of the mathematical morphology, offered by {@link Morphology} interface.In the case of minimum, the percentile by some pattern P is equivalent to erosion by the symmetric pattern P. {@link Pattern#symmetric() symmetric()}. It allows to consider rank operations as an extension of the operation set of the traditional mathematical morphology. Really, this interface extends {@link Morphology} interface, and it is supposed, by definition, that {@link #dilation(Matrix,Pattern) dilation}(m,pattern) method is equivalent to {@link #percentile(Matrix,double,Pattern) percentile}(m,c*N,pattern) and {@link #erosion(Matrix,Pattern) erosion}(m,pattern) method is equivalent to {@link #percentile(Matrix,double,Pattern) percentile}(m,c*N,pattern. {@link Pattern#symmetric() symmetric()}), where N=pattern. {@link Pattern#pointCount() pointCount()}-1 and c is some constant, specified while instantiating this object (it is dilationLevel argument of {@link BasicRankMorphology#getInstance(ArrayContext,double,CustomRankPrecision) BasicRankMorphology.getInstance}method).
Below is the formal definition of 5 basic rank operations with the given pattern P and matrix M: percentile, rank, mean between 2 percentiles, mean between 2 ranks and aperture sum, calculated by this class.
- For any integer point, or position x = (x0, x1, ..., xn−1), n=M. {@link Matrix#dimCount() dimCount()}, the aperture of this point, or the aperture at the position x, is a set of points x−pi = (x0−pi0, x1−pi1, ..., xn−1−pi,n−1) for all pi∈P ( {@link Pattern#roundedPoints() points}of the pattern P). We always consider that the point x lies inside M matrix (0≤xk<M. {@link Matrix#dim(int) dim}(k) for all k), but this condition can be not true for points of the aperture x−pi.
- For every point x' = x−pi of the aperture we consider the corresponding value vi of the source matrix M. The precise definition of the value can depend on implementation of this interface. Usually, if the point lies inside the matrix (0≤xk−pi,k<M. {@link Matrix#dim(int) dim}(k) for all k), it is the value of the element (integer: {@link PFixedArray#getLong(long)}, if the type of the matrix elements is boolean, char, byte, short, int or long, or real: {@link PArray#getDouble(long)}, if the element type is float or double) of the underlying array M. {@link Matrix#array() array()} with an index M. {@link Matrix#index(long) index}(x'0, x'1, ..., x'n−1), where x'k = xk−pi,k. In particular, it is true for all implementations offered by this package. If the point x' = x−pi lies outside the matrix (x'k<0 or x'k≥M. {@link Matrix#dim(int) dim}(k) for some k), then:
- in the {@link BasicRankMorphology} implementation, vi is the value of the element( {@link PFixedArray#getLong(long)} for the fixed-point case or {@link PFloatingArray#getDouble(long)}for the floating-point case) of the underlying array M. {@link Matrix#array() array()} with an index M. {@link Matrix#pseudoCyclicIndex(long) pseudoCyclicIndex}(x'0, x'1, ..., x'n−1);
- in the {@link ContinuedRankMorphology} implementation, vi is thecalculated according to the continuation mode, passed to {@link ContinuedRankMorphology#getInstance(RankMorphology,Matrix.ContinuationMode)}method;
- other implementations may offer other ways for calculating vi.
- So, for every point x we have an aperture, consisting of N=P. {@link Pattern#pointCount() pointCount()} "neighbour" points x−pi, and a corresponding set of integer or real values vi, i=0,1,...,N−1. Then we consider a histogram built on the base of vi values — the histogram, corresponding to the point x. Namely, this histogram is an array of non-negative integer numbers b[w], 0≤w<M, where every element b[w] represents the number of occurrence of the value w in array A, consisting of the following N integer elements a0, a1, ..., xN−1:
- ai = min(M−1, ⌊max(0, vi) * σ⌋) (here and below ⌊y⌋ means the integer part of y or (long)y for non-negative numbers, M and σ are defined below); in other words, ai is an integer part of σvi, truncated to 0..M−1 range of allowed histogram indexes;
- the histogram length M is the global parameter of this object and is equal to some power of two: M=2μ, μ=0,1,2... There is a guarantee that if the matrix is fixed-point (M. {@link Matrix#elementType()} is boolean, char, byte, short, int or long), then μ≤β, where β is the number of bits per element: β = M. {@link Matrix#array() array()}. {@link PArray#bitsPerElement() bitsPerElement()}. If this object is an instance of {@link BasicRankMorphology}, then μ = bitLevels[bitLevels.length-1] for floating-point matrix elements (M. {@link Matrix#elementType()} is float or double) or μ = min(bitLevels[bitLevels.length-1], β) for fixed-point matrix elements, where bitLevels is an array of {@link CustomRankPrecision#bitLevels() bit levels}, specified while instantiating this class via the last argument of {@link BasicRankMorphology#getInstance(ArrayContext,double,CustomRankPrecision)} method;
- the real number σ ("scale") is equal to M for a floating-point matrix or equal to M/2β=2μ−β, β = M. {@link Matrix#array() array()}. {@link PArray#bitsPerElement() bitsPerElement()} for a fixed-point matrix. So, in the case of a fixed-point matrix there is a guarantee that 1/σ is a positive integer number (2β−μ).
In other words, the "standard" allowed range of element values 0.. {@link Arrays#maxPossibleValue(Class,double) Arrays.maxPossibleValue}(M. {@link Matrix#elementType() elementType()},1.0) is split into M=2μ histogram bars and all aperture values vi are distributed between these bars; elements, which are out of the allowed range, are distributed between the first and last bars. In the simplest case of byte elements, M is usually chosen to be 256; then σ=1.0 and ai = vi (because byte elements, in terms of AlgART libraries, cannot be out of 0..255 range).
- The histogram b[0], b[1], ..., b[M−1], specified above, is interpreted in terms of {@link Histogram} and {@link SummingHistogram} classes.Namely, we define the following rank characteristics:
- the percentile with the given real index r is v(r)/σ, where v(r) function is defined in comments to {@link Histogram} class;
- the rank of the given real value v is r(v*σ), where r(v) function is defined in comments to {@link Histogram} class;
- the mean between 2 given percentiles with the real indexes r1 and r2 is (S(r2)−S(r1)) / ((r2−r1)*σ) if r1<r2 or some reserved value filler if r1≥r2, where S(r) function is defined in comments to {@link SummingHistogram} class;
- the mean between 2 given values, the real numbers v1 and v2, is (s(v2*σ)−s(v1*σ)) / (r(v2*σ)−r(v1*σ))*σ) if v1<v2 and r(v1*σ)<r(v2*σ), where r(v) function is defined in comments to {@link Histogram} class ands(v) function is defined in comments to {@link SummingHistogram} class.If the conditions v1<v2 and r(v1*σ)<r(v2*σ) are not fulfilled, we use one of 4 following modes of calculation:
- if any of these two conditions is not fulfilled, it is equal to some reserved value filler;
- if any of these two conditions is not fulfilled, it is equal to v1;
- if any of these two conditions is not fulfilled, it is equal to v2;
- (most useful definition)
- if v1≥v2, it is equal to (v1+v2)/2;
- if v1<v2 and r(v2*σ)=r(v1*σ)=0, it is equal to v2 (see also {@link net.algart.arrays.SummingHistogram.CountOfValues#isLeftBound() CountOfValues.isLeftBound()});
- if v1<v2 and r(v2*σ)=r(v1*σ)=N, it is equal to v1 (see also {@link net.algart.arrays.SummingHistogram.CountOfValues#isRightBound() CountOfValues.isRightBound()});
- in other cases, it is equal to (v1+v2)/2.
Remember that r(v) is always a non-decreasing function, so, the sentence "the conditions v1<v2 and r(v1*σ)<r(v2*σ) are not fulfilled" means, that either v1≥v2, or v1<v2 and r(v1*σ)=r(v2*σ). The last situation usually occurs "outside" the histogram, when both v1 and v2 values are less than (or equal to) the minimal value in the aperture or greater than (or equal to) the maximum value in the aperture. However, while using the simple histogram model, such situation is also possible on a sparse histogram with many zero bars; - the aperture sum is just a usual sum of all values v0+v1+...+vN−1 — the only rank characteristic which does not use the histogram b[k]. (Of course, you can also calculate the mean of all values on the base of this sum: it is enough to divide the sum by N.)
Note 1: it is obvious that all rank characteristics, excepting the aperture sum, depend on the histogram model: simple or precise (see comments to {@link Histogram} and {@link SummingHistogram} classes).The used model is chosen while instantiating this class, usually via {@link CustomRankPrecision#interpolated()} flag in the argument of {@link CustomRankPrecision} class:true value ("interpolated") means the precise model, false means the simple one.
Note 2: for int, long, float and double element type of the source matrix M and for all characteristics, excepting the aperture sum, this definition supposes that all matrix elements lie in the "standard range": 0..Integer/Long.MAX_VALUE (all non-negative values) for integers or 0.0..1.0 for floating-point elements. If some matrix elements are negative, they are interpreted as 0; if some elements of a floating-point matrix are greater than 1.0, they are interpreted as 1.0. For floating-point case, the histogram length M actually specifies the precision of calculations: the source elements are represented with the precision 1/M. The aperture sum characteristic is an exception from this rule: aperture sums are calculated as usual sums of the source elements vi with double precision.
Note 3: for floating-point case, if the value of some element vi of the source matrix M inside the aperture is NaN (Float.NaN for float type, Double.NaN for double type), this situation does not lead to an exception, but the resulting values of all characteristics, listed above, are undocumented.
Note 4: unlike this, if some arguments of the characteristics, listed above — the real index r for the percentile, the real value v for the rank, the real indexes r1 and r2 for the mean between percentiles or the real values v1 and v2 for the mean between values — are NaN, then any attempt to calculate these characteristics by methods of this interface can lead to IllegalArgumentException.
This interface provides method for calculating the described 5 rank characteristics for every integer point x lying in the matrix M. The resulting characteristics are returned in the result matrix with the same dimensions as M. The necessary arguments — the real index r for the percentile, the real value v for the rank, the real indexes r1 and r2 for the mean between percentiles or the real values v1 and v2 for the mean between values — are retrieved from the corresponding element (with the index M. {@link Matrix#index(long) index}(x0, x1, ..., xn−1)) of the {@link Matrix#array() built-in array} of some additional matrix or pair of matrices,passed to the methods, by {@link PArray#getDouble(long)} method. It is supposed that those matriceshave the same dimensions as M. For the percentile and the mean between percentiles, there are simplified versions of the methods, which use the constant rank indexes instead of matrices of indexes.
Most of methods of this interface allow to return not only floating-point, but also integer result matrix. In this case, the real rank characteristics, defined above in the section 4, are rounded by some rules, that are specified in comments to the concrete methods.
For every rank characteristics this interface offers 3 methods for calculating it. Below are some comments about them.
- The first version is always called "asOperation", for example, {@link #asPercentile(Matrix,Matrix,Pattern) asPercentile}. This method returns an immutable view of the passed source matrix, such that any reading data from it calculates and returns the necessary rank characteristic of the source matrix with the specified pattern (aperture shape). The result of such method is usually "lazy", that means that this method finishes immediately and all actual calculations are performed while getting elements of the returned matrix. It is true for all implementations provided by this package. However, some implementations may not support lazy execution; then the method may be equivalent to the second version described below. Note that the sequential access to the lazy result, returned by this method (via {@link Array#getData(long,Object,int,int) Array.getData} method, called for the{@link Matrix#array() built-in array} of the returned matrix), usually works much fasterthan the random access to elements of the matrix.
- The second version is called "operation", for example, {@link #percentile(Matrix,Matrix,Pattern) percentile}. This method always returns actual (non-lazy) updatable result: Matrix<? extends UpdatablePArray>. This method can work essentially faster than an access to the lazy matrix returned by the first variant of the method (for example, than copying it into a new matrix). In implementations, offered by this package, there are no difference if the source matrix src (for rank operations, baseMatrix) is direct accessible: src. {@link Matrix#array() array()} instanceof {@link DirectAccessible} &&((DirectAccessible)src. {@link Matrix#array() array()}). {@link DirectAccessible#hasJavaArray() hasJavaArray()}. If the source matrix is not direct accessible, the implementations, offered by this package, use {@link net.algart.matrices.StreamingApertureProcessor} techniqueto accelerate processing. Calculating aperture sums is an exception: {@link #functionOfSum(Matrix,Pattern,Func) functionOfSum} method uses some optimization for some kinds ofpatterns and can work much faster than accessing to {@link #asFunctionOfSum(Matrix,Pattern,Func) asFunctionOfSum}result.
- The third version is also called "operation", but it is a void method: for example, {@link #percentile(Matrix,Matrix,Matrix,Pattern)}. The result matrix is passed via first argument named dest and supposed to be allocated before calling the method. This way allows to save memory and time, if you need to perform several rank operation, because you can use the same matrices for temporary results. In addition, these methods allow to choose the type of element of the resulting matrix for any operation. The precise rules of type conversions are described in comments to concrete methods.
This package provides the following basic methods for creating objects, implementing this interface:
- {@link BasicRankMorphology#getInstance(ArrayContext,double,CustomRankPrecision)};
- {@link ContinuedRankMorphology#getInstance(RankMorphology,Matrix.ContinuationMode)}.
Warning: all implementations of this interface, provided by this package, can process only patterns where {@link Pattern#pointCount() pointCount()}≤Integer.MAX_VALUE. More precisely, any methods of this interface (including methods, inherited from its superinterface {@link Morphology}), implemented by classes of this package, which have {@link Pattern} argument,can throw {@link net.algart.math.patterns.TooManyPointsInPatternError TooManyPointsInPatternError}or OutOfMemoryError in the same situations as {@link Pattern#points()} method.
Warning: the methods of this interface, which save results into the passed dest matrix (like {@link #percentile(Matrix,Matrix,Matrix,Pattern)}), as well as any attempts to read the "lazy" results (of the methods like {@link #asPercentile(Matrix,Matrix,Pattern)}), can be non-atomic regarding the failure, if the arguments of the calculated rank characteristics — the real index r for the percentile, the real value v for the rank, the real indexes r1 and r2 for the mean between percentiles or the real values v1 and v2 for the mean between values — are floating-point NaN values for some aperture positions. In this case, it is possible that the result will be partially filled, and only after this the NaN value will lead to IllegalArgumentException.
The floating-point calculations in the implementations of this interface are usually performed not in strictfp, but in the usual mode. So, there is no guarantee that the results are absolutely identical on all platforms. Moreover, there is no guarantee that the same results, got by different ways, are absolutely identical: little mismatches in the last digits after the decimal point are possible.
The classes, implementing this interface, are immutable and thread-safe: there are no ways to modify settings of the created instance.
AlgART Laboratory 2007–2014
@author Daniel Alievsky
@version 1.2
@since JDK 1.5