Repository: wright-group/WrightTools

Issue:
Recover trim, a method of channel



Discussion:
@p770193, care to take this on?
What is required to recover this method?
Quite little, I think, most of the WT2 code is still there, And I don't see anything that obviously wouldn't work, other than hdf5 not supporting masked arrays, and so that option probably needs to be taken out.

It is likely possible to convert it to using `chunkwise`, which we might want, but that is probably the most major surgery for `trim`

I would start by simply taking out the exception and seeing if it works/writing some tests

Resolving patch:
diff --git a/WrightTools/data/_channel.py b/WrightTools/data/_channel.py
--- a/WrightTools/data/_channel.py
+++ b/WrightTools/data/_channel.py
@@ -8,8 +8,12 @@
 
 import h5py
 
+import warnings
+import numbers
+
 from .. import kit as wt_kit
 from .._dataset import Dataset
+from .. import exceptions as wt_exceptions
 
 __all__ = ["Channel"]
 
@@ -148,7 +152,7 @@ def trim(self, neighborhood, method="ztest", factor=3, replace="nan", verbose=Tr
 
         factor : number (optional)
             Tolerance factor.  Default is 3.
-        replace : {'nan', 'mean', 'mask', number} (optional)
+        replace : {'nan', 'mean', number} (optional)
             Behavior of outlier replacement. Default is nan.
 
             nan
@@ -157,9 +161,6 @@ def trim(self, neighborhood, method="ztest", factor=3, replace="nan", verbose=Tr
             mean
                 Outliers are replaced by the mean of its neighborhood.
 
-            mask
-                Array is masked at outliers.
-
             number
                 Array becomes given number.
 
@@ -173,7 +174,7 @@ def trim(self, neighborhood, method="ztest", factor=3, replace="nan", verbose=Tr
         clip
             Remove pixels outside of a certain range.
         """
-        raise NotImplementedError
+        warnings.warn("trim", category=wt_exceptions.EntireDatasetInMemoryWarning)
         outliers = []
         means = []
         # find outliers
@@ -192,16 +193,19 @@ def trim(self, neighborhood, method="ztest", factor=3, replace="nan", verbose=Tr
         # replace outliers
         i = tuple(zip(*outliers))
         if replace == "nan":
-            self[i] = np.nan
+            arr = self[:]
+            arr[i] = np.nan
+            self[:] = arr
         elif replace == "mean":
-            self[i] = means
-        elif replace == "mask":
-            self[:] = np.ma.array(self[:])
-            self[i] = np.ma.masked
-        elif type(replace) in [int, float]:
-            self[i] = replace
+            arr = self[:]
+            arr[i] = means
+            self[:] = arr
+        elif isinstance(replace, numbers.Number):
+            arr = self[:]
+            arr[i] = replace
+            self[:] = arr
         else:
-            raise KeyError("replace must be one of {nan, mean, mask} or some number")
+            raise KeyError("replace must be one of {nan, mean} or some number")
         # finish
         if verbose:
             print("%i outliers removed" % len(outliers))


Test patch:
diff --git a/tests/data/trim.py b/tests/data/trim.py
new file mode 100644
--- /dev/null
+++ b/tests/data/trim.py
@@ -0,0 +1,75 @@
+#! /usr/bin/env python3
+"""Test channel.trim."""
+
+
+# --- import --------------------------------------------------------------------------------------
+
+
+import numpy as np
+
+import WrightTools as wt
+
+import matplotlib.pyplot as plt
+
+
+# --- test ----------------------------------------------------------------------------------------
+
+
+def test_trim_2Dgauss():
+    # create original arrays
+    x = np.linspace(-3, 3, 31)[:, None]
+    y = np.linspace(-3, 3, 31)[None, :]
+    arr = np.exp(-1 * (x ** 2 + y ** 2))
+    # create damaged array
+    arr2 = arr.copy()
+    np.random.seed(11)  # set seed for reproducibility
+    arr2[np.random.random(arr2.shape) < .05] = 2
+    # create data object
+    d = wt.data.Data()
+    d.create_variable("x", values=x)
+    d.create_variable("y", values=y)
+    d.create_channel("original", arr)
+    d.create_channel("damaged1", arr2)
+    d.create_channel("damaged2", arr2)
+    d.create_channel("damaged3", arr2)
+    d.transform("x", "y")
+    # trim
+    d.damaged1.trim([2, 2], factor=2)
+    d.damaged2.trim([2, 2], factor=2, replace="mean")
+    d.damaged3.trim([2, 2], factor=2, replace=0.5)
+    # now heal
+    d.create_channel("healed_linear", d.damaged1[:])
+    d.heal(channel="healed_linear", fill_value=0, method="linear")
+    # check
+    assert np.allclose(d.original[:], d.healed_linear[:], rtol=1e-1, atol=1e-1)
+    assert np.allclose(d.original[:], d.damaged2[:], rtol=1e-1, atol=9e-1)
+    assert np.allclose(d.original[:], d.damaged3[:], rtol=1e-1, atol=5e-1)
+
+
+def test_trim_3Dgauss():
+    # create original arrays
+    x = np.linspace(-3, 3, 31)[:, None, None]
+    y = np.linspace(-3, 3, 31)[None, :, None]
+    z = np.linspace(-3, 3, 31)[None, None, :]
+    arr = np.exp(-1 * (x ** 2 + y ** 2 + z ** 2))
+    # create damaged array
+    arr2 = arr.copy()
+    np.random.seed(11)  # set seed for reproducibility
+    arr2[np.random.random(arr2.shape) < .05] = 1
+    # create data object
+    d = wt.data.Data()
+    d.create_variable("x", values=x)
+    d.create_variable("y", values=y)
+    d.create_variable("z", values=z)
+    d.create_channel("original", arr)
+    d.create_channel("damaged", arr2)
+    d.transform("x", "y", "z")
+    # trim
+    d.damaged.trim([2, 2, 2], factor=2, replace="mean")
+    # check
+    assert np.allclose(d.original[:], d.damaged[:], rtol=1e-1, atol=9e-1)
+
+
+if __name__ == "__main__":
+    test_trim_2Dgauss()
+    test_trim_3Dgauss()