@@ -1826,3 +1826,126 @@ def ppi(X, niters, threshold=0, centered=False, start=None, display=0,
18261826 spy ._status .end_percentage ()
18271827
18281828 return counts .reshape (shape [:2 ])
1829+
1830+
1831+ def smacc (spectra , min_endmembers = None , max_residual_norm = float ('Inf' )):
1832+ '''Returns SMACC decomposition (H = F * S + R) matrices for an image or
1833+ array of spectra.
1834+
1835+ Let `H` be matrix of shape NxB, where B is number of bands, and N number of
1836+ spectra, then if `spectra` is of the same shape, `H` will be equal to `spectra`.
1837+ Otherwise, `spectra` is assumed to be 3D spectral image, and it is reshaped
1838+ to match shape of `H`.
1839+
1840+ Arguments:
1841+
1842+ `spectra` (ndarray):
1843+
1844+ Image data for which to calculate SMACC decomposition matrices.
1845+
1846+ `min_endmembers` (int):
1847+
1848+ Minimal number of endmembers to find. Defaults to rank of `H`,
1849+ computed numerically with `numpy.linalg.matrix_rank`.
1850+
1851+ `max_residual_norm`:
1852+
1853+ Maximum value of residual vectors' norms. Algorithm will keep finding
1854+ new endmembers until max value of residual norms is less than this
1855+ argument. Defaults to float('Inf')
1856+
1857+ Returns:
1858+ 3 matrices, S, F and R, such that H = F * S + R (but it might not always hold).
1859+ F is matrix of expansion coefficients of shape N x num_endmembers.
1860+ All values of F are equal to, or greater than zero.
1861+ S is matrix of endmember spectra, extreme vectors, of shape num_endmembers x B.
1862+ R is matrix of residuals of same shape as H (N x B).
1863+
1864+ If values of H are large (few tousands), H = F * S + R, might not hold,
1865+ because of numeric errors. It is advisable to scale numbers, by dividing
1866+ by 10000, for example. Depending on how accurate you want it to be,
1867+ you can check if H is really strictly equal to F * S + R,
1868+ and adjust R: R = H - np.matmul(F, S).
1869+
1870+ References:
1871+
1872+ John H. Gruninger, Anthony J. Ratkowski, and Michael L. Hoke "The sequential
1873+ maximum angle convex cone (SMACC) endmember model", Proc. SPIE 5425, Algorithms
1874+ and Technologies for Multispectral, Hyperspectral, and Ultraspectral Imagery X,
1875+ (12 August 2004); https://doi.org/10.1117/12.543794
1876+ '''
1877+ # Indices of vectors in S.
1878+ q = []
1879+
1880+ H = spectra if len (spectra .shape ) == 2 else spectra .reshape (
1881+ (spectra .shape [0 ] * spectra .shape [1 ], spectra .shape [2 ]))
1882+ R = H
1883+ Fs = []
1884+
1885+ F = None
1886+ S = None
1887+
1888+ if min_endmembers is None :
1889+ min_endmembers = np .linalg .matrix_rank (H )
1890+
1891+ # Add the longest vector to q.
1892+ residual_norms = np .sqrt (np .einsum ('ij,ij->i' , H , H ))
1893+ current_max_residual_norm = np .max (residual_norms )
1894+
1895+ if max_residual_norm is None :
1896+ max_residual_norm = current_max_residual_norm / min_endmembers
1897+
1898+ while len (q ) < min_endmembers or current_max_residual_norm > max_residual_norm :
1899+ q .append (np .argmax (residual_norms ))
1900+ n = len (q ) - 1
1901+ # Current basis vector.
1902+ w = R [q [n ]]
1903+ # Temporary to be used for projection calculation.
1904+ wt = w / (np .dot (w , w ))
1905+ # Calculate projection coefficients.
1906+ On = np .dot (R , wt )
1907+ alpha = np .ones (On .shape , dtype = np .float64 )
1908+ # Make corrections to satisfy convex cone conditions.
1909+ # First correct alphas for oblique projection when needed.
1910+ for k in range (len (Fs )):
1911+ t = On * Fs [k ][q [n ]]
1912+ # This is not so important for the algorithm itself.
1913+ # These values correpond to values where On == 0.0, and these
1914+ # will be zeroed out below. But to avoid divide-by-zero warning
1915+ # we set small values instead of zero.
1916+ t [t == 0.0 ] = 1e-10
1917+ np .minimum (Fs [k ]/ t , alpha , out = alpha )
1918+ # Clip negative projection coefficients.
1919+ alpha [On <= 0.0 ] = 0.0
1920+ # Current extreme vector should always be removed completely.
1921+ alpha [q [n ]] = 1.0
1922+ # Calculate oblique projection coefficients.
1923+ Fn = alpha * On
1924+ # Correction for numerical stability.
1925+ Fn [Fn <= 0.0 ] = 0.0
1926+ # Remove projection to current basis from R.
1927+ R = R - np .outer (Fn , w )
1928+ # Update projection coefficients.
1929+ for k in range (len (Fs )):
1930+ Fs [k ] -= Fs [k ][q [n ]] * Fn
1931+ # Correction because of numerical problems.
1932+ Fs [k ][Fs [k ] <= 0.0 ] = 0.0
1933+
1934+ # Add new Fn.
1935+ Fs .append (Fn )
1936+
1937+ residual_norms [:] = np .sqrt (np .einsum ('ij,ij->i' , R , R ))
1938+ current_max_residual_norm = np .max (residual_norms )
1939+ print ('Found {0} endmembers, current max residual norm is {1:.4f}\r '
1940+ .format (len (q ), current_max_residual_norm ), end = '' )
1941+
1942+ # Correction as suggested in the SMACC paper.
1943+ for k , s in enumerate (q ):
1944+ Fs [k ][q ] = 0.0
1945+ Fs [k ][s ] = 1.0
1946+
1947+ F = np .array (Fs ).T
1948+ S = H [q ]
1949+
1950+ # H = F * S + R
1951+ return S , F , R
0 commit comments