From d8fb22f8f3b10fe85522f9d3f5e02653f61b970d Mon Sep 17 00:00:00 2001
From: Austin Sanders <arsanders@usgs.gov>
Date: Tue, 19 Nov 2024 10:07:38 -0700
Subject: [PATCH] Add majority replacement option for Reduce (#5670)

* Added option for majority replacement

* Updated changelog
---
 CHANGELOG.md                         |  1 +
 isis/src/base/apps/reduce/reduce.xml |  7 ++++++
 isis/src/base/objs/Reduce/Reduce.cpp | 33 +++++++++++++++++++++++++++-
 isis/src/base/objs/Reduce/Reduce.h   |  2 +-
 4 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 490f3f7f14..44965ad94f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ release.
 - Added Vectorize to ProcessGroundPolygon library
 - Added gtest files for the app and unit test 
 - Added Chandrayaan2 template for isisimport
+- Added majority replacement for reduce app [#5101](https://github.com/DOI-USGS/ISIS3/issues/5101).
 - Added HRSC support in socetlinescankeywords [#5465](https://github.com/DOI-USGS/ISIS3/issues/5465)
 
 ### Changed
diff --git a/isis/src/base/apps/reduce/reduce.xml b/isis/src/base/apps/reduce/reduce.xml
index e53f5845c0..65eb9ff79e 100644
--- a/isis/src/base/apps/reduce/reduce.xml
+++ b/isis/src/base/apps/reduce/reduce.xml
@@ -334,6 +334,13 @@
 	      is closest to the center of the window being analyzed.
     	    </description>
     	  </option>
+        <option value="MAJORITY">
+          <brief>Majority replacement</brief>
+          <description>
+            When VALIDPER is not met, propagate the most commonly occurring special pixel
+            within the input region to the output pixel.
+          </description>
+        </option>
     	</list>
       </parameter>
     </group>
diff --git a/isis/src/base/objs/Reduce/Reduce.cpp b/isis/src/base/objs/Reduce/Reduce.cpp
index f6210eb7f2..7c1723bd2b 100644
--- a/isis/src/base/objs/Reduce/Reduce.cpp
+++ b/isis/src/base/objs/Reduce/Reduce.cpp
@@ -171,9 +171,11 @@ namespace Isis {
    */
   void Average::operator() (Isis::Buffer & out) const
   {
+    // Index of the last input line that corresponds to this output line.
     double rline = (double)out.Line() * mdLineScale;
 
     if(out.Line() == 1 && out.Band() == 1) {
+      // Index of the last input sample that corresponds to the output sample.
       mdIncTab = new double[miOutputSamples];
       mdSum    = new double[miOutputSamples];
       mdNpts   = new double[miOutputSamples];
@@ -191,7 +193,9 @@ namespace Isis {
       mdIncTab[miOutputSamples-1] = miInputSamples;
     }
 
-    while(mdLine <= rline) {
+    unordered_map<QString, int> specialPixelCounts[miOutputSamples];
+
+    while(mdLine < rline) {
       if((int)mdLine <= miInputLines) {
         m_iPortal->SetPosition(miStartSample, mdLine, miBandIndex);
         mInCube->read(*m_iPortal);
@@ -204,6 +208,9 @@ namespace Isis {
             mdSum[osamp]  += (*m_iPortal)[isamp-1];
             mdNpts[osamp] += 1.0;
           }
+          if (IsSpecial((*m_iPortal)[isamp-1])) {
+            specialPixelCounts[osamp][PixelToString((*m_iPortal)[isamp-1])]++;
+          } 
           isamp++;
         }
 
@@ -218,6 +225,12 @@ namespace Isis {
             mdNpts[osamp+1] += sdel;
           }
         }
+        if (IsSpecial((*m_iPortal)[isamp-1])) {
+          specialPixelCounts[osamp][PixelToString((*m_iPortal)[isamp-1])]++;
+          if(osamp+1 <= miOutputSamples){
+            specialPixelCounts[osamp+1][PixelToString((*m_iPortal)[isamp-1])]++;
+          }
+        } 
         isamp++;
       }
       mdLine++;
@@ -238,6 +251,9 @@ namespace Isis {
           mdSum2[osamp]  += (*m_iPortal)[isamp-1] * ldel;
           mdNpts2[osamp] += ldel;
         }
+        if (IsSpecial((*m_iPortal)[isamp-1])) {
+          specialPixelCounts[osamp][PixelToString((*m_iPortal)[isamp-1])]++;
+        } 
         isamp++;
       }
 
@@ -257,6 +273,12 @@ namespace Isis {
           mdNpts2[osamp+1] += sdel * ldel;
         }
       }
+      if (IsSpecial((*m_iPortal)[isamp-1])) {
+        specialPixelCounts[osamp][PixelToString((*m_iPortal)[isamp-1])]++;
+        if(osamp+1 <= miOutputSamples){
+          specialPixelCounts[osamp+1][PixelToString((*m_iPortal)[isamp-1])]++;
+        }
+      } 
       isamp++;
     }
 
@@ -271,6 +293,14 @@ namespace Isis {
         if(msReplaceMode == "NEAREST") {
           out[osamp] = (*m_iPortal)[(int)(mdIncTab[osamp] + 0.5) - 1];
         }
+        else if (msReplaceMode == "MAJORITY") {
+          // Iterate over the map and compare element counts
+          // Map iterator yields a pair:  pair.first == pixel names, pair.second == pixel count.
+          auto mode = std::max_element(specialPixelCounts[osamp].begin(),
+                                      specialPixelCounts[osamp].end(),
+                                      [](const pair<QString, int> &a, const pair<QString, int> &b) {return a.second < b.second;});
+          out[osamp] = StringToPixel(mode->first);
+        }
         else {
           out[osamp] = Isis::Null;
         }
@@ -281,6 +311,7 @@ namespace Isis {
       mdNpts2[osamp] = 0.0;
     }
 
+    // If this is the last line of the band, reset stats and increment band
     if(out.Line() == miOutputLines && out.Band() != miInputBands) {
       miBandIndex++;
       mdLine = 1;
diff --git a/isis/src/base/objs/Reduce/Reduce.h b/isis/src/base/objs/Reduce/Reduce.h
index 9a6f1dd408..d69e347cea 100644
--- a/isis/src/base/objs/Reduce/Reduce.h
+++ b/isis/src/base/objs/Reduce/Reduce.h
@@ -111,7 +111,7 @@ namespace Isis {
               double pdValidPer, QString psReplaceMode)
       : Reduce(pInCube, pdSampleScale, pdLineScale){
         mdValidPer    = pdValidPer;
-        msReplaceMode = psReplaceMode;
+        msReplaceMode = psReplaceMode.toUpper();
       }
 
       //! Operator () overload
-- 
GitLab