/* * Copyright (C) 2005-2019 Centre National d'Etudes Spatiales (CNES) * * This file is part of Orfeo Toolbox * * https://www.orfeo-toolbox.org/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef otbSFSTexturesFunctor_h #define otbSFSTexturesFunctor_h #include "otbMath.h" #include "itkNumericTraits.h" #include namespace otb { /** \class SFSTexturesFunctor * \brief This functor computes textures based on line direction analysis through the central pixel. * * Directions are computed using NumberOfDirection, used to compute a constant step angle. * A direction is defined as : \f$ \mathit{d_{i} = \sqrt{(m^{e1}-m{e2})^{2}+(n^{e1}-n{e2})^{2}}} \f$ * From \f$ \mathit{d_{i}} \f$, histograms are defined : * \f$ \mathit{H(c) : \{c \in I \mid \lbrack d_{1}(c), \ldots , d_{i}(c), \ldots , d_{D}(c)\rbrack \}} \f$ * Thus, 6 textures are defined : * \f$ \mathit{length = \max_{i \in \lbrack1; D\rbrack}(d_{i}(c)} \f$ * \f$ \mathit{width = \min_{i \in \lbrack1; D\rbrack}(d_{i}(c)} \f$ * \f$ \mathit{PSI = \frac{1}{D}\sum_{1=1}^{D}d_{i}(c)} \f$ * \f$ \mathit{\omega-mean = \frac{1}{D}\sum_{1=1}^{D}\frac{\alpha.(k_{i}-1)}{st_{i}}d_{i}(c)} \f$ * \f$ \mathit{ratio = \arctan{\frac{\sum_{j=1}^{n}{sort_{min}^{j}(H(c))}}{\sum_{j=1}^{n}{sort_{max}^{j}(H(c))}}}} \f$ * \f$ \mathit{SD = \frac{1}{D-1}\sqrt{\sum_{1=1}^{D}(d_{i}(c)-PSI)^{2}}} \f$ * * For more details, please refer to refer to Xin Huang, Liangpei Zhang and Pingxiang Li publication, * Classification and Extraction of Spatial Features in Urban Areas * Using High-Resolution Multispectral Imagery. * IEEE Geoscience and Remote Sensing Letters, * vol. 4, n. 2, 2007, pp 260-264 * * \ingroup Textures * * \ingroup OTBTextures */ namespace Functor { template class SFSTexturesFunctor { public: SFSTexturesFunctor() { m_SpatialThreshold = 100; m_SpectralThreshold = 50; m_RatioMaxConsiderationNumber = 5; m_Alpha = 1; this->SetNumberOfDirections(20); // set the step too m_SelectedTextures = std::vector(6, 1); } virtual ~SFSTexturesFunctor() { } typedef typename TIter::InternalPixelType InternalPixelType; typedef typename TIter::SizeType SizeType; typedef typename TIter::IndexType IndexType; typedef typename TIter::OffsetType OffsetType; typedef TOutputValue OutputValueType; typedef std::vector OutputType; void SetSpatialThreshold(unsigned int thresh) { m_SpatialThreshold = thresh; } void SetSpectralThreshold(InternalPixelType thresh) { m_SpectralThreshold = thresh; } void SetRatioMaxConsiderationNumber(unsigned int value) { m_RatioMaxConsiderationNumber = value; } void SetAlpha(double alpha) { m_Alpha = alpha; } void SetNumberOfDirections(unsigned int D) { m_NumberOfDirections = D; m_DirectionStep = CONST_PI / static_cast(D); } void SetDirectionStep(double step) { m_DirectionStep = step; } void SetSelectedTextures(std::vector vect) { m_SelectedTextures.clear(); m_SelectedTextures = vect; } void SetTextureStatus(unsigned int id, bool isSelected) { m_SelectedTextures[id] = isSelected; } // Define some getters using itkGetMacro itkGetMacro(SpatialThreshold, unsigned int); itkGetMacro(SpectralThreshold, InternalPixelType); itkGetMacro(RatioMaxConsiderationNumber, unsigned int); itkGetMacro(Alpha, double); itkGetMacro(NumberOfDirections, unsigned int); std::vector GetTexturesStatus() { return m_SelectedTextures; } inline OutputType operator()(const TIter& it) { double length = itk::NumericTraits::NonpositiveMin(); double width = itk::NumericTraits::max(); double SpatialThresholdDouble = static_cast(m_SpatialThreshold); double NumberOfDirectionsDouble = static_cast(m_NumberOfDirections); double dist = 0.; double angle = 0.; double sdiVal = 0.; double sumWMean = 0.; double sum = 0.; std::vector di(m_NumberOfDirections, 0.); std::vector minSorted(m_RatioMaxConsiderationNumber, width); std::vector maxSorted(m_RatioMaxConsiderationNumber, length); std::vector sti(m_NumberOfDirections, 0.); std::vector lengthLine(m_NumberOfDirections, 0); std::vector::iterator itVector; OutputType out(6, 0); OffsetType off; off.Fill(0); for (unsigned int d = 0; d < m_NumberOfDirections; d++) { // Current angle direction angle = m_DirectionStep * static_cast(d); // last offset in the direction respecting spatial threshold off[0] = static_cast(std::floor(SpatialThresholdDouble * std::cos(angle) + 0.5)); off[1] = static_cast(std::floor(SpatialThresholdDouble * std::sin(angle) + 0.5)); // last indices in the direction respecting spectral threshold OffsetType offEnd = this->FindLastOffset(it, off); // Check the opposite side off[0] *= -1.0; off[1] *= -1.0; OffsetType offStart = this->FindLastOffset(it, off); // computes distance = dist between the 2 segment point. dist = std::sqrt(std::pow(static_cast(offEnd[0] - offStart[0]), 2) + std::pow(static_cast(offEnd[1] - offStart[1]), 2)); // for length computation if (m_SelectedTextures[0] == true) if (dist > length) length = dist; // for width computation if (m_SelectedTextures[1] == true) if (dist < width) width = dist; // for PSI computation if (m_SelectedTextures[2] == true || m_SelectedTextures[5] == true) sum += dist; // for w-mean computation if (m_SelectedTextures[3] == true) sdiVal = this->ComputeSDi(it, offEnd); // for Ratio computation if (m_SelectedTextures[4] == true) { bool doo = false; itVector = maxSorted.begin(); while (itVector != maxSorted.end() && doo == false) { if (dist > (*itVector)) { itVector = maxSorted.insert(itVector, dist); maxSorted.pop_back(); doo = true; } ++itVector; } doo = false; itVector = minSorted.begin(); while (itVector != minSorted.end() && doo == false) { if (dist < (*itVector)) { itVector = minSorted.insert(itVector, dist); minSorted.pop_back(); doo = true; } ++itVector; } } di[d] = dist; if (m_SelectedTextures[3] == true) { lengthLine[d] = static_cast( dist); // static_cast( std::sqrt(std::pow(static_cast(offEnd[0]), 2) + std::pow(static_cast(offEnd[1]), 2)) ); sti[d] = sdiVal; if (sdiVal != 0.) sumWMean += (m_Alpha * (dist - 1) * dist /*lengthLine[n]*di[n]*/) / sdiVal; } } /////// FILL OUTPUT // length if (m_SelectedTextures[0] == true) out[0] = static_cast(length); // width if (m_SelectedTextures[1] == true) out[1] = static_cast(width); // PSI if (m_SelectedTextures[2] == true) out[2] = static_cast(sum / NumberOfDirectionsDouble); // w-mean if (m_SelectedTextures[3] == true) out[3] = static_cast(sumWMean / NumberOfDirectionsDouble); // ratio if (m_SelectedTextures[4] == true) { double sumMin = 0; double sumMax = 0; for (unsigned int t = 0; t < m_RatioMaxConsiderationNumber; t++) { sumMin += minSorted[t]; sumMax += maxSorted[t]; } if (sumMax != 0.) out[4] = static_cast(std::atan(sumMin / sumMax)); else if (sumMax == 0. && sumMin == 0.) out[4] = static_cast(1.); } // SD if (m_SelectedTextures[5] == true) { double sumPSI = 0; for (unsigned int n = 0; n < di.size(); ++n) sumPSI += std::pow(di[n] - sumWMean / NumberOfDirectionsDouble, 2); out[5] = static_cast(std::sqrt(sumPSI) / (NumberOfDirectionsDouble - 1.)); } return out; } /** Checks spectral threshold condition * the last point in the directiuon is the first that doesn't * respect the spectral condition. */ OffsetType FindLastOffset(const TIter& it, const OffsetType& stopOffset) { bool res = true; int signX = this->ComputeStep(stopOffset[0]); int signY = this->ComputeStep(stopOffset[1]); OffsetType currentOff; currentOff.Fill(0); currentOff[0] = signX; double slop = 0.; if (stopOffset[0] != 0) slop = static_cast(stopOffset[1] / static_cast(stopOffset[0])); bool isInside = true; while (isInside == true && res == true) { this->ComputePointLine(currentOff, slop, signY, stopOffset[0]); if (std::abs(it.GetPixel(currentOff) - it.GetCenterPixel()) > m_SpectralThreshold) { res = false; } else currentOff[0] += signX; isInside = this->CheckIsInside(signX, signY, currentOff, stopOffset); } return currentOff; } /** Computes SD in the ith direction */ double ComputeSDi(const TIter& it, const OffsetType& stopOffset) { bool canGo = true; unsigned int nbElt = 0; double SDi = 0.; double mean = 0.; double slop = 0.; if (stopOffset[0] != 0) slop = static_cast(stopOffset[1] / static_cast(stopOffset[0])); int signX = this->ComputeStep(stopOffset[0]); int signY = this->ComputeStep(stopOffset[1]); OffsetType currentOff; currentOff.Fill(0); currentOff[0] = signX; bool isInside = true; // First compute mean while (isInside == true && canGo == true) { this->ComputePointLine(currentOff, slop, signY, stopOffset[0]); mean += static_cast(it.GetPixel(currentOff)); nbElt++; if (std::abs(it.GetPixel(currentOff) - it.GetCenterPixel()) >= m_SpectralThreshold) canGo = false; else currentOff[0] += signX; isInside = this->CheckIsInside(signX, signY, currentOff, stopOffset); } mean /= static_cast(nbElt); currentOff[0] = signX; currentOff[1] = 0; isInside = true; while (isInside == true && canGo == true) { this->ComputePointLine(currentOff, slop, signY, stopOffset[0]); SDi += std::pow((static_cast(it.GetPixel(currentOff)) - mean), 2); if (std::abs(it.GetPixel(currentOff) - it.GetCenterPixel()) >= m_SpectralThreshold) canGo = false; else currentOff[0] += signX; isInside = this->CheckIsInside(signX, signY, currentOff, stopOffset); } return std::sqrt(SDi); } /** Check if the current offset is inside the stop one. */ bool CheckIsInside(const int& signX, const int& signY, const OffsetType& currentOff, const OffsetType& stopOffset) { bool isInside = true; if (signX * currentOff[0] >= signX * stopOffset[0] && stopOffset[0] != 0) isInside = false; else if (signY * currentOff[1] >= signY * stopOffset[1] && stopOffset[1] != 0) isInside = false; return isInside; } /** Compute the y coordinate according to a given x coordinate. * Use the Bresenham algo if the line is not horizontal (stopOffsetX==0). * Otherwise, it increments of 1 Y. */ void ComputePointLine(OffsetType& currentOff, const double& slop, const int& signY, const int& stopOffsetX) { if (stopOffsetX != 0) currentOff[1] = static_cast(std::floor(slop * static_cast(currentOff[0]) + 0.5)); else currentOff[1] += signY; } /** Compute the to give at x for line computation. * according to the signof stopOffset. */ int ComputeStep(const int& stopOffset) { int sign = 1; if (stopOffset < 0) sign = -1; return sign; } protected: /** spectral threshold conditon*/ InternalPixelType m_SpectralThreshold; /** spatial threshold condition */ unsigned int m_SpatialThreshold; /** Max nulber of min and considered for Ration computation */ unsigned int m_RatioMaxConsiderationNumber; /** constant to adjust w-mean values */ double m_Alpha; /** Number of direction considered */ unsigned int m_NumberOfDirections; /** Angular step between 2 directions (between and 2*pi). */ double m_DirectionStep; /** List of wanted textures : * 0: length * 1: width * 2: PSI * 3: w-mean * 4: ratio * 5: SD * Set to 1 means the texture will be computed. **/ std::vector m_SelectedTextures; }; } // end namespace functor } // end namespace otb #endif