From 9c380776e57ea0bc815998e694fda143f3f667b3 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 11 Dec 2024 15:20:03 +0000 Subject: [PATCH 01/90] Added new features to the ndcube._add_ method --- ndcube/ndcube.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 29d258977..e1bf3abf5 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -11,6 +11,7 @@ import astropy.nddata import astropy.units as u +from astropy.nddata import NDData from astropy.units import UnitsError try: @@ -918,6 +919,37 @@ def __neg__(self): return self._new_instance(data=-self.data) def __add__(self, value): + if isinstance(value, NDData) and value.wcs is None: + if self.unit is not None and value.unit is not None: + value_data = value.data * value.unit.to(self.unit) + elif self.unit is None: + value_data = value.data + else: + raise TypeError("Cannot add unitless NDData to a unitful NDCube.") + + # addition + new_data = self.data + value_data + # combine the uncertainty + new_uncertainty = None + if self.uncertainty is not None and value.uncertainty is not None: + new_uncertainty = self.uncertainty.propagate( + np.add, value.uncertainty, correlation=0 + ) + elif self.uncertainty is not None: + new_uncertainty = self.uncertainty + elif value.uncertainty is not None: + new_uncertainty = value.uncertainty + + # combine mask + self_ma = np.ma.MaskedArray(self.data, mask=self.mask) + value_ma = np.ma.MaskedArray(value_data, mask=value.mask) + result_ma = self_ma + value_ma + new_mask = result_ma.mask + + # return the new NDCube instance + return self._new_instance( + data=new_data, uncertainty=new_uncertainty, mask=new_mask + ) if hasattr(value, 'unit'): if isinstance(value, u.Quantity): # NOTE: if the cube does not have units, we cannot From aaa9ef01177146572f3978f0c00bfeaccd574e59 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 17 Dec 2024 00:29:59 +0800 Subject: [PATCH 02/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index e1bf3abf5..63815aaaa 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -921,7 +921,7 @@ def __neg__(self): def __add__(self, value): if isinstance(value, NDData) and value.wcs is None: if self.unit is not None and value.unit is not None: - value_data = value.data * value.unit.to(self.unit) + value_data = (value.data * value.unit).to_value(self.unit) elif self.unit is None: value_data = value.data else: From ed4f61e9a427c2e6b3e5cb64e417076fb6797de5 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 17 Dec 2024 00:30:35 +0800 Subject: [PATCH 03/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 63815aaaa..6d4a4fc54 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -930,7 +930,6 @@ def __add__(self, value): # addition new_data = self.data + value_data # combine the uncertainty - new_uncertainty = None if self.uncertainty is not None and value.uncertainty is not None: new_uncertainty = self.uncertainty.propagate( np.add, value.uncertainty, correlation=0 @@ -939,6 +938,8 @@ def __add__(self, value): new_uncertainty = self.uncertainty elif value.uncertainty is not None: new_uncertainty = value.uncertainty + else: + new_uncertainty = None # combine mask self_ma = np.ma.MaskedArray(self.data, mask=self.mask) From ea43a1dabfb7369793431f34e6160f88bef6d952 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 18 Dec 2024 23:23:24 +0800 Subject: [PATCH 04/90] Modified the _add_ method further. --- ndcube/ndcube.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 0399e0d70..98e0654ed 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1035,37 +1035,45 @@ def __neg__(self): return self._new_instance(data=-self.data) def __add__(self, value): + kwargs = {} if isinstance(value, NDData) and value.wcs is None: if self.unit is not None and value.unit is not None: - value_data = value.data * value.unit.to(self.unit) + value_data = value.data * value.unit.to_value(self.unit) elif self.unit is None: value_data = value.data else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") - # addition - new_data = self.data + value_data # combine the uncertainty new_uncertainty = None if self.uncertainty is not None and value.uncertainty is not None: new_uncertainty = self.uncertainty.propagate( - np.add, value.uncertainty, correlation=0 + np.add, value.uncertainty, result_data = value.data, correlation=0 ) + kwargs["uncertainty"] = new_uncertainty elif self.uncertainty is not None: new_uncertainty = self.uncertainty + kwargs["uncertainty"] = new_uncertainty elif value.uncertainty is not None: new_uncertainty = value.uncertainty + kwargs["uncertainty"] = new_uncertainty + else: + new_uncertainty = None # combine mask self_ma = np.ma.MaskedArray(self.data, mask=self.mask) value_ma = np.ma.MaskedArray(value_data, mask=value.mask) + + # addition result_ma = self_ma + value_ma - new_mask = result_ma.mask + + # extract new mask and new data + kwargs["mask"] = result_ma.mask + kwargs["data"] = result_ma.data # return the new NDCube instance - return self._new_instance( - data=new_data, uncertainty=new_uncertainty, mask=new_mask - ) + return self._new_instance(**kwargs) + if hasattr(value, 'unit'): if isinstance(value, u.Quantity): # NOTE: if the cube does not have units, we cannot From f575e2c22fee8671968f6e6436342f1e7a69c85a Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 23 Dec 2024 14:10:11 +0800 Subject: [PATCH 05/90] Further modifies the _add_ method. --- ndcube/ndcube.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 3cb5ec941..c39ede2b4 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1044,10 +1044,21 @@ def __add__(self, value): else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") + # combine mask + self_ma = np.ma.MaskedArray(self.data, mask=self.mask) + value_ma = np.ma.MaskedArray(value_data, mask=value.mask) + + # addition + result_ma = self_ma + value_ma + + # extract new mask and new data + kwargs["mask"] = result_ma.mask + kwargs["data"] = result_ma.data + # combine the uncertainty if self.uncertainty is not None and value.uncertainty is not None: new_uncertainty = self.uncertainty.propagate( - np.add, value.uncertainty, result_data = value.data, correlation=0 + np.add, value.uncertainty, result_data = kwargs["data"], correlation=0 ) kwargs["uncertainty"] = new_uncertainty elif self.uncertainty is not None: @@ -1058,20 +1069,6 @@ def __add__(self, value): else: new_uncertainty = None - # combine mask - self_ma = np.ma.MaskedArray(self.data, mask=self.mask) - value_ma = np.ma.MaskedArray(value_data, mask=value.mask) - - # addition - result_ma = self_ma + value_ma - - # extract new mask and new data - kwargs["mask"] = result_ma.mask - kwargs["data"] = result_ma.data - - # return the new NDCube instance - return self._new_instance(**kwargs) - if hasattr(value, 'unit'): if isinstance(value, u.Quantity): # NOTE: if the cube does not have units, we cannot @@ -1079,7 +1076,7 @@ def __add__(self, value): # This forces a conversion to a dimensionless quantity # so that an error is thrown if value is not dimensionless cube_unit = u.Unit('') if self.unit is None else self.unit - new_data = self.data + value.to_value(cube_unit) + kwargs["data"] = self.data + value.to_value(cube_unit) else: # NOTE: This explicitly excludes other NDCube objects and NDData objects # which could carry a different WCS than the NDCube @@ -1087,8 +1084,10 @@ def __add__(self, value): elif self.unit not in (None, u.Unit("")): raise TypeError("Cannot add a unitless object to an NDCube with a unit.") else: - new_data = self.data + value - return self._new_instance(data=new_data) + kwargs["data"] = self.data + value + + # return the new NDCube instance + return self._new_instance(**kwargs) def __radd__(self, value): return self.__add__(value) From 8951635681a621268dcb9a187e6ea23adb9582b6 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 23 Dec 2024 15:25:51 +0800 Subject: [PATCH 06/90] Added a changelog file for this new feature. --- changelog/794.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/794.feature.rst diff --git a/changelog/794.feature.rst b/changelog/794.feature.rst new file mode 100644 index 000000000..8b35ab7cf --- /dev/null +++ b/changelog/794.feature.rst @@ -0,0 +1 @@ +Allows addition of an ``NDCube`` and ``NDData`` (with the WCS of ``NDData`` being set to None), and combines their uncertainties and masks. From bcf4fb90d9ab85c97a6246a6ed42e0e2d9fd343b Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 23 Dec 2024 16:37:22 +0800 Subject: [PATCH 07/90] Added a new method test_cube_add_uncertainty_and_mask to test_ndcube.py. --- ndcube/tests/test_ndcube.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index daf263e5f..b879fda51 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -12,7 +12,7 @@ import astropy.wcs from astropy.coordinates import SkyCoord, SpectralCoord from astropy.io import fits -from astropy.nddata import UnknownUncertainty +from astropy.nddata import NDData, StdDevUncertainty, UnknownUncertainty from astropy.tests.helper import assert_quantity_allclose from astropy.time import Time from astropy.units import UnitsError @@ -1124,6 +1124,26 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): check_arithmetic_value_and_units(new_cube, cube_quantity + value) +@pytest.mark.parametrize('value', [ + NDData(np.random.rand(10, 12), + unit=u.ct, + uncertainty=StdDevUncertainty(np.random.rand(10, 12)), + mask=np.random.choice([True, False], size=(10, 12))), +]) +def test_cube_add_uncertainty_and_mask(ndcube_2d_ln_lt_units, value): + new_cube = ndcube_2d_ln_lt_units + value + # Check uncertainty propagation + expected_uncertainty = np.sqrt( + ndcube_2d_ln_lt_units.uncertainty.array**2 + value.uncertainty.array**2 + ) + assert np.allclose(new_cube.uncertainty.array, expected_uncertainty), \ + f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" + # Check mask combination + expected_mask = np.logical_or(ndcube_2d_ln_lt_units.mask, value.mask) + assert np.array_equal(new_cube.mask, expected_mask), \ + f"Expected mask: {expected_mask}, but got: {new_cube.mask}" + + @pytest.mark.parametrize('value', [ 10 * u.ct, u.Quantity([10], u.ct), From c4d639af172cd09e057ef63d5cb0fb2e2dd373f7 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 23 Dec 2024 16:52:19 +0800 Subject: [PATCH 08/90] Modified the test_cube_add_uncertainty_and_mask method in test_ndcube.py --- ndcube/tests/test_ndcube.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index b879fda51..2a2e39010 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1127,6 +1127,7 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): @pytest.mark.parametrize('value', [ NDData(np.random.rand(10, 12), unit=u.ct, + wcs=None, uncertainty=StdDevUncertainty(np.random.rand(10, 12)), mask=np.random.choice([True, False], size=(10, 12))), ]) From bd317e390a3a74642e482901f6cc360913d6d3f0 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 23 Dec 2024 17:07:06 +0800 Subject: [PATCH 09/90] Modified the test_cube_add_uncertainty_and_mask further. --- ndcube/tests/test_ndcube.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 2a2e39010..988fcee7d 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1134,13 +1134,17 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): def test_cube_add_uncertainty_and_mask(ndcube_2d_ln_lt_units, value): new_cube = ndcube_2d_ln_lt_units + value # Check uncertainty propagation - expected_uncertainty = np.sqrt( - ndcube_2d_ln_lt_units.uncertainty.array**2 + value.uncertainty.array**2 + expected_uncertainty = ndcube_2d_ln_lt_units.uncertainty.propagate( + operation=np.add, + other_nddata=value, + result_data=new_cube.data, + correlation=0, ) assert np.allclose(new_cube.uncertainty.array, expected_uncertainty), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # Check mask combination - expected_mask = np.logical_or(ndcube_2d_ln_lt_units.mask, value.mask) + expected_mask = (np.ma.MaskedArray(ndcube_2d_ln_lt_units.data, mask=ndcube_2d_ln_lt_units.mask) + \ + np.ma.MaskedArray(ndcube_2d_ln_lt_units.data, mask=ndcube_2d_ln_lt_units.mask)).mask assert np.array_equal(new_cube.mask, expected_mask), \ f"Expected mask: {expected_mask}, but got: {new_cube.mask}" From e0375ec38f0700cf94dfa0816b13c023da52daa9 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 14 Jan 2025 07:06:22 +0000 Subject: [PATCH 10/90] Fixed how the masks are combined. --- ndcube/ndcube.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index c39ede2b4..a9ae51c3b 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1044,21 +1044,21 @@ def __add__(self, value): else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") - # combine mask + # use the format of the output of np.ma.MaskedArray, for combining mask self_ma = np.ma.MaskedArray(self.data, mask=self.mask) value_ma = np.ma.MaskedArray(value_data, mask=value.mask) - # addition + # addition, (and combining mask) result_ma = self_ma + value_ma # extract new mask and new data kwargs["mask"] = result_ma.mask - kwargs["data"] = result_ma.data + kwargs["data"] = result_ma # combine the uncertainty if self.uncertainty is not None and value.uncertainty is not None: new_uncertainty = self.uncertainty.propagate( - np.add, value.uncertainty, result_data = kwargs["data"], correlation=0 + np.add, value, result_data = kwargs["data"], correlation=0 ) kwargs["uncertainty"] = new_uncertainty elif self.uncertainty is not None: From 015873772829c6d4ebce31dca6bcc73bcdc01070 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Sat, 18 Jan 2025 21:31:46 +0000 Subject: [PATCH 11/90] Set masked uncertainty entries to 0. --- ndcube/ndcube.py | 78 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index a9ae51c3b..b973ba4e2 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1044,30 +1044,62 @@ def __add__(self, value): else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") - # use the format of the output of np.ma.MaskedArray, for combining mask - self_ma = np.ma.MaskedArray(self.data, mask=self.mask) - value_ma = np.ma.MaskedArray(value_data, mask=value.mask) - - # addition, (and combining mask) - result_ma = self_ma + value_ma - - # extract new mask and new data - kwargs["mask"] = result_ma.mask - kwargs["data"] = result_ma - - # combine the uncertainty - if self.uncertainty is not None and value.uncertainty is not None: - new_uncertainty = self.uncertainty.propagate( - np.add, value, result_data = kwargs["data"], correlation=0 - ) - kwargs["uncertainty"] = new_uncertainty - elif self.uncertainty is not None: - new_uncertainty = self.uncertainty - kwargs["uncertainty"] = new_uncertainty - elif value.uncertainty is not None: - new_uncertainty = value.uncertainty + # Neither self nor value has a mask + self_unmasked = self.mask is None or self.mask is False or not self.mask.any() + value_unmasked = value.mask is None or value.mask is False or not value.mask.any() + + # situations in which at least one of the two objects have a meaningful mask + if not (self_unmasked and value_unmasked): + self_ma = np.ma.MaskedArray(self.data, mask=self.mask) + value_ma = np.ma.MaskedArray(value_data, mask=value.mask) + + # addition, (and combining mask) + result_ma = self_ma + value_ma + + # extract new mask and new data + kwargs["mask"] = result_ma.mask + kwargs["data"] = result_ma.data # keep the data and mask separate + + # we cannot directly apply the mask on result_ma for further operations on uncertainty, because it does not have it. + # we can only handle the uncertainty on self and value individually. + # check if the uncertainty is not None, and if it is masked, set it to 0. + if hasattr(self, 'uncertainty') and self.uncertainty is not None: + if not self_unmasked: + self.uncertainty.array[self.mask] = 0 + + if hasattr(value, 'uncertainty') and value.uncertainty is not None: + if not value_unmasked: + value.uncertainty.array[value.mask] = 0 + + # combine the uncertainty + if self.uncertainty is not None and value.uncertainty is not None: + kwargs["uncertainty"] = self.uncertainty.propagate( + np.add, value, result_data=kwargs["data"], correlation=0 + ) + elif self.uncertainty is not None: + kwargs["uncertainty"] = self.uncertainty + elif value.uncertainty is not None: + kwargs["uncertainty"] = value.uncertainty + else: + kwargs["uncertainty"] = None + + # situations in which neither of the two objects has a meaningful mask else: - new_uncertainty = None + kwargs["mask"] = self.mask + kwargs["data"] = self.data + value.data + # combine the uncertainty + if self.uncertainty is not None and value.uncertainty is not None: + new_uncertainty = self.uncertainty.propagate( + np.add, value, result_data = kwargs["data"], correlation=0 + ) + kwargs["uncertainty"] = new_uncertainty + elif self.uncertainty is not None: + new_uncertainty = self.uncertainty + kwargs["uncertainty"] = new_uncertainty + elif value.uncertainty is not None: + new_uncertainty = value.uncertainty + else: + new_uncertainty = None if hasattr(value, 'unit'): if isinstance(value, u.Quantity): From 9074f4525fbe7369f34a6cdd1a84020bffc9eceb Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 21 Jan 2025 08:51:59 +0000 Subject: [PATCH 12/90] Moved uncertainty combination out of the mask-combining If Statements. --- ndcube/ndcube.py | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index b973ba4e2..d2c5792e8 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1071,35 +1071,24 @@ def __add__(self, value): if not value_unmasked: value.uncertainty.array[value.mask] = 0 - # combine the uncertainty - if self.uncertainty is not None and value.uncertainty is not None: - kwargs["uncertainty"] = self.uncertainty.propagate( - np.add, value, result_data=kwargs["data"], correlation=0 - ) - elif self.uncertainty is not None: - kwargs["uncertainty"] = self.uncertainty - elif value.uncertainty is not None: - kwargs["uncertainty"] = value.uncertainty - else: - kwargs["uncertainty"] = None - # situations in which neither of the two objects has a meaningful mask else: kwargs["mask"] = self.mask kwargs["data"] = self.data + value.data - # combine the uncertainty - if self.uncertainty is not None and value.uncertainty is not None: - new_uncertainty = self.uncertainty.propagate( - np.add, value, result_data = kwargs["data"], correlation=0 - ) - kwargs["uncertainty"] = new_uncertainty - elif self.uncertainty is not None: - new_uncertainty = self.uncertainty - kwargs["uncertainty"] = new_uncertainty - elif value.uncertainty is not None: - new_uncertainty = value.uncertainty - else: - new_uncertainty = None + + # combine the uncertainty + if self.uncertainty is not None and value.uncertainty is not None: + new_uncertainty = self.uncertainty.propagate( + np.add, value, result_data = kwargs["data"], correlation=0 + ) + kwargs["uncertainty"] = new_uncertainty + elif self.uncertainty is not None: + new_uncertainty = self.uncertainty + kwargs["uncertainty"] = new_uncertainty + elif value.uncertainty is not None: + new_uncertainty = value.uncertainty + else: + new_uncertainty = None if hasattr(value, 'unit'): if isinstance(value, u.Quantity): From 5f422f54cfa2a2f08ddb144caeb4f1266132d86f Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 22 Jan 2025 14:55:54 +0000 Subject: [PATCH 13/90] Removed mask-dealing in the add method. --- ndcube/ndcube.py | 41 ++++++++--------------------------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index c1181af79..e285ecfa6 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -965,47 +965,18 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def __add__(self, value): + def add(self, value): kwargs = {} if isinstance(value, NDData) and value.wcs is None: if self.unit is not None and value.unit is not None: - value_data = (value.data * value.unit).to_value(self.unit) + value_data = (value.data * value.unit).to_value(self.unit) elif self.unit is None: value_data = value.data else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") - # Neither self nor value has a mask - self_unmasked = self.mask is None or self.mask is False or not self.mask.any() - value_unmasked = value.mask is None or value.mask is False or not value.mask.any() - - # situations in which at least one of the two objects have a meaningful mask - if not (self_unmasked and value_unmasked): - self_ma = np.ma.MaskedArray(self.data, mask=self.mask) - value_ma = np.ma.MaskedArray(value_data, mask=value.mask) - - # addition, (and combining mask) - result_ma = self_ma + value_ma - - # extract new mask and new data - kwargs["mask"] = result_ma.mask - kwargs["data"] = result_ma.data # keep the data and mask separate - - # we cannot directly apply the mask on result_ma for further operations on uncertainty, because it does not have it. - # we can only handle the uncertainty on self and value individually. - # check if the uncertainty is not None, and if it is masked, set it to 0. - if hasattr(self, 'uncertainty') and self.uncertainty is not None: - if not self_unmasked: - self.uncertainty.array[self.mask] = 0 - - if hasattr(value, 'uncertainty') and value.uncertainty is not None: - if not value_unmasked: - value.uncertainty.array[value.mask] = 0 - - # situations in which neither of the two objects has a meaningful mask - else: - kwargs["mask"] = self.mask - kwargs["data"] = self.data + value.data + # addition, (and combining mask) + kwargs["data"] = self.data + value_data # combine the uncertainty if self.uncertainty is not None and value.uncertainty is not None: @@ -1041,6 +1012,10 @@ def __add__(self, value): # return the new NDCube instance return self._new_instance(**kwargs) + def __add__(self, value): + # when value has a mask, raise error and point user to the add method. TODO + pass + def __radd__(self, value): return self.__add__(value) From f17da78c1ee505bfeba9335656cfdf1912d3294b Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 22 Jan 2025 15:15:04 +0000 Subject: [PATCH 14/90] Removed mask-dealing in the Add method. --- ndcube/ndcube.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index e285ecfa6..3671c80cd 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -966,6 +966,7 @@ def __neg__(self): return self._new_instance(data=-self.data) def add(self, value): + # Version at 22/Jan. kwargs = {} if isinstance(value, NDData) and value.wcs is None: if self.unit is not None and value.unit is not None: From 344b6f7e64e6d656eff24bc38880b137d7b773e1 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 22 Jan 2025 17:11:24 +0000 Subject: [PATCH 15/90] use a conditional statement to still check whether there is a mask. --- ndcube/ndcube.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 3671c80cd..be5ce0993 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -965,9 +965,9 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def add(self, value): - # Version at 22/Jan. + def add(self, value, operation_ignores_mask=False, handle_mask=np.logical_and,): kwargs = {} + if isinstance(value, NDData) and value.wcs is None: if self.unit is not None and value.unit is not None: value_data = (value.data * value.unit).to_value(self.unit) @@ -976,22 +976,30 @@ def add(self, value): else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") - # addition, (and combining mask) - kwargs["data"] = self.data + value_data - - # combine the uncertainty - if self.uncertainty is not None and value.uncertainty is not None: - new_uncertainty = self.uncertainty.propagate( - np.add, value, result_data = kwargs["data"], correlation=0 - ) - kwargs["uncertainty"] = new_uncertainty - elif self.uncertainty is not None: - new_uncertainty = self.uncertainty - kwargs["uncertainty"] = new_uncertainty - elif value.uncertainty is not None: - new_uncertainty = value.uncertainty + # check whether there is a mask. + # Neither self nor value has a mask + self_unmasked = self.mask is None or self.mask is False or not self.mask.any() + value_unmasked = value.mask is None or value.mask is False or not value.mask.any() + + if (self_unmasked and value_unmasked): + # addition + kwargs["data"] = self.data + value_data + + # combine the uncertainty; + if self.uncertainty is not None and value.uncertainty is not None: + new_uncertainty = self.uncertainty.propagate( + np.add, value, result_data = kwargs["data"], correlation=0 + ) + kwargs["uncertainty"] = new_uncertainty + elif self.uncertainty is not None: + new_uncertainty = self.uncertainty + kwargs["uncertainty"] = new_uncertainty + elif value.uncertainty is not None: + new_uncertainty = value.uncertainty + else: + new_uncertainty = None else: - new_uncertainty = None + raise NotImplementedError if hasattr(value, 'unit'): if isinstance(value, u.Quantity): From 7ff78aa616c4f84b62fbf1e3584efc9293de630f Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 22 Jan 2025 17:16:17 +0000 Subject: [PATCH 16/90] Changed mask to False and removed mask-checking in test_cube_add_uncertainty function. --- ndcube/tests/test_ndcube.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index cf55b0ec4..73608c721 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1143,9 +1143,9 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): unit=u.ct, wcs=None, uncertainty=StdDevUncertainty(np.random.rand(10, 12)), - mask=np.random.choice([True, False], size=(10, 12))), + mask=np.random.choice([False, False], size=(10, 12))), ]) -def test_cube_add_uncertainty_and_mask(ndcube_2d_ln_lt_units, value): +def test_cube_add_uncertainty(ndcube_2d_ln_lt_units, value): new_cube = ndcube_2d_ln_lt_units + value # Check uncertainty propagation expected_uncertainty = ndcube_2d_ln_lt_units.uncertainty.propagate( @@ -1156,11 +1156,6 @@ def test_cube_add_uncertainty_and_mask(ndcube_2d_ln_lt_units, value): ) assert np.allclose(new_cube.uncertainty.array, expected_uncertainty), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" - # Check mask combination - expected_mask = (np.ma.MaskedArray(ndcube_2d_ln_lt_units.data, mask=ndcube_2d_ln_lt_units.mask) + \ - np.ma.MaskedArray(ndcube_2d_ln_lt_units.data, mask=ndcube_2d_ln_lt_units.mask)).mask - assert np.array_equal(new_cube.mask, expected_mask), \ - f"Expected mask: {expected_mask}, but got: {new_cube.mask}" @pytest.mark.parametrize('value', [ From 5852daa7b255afd69abd3e1cfd6155f2c384436f Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 22 Jan 2025 18:04:55 +0000 Subject: [PATCH 17/90] Added placeholders for using the new parameters and modified the no-mask condition. --- ndcube/ndcube.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index be5ce0993..c419f3d42 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -965,7 +965,11 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def add(self, value, operation_ignores_mask=False, handle_mask=np.logical_and,): + def add(self, value, operation_ignores_mask=False, handle_mask=np.logical_and): + """ + Users are allowed to choose whether they want operation_ignores_mask to be True or False, + and are allowed to choose whether they want handle_mask to be AND / OR . + """ kwargs = {} if isinstance(value, NDData) and value.wcs is None: @@ -981,7 +985,7 @@ def add(self, value, operation_ignores_mask=False, handle_mask=np.logical_and,): self_unmasked = self.mask is None or self.mask is False or not self.mask.any() value_unmasked = value.mask is None or value.mask is False or not value.mask.any() - if (self_unmasked and value_unmasked): + if (self_unmasked and value_unmasked) or operation_ignores_mask is True: # addition kwargs["data"] = self.data + value_data @@ -999,7 +1003,17 @@ def add(self, value, operation_ignores_mask=False, handle_mask=np.logical_and,): else: new_uncertainty = None else: - raise NotImplementedError + # TODO + # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture. + # Conditional statements to permutate the two different scenarios (when it does not ignore the mask). + if operation_ignores_mask is False: + if handle_mask is np.logical_and: + pass + else: + pass + else: + raise NotImplementedError + if hasattr(value, 'unit'): if isinstance(value, u.Quantity): From 5dcb8ff42de32f9919d58c140f2ad4c85acff3b1 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 22 Jan 2025 19:13:04 +0000 Subject: [PATCH 18/90] Set default of operation_ignores_mask to be True. --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index c419f3d42..9976c50a7 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -965,7 +965,7 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def add(self, value, operation_ignores_mask=False, handle_mask=np.logical_and): + def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): """ Users are allowed to choose whether they want operation_ignores_mask to be True or False, and are allowed to choose whether they want handle_mask to be AND / OR . From b385643241f0697284fcb9a5ac668b72befb3df8 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 28 Jan 2025 10:57:10 +0000 Subject: [PATCH 19/90] Make NDCube.__add__ call the NDCube.add method. --- ndcube/ndcube.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 9976c50a7..f0fe0575b 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1037,7 +1037,21 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): def __add__(self, value): # when value has a mask, raise error and point user to the add method. TODO - pass + # + # check whether there is a mask. + # Neither self nor value has a mask + + self_masked = not(self.mask is None or self.mask is False or not self.mask.any()) + value_masked = not(value.mask is None or value.mask is False or not value.mask.any()) if hasattr(value, "mask") else False + + # tidying this up. + if value_masked: # value has a mask, + # let the users call the add method + raise TypeError('Please use the add method.') + if (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None): + raise TypeError('Please use the add method.') + + return self.add(value) # the mask keywords cannot be given by users. def __radd__(self, value): return self.__add__(value) From 9941993e188582b56c8ae0a6c9a6350a6b828f10 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 29 Jan 2025 15:06:13 +0000 Subject: [PATCH 20/90] tidied up the __add__ method, copied the original test_cube_arithmetic_add to make sure it is not the error source. --- ndcube/ndcube.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index f0fe0575b..b2029afd0 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1045,11 +1045,9 @@ def __add__(self, value): value_masked = not(value.mask is None or value.mask is False or not value.mask.any()) if hasattr(value, "mask") else False # tidying this up. - if value_masked: # value has a mask, + if (value_masked or (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None)): # value has a mask, # let the users call the add method raise TypeError('Please use the add method.') - if (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None): - raise TypeError('Please use the add method.') return self.add(value) # the mask keywords cannot be given by users. From 4568ae2b2d614fd3eb0a8b94371a17929e069a91 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:21:47 +0000 Subject: [PATCH 21/90] Only check whether value has unit if it is not an NDData Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index b2029afd0..37e14bb54 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1015,7 +1015,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): raise NotImplementedError - if hasattr(value, 'unit'): + elif hasattr(value, 'unit'): if isinstance(value, u.Quantity): # NOTE: if the cube does not have units, we cannot # perform arithmetic between a unitful quantity. From bb2c54101421e27e915e5ff61992ce24205fbff4 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:17:34 +0000 Subject: [PATCH 22/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 37e14bb54..3a90fe02c 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -975,7 +975,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): if isinstance(value, NDData) and value.wcs is None: if self.unit is not None and value.unit is not None: value_data = (value.data * value.unit).to_value(self.unit) - elif self.unit is None: + elif self.unit is None and value.unit is None: value_data = value.data else: raise TypeError("Cannot add unitless NDData to a unitful NDCube.") From 3f6ebede23020bd8a0d2ab3dba76880fc2bd7466 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:21:34 +0000 Subject: [PATCH 23/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 3a90fe02c..a06a5f9a5 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1006,12 +1006,6 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): # TODO # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture. # Conditional statements to permutate the two different scenarios (when it does not ignore the mask). - if operation_ignores_mask is False: - if handle_mask is np.logical_and: - pass - else: - pass - else: raise NotImplementedError From 3b76d54a62d3a763024c1884b69a2d7efd65d953 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:22:04 +0000 Subject: [PATCH 24/90] Update ndcube/tests/test_ndcube.py Co-authored-by: DanRyanIrish --- ndcube/tests/test_ndcube.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 73608c721..5a220f849 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1143,7 +1143,6 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): unit=u.ct, wcs=None, uncertainty=StdDevUncertainty(np.random.rand(10, 12)), - mask=np.random.choice([False, False], size=(10, 12))), ]) def test_cube_add_uncertainty(ndcube_2d_ln_lt_units, value): new_cube = ndcube_2d_ln_lt_units + value From 64cc02e816812c389a4d8746c631e3cdbb735647 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 4 Feb 2025 04:38:32 +0000 Subject: [PATCH 25/90] Fix uncertainty propagation and ensure expected_uncertainty is numpy array --- ndcube/conftest.py | 9 +++++++++ ndcube/ndcube.py | 2 +- ndcube/tests/test_ndcube.py | 22 +++++++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index e52ad7643..e574e02c1 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -678,6 +678,15 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): return NDCube(data_cube, wcs=wcs_2d_lt_ln, unit=u.ct) +@pytest.fixture +def ndcube_2d_with_uncertainty(wcs_2d_lt_ln): + shape = (10, 12) + data_cube = data_nd(shape).astype(float) + uncertainty = StdDevUncertainty(np.random.rand(*shape), unit=u.ct) + + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct) + + @pytest.fixture def ndcube_2d_dask(wcs_2d_lt_ln): shape = (8, 4) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index a06a5f9a5..4ebfe4620 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -987,7 +987,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): if (self_unmasked and value_unmasked) or operation_ignores_mask is True: # addition - kwargs["data"] = self.data + value_data + kwargs["data"] = (self.data + value_data) * self.unit # combine the uncertainty; if self.uncertainty is not None and value.uncertainty is not None: diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 5a220f849..a64db448c 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1142,17 +1142,29 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): NDData(np.random.rand(10, 12), unit=u.ct, wcs=None, - uncertainty=StdDevUncertainty(np.random.rand(10, 12)), + uncertainty=StdDevUncertainty(np.random.rand(10, 12), unit=u.ct)), ]) -def test_cube_add_uncertainty(ndcube_2d_ln_lt_units, value): - new_cube = ndcube_2d_ln_lt_units + value +def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): + #print("ndcube_2d_with_uncertainty:", ndcube_2d_with_uncertainty) + #print("ndcube_2d_with_uncertainty.uncertainty:", ndcube_2d_with_uncertainty.uncertainty) + #print("ndcube_2d_with_uncertainty.uncertainty.unit:", ndcube_2d_with_uncertainty.uncertainty.unit) + #print("value:", value) + #print("value.uncertainty:", value.uncertainty) + #print("value.uncertainty.unit:", value.uncertainty.unit) + + new_cube = ndcube_2d_with_uncertainty + value # Check uncertainty propagation - expected_uncertainty = ndcube_2d_ln_lt_units.uncertainty.propagate( + expected_uncertainty = ndcube_2d_with_uncertainty.uncertainty.propagate( operation=np.add, other_nddata=value, - result_data=new_cube.data, + result_data=new_cube.data * new_cube.unit, correlation=0, ) + if expected_uncertainty.unit is None: + expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) + if isinstance(expected_uncertainty, StdDevUncertainty): + expected_uncertainty = expected_uncertainty.array + assert np.allclose(new_cube.uncertainty.array, expected_uncertainty), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" From 48b313e69130a236fe38722e8b519c283f789c10 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:21:08 +0000 Subject: [PATCH 26/90] Apply suggestions from code review Co-authored-by: DanRyanIrish --- ndcube/tests/test_ndcube.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index a64db448c..f371d24ac 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1145,12 +1145,6 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): uncertainty=StdDevUncertainty(np.random.rand(10, 12), unit=u.ct)), ]) def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): - #print("ndcube_2d_with_uncertainty:", ndcube_2d_with_uncertainty) - #print("ndcube_2d_with_uncertainty.uncertainty:", ndcube_2d_with_uncertainty.uncertainty) - #print("ndcube_2d_with_uncertainty.uncertainty.unit:", ndcube_2d_with_uncertainty.uncertainty.unit) - #print("value:", value) - #print("value.uncertainty:", value.uncertainty) - #print("value.uncertainty.unit:", value.uncertainty.unit) new_cube = ndcube_2d_with_uncertainty + value # Check uncertainty propagation @@ -1162,10 +1156,8 @@ def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): ) if expected_uncertainty.unit is None: expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) - if isinstance(expected_uncertainty, StdDevUncertainty): - expected_uncertainty = expected_uncertainty.array - - assert np.allclose(new_cube.uncertainty.array, expected_uncertainty), \ + assert type(new_cube.uncertainty) is type(expected_uncertainty) + assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" From 23fef8a92e766750746c185a1a6c5b76cbd51d15 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:36:18 +0000 Subject: [PATCH 27/90] check value and unit of addition --- ndcube/tests/test_ndcube.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index f371d24ac..0bb20d7e9 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1156,6 +1156,8 @@ def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): ) if expected_uncertainty.unit is None: expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) + assert np.allclose(new_cube.data, ndcube_2d_with_uncertainty.data + value.data) + assert new_cube.unit == u.ct assert type(new_cube.uncertainty) is type(expected_uncertainty) assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" From ed9d5f1ee90ba10b1b08e84e0a4fb46787036f84 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:44:03 +0000 Subject: [PATCH 28/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 4ebfe4620..a06a5f9a5 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -987,7 +987,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): if (self_unmasked and value_unmasked) or operation_ignores_mask is True: # addition - kwargs["data"] = (self.data + value_data) * self.unit + kwargs["data"] = self.data + value_data # combine the uncertainty; if self.uncertainty is not None and value.uncertainty is not None: From 7ea75f3b5eb8e5849fd09d4b76b3e4f8f9398385 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:21:21 +0000 Subject: [PATCH 29/90] change values for uncertainty in a fixture to fixed values. --- ndcube/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index e574e02c1..6399f4e34 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -682,7 +682,7 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): def ndcube_2d_with_uncertainty(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) - uncertainty = StdDevUncertainty(np.random.rand(*shape), unit=u.ct) + uncertainty = StdDevUncertainty(np.ones(shape), unit=u.ct) return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct) From 83d99cf7db48ee5f915e14b52b848c708d5f7abf Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 18 Feb 2025 07:54:31 +0000 Subject: [PATCH 30/90] added unit in ndcube for kwargs['data'], changed values for uncertainty. --- ndcube/conftest.py | 2 +- ndcube/ndcube.py | 2 +- ndcube/tests/test_ndcube.py | 22 ++++++++++++---------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 6399f4e34..369a85a95 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -682,7 +682,7 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): def ndcube_2d_with_uncertainty(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) - uncertainty = StdDevUncertainty(np.ones(shape), unit=u.ct) + uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=u.ct) return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index a06a5f9a5..6d9c7aff6 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -987,7 +987,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): if (self_unmasked and value_unmasked) or operation_ignores_mask is True: # addition - kwargs["data"] = self.data + value_data + kwargs["data"] = (self.data + value_data)*self.unit # combine the uncertainty; if self.uncertainty is not None and value.uncertainty is not None: diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 0bb20d7e9..b11394905 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1124,29 +1124,31 @@ def check_arithmetic_value_and_units(cube_new, data_expected): cube_quantity = u.Quantity(cube_new.data, cube_new.unit) assert u.allclose(cube_quantity, data_expected) - +# values passed in are integers/random integers with a unit being ct. should not be random here? @pytest.mark.parametrize('value', [ 10 * u.ct, u.Quantity([10], u.ct), u.Quantity(np.random.rand(12), u.ct), u.Quantity(np.random.rand(10, 12), u.ct), ]) -def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): +def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): # this test methods aims for the special scenario of only integers being added together. cube_quantity = u.Quantity(ndcube_2d_ln_lt_units.data, ndcube_2d_ln_lt_units.unit) # Add new_cube = ndcube_2d_ln_lt_units + value check_arithmetic_value_and_units(new_cube, cube_quantity + value) +# The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. @pytest.mark.parametrize('value', [ - NDData(np.random.rand(10, 12), + NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. unit=u.ct, wcs=None, - uncertainty=StdDevUncertainty(np.random.rand(10, 12), unit=u.ct)), + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)), ]) def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): - - new_cube = ndcube_2d_with_uncertainty + value + #print('Evidence of entering.') + new_cube = ndcube_2d_with_uncertainty + value # perform the addition + #new_cube.unit = value.unit # Check uncertainty propagation expected_uncertainty = ndcube_2d_with_uncertainty.uncertainty.propagate( operation=np.add, @@ -1156,11 +1158,11 @@ def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): ) if expected_uncertainty.unit is None: expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) - assert np.allclose(new_cube.data, ndcube_2d_with_uncertainty.data + value.data) - assert new_cube.unit == u.ct - assert type(new_cube.uncertainty) is type(expected_uncertainty) + assert np.allclose(new_cube.data, ndcube_2d_with_uncertainty.data + value.data) # check value of addition result + assert new_cube.unit == u.ct # check unit + assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ - f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" + f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty @pytest.mark.parametrize('value', [ From 3b5a0cec00d8ad25f86171f46d28151752249777 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 18 Feb 2025 08:56:07 +0000 Subject: [PATCH 31/90] new test method for units of both objects being None. --- ndcube/conftest.py | 9 +++++++++ ndcube/tests/test_ndcube.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 369a85a95..d5ee93208 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -687,6 +687,15 @@ def ndcube_2d_with_uncertainty(wcs_2d_lt_ln): return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct) +@pytest.fixture +def ndcube_2d_unit_None(wcs_2d_lt_ln): + shape = (10, 12) + data_cube = data_nd(shape).astype(float) + uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=None) + + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=None) + + @pytest.fixture def ndcube_2d_dask(wcs_2d_lt_ln): shape = (8, 4) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index b11394905..d2c2b37dd 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1138,6 +1138,30 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): # this test methods check_arithmetic_value_and_units(new_cube, cube_quantity + value) +# value is an NDData, The case when neither NDData nor NDCube has a unit. +@pytest.mark.parametrize('value', [ + NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + unit=None, + wcs=None, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=None)), +]) +def test_cube_add_unit_none(ndcube_2d_unit_None, value): + new_cube = ndcube_2d_unit_None + value # perform the addition + expected_uncertainty = ndcube_2d_unit_None.uncertainty.propagate( + operation=np.add, + other_nddata=value, + result_data=new_cube.data * new_cube.unit, + correlation=0, + ) + if expected_uncertainty.unit is None: + expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) + assert np.allclose(new_cube.data, ndcube_2d_unit_None.data + value.data) # check value of addition result + assert new_cube.unit is None # check unit + assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty + assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ + f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + + # The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. From 1f0ffc69c0d0a52d0d420434c7b92ac1714cc007 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Thu, 20 Feb 2025 15:59:42 +0000 Subject: [PATCH 32/90] within a new ndcube-dev env, removed any unit involved for now. --- ndcube/conftest.py | 8 ++++---- ndcube/ndcube.py | 4 +++- ndcube/tests/test_ndcube.py | 15 +++++---------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index d5ee93208..453cd778d 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -682,18 +682,18 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): def ndcube_2d_with_uncertainty(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) - uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=u.ct) + uncertainty = StdDevUncertainty(np.ones(shape)*0.2) - return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct) + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) @pytest.fixture def ndcube_2d_unit_None(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) - uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=None) + uncertainty = StdDevUncertainty(np.ones(shape)*0.2) - return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=None) + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) @pytest.fixture diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 6d9c7aff6..237e8de3f 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -987,10 +987,12 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): if (self_unmasked and value_unmasked) or operation_ignores_mask is True: # addition - kwargs["data"] = (self.data + value_data)*self.unit + kwargs["data"] = self.data + value_data # combine the uncertainty; if self.uncertainty is not None and value.uncertainty is not None: + #new_uncertainty = new_uncertainty.propagate(np.power, self, self.data ** value, correlation=1) + #print(self.unit, value.unit, kwargs['data']) new_uncertainty = self.uncertainty.propagate( np.add, value, result_data = kwargs["data"], correlation=0 ) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index d2c2b37dd..cd0922c94 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1141,20 +1141,19 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): # this test methods # value is an NDData, The case when neither NDData nor NDCube has a unit. @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. - unit=None, wcs=None, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=None)), + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), ]) def test_cube_add_unit_none(ndcube_2d_unit_None, value): new_cube = ndcube_2d_unit_None + value # perform the addition expected_uncertainty = ndcube_2d_unit_None.uncertainty.propagate( operation=np.add, other_nddata=value, - result_data=new_cube.data * new_cube.unit, + result_data=new_cube.data, correlation=0, ) if expected_uncertainty.unit is None: - expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) + expected_uncertainty = StdDevUncertainty(expected_uncertainty.array) assert np.allclose(new_cube.data, ndcube_2d_unit_None.data + value.data) # check value of addition result assert new_cube.unit is None # check unit assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty @@ -1165,9 +1164,8 @@ def test_cube_add_unit_none(ndcube_2d_unit_None, value): # The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. - unit=u.ct, wcs=None, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)), + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), ]) def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): #print('Evidence of entering.') @@ -1177,13 +1175,10 @@ def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): expected_uncertainty = ndcube_2d_with_uncertainty.uncertainty.propagate( operation=np.add, other_nddata=value, - result_data=new_cube.data * new_cube.unit, + result_data=new_cube.data, correlation=0, ) - if expected_uncertainty.unit is None: - expected_uncertainty = StdDevUncertainty(expected_uncertainty.array, unit=new_cube.unit) assert np.allclose(new_cube.data, ndcube_2d_with_uncertainty.data + value.data) # check value of addition result - assert new_cube.unit == u.ct # check unit assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty From fd78f6ff3e832c51a7544bc47b0c0efb57df75da Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Thu, 20 Feb 2025 17:12:57 +0000 Subject: [PATCH 33/90] Added new test case for only one of them having a unit. --- ndcube/tests/test_ndcube.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index cd0922c94..8f20ef93f 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1152,25 +1152,33 @@ def test_cube_add_unit_none(ndcube_2d_unit_None, value): result_data=new_cube.data, correlation=0, ) - if expected_uncertainty.unit is None: - expected_uncertainty = StdDevUncertainty(expected_uncertainty.array) assert np.allclose(new_cube.data, ndcube_2d_unit_None.data + value.data) # check value of addition result - assert new_cube.unit is None # check unit assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty -# The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. +# The case when only one of them has a unit. other attributes such as result value or uncertainty do not matter. +# An expected typeError should be raised. +@pytest.mark.parametrize('value', [ + NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + unit=u.m, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), +]) +def test_cube_add_unit(ndcube_2d_unit_None, value): + #new_cube = ndcube_2d_with_uncertainty + value # perform the addition + with pytest.raises(TypeError, match="Cannot add unitless NDData to a unitful NDCube."): + ndcube_2d_unit_None + value # This should raise a TypeError + +# The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. NO UNIT. @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. wcs=None, uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), ]) def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): - #print('Evidence of entering.') new_cube = ndcube_2d_with_uncertainty + value # perform the addition - #new_cube.unit = value.unit # Check uncertainty propagation expected_uncertainty = ndcube_2d_with_uncertainty.uncertainty.propagate( operation=np.add, From b284e1fc434eb46b7fb5656bfb6d10748962c5db Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 21 Feb 2025 15:57:09 +0000 Subject: [PATCH 34/90] Test case for both objects having the same unit, and causes TypeError. --- ndcube/conftest.py | 6 +++--- ndcube/ndcube.py | 5 +++++ ndcube/tests/test_ndcube.py | 23 +++++++++++++++-------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 453cd778d..ff78decb8 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -679,12 +679,12 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): @pytest.fixture -def ndcube_2d_with_uncertainty(wcs_2d_lt_ln): +def ndcube_2d_with_unit_uncertainty(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) - uncertainty = StdDevUncertainty(np.ones(shape)*0.2) + uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=u.ct) - return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, unit=u.ct) @pytest.fixture diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 237e8de3f..2eb33fd99 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -993,6 +993,11 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): if self.uncertainty is not None and value.uncertainty is not None: #new_uncertainty = new_uncertainty.propagate(np.power, self, self.data ** value, correlation=1) #print(self.unit, value.unit, kwargs['data']) + #print(f"Before propagate - NDCube Uncertainty Type: {type(self.uncertainty)}") # Output: + #print(f"Before propagate - NDCube Uncertainty Unit: {getattr(self.uncertainty, 'unit', None)}") # Output: ct + #print(f"Before propagate - NDData Uncertainty Type: {type(value.uncertainty)}") # Output: + #print(f"Before propagate - NDData Uncertainty Unit: {getattr(value.uncertainty, 'unit', None)}") # Output: ct + new_uncertainty = self.uncertainty.propagate( np.add, value, result_data = kwargs["data"], correlation=0 ) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 8f20ef93f..82722593a 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1124,7 +1124,7 @@ def check_arithmetic_value_and_units(cube_new, data_expected): cube_quantity = u.Quantity(cube_new.data, cube_new.unit) assert u.allclose(cube_quantity, data_expected) -# values passed in are integers/random integers with a unit being ct. should not be random here? + @pytest.mark.parametrize('value', [ 10 * u.ct, u.Quantity([10], u.ct), @@ -1160,33 +1160,40 @@ def test_cube_add_unit_none(ndcube_2d_unit_None, value): # The case when only one of them has a unit. other attributes such as result value or uncertainty do not matter. # An expected typeError should be raised. +# TODO: another similar case, but NDCube having a unit and NDData has None unit (the other way around). @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. wcs=None, unit=u.m, uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), ]) -def test_cube_add_unit(ndcube_2d_unit_None, value): - #new_cube = ndcube_2d_with_uncertainty + value # perform the addition +def test_cube_add_one_unit(ndcube_2d_unit_None, value): with pytest.raises(TypeError, match="Cannot add unitless NDData to a unitful NDCube."): ndcube_2d_unit_None + value # This should raise a TypeError -# The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. NO UNIT. +# The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. +# Both NDData NDCube have a unit. +# Test different scenarios when units are equivalent and when they are not. @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. wcs=None, + unit=u.ct, uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), ]) -def test_cube_add_uncertainty(ndcube_2d_with_uncertainty, value): - new_cube = ndcube_2d_with_uncertainty + value # perform the addition +def test_cube_add_both_unit(ndcube_2d_with_unit_uncertainty, value): + #print(f"NDCube unit: {ndcube_2d_with_unit_uncertainty.unit}") # Output: NDCube unit: ct + #print(f"NDData unit: {value.unit}") # Output: NDData unit: ct + + new_cube = ndcube_2d_with_unit_uncertainty + value # perform the addition # Check uncertainty propagation - expected_uncertainty = ndcube_2d_with_uncertainty.uncertainty.propagate( + expected_uncertainty = ndcube_2d_with_unit_uncertainty.uncertainty.propagate( operation=np.add, other_nddata=value, result_data=new_cube.data, correlation=0, ) - assert np.allclose(new_cube.data, ndcube_2d_with_uncertainty.data + value.data) # check value of addition result + assert np.allclose(new_cube.data, ndcube_2d_with_unit_uncertainty.data + value.data) + assert new_cube.unit == u.ct assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty From 379faacef73447f1042e34980e43df554d9ac2cc Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 24 Feb 2025 12:05:46 +0000 Subject: [PATCH 35/90] Fix test for adding nddata and ndcube uncertainties. --- ndcube/ndcube.py | 12 ++++-------- ndcube/tests/test_ndcube.py | 7 ++----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 2eb33fd99..4ed83c95f 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -991,15 +991,11 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): # combine the uncertainty; if self.uncertainty is not None and value.uncertainty is not None: - #new_uncertainty = new_uncertainty.propagate(np.power, self, self.data ** value, correlation=1) - #print(self.unit, value.unit, kwargs['data']) - #print(f"Before propagate - NDCube Uncertainty Type: {type(self.uncertainty)}") # Output: - #print(f"Before propagate - NDCube Uncertainty Unit: {getattr(self.uncertainty, 'unit', None)}") # Output: ct - #print(f"Before propagate - NDData Uncertainty Type: {type(value.uncertainty)}") # Output: - #print(f"Before propagate - NDData Uncertainty Unit: {getattr(value.uncertainty, 'unit', None)}") # Output: ct - + result_data = kwargs["data"] + if self.unit is not None: + result_data *= self.unit new_uncertainty = self.uncertainty.propagate( - np.add, value, result_data = kwargs["data"], correlation=0 + np.add, value, result_data=result_data, correlation=0 ) kwargs["uncertainty"] = new_uncertainty elif self.uncertainty is not None: diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 82722593a..1a16a9f29 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1178,18 +1178,15 @@ def test_cube_add_one_unit(ndcube_2d_unit_None, value): NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. wcs=None, unit=u.ct, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)), ]) def test_cube_add_both_unit(ndcube_2d_with_unit_uncertainty, value): - #print(f"NDCube unit: {ndcube_2d_with_unit_uncertainty.unit}") # Output: NDCube unit: ct - #print(f"NDData unit: {value.unit}") # Output: NDData unit: ct - new_cube = ndcube_2d_with_unit_uncertainty + value # perform the addition # Check uncertainty propagation expected_uncertainty = ndcube_2d_with_unit_uncertainty.uncertainty.propagate( operation=np.add, other_nddata=value, - result_data=new_cube.data, + result_data=new_cube.data*new_cube.unit, correlation=0, ) assert np.allclose(new_cube.data, ndcube_2d_with_unit_uncertainty.data + value.data) From c6070c07f6349d95287789a296327b7303846d51 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 24 Feb 2025 19:03:21 +0000 Subject: [PATCH 36/90] Added more test functions for full coverage. --- ndcube/conftest.py | 15 ++++++++++ ndcube/ndcube.py | 1 - ndcube/tests/test_ndcube.py | 55 ++++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index ff78decb8..e9ad24ac2 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -678,6 +678,13 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): return NDCube(data_cube, wcs=wcs_2d_lt_ln, unit=u.ct) +@pytest.fixture +def ndcube_2d_ln_lt_no_unit_uncert(wcs_2d_lt_ln): + shape = (10, 12) + data_cube = data_nd(shape).astype(float) + return NDCube(data_cube, wcs=wcs_2d_lt_ln) + + @pytest.fixture def ndcube_2d_with_unit_uncertainty(wcs_2d_lt_ln): shape = (10, 12) @@ -696,6 +703,14 @@ def ndcube_2d_unit_None(wcs_2d_lt_ln): return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) +@pytest.fixture +def ndcube_2d_ln_lt_mask(wcs_2d_lt_ln): + shape = (10, 12) + data_cube = data_nd(shape).astype(float) + mask = np.ones(data_cube.shape, dtype=bool) + return NDCube(data_cube, wcs=wcs_2d_lt_ln, mask=mask) + + @pytest.fixture def ndcube_2d_dask(wcs_2d_lt_ln): shape = (8, 4) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 4ed83c95f..0158cac2e 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1041,7 +1041,6 @@ def __add__(self, value): self_masked = not(self.mask is None or self.mask is False or not self.mask.any()) value_masked = not(value.mask is None or value.mask is False or not value.mask.any()) if hasattr(value, "mask") else False - # tidying this up. if (value_masked or (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None)): # value has a mask, # let the users call the add method raise TypeError('Please use the add method.') diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 1a16a9f29..c2b33dd49 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1171,7 +1171,7 @@ def test_cube_add_one_unit(ndcube_2d_unit_None, value): with pytest.raises(TypeError, match="Cannot add unitless NDData to a unitful NDCube."): ndcube_2d_unit_None + value # This should raise a TypeError -# The case when NDData has uncertainty, NDCube has uncertainty, but no mask is involved. +# The case when both NDData and NDCube have uncertainty. No mask is involved. # Both NDData NDCube have a unit. # Test different scenarios when units are equivalent and when they are not. @pytest.mark.parametrize('value', [ @@ -1196,6 +1196,59 @@ def test_cube_add_both_unit(ndcube_2d_with_unit_uncertainty, value): f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty +# NDCube has no uncertainty. +@pytest.mark.parametrize('value', [ + NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + unit=u.ct, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)), +]) +def test_cube_add_ndcube_uncertainty_none(ndcube_2d_ln_lt_units, value): + new_cube = ndcube_2d_ln_lt_units + value # perform the addition + + assert new_cube.unit == u.ct + assert np.allclose(new_cube.data, ndcube_2d_ln_lt_units.data + value.data) # check value of addition result + + +# NDData has no uncertainty. Both have units. +@pytest.mark.parametrize('value', [ + NDData(np.ones((10, 12)), unit=u.ct), +]) + +def test_cube_add_no_uncertainty(ndcube_2d_ln_lt_units, value): + new_cube = ndcube_2d_ln_lt_units + value # perform the addition + + assert new_cube.unit == u.ct + assert np.allclose(new_cube.data, ndcube_2d_ln_lt_units.data + value.data) # check value of addition result + + +# Neither NDData nor NDCube has uncertainty or unit. +@pytest.mark.parametrize('value', [ + NDData(np.ones((10, 12))), # NDData without unit, without uncertainty +]) +def test_cube_add_nddata_uncertainty_none(ndcube_2d_ln_lt_no_unit_uncert, value): + new_cube = ndcube_2d_ln_lt_no_unit_uncert + value # perform the addition + + assert np.allclose(new_cube.data, ndcube_2d_ln_lt_no_unit_uncert.data + value.data) # check value of addition result + + +# The case when both NDData and NDCube have uncertainty, unit. Also: +# NDCube has mask +# Then, NDData has mask (add one new parametrize). +@pytest.mark.parametrize('value', [ + NDData(np.ones((10, 12)), + wcs=None, + uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1)), + + NDData(np.ones((10, 12)) * 2, + wcs=None, + uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.05), + mask=np.ones((10, 12), dtype=bool)) +]) +def test_cube_add_masked_value(ndcube_2d_ln_lt_mask, value): + with pytest.raises(TypeError, match='Please use the add method.'): + ndcube_2d_ln_lt_mask + value + @pytest.mark.parametrize('value', [ 10 * u.ct, u.Quantity([10], u.ct), From d88838dd92cdf85126cbbe2fee64d1a69ddf78ec Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 26 Feb 2025 18:56:03 +0000 Subject: [PATCH 37/90] Fix pytest indirect issue: Added cube(request) fixture to correctly resolve parameterized fixtures via getfixturevalue(). --- ndcube/conftest.py | 1 + ndcube/ndcube.py | 2 +- ndcube/tests/test_ndcube.py | 43 ++++++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index e9ad24ac2..20a1448fc 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -752,6 +752,7 @@ def ndcube_1d_l(wcs_1d_l): "ndcube_2d_ln_lt_units", "ndcube_2d_dask", "ndcube_1d_l", + "ndcube_2d_unit_None", ]) def all_ndcubes(request): """ diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 0158cac2e..1b14fdd61 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -978,7 +978,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): elif self.unit is None and value.unit is None: value_data = value.data else: - raise TypeError("Cannot add unitless NDData to a unitful NDCube.") + raise TypeError("Adding objects requires both have a unit or neither has a unit.") # change the test as well. # check whether there is a mask. # Neither self nor value has a mask diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index c2b33dd49..62eec4a07 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1158,22 +1158,39 @@ def test_cube_add_unit_none(ndcube_2d_unit_None, value): f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty +@pytest.fixture +def cube(request): + """Explicitly get fixture values.""" + return request.getfixturevalue(request.param) + # The case when only one of them has a unit. other attributes such as result value or uncertainty do not matter. # An expected typeError should be raised. -# TODO: another similar case, but NDCube having a unit and NDData has None unit (the other way around). -@pytest.mark.parametrize('value', [ - NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. - wcs=None, - unit=u.m, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), -]) -def test_cube_add_one_unit(ndcube_2d_unit_None, value): - with pytest.raises(TypeError, match="Cannot add unitless NDData to a unitful NDCube."): - ndcube_2d_unit_None + value # This should raise a TypeError +# TODO: both have a unit, neither has a unit, one has a unit. +# For now, try figuring out how to make the test be able to +@pytest.mark.parametrize(("cube", "value"), + [ + ("ndcube_2d_unit_None", NDData(np.ones((10, 12)), + wcs=None, + unit=u.m, + uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1)) + ), + ("ndcube_2d_ln_lt_units", NDData(np.ones((10, 12)), + wcs=None, + uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1)) + ), + ], + indirect=["cube"]) +def test_cube_add_one_unit(cube, value): + #print(f"cube: {cube}") + assert isinstance(cube, NDCube) + with pytest.raises(TypeError, match="Adding objects requires both have a unit or neither has a unit."): + cube + value # The case when both NDData and NDCube have uncertainty. No mask is involved. # Both NDData NDCube have a unit. -# Test different scenarios when units are equivalent and when they are not. +# Test different scenarios when units are equivalent and when they are not. TODO (bc somewhere is checking the units are the same) +# 1, user input, expected output +# 2, check the codebase itself @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. wcs=None, @@ -1190,7 +1207,7 @@ def test_cube_add_both_unit(ndcube_2d_with_unit_uncertainty, value): correlation=0, ) assert np.allclose(new_cube.data, ndcube_2d_with_unit_uncertainty.data + value.data) - assert new_cube.unit == u.ct + assert new_cube.unit == u.ct # sometimes explicit, in case the other part is also wrong. assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty @@ -1214,7 +1231,6 @@ def test_cube_add_ndcube_uncertainty_none(ndcube_2d_ln_lt_units, value): @pytest.mark.parametrize('value', [ NDData(np.ones((10, 12)), unit=u.ct), ]) - def test_cube_add_no_uncertainty(ndcube_2d_ln_lt_units, value): new_cube = ndcube_2d_ln_lt_units + value # perform the addition @@ -1249,6 +1265,7 @@ def test_cube_add_masked_value(ndcube_2d_ln_lt_mask, value): with pytest.raises(TypeError, match='Please use the add method.'): ndcube_2d_ln_lt_mask + value + @pytest.mark.parametrize('value', [ 10 * u.ct, u.Quantity([10], u.ct), From f0971103547ff385e4b1fa94e3410de64aa7fffb Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Wed, 26 Feb 2025 20:05:33 +0000 Subject: [PATCH 38/90] Fix indirect fixture reference. --- ndcube/tests/test_ndcube.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 62eec4a07..0230e603a 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1158,16 +1158,11 @@ def test_cube_add_unit_none(ndcube_2d_unit_None, value): f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty -@pytest.fixture -def cube(request): - """Explicitly get fixture values.""" - return request.getfixturevalue(request.param) - # The case when only one of them has a unit. other attributes such as result value or uncertainty do not matter. # An expected typeError should be raised. # TODO: both have a unit, neither has a unit, one has a unit. # For now, try figuring out how to make the test be able to -@pytest.mark.parametrize(("cube", "value"), +@pytest.mark.parametrize(("ndc", "value"), [ ("ndcube_2d_unit_None", NDData(np.ones((10, 12)), wcs=None, @@ -1179,12 +1174,11 @@ def cube(request): uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1)) ), ], - indirect=["cube"]) -def test_cube_add_one_unit(cube, value): - #print(f"cube: {cube}") - assert isinstance(cube, NDCube) + indirect=("ndc",)) +def test_cube_add_one_unit(ndc, value): + assert isinstance(ndc, NDCube) with pytest.raises(TypeError, match="Adding objects requires both have a unit or neither has a unit."): - cube + value + ndc + value # The case when both NDData and NDCube have uncertainty. No mask is involved. # Both NDData NDCube have a unit. From 55654088dcfc1dc62a0eaeebbe92e5852fe360dc Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Thu, 27 Feb 2025 01:06:48 +0000 Subject: [PATCH 39/90] Written all tests and fixed an error in ndcube with test results. --- ndcube/conftest.py | 10 +- ndcube/ndcube.py | 1 + ndcube/tests/test_ndcube.py | 204 +++++++++++++++++++++++++----------- 3 files changed, 147 insertions(+), 68 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 20a1448fc..bfef8d6a8 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -679,14 +679,14 @@ def ndcube_2d_ln_lt_units(wcs_2d_lt_ln): @pytest.fixture -def ndcube_2d_ln_lt_no_unit_uncert(wcs_2d_lt_ln): +def ndcube_2d_ln_lt_no_unit_no_unc(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) return NDCube(data_cube, wcs=wcs_2d_lt_ln) @pytest.fixture -def ndcube_2d_with_unit_uncertainty(wcs_2d_lt_ln): +def ndcube_2d_unit_unc(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) uncertainty = StdDevUncertainty(np.ones(shape)*0.2, unit=u.ct) @@ -695,7 +695,7 @@ def ndcube_2d_with_unit_uncertainty(wcs_2d_lt_ln): @pytest.fixture -def ndcube_2d_unit_None(wcs_2d_lt_ln): +def ndcube_2d_uncertainty_no_unit(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape).astype(float) uncertainty = StdDevUncertainty(np.ones(shape)*0.2) @@ -752,7 +752,9 @@ def ndcube_1d_l(wcs_1d_l): "ndcube_2d_ln_lt_units", "ndcube_2d_dask", "ndcube_1d_l", - "ndcube_2d_unit_None", + "ndcube_2d_ln_lt_no_unit_no_unc", + "ndcube_2d_uncertainty_no_unit", + "ndcube_2d_unit_unc", ]) def all_ndcubes(request): """ diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 1b14fdd61..9f62cff62 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1003,6 +1003,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): kwargs["uncertainty"] = new_uncertainty elif value.uncertainty is not None: new_uncertainty = value.uncertainty + kwargs["uncertainty"] = new_uncertainty else: new_uncertainty = None else: diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 0230e603a..d5395cede 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1138,33 +1138,11 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): # this test methods check_arithmetic_value_and_units(new_cube, cube_quantity + value) -# value is an NDData, The case when neither NDData nor NDCube has a unit. -@pytest.mark.parametrize('value', [ - NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. - wcs=None, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)), -]) -def test_cube_add_unit_none(ndcube_2d_unit_None, value): - new_cube = ndcube_2d_unit_None + value # perform the addition - expected_uncertainty = ndcube_2d_unit_None.uncertainty.propagate( - operation=np.add, - other_nddata=value, - result_data=new_cube.data, - correlation=0, - ) - assert np.allclose(new_cube.data, ndcube_2d_unit_None.data + value.data) # check value of addition result - assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ - f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty - - -# The case when only one of them has a unit. other attributes such as result value or uncertainty do not matter. +# Only one of them has a unit. # An expected typeError should be raised. -# TODO: both have a unit, neither has a unit, one has a unit. -# For now, try figuring out how to make the test be able to @pytest.mark.parametrize(("ndc", "value"), [ - ("ndcube_2d_unit_None", NDData(np.ones((10, 12)), + ("ndcube_2d_uncertainty_no_unit", NDData(np.ones((10, 12)), wcs=None, unit=u.m, uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.1)) @@ -1180,66 +1158,164 @@ def test_cube_add_one_unit(ndc, value): with pytest.raises(TypeError, match="Adding objects requires both have a unit or neither has a unit."): ndc + value -# The case when both NDData and NDCube have uncertainty. No mask is involved. -# Both NDData NDCube have a unit. + +# Both NDData and NDCube have unit and uncertainty. No mask is involved. # Test different scenarios when units are equivalent and when they are not. TODO (bc somewhere is checking the units are the same) -# 1, user input, expected output -# 2, check the codebase itself -@pytest.mark.parametrize('value', [ - NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. - wcs=None, - unit=u.ct, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)), -]) -def test_cube_add_both_unit(ndcube_2d_with_unit_uncertainty, value): - new_cube = ndcube_2d_with_unit_uncertainty + value # perform the addition +# what is an equivalent unit in astropy for count (ct)? +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_unit_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + unit=u.ct, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_unit_unc_nddata_unit_unc(ndc, value): + new_cube = ndc + value # perform the addition # Check uncertainty propagation - expected_uncertainty = ndcube_2d_with_unit_uncertainty.uncertainty.propagate( + expected_uncertainty = ndc.uncertainty.propagate( operation=np.add, other_nddata=value, result_data=new_cube.data*new_cube.unit, correlation=0, ) - assert np.allclose(new_cube.data, ndcube_2d_with_unit_uncertainty.data + value.data) - assert new_cube.unit == u.ct # sometimes explicit, in case the other part is also wrong. + assert np.allclose(new_cube.data, ndc.data + value.data) + assert new_cube.unit == u.ct assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty -# NDCube has no uncertainty. -@pytest.mark.parametrize('value', [ - NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. - wcs=None, - unit=u.ct, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)), -]) -def test_cube_add_ndcube_uncertainty_none(ndcube_2d_ln_lt_units, value): - new_cube = ndcube_2d_ln_lt_units + value # perform the addition +# Both have unit, NDCube has no uncertainty and NDData has uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_ln_lt_units", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + unit=u.ct, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_unit_nddata_unit_unc(ndc, value): + new_cube = ndc + value # perform the addition assert new_cube.unit == u.ct - assert np.allclose(new_cube.data, ndcube_2d_ln_lt_units.data + value.data) # check value of addition result + assert type(new_cube.uncertainty) is type(value.uncertainty) # check type of uncertainty + assert np.allclose(new_cube.uncertainty.array, value.uncertainty.array), \ + f"Expected uncertainty: {value.uncertainty.array}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result -# NDData has no uncertainty. Both have units. -@pytest.mark.parametrize('value', [ - NDData(np.ones((10, 12)), unit=u.ct), -]) -def test_cube_add_no_uncertainty(ndcube_2d_ln_lt_units, value): - new_cube = ndcube_2d_ln_lt_units + value # perform the addition + +# Both have units, NDData has no uncertainty and NDCube has uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_unit_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + unit=u.ct) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_unit_unc_nddata_unit(ndc, value): + new_cube = ndc + value # perform the addition assert new_cube.unit == u.ct - assert np.allclose(new_cube.data, ndcube_2d_ln_lt_units.data + value.data) # check value of addition result + assert type(new_cube.uncertainty) is type(ndc.uncertainty) # check type of uncertainty + assert np.allclose(new_cube.uncertainty.array, ndc.uncertainty.array), \ + f"Expected uncertainty: {ndc.uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result -# Neither NDData nor NDCube has uncertainty or unit. -@pytest.mark.parametrize('value', [ - NDData(np.ones((10, 12))), # NDData without unit, without uncertainty -]) -def test_cube_add_nddata_uncertainty_none(ndcube_2d_ln_lt_no_unit_uncert, value): - new_cube = ndcube_2d_ln_lt_no_unit_uncert + value # perform the addition - assert np.allclose(new_cube.data, ndcube_2d_ln_lt_no_unit_uncert.data + value.data) # check value of addition result +# Both have units, neither has uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_ln_lt_units", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + unit=u.ct) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_unit_nddata_unit(ndc, value): + new_cube = ndc + value # perform the addition + + assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + + +# Neither has a unit, both have uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_uncertainty_no_unit", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_unc_nddata_unc(ndc, value): + new_cube = ndc + value # perform the addition + + # Check uncertainty propagation + expected_uncertainty = ndc.uncertainty.propagate( + operation=np.add, + other_nddata=value, + result_data=new_cube.data, + correlation=0, + ) + assert np.allclose(new_cube.data, ndc.data + value.data) + assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty + assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ + f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + + +# Neither has a unit, NDData has uncertainty and NDCube has no uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_ln_lt_no_unit_no_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None, + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_nddata_unc(ndc, value): + new_cube = ndc + value # perform the addition + assert type(new_cube.uncertainty) is type(value.uncertainty) # check type of uncertainty + assert np.allclose(new_cube.uncertainty.array, value.uncertainty.array), \ + f"Expected uncertainty: {value.uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + + assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + + +# Neither has a unit, NDData has no uncertainty and NDCube has uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_uncertainty_no_unit", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_unc_nddata(ndc, value): + new_cube = ndc + value # perform the addition + + assert type(new_cube.uncertainty) is type(ndc.uncertainty) # check type of uncertainty + assert np.allclose(new_cube.uncertainty.array, ndc.uncertainty.array), \ + f"Expected uncertainty: {ndc.uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + + assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + + +# Neither has unit or uncertainty. +@pytest.mark.parametrize(("ndc", "value"), + [ + ("ndcube_2d_ln_lt_no_unit_no_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. + wcs=None) + ), + ], + indirect=("ndc",)) +def test_cube_add_cube_nddata(ndc, value): + new_cube = ndc + value # perform the addition + + assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result # The case when both NDData and NDCube have uncertainty, unit. Also: @@ -1255,7 +1331,7 @@ def test_cube_add_nddata_uncertainty_none(ndcube_2d_ln_lt_no_unit_uncert, value) uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.05), mask=np.ones((10, 12), dtype=bool)) ]) -def test_cube_add_masked_value(ndcube_2d_ln_lt_mask, value): +def test_cube_add_cube_unit_mask_nddata_unc_unit_mask(ndcube_2d_ln_lt_mask, value): with pytest.raises(TypeError, match='Please use the add method.'): ndcube_2d_ln_lt_mask + value From 51d26c167407f2807fced177fc764f29542d0f36 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Thu, 27 Feb 2025 01:16:40 +0000 Subject: [PATCH 40/90] Fixed a small error in a test function. --- ndcube/tests/test_ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index d5395cede..d2ae1418c 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1273,7 +1273,7 @@ def test_cube_add_cube_unc_nddata_unc(ndc, value): [ ("ndcube_2d_ln_lt_no_unit_no_unc", NDData(np.ones((10, 12)), # pass in the values to be tested as a set of ones. wcs=None, - uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1, unit=u.ct)) + uncertainty=StdDevUncertainty(np.ones((10, 12))*0.1)) ), ], indirect=("ndc",)) From 46cef9db0ed34e7c7c920ea5ba33543dd3b854c0 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 28 Feb 2025 18:06:20 +0000 Subject: [PATCH 41/90] changed assert_cubes_equal, fixed self-referring of tests. --- ndcube/tests/helpers.py | 12 ++++- ndcube/tests/test_ndcube.py | 105 +++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 45 deletions(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index a9e7d0727..b898c1b8e 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -122,13 +122,21 @@ def assert_metas_equal(test_input, expected_output): assert test_input[key] == expected_output[key] -def assert_cubes_equal(test_input, expected_cube, check_data=True): +def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncertainty_values=False): assert isinstance(test_input, type(expected_cube)) assert np.all(test_input.mask == expected_cube.mask) if check_data: np.testing.assert_array_equal(test_input.data, expected_cube.data) assert_wcs_are_equal(test_input.wcs, expected_cube.wcs) - if test_input.uncertainty: + if check_uncertainty_values: + # Check output and expected uncertainty are of same type. Remember they could be None. + # If the uncertainties are not None,... + # Check units, shape, and values of the uncertainty. + if (test_input.uncertainty is not None and expected_cube.uncertainty is not None): + assert type(test_input.uncertainty) is type(expected_cube.uncertainty) + assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ + f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" + elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) assert_metas_equal(test_input.meta, expected_cube.meta) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index d2ae1418c..5d8e1abaa 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -23,6 +23,7 @@ from ndcube import ExtraCoords, NDCube, NDMeta from ndcube.tests import helpers +from ndcube.tests.helpers import assert_cubes_equal from ndcube.utils.exceptions import NDCubeUserWarning @@ -1172,20 +1173,19 @@ def test_cube_add_one_unit(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_unit_unc_nddata_unit_unc(ndc, value): - new_cube = ndc + value # perform the addition - # Check uncertainty propagation + output_cube = ndc + value # perform the addition + # Construct expected cube + expected_unit = u.ct + expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit) expected_uncertainty = ndc.uncertainty.propagate( operation=np.add, other_nddata=value, - result_data=new_cube.data*new_cube.unit, + result_data=expected_data*expected_unit, correlation=0, ) - assert np.allclose(new_cube.data, ndc.data + value.data) - assert new_cube.unit == u.ct - assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ - f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty - + expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty, unit=expected_unit) + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True) # Both have unit, NDCube has no uncertainty and NDData has uncertainty. @pytest.mark.parametrize(("ndc", "value"), @@ -1198,14 +1198,16 @@ def test_cube_add_cube_unit_unc_nddata_unit_unc(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_unit_nddata_unit_unc(ndc, value): - new_cube = ndc + value # perform the addition + output_cube = ndc + value # perform the addition - assert new_cube.unit == u.ct - assert type(new_cube.uncertainty) is type(value.uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, value.uncertainty.array), \ - f"Expected uncertainty: {value.uncertainty.array}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + # Construct expected cube + expected_unit = u.ct + expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit) + expected_uncertainty = value.uncertainty - assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty, unit=expected_unit) + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True) # Both have units, NDData has no uncertainty and NDCube has uncertainty. @@ -1218,14 +1220,16 @@ def test_cube_add_cube_unit_nddata_unit_unc(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_unit_unc_nddata_unit(ndc, value): - new_cube = ndc + value # perform the addition + output_cube = ndc + value # perform the addition - assert new_cube.unit == u.ct - assert type(new_cube.uncertainty) is type(ndc.uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, ndc.uncertainty.array), \ - f"Expected uncertainty: {ndc.uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + # Construct expected cube + expected_unit = u.ct + expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit) + expected_uncertainty = ndc.uncertainty - assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty, unit=expected_unit) + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True) # Both have units, neither has uncertainty. @@ -1238,9 +1242,15 @@ def test_cube_add_cube_unit_unc_nddata_unit(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_unit_nddata_unit(ndc, value): - new_cube = ndc + value # perform the addition + output_cube = ndc + value # perform the addition + + # Construct expected cube + expected_unit = u.ct + expected_data = ((ndc.data * ndc.unit) + (value.data * value.unit)).to_value(expected_unit) + expected_cube = NDCube(expected_data, ndc.wcs, unit=expected_unit) - assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube) # Neither has a unit, both have uncertainty. @@ -1253,19 +1263,18 @@ def test_cube_add_cube_unit_nddata_unit(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_unc_nddata_unc(ndc, value): - new_cube = ndc + value # perform the addition - - # Check uncertainty propagation + output_cube = ndc + value # perform the addition + # Construct expected cube + expected_data = ndc.data + value.data expected_uncertainty = ndc.uncertainty.propagate( operation=np.add, other_nddata=value, - result_data=new_cube.data, + result_data=expected_data, correlation=0, ) - assert np.allclose(new_cube.data, ndc.data + value.data) - assert type(new_cube.uncertainty) is type(expected_uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, expected_uncertainty.array), \ - f"Expected uncertainty: {expected_uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty) + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube, check_uncertainty_values=True) # Neither has a unit, NDData has uncertainty and NDCube has no uncertainty. @@ -1278,12 +1287,15 @@ def test_cube_add_cube_unc_nddata_unc(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_nddata_unc(ndc, value): - new_cube = ndc + value # perform the addition - assert type(new_cube.uncertainty) is type(value.uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, value.uncertainty.array), \ - f"Expected uncertainty: {value.uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + output_cube = ndc + value # perform the addition - assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + # Construct expected cube + expected_data = ndc.data + value.data + expected_uncertainty = value.uncertainty + expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty) + + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube) # Neither has a unit, NDData has no uncertainty and NDCube has uncertainty. @@ -1295,13 +1307,15 @@ def test_cube_add_cube_nddata_unc(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_unc_nddata(ndc, value): - new_cube = ndc + value # perform the addition + output_cube = ndc + value # perform the addition - assert type(new_cube.uncertainty) is type(ndc.uncertainty) # check type of uncertainty - assert np.allclose(new_cube.uncertainty.array, ndc.uncertainty.array), \ - f"Expected uncertainty: {ndc.uncertainty}, but got: {new_cube.uncertainty.array}" # check value of uncertainty + # Construct expected cube + expected_data = ndc.data + value.data + expected_uncertainty = ndc.uncertainty + expected_cube = NDCube(expected_data, ndc.wcs, uncertainty=expected_uncertainty) - assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube) # Neither has unit or uncertainty. @@ -1313,9 +1327,14 @@ def test_cube_add_cube_unc_nddata(ndc, value): ], indirect=("ndc",)) def test_cube_add_cube_nddata(ndc, value): - new_cube = ndc + value # perform the addition + output_cube = ndc + value # perform the addition + + # Construct expected cube + expected_data = ndc.data + value.data + expected_cube = NDCube(expected_data, ndc.wcs) - assert np.allclose(new_cube.data, ndc.data + value.data) # check value of addition result + # Assert output cube is same as expected cube + assert_cubes_equal(output_cube, expected_cube) # The case when both NDData and NDCube have uncertainty, unit. Also: From 1eb0357f99196b94e6e6ab5e653f1517f0dc7674 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:13:47 +0000 Subject: [PATCH 42/90] Update ndcube/tests/helpers.py Co-authored-by: DanRyanIrish --- ndcube/tests/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index b898c1b8e..a19f7944e 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -136,6 +136,8 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta assert type(test_input.uncertainty) is type(expected_cube.uncertainty) assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" + else: + assert (test_input.uncertainty is None and expected_cube.uncertainty is None) elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) From 553b1d723c63d200a133a0c64dc9edb01d0c82e6 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 3 Mar 2025 15:09:32 +0000 Subject: [PATCH 43/90] Changed the naming of test functions. --- ndcube/tests/test_ndcube.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 5d8e1abaa..0247609fd 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1154,7 +1154,7 @@ def test_cube_arithmetic_add(ndcube_2d_ln_lt_units, value): # this test methods ), ], indirect=("ndc",)) -def test_cube_add_one_unit(ndc, value): +def test_arithmetic_add_one_unit(ndc, value): assert isinstance(ndc, NDCube) with pytest.raises(TypeError, match="Adding objects requires both have a unit or neither has a unit."): ndc + value @@ -1172,7 +1172,7 @@ def test_cube_add_one_unit(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_unit_unc_nddata_unit_unc(ndc, value): +def test_arithmetic_add_cube_unit_unc_nddata_unit_unc(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube expected_unit = u.ct @@ -1197,7 +1197,7 @@ def test_cube_add_cube_unit_unc_nddata_unit_unc(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_unit_nddata_unit_unc(ndc, value): +def test_arithmetic_add_cube_unit_nddata_unit_unc(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube @@ -1219,7 +1219,7 @@ def test_cube_add_cube_unit_nddata_unit_unc(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_unit_unc_nddata_unit(ndc, value): +def test_arithmetic_add_cube_unit_unc_nddata_unit(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube @@ -1241,7 +1241,7 @@ def test_cube_add_cube_unit_unc_nddata_unit(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_unit_nddata_unit(ndc, value): +def test_arithmetic_add_cube_unit_nddata_unit(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube @@ -1262,7 +1262,7 @@ def test_cube_add_cube_unit_nddata_unit(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_unc_nddata_unc(ndc, value): +def test_arithmetic_add_cube_unc_nddata_unc(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube expected_data = ndc.data + value.data @@ -1286,7 +1286,7 @@ def test_cube_add_cube_unc_nddata_unc(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_nddata_unc(ndc, value): +def test_arithmetic_add_cube_nddata_unc(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube @@ -1306,7 +1306,7 @@ def test_cube_add_cube_nddata_unc(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_unc_nddata(ndc, value): +def test_arithmetic_add_cube_unc_nddata(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube @@ -1326,7 +1326,7 @@ def test_cube_add_cube_unc_nddata(ndc, value): ), ], indirect=("ndc",)) -def test_cube_add_cube_nddata(ndc, value): +def test_arithmetic_add_cube_nddata(ndc, value): output_cube = ndc + value # perform the addition # Construct expected cube @@ -1350,7 +1350,7 @@ def test_cube_add_cube_nddata(ndc, value): uncertainty=StdDevUncertainty(np.ones((10, 12)) * 0.05), mask=np.ones((10, 12), dtype=bool)) ]) -def test_cube_add_cube_unit_mask_nddata_unc_unit_mask(ndcube_2d_ln_lt_mask, value): +def test_arithmetic_add_cube_unit_mask_nddata_unc_unit_mask(ndcube_2d_ln_lt_mask, value): with pytest.raises(TypeError, match='Please use the add method.'): ndcube_2d_ln_lt_mask + value From fdecacd19d69640b5120dcd19de943b5daf20c54 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 3 Mar 2025 15:23:19 +0000 Subject: [PATCH 44/90] Changed the way to check whether both objects' uncertainty are None. --- ndcube/tests/helpers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index a19f7944e..8b1b7863e 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -136,8 +136,12 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta assert type(test_input.uncertainty) is type(expected_cube.uncertainty) assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" + else: + if test_input.uncertainty is None: + assert expected_cube.uncertainty is None, "Expected cube's uncertainty should also be None" else: - assert (test_input.uncertainty is None and expected_cube.uncertainty is None) + assert expected_cube.uncertainty is not None, "test_input has uncertainty but expected_cube does not" + elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) From 3ab2eadc8d9fa3ee6d02175debf30c5484fc42c4 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 3 Mar 2025 16:19:35 +0000 Subject: [PATCH 45/90] Three conditional scenarios. --- ndcube/tests/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 8b1b7863e..0f660b3bf 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -139,8 +139,10 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta else: if test_input.uncertainty is None: assert expected_cube.uncertainty is None, "Expected cube's uncertainty should also be None" + elif expected_cube.uncertainty is None: + assert test_input.uncertainty is None, "test_input should also be None" else: - assert expected_cube.uncertainty is not None, "test_input has uncertainty but expected_cube does not" + pass elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape From fdee14605ec87a331c543fdfc6f3ab4a682d0524 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 4 Mar 2025 11:01:37 +0000 Subject: [PATCH 46/90] Rewrote the uncertainty results checking. --- ndcube/tests/helpers.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 0f660b3bf..55ec34857 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -136,13 +136,12 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta assert type(test_input.uncertainty) is type(expected_cube.uncertainty) assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" - else: - if test_input.uncertainty is None: - assert expected_cube.uncertainty is None, "Expected cube's uncertainty should also be None" - elif expected_cube.uncertainty is None: + elif test_input.uncertainty is None: + assert expected_cube.uncertainty is None, "Expected cube's uncertainty should also be None" + elif expected_cube.uncertainty is None: assert test_input.uncertainty is None, "test_input should also be None" - else: - pass + else: + assert type(test_input.uncertainty) is type(expected_cube.uncertainty) elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape From d7558499b53670938260c5e3b6d8f9b81919efb0 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 4 Mar 2025 11:28:20 +0000 Subject: [PATCH 47/90] Rewrote the uncertainty checking again. --- ndcube/tests/helpers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 55ec34857..93944921d 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -137,11 +137,9 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" elif test_input.uncertainty is None: - assert expected_cube.uncertainty is None, "Expected cube's uncertainty should also be None" + assert expected_cube.uncertainty is None, "Test uncertainty should not be None." elif expected_cube.uncertainty is None: - assert test_input.uncertainty is None, "test_input should also be None" - else: - assert type(test_input.uncertainty) is type(expected_cube.uncertainty) + assert test_input.uncertainty is None, "Test uncertainty should be None." elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape From 5b9626f282c7b10b3c4bacb8cba2773b6cc6a561 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 11 Mar 2025 09:18:51 +0000 Subject: [PATCH 48/90] Implementing mask. --- ndcube/ndcube.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 9f62cff62..627fdfb6b 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1010,8 +1010,31 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): # TODO # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture. # Conditional statements to permutate the two different scenarios (when it does not ignore the mask). - raise NotImplementedError - + kwargs["data"] = self.data + value_data + self_data, value_data = self.data, value.data # May require a copy + self_mask, value_mask = self.mask, value.mask # May require handling/converting of cases when masks aren't boolean arrays but are None, True, or False. + if not operation_ignores_mask: + no_op_value = 0 # Value to set masked values since we are doing addition. (Would need to be 1 if we were doing multiplication.) + if (self_mask is True and value_mask is False): + idx = np.logical_and(self_mask, np.logical_not(value_mask)) + self_data[idx] = no_op_value + elif (self_mask is False and value_mask is True): + idx = np.logical_and(value_mask, np.logical_not(self_mask)) + value_data[idx] = no_op_value + elif (self_mask is True and value_mask is True): + idx = np.logical_and(self_mask, value_mask) + + #self_data[idx], value_data[idx] = ?, ? # Handle case when both values are masked here. # We are yet to decide the best behaviour here. + # if both are F, no operation of setting the values to be 0 needs to be done. + else: + pass # If operation ignores mask, nothing needs to be done. This line not needed in actual code. Only here for clarity. + + # Perform addition + new_data = self_data + value_data + # Calculate new mask. + new_mask = handle_mask(self_mask, value_mask) if handle_mask else None + kwargs["data"] = new_data + kwargs['mask'] = new_mask elif hasattr(value, 'unit'): if isinstance(value, u.Quantity): From 3f1b8efb4a15ffaa63ac5e25dc64b5c4f6e81c32 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 11 Mar 2025 09:23:23 +0000 Subject: [PATCH 49/90] Implementing mask. --- ndcube/ndcube.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 627fdfb6b..e12395f92 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1023,6 +1023,8 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): value_data[idx] = no_op_value elif (self_mask is True and value_mask is True): idx = np.logical_and(self_mask, value_mask) + self_data[idx] = no_op_value + value_data[idx] = no_op_value #self_data[idx], value_data[idx] = ?, ? # Handle case when both values are masked here. # We are yet to decide the best behaviour here. # if both are F, no operation of setting the values to be 0 needs to be done. From 22a673b69999fe01a6188a866e1c717f3d6519dd Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 19:33:16 +0000 Subject: [PATCH 50/90] Added Fill() Method's skeleton. --- ndcube/ndcube.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index e12395f92..cf397e267 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1036,7 +1036,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): # Calculate new mask. new_mask = handle_mask(self_mask, value_mask) if handle_mask else None kwargs["data"] = new_data - kwargs['mask'] = new_mask + kwargs["mask"] = new_mask elif hasattr(value, 'unit'): if isinstance(value, u.Quantity): @@ -1420,6 +1420,28 @@ def squeeze(self, axis=None): return self[tuple(item)] +def fill(fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): + """ + Replaces masked data values with input value. + + Returns a new instance or alters values in place. + + Parameters + ---------- + fill_value: `numbers.Number` or scalar `astropy.unit.Quantity` + The value to replace masked data with. + unmask: `bool`, optional + If True, the newly filled masked values are unmasked. If False, they remain masked + Default=False + uncertainty_fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`, optional + The value to replace masked uncertainties with. + fill_in_place: `bool`, optional + If `True`, the masked values are filled in place. If `False`, a new instance is returned + with masked values filled. Default=False. + """ + # ...code implementation here. + + def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): m = None if (mask is None or mask is False or operation_ignores_mask) else mask if m is None: From 8bb8e9f9ac36fed2d386f1f78d34c2749b1a6bbe Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 20:17:16 +0000 Subject: [PATCH 51/90] New PR, copied ndcube.py from the other branch nddataArithmetic. --- ndcube/ndcube.py | 122 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 5f5f4ee40..cf397e267 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -11,6 +11,7 @@ import astropy.nddata import astropy.units as u +from astropy.nddata import NDData from astropy.units import UnitsError from astropy.wcs.utils import _split_matrix @@ -964,15 +965,87 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def __add__(self, value): - if hasattr(value, 'unit'): + def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): + """ + Users are allowed to choose whether they want operation_ignores_mask to be True or False, + and are allowed to choose whether they want handle_mask to be AND / OR . + """ + kwargs = {} + + if isinstance(value, NDData) and value.wcs is None: + if self.unit is not None and value.unit is not None: + value_data = (value.data * value.unit).to_value(self.unit) + elif self.unit is None and value.unit is None: + value_data = value.data + else: + raise TypeError("Adding objects requires both have a unit or neither has a unit.") # change the test as well. + + # check whether there is a mask. + # Neither self nor value has a mask + self_unmasked = self.mask is None or self.mask is False or not self.mask.any() + value_unmasked = value.mask is None or value.mask is False or not value.mask.any() + + if (self_unmasked and value_unmasked) or operation_ignores_mask is True: + # addition + kwargs["data"] = self.data + value_data + + # combine the uncertainty; + if self.uncertainty is not None and value.uncertainty is not None: + result_data = kwargs["data"] + if self.unit is not None: + result_data *= self.unit + new_uncertainty = self.uncertainty.propagate( + np.add, value, result_data=result_data, correlation=0 + ) + kwargs["uncertainty"] = new_uncertainty + elif self.uncertainty is not None: + new_uncertainty = self.uncertainty + kwargs["uncertainty"] = new_uncertainty + elif value.uncertainty is not None: + new_uncertainty = value.uncertainty + kwargs["uncertainty"] = new_uncertainty + else: + new_uncertainty = None + else: + # TODO + # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture. + # Conditional statements to permutate the two different scenarios (when it does not ignore the mask). + kwargs["data"] = self.data + value_data + self_data, value_data = self.data, value.data # May require a copy + self_mask, value_mask = self.mask, value.mask # May require handling/converting of cases when masks aren't boolean arrays but are None, True, or False. + if not operation_ignores_mask: + no_op_value = 0 # Value to set masked values since we are doing addition. (Would need to be 1 if we were doing multiplication.) + if (self_mask is True and value_mask is False): + idx = np.logical_and(self_mask, np.logical_not(value_mask)) + self_data[idx] = no_op_value + elif (self_mask is False and value_mask is True): + idx = np.logical_and(value_mask, np.logical_not(self_mask)) + value_data[idx] = no_op_value + elif (self_mask is True and value_mask is True): + idx = np.logical_and(self_mask, value_mask) + self_data[idx] = no_op_value + value_data[idx] = no_op_value + + #self_data[idx], value_data[idx] = ?, ? # Handle case when both values are masked here. # We are yet to decide the best behaviour here. + # if both are F, no operation of setting the values to be 0 needs to be done. + else: + pass # If operation ignores mask, nothing needs to be done. This line not needed in actual code. Only here for clarity. + + # Perform addition + new_data = self_data + value_data + # Calculate new mask. + new_mask = handle_mask(self_mask, value_mask) if handle_mask else None + kwargs["data"] = new_data + kwargs["mask"] = new_mask + + elif hasattr(value, 'unit'): if isinstance(value, u.Quantity): # NOTE: if the cube does not have units, we cannot # perform arithmetic between a unitful quantity. # This forces a conversion to a dimensionless quantity # so that an error is thrown if value is not dimensionless cube_unit = u.Unit('') if self.unit is None else self.unit - new_data = self.data + value.to_value(cube_unit) + kwargs["data"] = self.data + value.to_value(cube_unit) else: # NOTE: This explicitly excludes other NDCube objects and NDData objects # which could carry a different WCS than the NDCube @@ -980,8 +1053,25 @@ def __add__(self, value): elif self.unit not in (None, u.Unit("")): raise TypeError("Cannot add a unitless object to an NDCube with a unit.") else: - new_data = self.data + value - return self._new_instance(data=new_data) + kwargs["data"] = self.data + value + + # return the new NDCube instance + return self._new_instance(**kwargs) + + def __add__(self, value): + # when value has a mask, raise error and point user to the add method. TODO + # + # check whether there is a mask. + # Neither self nor value has a mask + + self_masked = not(self.mask is None or self.mask is False or not self.mask.any()) + value_masked = not(value.mask is None or value.mask is False or not value.mask.any()) if hasattr(value, "mask") else False + + if (value_masked or (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None)): # value has a mask, + # let the users call the add method + raise TypeError('Please use the add method.') + + return self.add(value) # the mask keywords cannot be given by users. def __radd__(self, value): return self.__add__(value) @@ -1330,6 +1420,28 @@ def squeeze(self, axis=None): return self[tuple(item)] +def fill(fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): + """ + Replaces masked data values with input value. + + Returns a new instance or alters values in place. + + Parameters + ---------- + fill_value: `numbers.Number` or scalar `astropy.unit.Quantity` + The value to replace masked data with. + unmask: `bool`, optional + If True, the newly filled masked values are unmasked. If False, they remain masked + Default=False + uncertainty_fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`, optional + The value to replace masked uncertainties with. + fill_in_place: `bool`, optional + If `True`, the masked values are filled in place. If `False`, a new instance is returned + with masked values filled. Default=False. + """ + # ...code implementation here. + + def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): m = None if (mask is None or mask is False or operation_ignores_mask) else mask if m is None: From 4e5dcd06c6c90615d695e7016d281d3b73437bfa Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 20:24:24 +0000 Subject: [PATCH 52/90] Changed the code to be consistent with main/ndcube.py instead. --- ndcube/ndcube.py | 100 +++-------------------------------------------- 1 file changed, 5 insertions(+), 95 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index cf397e267..e06228960 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -11,7 +11,6 @@ import astropy.nddata import astropy.units as u -from astropy.nddata import NDData from astropy.units import UnitsError from astropy.wcs.utils import _split_matrix @@ -965,87 +964,15 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): - """ - Users are allowed to choose whether they want operation_ignores_mask to be True or False, - and are allowed to choose whether they want handle_mask to be AND / OR . - """ - kwargs = {} - - if isinstance(value, NDData) and value.wcs is None: - if self.unit is not None and value.unit is not None: - value_data = (value.data * value.unit).to_value(self.unit) - elif self.unit is None and value.unit is None: - value_data = value.data - else: - raise TypeError("Adding objects requires both have a unit or neither has a unit.") # change the test as well. - - # check whether there is a mask. - # Neither self nor value has a mask - self_unmasked = self.mask is None or self.mask is False or not self.mask.any() - value_unmasked = value.mask is None or value.mask is False or not value.mask.any() - - if (self_unmasked and value_unmasked) or operation_ignores_mask is True: - # addition - kwargs["data"] = self.data + value_data - - # combine the uncertainty; - if self.uncertainty is not None and value.uncertainty is not None: - result_data = kwargs["data"] - if self.unit is not None: - result_data *= self.unit - new_uncertainty = self.uncertainty.propagate( - np.add, value, result_data=result_data, correlation=0 - ) - kwargs["uncertainty"] = new_uncertainty - elif self.uncertainty is not None: - new_uncertainty = self.uncertainty - kwargs["uncertainty"] = new_uncertainty - elif value.uncertainty is not None: - new_uncertainty = value.uncertainty - kwargs["uncertainty"] = new_uncertainty - else: - new_uncertainty = None - else: - # TODO - # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture. - # Conditional statements to permutate the two different scenarios (when it does not ignore the mask). - kwargs["data"] = self.data + value_data - self_data, value_data = self.data, value.data # May require a copy - self_mask, value_mask = self.mask, value.mask # May require handling/converting of cases when masks aren't boolean arrays but are None, True, or False. - if not operation_ignores_mask: - no_op_value = 0 # Value to set masked values since we are doing addition. (Would need to be 1 if we were doing multiplication.) - if (self_mask is True and value_mask is False): - idx = np.logical_and(self_mask, np.logical_not(value_mask)) - self_data[idx] = no_op_value - elif (self_mask is False and value_mask is True): - idx = np.logical_and(value_mask, np.logical_not(self_mask)) - value_data[idx] = no_op_value - elif (self_mask is True and value_mask is True): - idx = np.logical_and(self_mask, value_mask) - self_data[idx] = no_op_value - value_data[idx] = no_op_value - - #self_data[idx], value_data[idx] = ?, ? # Handle case when both values are masked here. # We are yet to decide the best behaviour here. - # if both are F, no operation of setting the values to be 0 needs to be done. - else: - pass # If operation ignores mask, nothing needs to be done. This line not needed in actual code. Only here for clarity. - - # Perform addition - new_data = self_data + value_data - # Calculate new mask. - new_mask = handle_mask(self_mask, value_mask) if handle_mask else None - kwargs["data"] = new_data - kwargs["mask"] = new_mask - - elif hasattr(value, 'unit'): + def __add__(self, value): + if hasattr(value, 'unit'): if isinstance(value, u.Quantity): # NOTE: if the cube does not have units, we cannot # perform arithmetic between a unitful quantity. # This forces a conversion to a dimensionless quantity # so that an error is thrown if value is not dimensionless cube_unit = u.Unit('') if self.unit is None else self.unit - kwargs["data"] = self.data + value.to_value(cube_unit) + new_data = self.data + value.to_value(cube_unit) else: # NOTE: This explicitly excludes other NDCube objects and NDData objects # which could carry a different WCS than the NDCube @@ -1053,25 +980,8 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): elif self.unit not in (None, u.Unit("")): raise TypeError("Cannot add a unitless object to an NDCube with a unit.") else: - kwargs["data"] = self.data + value - - # return the new NDCube instance - return self._new_instance(**kwargs) - - def __add__(self, value): - # when value has a mask, raise error and point user to the add method. TODO - # - # check whether there is a mask. - # Neither self nor value has a mask - - self_masked = not(self.mask is None or self.mask is False or not self.mask.any()) - value_masked = not(value.mask is None or value.mask is False or not value.mask.any()) if hasattr(value, "mask") else False - - if (value_masked or (self_masked and hasattr(value,'uncertainty') and value.uncertainty is not None)): # value has a mask, - # let the users call the add method - raise TypeError('Please use the add method.') - - return self.add(value) # the mask keywords cannot be given by users. + new_data = self.data + value + return self._new_instance(data=new_data) def __radd__(self, value): return self.__add__(value) From 4ffb573f2d40ed5b510e7597a61614e4d0953875 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 21:30:40 +0000 Subject: [PATCH 53/90] Implementing NDCube.fill(). --- ndcube/ndcube.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index e06228960..edb1e209f 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1330,7 +1330,7 @@ def squeeze(self, axis=None): return self[tuple(item)] -def fill(fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): +def fill(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): """ Replaces masked data values with input value. @@ -1350,6 +1350,23 @@ def fill(fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=Fa with masked values filled. Default=False. """ # ...code implementation here. + kwargs = {} + kwargs["data"] = self.data + kwargs["mask"] = self.mask + kwargs["uncertainty"] = self.uncertainty + + # If there is a not None mask and a not None fill_value, do: change the corresponding data to fill_value. + if (fill_value is not None and self.mask is not None): + kwargs["data"][self.mask] = fill_value # Boolean indexing in Python. + # else, do nothing. + + # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. + if (unmask): + kwargs["mask"] = False + + if (self.mask is not None and uncertainty_fill_value is not None): + kwargs["uncertainty"][self.mask] = uncertainty_fill_value + def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): From fb36c8072a42786ac7ed97d83df3bf50bb7cbdcf Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 21:58:06 +0000 Subject: [PATCH 54/90] Added changelog, implementing NDCube.fill(). --- ndcube/ndcube.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index edb1e209f..2c1112eba 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1358,7 +1358,7 @@ def fill(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_pl # If there is a not None mask and a not None fill_value, do: change the corresponding data to fill_value. if (fill_value is not None and self.mask is not None): kwargs["data"][self.mask] = fill_value # Boolean indexing in Python. - # else, do nothing. + # else, do nothing to the data. # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. if (unmask): @@ -1368,6 +1368,10 @@ def fill(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_pl kwargs["uncertainty"][self.mask] = uncertainty_fill_value + # if fill_in_place is True, do: ; otherwise, do: + + return kwargs + def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): m = None if (mask is None or mask is False or operation_ignores_mask) else mask From e1919fa924304f6109c36253f9131a4171993c23 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 22:04:46 +0000 Subject: [PATCH 55/90] Added changelog again --- changelog/829.feature.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/829.feature.rst diff --git a/changelog/829.feature.rst b/changelog/829.feature.rst new file mode 100644 index 000000000..92d31989d --- /dev/null +++ b/changelog/829.feature.rst @@ -0,0 +1,2 @@ +Added ``fill`` method to ``NDCube``, a new feature which allows users to replace masked values and uncertainty values with user-given fill values, +to change the mask values back to False or not (Default), and to set whether the new instance is returned (Default) or not. From b06a1ccc38dce8b22982426747c7915f5e77751d Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 14 Mar 2025 22:29:03 +0000 Subject: [PATCH 56/90] If fill_in_place is False, then return kwargs. --- ndcube/ndcube.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 2c1112eba..5267e0a7a 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1355,20 +1355,21 @@ def fill(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_pl kwargs["mask"] = self.mask kwargs["uncertainty"] = self.uncertainty - # If there is a not None mask and a not None fill_value, do: change the corresponding data to fill_value. - if (fill_value is not None and self.mask is not None): - kwargs["data"][self.mask] = fill_value # Boolean indexing in Python. - # else, do nothing to the data. - # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. - if (unmask): - kwargs["mask"] = False + if (fill_in_place is False): + # If there is a not None mask and a not None fill_value, do: change the corresponding data to fill_value. + if (fill_value is not None and self.mask is not None): + kwargs["data"][self.mask] = fill_value # Boolean indexing in Python. + # else, do nothing to the data. - if (self.mask is not None and uncertainty_fill_value is not None): - kwargs["uncertainty"][self.mask] = uncertainty_fill_value + # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. + if (unmask): + kwargs["mask"] = False + if (self.mask is not None and uncertainty_fill_value is not None): + kwargs["uncertainty"][self.mask] = uncertainty_fill_value - # if fill_in_place is True, do: ; otherwise, do: + # if fill_in_place is True, then change self directly? without creating kwargs? return kwargs From 73cb945b1faf4402e48ec15be47162442156db9f Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 00:57:29 +0000 Subject: [PATCH 57/90] Update changelog/829.feature.rst Co-authored-by: DanRyanIrish --- changelog/829.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/829.feature.rst b/changelog/829.feature.rst index 92d31989d..800dc9619 100644 --- a/changelog/829.feature.rst +++ b/changelog/829.feature.rst @@ -1,2 +1,2 @@ -Added ``fill`` method to ``NDCube``, a new feature which allows users to replace masked values and uncertainty values with user-given fill values, +Added ``fill_masked`` method to ``NDCube``, a new feature which allows users to replace masked values and uncertainty values with user-given fill values, to change the mask values back to False or not (Default), and to set whether the new instance is returned (Default) or not. From 1715862a2c9dd3a0b24892840688ab64d3225d72 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 00:57:56 +0000 Subject: [PATCH 58/90] Update ndcube/ndcube.py rename the method Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 5267e0a7a..c37cf6cd9 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1330,7 +1330,7 @@ def squeeze(self, axis=None): return self[tuple(item)] -def fill(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): +def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): """ Replaces masked data values with input value. From 20945f267b0e135d136400dee195aa562ee2961c Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 00:58:10 +0000 Subject: [PATCH 59/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index c37cf6cd9..0844fa282 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1349,7 +1349,6 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil If `True`, the masked values are filled in place. If `False`, a new instance is returned with masked values filled. Default=False. """ - # ...code implementation here. kwargs = {} kwargs["data"] = self.data kwargs["mask"] = self.mask From 157f4b357f6c1c6a14cb9d12c408694c8aa52210 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 02:51:43 +0000 Subject: [PATCH 60/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 0844fa282..8229a7a60 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1349,10 +1349,14 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil If `True`, the masked values are filled in place. If `False`, a new instance is returned with masked values filled. Default=False. """ - kwargs = {} - kwargs["data"] = self.data - kwargs["mask"] = self.mask - kwargs["uncertainty"] = self.uncertainty + if fill_in_place: + new_data = self.data + new_uncertainty = self.uncertainty + # Unmasking in-place should be handled later. + else: + new_data = copy.deepcopy(self.data) + new_uncertainty = copy.deepcopy(self.uncertainty) + new_mask = False if unmask else copy.deepcopy(self.mask) if (fill_in_place is False): From 41d3b247ee7eaf3fe7376bc47dd08db36a5c81f0 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:56:35 +0000 Subject: [PATCH 61/90] Update ndcube/ndcube.py remove code about kwargs when fill_in_place is True. Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 8229a7a60..9586ebddd 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1366,8 +1366,6 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil # else, do nothing to the data. # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. - if (unmask): - kwargs["mask"] = False if (self.mask is not None and uncertainty_fill_value is not None): kwargs["uncertainty"][self.mask] = uncertainty_fill_value From 1afe30aa337aee25b80a793727c1c0934c63c279 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:58:52 +0000 Subject: [PATCH 62/90] Update ndcube/ndcube.py Already know that self.mask is not None, now, check whether uncertainty_fill_value and self.uncertainty is None, raise an error if self.uncertainty is None. Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 9586ebddd..60cee9d23 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1367,7 +1367,9 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. - if (self.mask is not None and uncertainty_fill_value is not None): + if uncertainty_fill_value is not None: + if not self.uncertainty: + raise TypeError("Cannot fill uncertainty as uncertainty is None.") kwargs["uncertainty"][self.mask] = uncertainty_fill_value # if fill_in_place is True, then change self directly? without creating kwargs? From f280941f011de19c1d6191eac95614a9faeb3519 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Sun, 16 Mar 2025 14:13:37 +0000 Subject: [PATCH 63/90] Update ndcube/ndcube.py Filling in the uncertainty_fill_value Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 60cee9d23..46a8e31e1 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1370,7 +1370,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil if uncertainty_fill_value is not None: if not self.uncertainty: raise TypeError("Cannot fill uncertainty as uncertainty is None.") - kwargs["uncertainty"][self.mask] = uncertainty_fill_value + new_uncertainty.array[idx_mask] = uncertainty_fill_value # if fill_in_place is True, then change self directly? without creating kwargs? From fb00430ff27be528c5e43696e6dfb97d86051ca7 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 17 Mar 2025 13:32:38 +0000 Subject: [PATCH 64/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 46a8e31e1..1438d8d44 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1372,9 +1372,10 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil raise TypeError("Cannot fill uncertainty as uncertainty is None.") new_uncertainty.array[idx_mask] = uncertainty_fill_value - # if fill_in_place is True, then change self directly? without creating kwargs? - - return kwargs + if not fill_in_place: + # Create kwargs dictionary and return a new instance. + elif unmask: + self.mask = False def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): From eb3d0e8b59d55f81a358a1b31f0c1e0e4ecd9f2b Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 17 Mar 2025 13:44:50 +0000 Subject: [PATCH 65/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 1438d8d44..3ee923e31 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1359,9 +1359,9 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil new_mask = False if unmask else copy.deepcopy(self.mask) - if (fill_in_place is False): - # If there is a not None mask and a not None fill_value, do: change the corresponding data to fill_value. - if (fill_value is not None and self.mask is not None): + masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True + if masked: + idx_mask = slice(None) is self.mask is True else self.mask # Ensure indexing mask can index the data array. kwargs["data"][self.mask] = fill_value # Boolean indexing in Python. # else, do nothing to the data. From bad2a267c2b3a719b0227dc63469ac0d6df4a669 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 17 Mar 2025 22:54:03 +0000 Subject: [PATCH 66/90] Implementing the fill_masked method. --- ndcube/ndcube.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 3ee923e31..f02652de0 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1,4 +1,5 @@ import abc +import copy import inspect import numbers import textwrap @@ -1349,34 +1350,40 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil If `True`, the masked values are filled in place. If `False`, a new instance is returned with masked values filled. Default=False. """ + # variable creations for later use. + # If fill_in_place is true, do: assign data and uncertainty to variables. if fill_in_place: new_data = self.data new_uncertainty = self.uncertainty + new_mask = False if unmask else self.mask # Unmasking in-place should be handled later. + + # If fill_in_place is false, do: create new storage place for data and uncertainty and mask. + # TODO: is the logic repetitive? this else is the same with the if not fill_in_place below? No because the order matters. else: new_data = copy.deepcopy(self.data) new_uncertainty = copy.deepcopy(self.uncertainty) new_mask = False if unmask else copy.deepcopy(self.mask) - masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True if masked: - idx_mask = slice(None) is self.mask is True else self.mask # Ensure indexing mask can index the data array. - kwargs["data"][self.mask] = fill_value # Boolean indexing in Python. - # else, do nothing to the data. - - # if unmask is True, do: change the True mask values to False, otherwise, do nothing to the mask. + idx_mask = slice(None) if self.mask is True else self.mask # Ensure indexing mask can index the data array. + new_data[idx_mask] = fill_value # Q: can it be None?? - if uncertainty_fill_value is not None: + if uncertainty_fill_value is not None: # Q: can it be None?? if not self.uncertainty: raise TypeError("Cannot fill uncertainty as uncertainty is None.") new_uncertainty.array[idx_mask] = uncertainty_fill_value if not fill_in_place: # Create kwargs dictionary and return a new instance. - elif unmask: - self.mask = False + kwargs = [] + kwargs['data'] = new_data + kwargs['uncertainty'] = new_uncertainty + kwargs['mask'] = new_mask + return kwargs + return None def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): m = None if (mask is None or mask is False or operation_ignores_mask) else mask From 468fcb2dd12563a6626aa6ec1cb421add2ce258e Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 17 Mar 2025 23:15:42 +0000 Subject: [PATCH 67/90] About units. --- ndcube/ndcube.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index f02652de0..0b66af8e0 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1356,6 +1356,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil new_data = self.data new_uncertainty = self.uncertainty new_mask = False if unmask else self.mask + new_unit = self.unit # Unmasking in-place should be handled later. # If fill_in_place is false, do: create new storage place for data and uncertainty and mask. @@ -1364,6 +1365,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil new_data = copy.deepcopy(self.data) new_uncertainty = copy.deepcopy(self.uncertainty) new_mask = False if unmask else copy.deepcopy(self.mask) + new_unit = copy.deepcopy(self.unit) masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True if masked: @@ -1381,6 +1383,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil kwargs['data'] = new_data kwargs['uncertainty'] = new_uncertainty kwargs['mask'] = new_mask + kwargs['unit'] = new_unit return kwargs return None From a35feeb74e40889ceaa12a37c850fded9d0d502f Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 18 Mar 2025 00:47:06 +0000 Subject: [PATCH 68/90] Further implementing, preparing for testing. --- ndcube/conftest.py | 14 ++++++ ndcube/ndcube.py | 106 ++++++++++++++++++++-------------------- ndcube/tests/helpers.py | 17 ++++++- 3 files changed, 82 insertions(+), 55 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index e52ad7643..3ca3ade55 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -659,6 +659,20 @@ def ndcube_2d_ln_lt_mask_uncert(wcs_2d_lt_ln): return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask) +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit(wcs_2d_lt_ln): + shape = (10, 12) + unit = u.ct + data_cube = data_nd(shape) + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = np.zeros(shape, dtype=bool) + mask[1, 0] = True + mask[2, 0] = True + mask[3, 0] = True + mask[4, 0] = True + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + @pytest.fixture def ndcube_2d_ln_lt_uncert_ec(wcs_2d_lt_ln): shape = (4, 9) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 0b66af8e0..067c4c9b1 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1331,62 +1331,62 @@ def squeeze(self, axis=None): return self[tuple(item)] -def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): - """ - Replaces masked data values with input value. + def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): + """ + Replaces masked data values with input value. - Returns a new instance or alters values in place. + Returns a new instance or alters values in place. - Parameters - ---------- - fill_value: `numbers.Number` or scalar `astropy.unit.Quantity` - The value to replace masked data with. - unmask: `bool`, optional - If True, the newly filled masked values are unmasked. If False, they remain masked - Default=False - uncertainty_fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`, optional - The value to replace masked uncertainties with. - fill_in_place: `bool`, optional - If `True`, the masked values are filled in place. If `False`, a new instance is returned - with masked values filled. Default=False. - """ - # variable creations for later use. - # If fill_in_place is true, do: assign data and uncertainty to variables. - if fill_in_place: - new_data = self.data - new_uncertainty = self.uncertainty - new_mask = False if unmask else self.mask - new_unit = self.unit - # Unmasking in-place should be handled later. + Parameters + ---------- + fill_value: `numbers.Number` or scalar `astropy.unit.Quantity` + The value to replace masked data with. + unmask: `bool`, optional + If True, the newly filled masked values are unmasked. If False, they remain masked + Default=False + uncertainty_fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`, optional + The value to replace masked uncertainties with. + fill_in_place: `bool`, optional + If `True`, the masked values are filled in place. If `False`, a new instance is returned + with masked values filled. Default=False. + """ + # variable creations for later use. + # If fill_in_place is true, do: assign data and uncertainty to variables. + if fill_in_place: + new_data = self.data + new_uncertainty = self.uncertainty + new_mask = False if unmask else self.mask + new_unit = self.unit + # Unmasking in-place should be handled later. - # If fill_in_place is false, do: create new storage place for data and uncertainty and mask. - # TODO: is the logic repetitive? this else is the same with the if not fill_in_place below? No because the order matters. - else: - new_data = copy.deepcopy(self.data) - new_uncertainty = copy.deepcopy(self.uncertainty) - new_mask = False if unmask else copy.deepcopy(self.mask) - new_unit = copy.deepcopy(self.unit) - - masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True - if masked: - idx_mask = slice(None) if self.mask is True else self.mask # Ensure indexing mask can index the data array. - new_data[idx_mask] = fill_value # Q: can it be None?? - - if uncertainty_fill_value is not None: # Q: can it be None?? - if not self.uncertainty: - raise TypeError("Cannot fill uncertainty as uncertainty is None.") - new_uncertainty.array[idx_mask] = uncertainty_fill_value - - if not fill_in_place: - # Create kwargs dictionary and return a new instance. - kwargs = [] - kwargs['data'] = new_data - kwargs['uncertainty'] = new_uncertainty - kwargs['mask'] = new_mask - kwargs['unit'] = new_unit - return kwargs - - return None + # If fill_in_place is false, do: create new storage place for data and uncertainty and mask. + # TODO: is the logic repetitive? this else is the same with the if not fill_in_place below? No because the order matters. + else: + new_data = copy.deepcopy(self.data) + new_uncertainty = copy.deepcopy(self.uncertainty) + new_mask = False if unmask else copy.deepcopy(self.mask) + new_unit = copy.deepcopy(self.unit) + + masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True + if masked: + idx_mask = slice(None) if self.mask is True else self.mask # Ensure indexing mask can index the data array. + new_data[idx_mask] = fill_value # Q: can it be None?? + + if uncertainty_fill_value is not None: # Q: can it be None?? + if not self.uncertainty: + raise TypeError("Cannot fill uncertainty as uncertainty is None.") + new_uncertainty.array[idx_mask] = uncertainty_fill_value + + if not fill_in_place: + # Create kwargs dictionary and return a new instance. + kwargs = {} + kwargs['data'] = new_data + kwargs['uncertainty'] = new_uncertainty + kwargs['mask'] = new_mask + kwargs['unit'] = new_unit + return kwargs + + return None def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): m = None if (mask is None or mask is False or operation_ignores_mask) else mask diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index a9e7d0727..93944921d 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -122,13 +122,26 @@ def assert_metas_equal(test_input, expected_output): assert test_input[key] == expected_output[key] -def assert_cubes_equal(test_input, expected_cube, check_data=True): +def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncertainty_values=False): assert isinstance(test_input, type(expected_cube)) assert np.all(test_input.mask == expected_cube.mask) if check_data: np.testing.assert_array_equal(test_input.data, expected_cube.data) assert_wcs_are_equal(test_input.wcs, expected_cube.wcs) - if test_input.uncertainty: + if check_uncertainty_values: + # Check output and expected uncertainty are of same type. Remember they could be None. + # If the uncertainties are not None,... + # Check units, shape, and values of the uncertainty. + if (test_input.uncertainty is not None and expected_cube.uncertainty is not None): + assert type(test_input.uncertainty) is type(expected_cube.uncertainty) + assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ + f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" + elif test_input.uncertainty is None: + assert expected_cube.uncertainty is None, "Test uncertainty should not be None." + elif expected_cube.uncertainty is None: + assert test_input.uncertainty is None, "Test uncertainty should be None." + + elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) assert_metas_equal(test_input.meta, expected_cube.meta) From 0cbfbd724d5816cb1a5dbdaea97864658f5eaa1f Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 18 Mar 2025 00:57:43 +0000 Subject: [PATCH 69/90] Added test for the fill_masked method. --- ndcube/tests/test_ndcube.py | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 01f2aa0f2..f02677f4f 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1,5 +1,6 @@ import re import copy +import importlib from inspect import signature from textwrap import dedent @@ -21,10 +22,14 @@ from astropy.wcs.wcsapi import BaseHighLevelWCS, BaseLowLevelWCS from astropy.wcs.wcsapi.wrappers import SlicedLowLevelWCS +import ndcube.tests.helpers from ndcube import ExtraCoords, NDCube, NDMeta from ndcube.tests import helpers from ndcube.utils.exceptions import NDCubeUserWarning +importlib.reload(ndcube.tests.helpers) + + def generate_data(shape): data = np.arange(np.prod(shape)) @@ -1384,3 +1389,74 @@ def test_set_data_mask(ndcube_4d_mask): with pytest.raises(TypeError, match="Can not set the .data .* with a numpy masked array"): cube.data = masked_array + +@pytest.mark.parametrize( + ("fill_value", "uncertainty_fill_value", "unmask", "fill_in_place"), + [ + (1.0, 0.1, False, True), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + (1.0, None, False, True), # uncertainty_fill_value is None. + + (1.0, 0.1, False, False), # the same as above, but not in place. + (1.0, None, False, False), # uncertainty_fill_value is None. + + (1.0, 0.01, True, False), # unmask is true + (1.0 * u.cm, 0.02, False, False), # what if the fill_value has a unit?? + ] +) +def test_fill_masked(ndcube_2d_ln_lt_mask_uncert_unit, fill_value, uncertainty_fill_value, unmask, fill_in_place): + # What I need to test: + # when the fill_masked method is applied on the fixture argument, does it: + # 1, give me the correct data value and type? + # 2, give me the correct uncertainty? + # 3, give me the correct mask? + # 4, give me the correct unit? + # The above four + # + # when masked with a fill_value + # use assert_cubes_equal????? + + # perform the fill_masked method on the fixture, using parametrized as parameters. + ndc = ndcube_2d_ln_lt_mask_uncert_unit + + expected_data = ndc.data.copy() + expected_data[ndc.mask] = fill_value + expected_uncertainty = ndc.uncertainty.array.copy() + + if uncertainty_fill_value is not None: + expected_uncertainty[ndc.mask] = uncertainty_fill_value + + expected_mask = False if unmask else ndc.mask + + expected_ndc = NDCube( + expected_data, + wcs=ndc.wcs, + uncertainty=astropy.nddata.StdDevUncertainty(expected_uncertainty), + mask=expected_mask, + unit=ndc.unit, + meta=ndc.meta + ) + + # perform the fill_masked operation + if fill_in_place: + ndc.fill_masked(fill_value, uncertainty_fill_value=uncertainty_fill_value, unmask=unmask, fill_in_place=True) + + # check whether ndc has been masked correctly + helpers.assert_cubes_equal(ndc, expected_ndc, check_data=True, check_uncertainty_values=True) + + else: + filled_ndc = ndc.fill_masked(fill_value, uncertainty_fill_value=uncertainty_fill_value, unmask=unmask, fill_in_place=False) + + if isinstance(filled_ndc, dict): # convert it back from dictionary to NDCube + filled_ndc = NDCube( + data=filled_ndc['data'], + uncertainty=filled_ndc.get('uncertainty', None), + mask=filled_ndc.get('mask', None), + unit=filled_ndc['unit'], + wcs=ndc.wcs, + ) + + # check whether ndc has been masked correctly + helpers.assert_cubes_equal(filled_ndc, expected_ndc, check_data=True, check_uncertainty_values=True) + + # ensure the original ndc is not changed + helpers.assert_cubes_equal(ndc, ndcube_2d_ln_lt_mask_uncert_unit, check_data=True, check_uncertainty_values=True) From 0d4055fc759b97cda49a25789bb65b1bf1a16c99 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 18 Mar 2025 01:09:23 +0000 Subject: [PATCH 70/90] Fixed error about docstring. --- ndcube/ndcube.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 067c4c9b1..12ad6208a 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1339,12 +1339,12 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil Parameters ---------- - fill_value: `numbers.Number` or scalar `astropy.unit.Quantity` + fill_value: `numbers.Number` or scalar `~astropy.units.Quantity` The value to replace masked data with. unmask: `bool`, optional If True, the newly filled masked values are unmasked. If False, they remain masked Default=False - uncertainty_fill_value: `numbers.Number` or scalar `astropy.unit.Quantity`, optional + uncertainty_fill_value: `numbers.Number` or scalar `~astropy.units.Quantity`, optional The value to replace masked uncertainties with. fill_in_place: `bool`, optional If `True`, the masked values are filled in place. If `False`, a new instance is returned From be4639a327b170e31d86d6d494b63a763ed2f6fd Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 18 Mar 2025 01:15:23 +0000 Subject: [PATCH 71/90] Changed the docstring again. --- ndcube/ndcube.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 12ad6208a..1420ea922 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1339,12 +1339,12 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil Parameters ---------- - fill_value: `numbers.Number` or scalar `~astropy.units.Quantity` + fill_value: `numbers.Number` or scalar `astropy.units.Quantity` The value to replace masked data with. unmask: `bool`, optional If True, the newly filled masked values are unmasked. If False, they remain masked Default=False - uncertainty_fill_value: `numbers.Number` or scalar `~astropy.units.Quantity`, optional + uncertainty_fill_value: `numbers.Number` or scalar `astropy.units.Quantity`, optional The value to replace masked uncertainties with. fill_in_place: `bool`, optional If `True`, the masked values are filled in place. If `False`, a new instance is returned From 74c648ec3fa0f1b36a91205bc0327d0eeb287057 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:18:32 +0000 Subject: [PATCH 72/90] Update ndcube/ndcube.py make kwargs ndcube and return it Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 1420ea922..5885a365c 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1384,7 +1384,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil kwargs['uncertainty'] = new_uncertainty kwargs['mask'] = new_mask kwargs['unit'] = new_unit - return kwargs + return self._new_instance(**kwargs) return None From b7e99bd8371ff19f06087b252a00db2304cedb48 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:19:29 +0000 Subject: [PATCH 73/90] Update ndcube/conftest.py Co-authored-by: DanRyanIrish --- ndcube/conftest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 3ca3ade55..4ee81a958 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -666,10 +666,7 @@ def ndcube_2d_ln_lt_mask_uncert_unit(wcs_2d_lt_ln): data_cube = data_nd(shape) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) mask = np.zeros(shape, dtype=bool) - mask[1, 0] = True - mask[2, 0] = True - mask[3, 0] = True - mask[4, 0] = True + mask[1:5, 0] = True return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) From d422668fbb321a4d757e4815d2c955535cf77ad2 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:23:11 +0000 Subject: [PATCH 74/90] Update ndcube/ndcube.py what's needed to be done regarding units is checking whether units assigned are consistent, if users do assign it Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 5885a365c..8b6717837 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1383,7 +1383,6 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil kwargs['data'] = new_data kwargs['uncertainty'] = new_uncertainty kwargs['mask'] = new_mask - kwargs['unit'] = new_unit return self._new_instance(**kwargs) return None From cc65f5d167a0e452891e6f0c2c79a37650f1d219 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:24:05 +0000 Subject: [PATCH 75/90] Update ndcube/tests/helpers.py Co-authored-by: DanRyanIrish --- ndcube/tests/helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 93944921d..56170df01 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -140,7 +140,6 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta assert expected_cube.uncertainty is None, "Test uncertainty should not be None." elif expected_cube.uncertainty is None: assert test_input.uncertainty is None, "Test uncertainty should be None." - elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) From 75bf81abb48a6bd687febae37c2847f7232f91e1 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 24 Mar 2025 00:06:57 +0000 Subject: [PATCH 76/90] deal with unmasking after using self.mask Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 8b6717837..dbaaba8ac 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1384,7 +1384,8 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil kwargs['uncertainty'] = new_uncertainty kwargs['mask'] = new_mask return self._new_instance(**kwargs) - + elif unmask: + self.mask = False return None def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): From be1db6e69b59b60802ae4b650560e688b05a02ff Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Sun, 23 Mar 2025 23:44:20 +0000 Subject: [PATCH 77/90] Notes from Meeting. --- ndcube/ndcube.py | 9 +++++---- ndcube/tests/test_ndcube.py | 7 +------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index dbaaba8ac..1817d8e2b 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1364,16 +1364,17 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil else: new_data = copy.deepcopy(self.data) new_uncertainty = copy.deepcopy(self.uncertainty) - new_mask = False if unmask else copy.deepcopy(self.mask) + new_mask = False if unmask else copy.deepcopy(self.mask) # self.mask still exists. new_unit = copy.deepcopy(self.unit) masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True if masked: idx_mask = slice(None) if self.mask is True else self.mask # Ensure indexing mask can index the data array. - new_data[idx_mask] = fill_value # Q: can it be None?? + new_data[idx_mask] = fill_value # Q: can it be None?? we want the masked values here. + # error, data array knows what it can accept. - if uncertainty_fill_value is not None: # Q: can it be None?? - if not self.uncertainty: + if uncertainty_fill_value is not None: # Q: can it be None?? It must be numerical. + if not self.uncertainty: # or new_uncertainty raise TypeError("Cannot fill uncertainty as uncertainty is None.") new_uncertainty.array[idx_mask] = uncertainty_fill_value diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index f02677f4f..f0a96db90 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1,6 +1,5 @@ import re import copy -import importlib from inspect import signature from textwrap import dedent @@ -22,14 +21,10 @@ from astropy.wcs.wcsapi import BaseHighLevelWCS, BaseLowLevelWCS from astropy.wcs.wcsapi.wrappers import SlicedLowLevelWCS -import ndcube.tests.helpers from ndcube import ExtraCoords, NDCube, NDMeta from ndcube.tests import helpers from ndcube.utils.exceptions import NDCubeUserWarning -importlib.reload(ndcube.tests.helpers) - - def generate_data(shape): data = np.arange(np.prod(shape)) @@ -1418,7 +1413,7 @@ def test_fill_masked(ndcube_2d_ln_lt_mask_uncert_unit, fill_value, uncertainty_f # perform the fill_masked method on the fixture, using parametrized as parameters. ndc = ndcube_2d_ln_lt_mask_uncert_unit - expected_data = ndc.data.copy() + expected_data = ndc.data.copy() # Put in the value I expect, expected_data[ndc.mask] = fill_value expected_uncertainty = ndc.uncertainty.array.copy() From 68def22dfb2ec0835d07117604673c8588ca7330 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 24 Mar 2025 02:40:16 +0000 Subject: [PATCH 78/90] Modified NDCube.fill_masked method and its tests. --- ndcube/conftest.py | 71 +++++++++++++++++++++- ndcube/ndcube.py | 10 +-- ndcube/tests/test_ndcube.py | 117 +++++++++++++++++------------------- 3 files changed, 127 insertions(+), 71 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 4ee81a958..b49f0a9a1 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -660,13 +660,78 @@ def ndcube_2d_ln_lt_mask_uncert(wcs_2d_lt_ln): @pytest.fixture -def ndcube_2d_ln_lt_mask_uncert_unit(wcs_2d_lt_ln): - shape = (10, 12) +def ndcube_2d_ln_lt_mask_uncert_unit_mask_false(wcs_2d_lt_ln): + shape = (2, 3) + unit = u.ct + data_cube = data_nd(shape) + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = False + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true(wcs_2d_lt_ln): + shape = (2, 3) + unit = u.ct + data_cube = data_nd(shape) + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = np.zeros(shape, dtype=bool) + mask[0:1, 0] = True + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false(wcs_2d_lt_ln): + shape = (2, 3) + unit = u.ct + data_cube = np.array([[1, 1, 2], + [3, 4, 5]]), + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = np.zeros(shape, dtype=bool) + mask[0:1, 0] = True + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true(wcs_2d_lt_ln): + shape = (2, 3) + unit = u.ct + data_cube = np.array([[1, 1, 2], + [3, 4, 5]]), + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = np.zeros(shape, dtype=bool) + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit_mask_true(wcs_2d_lt_ln): + shape = (2, 3) unit = u.ct data_cube = data_nd(shape) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = np.ones(shape, dtype=bool) + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_true(wcs_2d_lt_ln): + shape = (2, 3) + unit = u.ct + data_cube = np.array([[1, 1, 1], + [1, 1, 1]]), + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) mask = np.zeros(shape, dtype=bool) - mask[1:5, 0] = True + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) + + +@pytest.fixture +def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false(wcs_2d_lt_ln): + shape = (2, 3) + unit = u.ct + data_cube = np.array([[1, 1, 1], + [1, 1, 1]]), + uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) + mask = np.ones(shape, dtype=bool) return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 1817d8e2b..d5bce5256 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1355,8 +1355,6 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil if fill_in_place: new_data = self.data new_uncertainty = self.uncertainty - new_mask = False if unmask else self.mask - new_unit = self.unit # Unmasking in-place should be handled later. # If fill_in_place is false, do: create new storage place for data and uncertainty and mask. @@ -1365,14 +1363,16 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil new_data = copy.deepcopy(self.data) new_uncertainty = copy.deepcopy(self.uncertainty) new_mask = False if unmask else copy.deepcopy(self.mask) # self.mask still exists. - new_unit = copy.deepcopy(self.unit) masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True if masked: idx_mask = slice(None) if self.mask is True else self.mask # Ensure indexing mask can index the data array. - new_data[idx_mask] = fill_value # Q: can it be None?? we want the masked values here. - # error, data array knows what it can accept. + if hasattr(fill_value, "unit"): + fill_value = fill_value.to_value(self.unit) + new_data[idx_mask] = fill_value # python will error based on whether data array can accept the passed value. + if hasattr(uncertainty_fill_value, "unit"): + uncertainty_fill_value = uncertainty_fill_value.to_value(self.unit) if uncertainty_fill_value is not None: # Q: can it be None?? It must be numerical. if not self.uncertainty: # or new_uncertainty raise TypeError("Cannot fill uncertainty as uncertainty is None.") diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index f0a96db90..616d2f6f4 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -21,7 +21,7 @@ from astropy.wcs.wcsapi import BaseHighLevelWCS, BaseLowLevelWCS from astropy.wcs.wcsapi.wrappers import SlicedLowLevelWCS -from ndcube import ExtraCoords, NDCube, NDMeta +from ndcube import ExtraCoords, NDCube, NDMeta, fill_masked from ndcube.tests import helpers from ndcube.utils.exceptions import NDCubeUserWarning @@ -1385,73 +1385,64 @@ def test_set_data_mask(ndcube_4d_mask): with pytest.raises(TypeError, match="Can not set the .data .* with a numpy masked array"): cube.data = masked_array + @pytest.mark.parametrize( - ("fill_value", "uncertainty_fill_value", "unmask", "fill_in_place"), + ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "fill_in_place", "expected_cube"), [ - (1.0, 0.1, False, True), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - (1.0, None, False, True), # uncertainty_fill_value is None. - - (1.0, 0.1, False, False), # the same as above, but not in place. - (1.0, None, False, False), # uncertainty_fill_value is None. - - (1.0, 0.01, True, False), # unmask is true - (1.0 * u.cm, 0.02, False, False), # what if the fill_value has a unit?? - ] + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, True), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, True), # uncertainty_fill_value has a unit + + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit + # TODO: test unit not aligned?? + + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. + + # TODO: are there more test cases needed? + ], + indirect=("ndc", "expected_cube") ) -def test_fill_masked(ndcube_2d_ln_lt_mask_uncert_unit, fill_value, uncertainty_fill_value, unmask, fill_in_place): - # What I need to test: - # when the fill_masked method is applied on the fixture argument, does it: - # 1, give me the correct data value and type? - # 2, give me the correct uncertainty? - # 3, give me the correct mask? - # 4, give me the correct unit? - # The above four - # - # when masked with a fill_value - # use assert_cubes_equal????? - - # perform the fill_masked method on the fixture, using parametrized as parameters. - ndc = ndcube_2d_ln_lt_mask_uncert_unit - - expected_data = ndc.data.copy() # Put in the value I expect, - expected_data[ndc.mask] = fill_value - expected_uncertainty = ndc.uncertainty.array.copy() - - if uncertainty_fill_value is not None: - expected_uncertainty[ndc.mask] = uncertainty_fill_value - - expected_mask = False if unmask else ndc.mask +def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place, expected_cube): + # when the fill_masked method is applied on the fixture argument, it should + # give me the correct data value and type, uncertainty, mask, unit. - expected_ndc = NDCube( - expected_data, - wcs=ndc.wcs, - uncertainty=astropy.nddata.StdDevUncertainty(expected_uncertainty), - mask=expected_mask, - unit=ndc.unit, - meta=ndc.meta - ) - - # perform the fill_masked operation - if fill_in_place: - ndc.fill_masked(fill_value, uncertainty_fill_value=uncertainty_fill_value, unmask=unmask, fill_in_place=True) + # original cube: [[0,1,2],[3,4,5]], + # original mask: scenario 1, [[T,F,F],[F,F,F]]; scenario 2, T; scenario 3, None. + # expected cube: [[1,1,2],[3,4,5]]; [[1,1,1], [1,1,1]]; [[0,1,2],[3,4,5]] + # expected mask: when unmask is T, becomes all false, when unmask is F, stays the same. - # check whether ndc has been masked correctly - helpers.assert_cubes_equal(ndc, expected_ndc, check_data=True, check_uncertainty_values=True) - - else: - filled_ndc = ndc.fill_masked(fill_value, uncertainty_fill_value=uncertainty_fill_value, unmask=unmask, fill_in_place=False) + # perform the fill_masked method on the fixture, using parametrized as parameters. + filled_cube = fill_masked(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place) + helpers.assert_cubes_equal(filled_cube, expected_cube) - if isinstance(filled_ndc, dict): # convert it back from dictionary to NDCube - filled_ndc = NDCube( - data=filled_ndc['data'], - uncertainty=filled_ndc.get('uncertainty', None), - mask=filled_ndc.get('mask', None), - unit=filled_ndc['unit'], - wcs=ndc.wcs, - ) - # check whether ndc has been masked correctly - helpers.assert_cubes_equal(filled_ndc, expected_ndc, check_data=True, check_uncertainty_values=True) +@pytest.mark.parametrize( + ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "fill_in_place", "expected_cube"), + [ + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, False), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, False), # uncertainty_fill_value has a unit + + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit + #TODO: test unit not aligned?? + + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. + + # TODO: are there more test cases needed? + ], + indirect=("ndc", "expected_cube") +) +def test_fill_masked_fill_in_place_false(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place, expected_cube): + # this time, fill_in_placve is wrong, meaning: I should compare the expected cube with the cube saved in the new place - # ensure the original ndc is not changed - helpers.assert_cubes_equal(ndc, ndcube_2d_ln_lt_mask_uncert_unit, check_data=True, check_uncertainty_values=True) + # perform the fill_masked method on the fixture, using parametrized as parameters. + filled_cube = fill_masked(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place) + helpers.assert_cubes_equal(filled_cube, expected_cube) From 26811c83875bfd38718f2dcb7c28051025b377b4 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 24 Mar 2025 03:19:47 +0000 Subject: [PATCH 79/90] Debugging --- ndcube/conftest.py | 24 ++++++++++++++++-------- ndcube/tests/test_ndcube.py | 14 +++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index b49f0a9a1..84f767135 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -684,8 +684,8 @@ def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true(wcs_2d_lt_ln): def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false(wcs_2d_lt_ln): shape = (2, 3) unit = u.ct - data_cube = np.array([[1, 1, 2], - [3, 4, 5]]), + data_cube = np.array([[1.0, 1.0, 2.0], + [3.0, 4.0, 5.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) mask = np.zeros(shape, dtype=bool) mask[0:1, 0] = True @@ -696,8 +696,8 @@ def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false(wcs_ def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true(wcs_2d_lt_ln): shape = (2, 3) unit = u.ct - data_cube = np.array([[1, 1, 2], - [3, 4, 5]]), + data_cube = np.array([[1.0, 1.0, 2.0], + [3.0, 4.0, 5.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) mask = np.zeros(shape, dtype=bool) return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) @@ -717,8 +717,8 @@ def ndcube_2d_ln_lt_mask_uncert_unit_mask_true(wcs_2d_lt_ln): def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_true(wcs_2d_lt_ln): shape = (2, 3) unit = u.ct - data_cube = np.array([[1, 1, 1], - [1, 1, 1]]), + data_cube = np.array([[1.0, 1.0, 1.0], + [1.0, 1.0, 1.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) mask = np.zeros(shape, dtype=bool) return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) @@ -728,8 +728,8 @@ def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_true(wcs_2d_lt_ln def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false(wcs_2d_lt_ln): shape = (2, 3) unit = u.ct - data_cube = np.array([[1, 1, 1], - [1, 1, 1]]), + data_cube = np.array([[1.0, 1.0, 1.0], + [1.0, 1.0, 1.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) mask = np.ones(shape, dtype=bool) return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) @@ -811,6 +811,14 @@ def ndc(request): return request.getfixturevalue(request.param) +@pytest.fixture +def expected_cube(request): + """ + A fixture for use with indirect to lookup other fixtures. + """ + return request.getfixturevalue(request.param) + + ################################################################################ # NDCubeSequence Fixtures ################################################################################ diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 616d2f6f4..05ce32869 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -21,7 +21,7 @@ from astropy.wcs.wcsapi import BaseHighLevelWCS, BaseLowLevelWCS from astropy.wcs.wcsapi.wrappers import SlicedLowLevelWCS -from ndcube import ExtraCoords, NDCube, NDMeta, fill_masked +from ndcube import ExtraCoords, NDCube, NDMeta from ndcube.tests import helpers from ndcube.utils.exceptions import NDCubeUserWarning @@ -1391,8 +1391,8 @@ def test_set_data_mask(ndcube_4d_mask): [ ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, True), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, True), # uncertainty_fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), @@ -1416,7 +1416,7 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, # expected mask: when unmask is T, becomes all false, when unmask is F, stays the same. # perform the fill_masked method on the fixture, using parametrized as parameters. - filled_cube = fill_masked(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place) + filled_cube = ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place) helpers.assert_cubes_equal(filled_cube, expected_cube) @@ -1425,8 +1425,8 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, [ ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, False), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, False), # uncertainty_fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), @@ -1444,5 +1444,5 @@ def test_fill_masked_fill_in_place_false(ndc, fill_value, uncertainty_fill_value # this time, fill_in_placve is wrong, meaning: I should compare the expected cube with the cube saved in the new place # perform the fill_masked method on the fixture, using parametrized as parameters. - filled_cube = fill_masked(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place) + filled_cube = ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place) helpers.assert_cubes_equal(filled_cube, expected_cube) From db285bafd4a4f13275714f5ace57b54e2133e9a2 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:16:07 +0000 Subject: [PATCH 80/90] Update ndcube/ndcube.py Co-authored-by: DanRyanIrish --- ndcube/ndcube.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index d5bce5256..fb04299bd 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1371,11 +1371,11 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil fill_value = fill_value.to_value(self.unit) new_data[idx_mask] = fill_value # python will error based on whether data array can accept the passed value. - if hasattr(uncertainty_fill_value, "unit"): - uncertainty_fill_value = uncertainty_fill_value.to_value(self.unit) if uncertainty_fill_value is not None: # Q: can it be None?? It must be numerical. if not self.uncertainty: # or new_uncertainty raise TypeError("Cannot fill uncertainty as uncertainty is None.") + if hasattr(uncertainty_fill_value, "unit"): + uncertainty_fill_value = uncertainty_fill_value.to_value(self.unit) new_uncertainty.array[idx_mask] = uncertainty_fill_value if not fill_in_place: From 8b6d9bc5a950088f0f903263c85c8ca0121154cd Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Thu, 3 Apr 2025 15:38:25 +0100 Subject: [PATCH 81/90] Small changes from previous meeting. --- ndcube/ndcube.py | 4 ++-- ndcube/tests/test_ndcube.py | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index fb04299bd..798ab1598 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1371,7 +1371,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil fill_value = fill_value.to_value(self.unit) new_data[idx_mask] = fill_value # python will error based on whether data array can accept the passed value. - if uncertainty_fill_value is not None: # Q: can it be None?? It must be numerical. + if uncertainty_fill_value is not None: if not self.uncertainty: # or new_uncertainty raise TypeError("Cannot fill uncertainty as uncertainty is None.") if hasattr(uncertainty_fill_value, "unit"): @@ -1385,7 +1385,7 @@ def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fil kwargs['uncertainty'] = new_uncertainty kwargs['mask'] = new_mask return self._new_instance(**kwargs) - elif unmask: + if unmask: self.mask = False return None diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 05ce32869..f408092a7 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1391,13 +1391,11 @@ def test_set_data_mask(ndcube_4d_mask): [ ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit # TODO: test unit not aligned?? ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. @@ -1416,8 +1414,8 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, # expected mask: when unmask is T, becomes all false, when unmask is F, stays the same. # perform the fill_masked method on the fixture, using parametrized as parameters. - filled_cube = ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place) - helpers.assert_cubes_equal(filled_cube, expected_cube) + ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place) + helpers.assert_cubes_equal(ndc, expected_cube) @pytest.mark.parametrize( @@ -1425,13 +1423,11 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, [ ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1* u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # uncertainty_fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit #TODO: test unit not aligned?? ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. From 1f59a445421cd3e18b683fcd069444d08e02dd35 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Tue, 8 Apr 2025 11:08:51 +0100 Subject: [PATCH 82/90] Changed test arguments. --- ndcube/tests/test_ndcube.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index f408092a7..4c31b3db5 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1387,24 +1387,24 @@ def test_set_data_mask(ndcube_4d_mask): @pytest.mark.parametrize( - ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "fill_in_place", "expected_cube"), + ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "expected_cube"), [ - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit # TODO: test unit not aligned?? - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, True, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. # TODO: are there more test cases needed? ], indirect=("ndc", "expected_cube") ) -def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place, expected_cube): +def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, unmask, expected_cube): # when the fill_masked method is applied on the fixture argument, it should # give me the correct data value and type, uncertainty, mask, unit. @@ -1414,7 +1414,7 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, # expected mask: when unmask is T, becomes all false, when unmask is F, stays the same. # perform the fill_masked method on the fixture, using parametrized as parameters. - ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place) + ndc.fill_masked(fill_value, unmask=unmask, uncertainty_fill_value=uncertainty_fill_value, fill_in_place=True) helpers.assert_cubes_equal(ndc, expected_cube) From c26563a99b01677ae7730baad08c6de647cc1fbf Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 9 Apr 2025 20:28:12 +0100 Subject: [PATCH 83/90] Fixing bugs in tests. --- ndcube/ndcube.py | 2 +- ndcube/tests/helpers.py | 1 + ndcube/tests/test_ndcube.py | 32 ++++++++++++++++---------------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 798ab1598..f7e3dce0c 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1331,7 +1331,7 @@ def squeeze(self, axis=None): return self[tuple(item)] - def fill_masked(self, fill_value, unmask=False, uncertainty_fill_value=None, fill_in_place=False): + def fill_masked(self, fill_value, uncertainty_fill_value=None, unmask=False, fill_in_place=False): """ Replaces masked data values with input value. diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 56170df01..54cabb351 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -143,6 +143,7 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) + assert_metas_equal(test_input.meta, expected_cube.meta) if type(test_input.extra_coords) is not type(expected_cube.extra_coords): raise AssertionError(f"NDCube extra_coords not of same type: " diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 4c31b3db5..5277ca642 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1389,13 +1389,13 @@ def test_set_data_mask(ndcube_4d_mask): @pytest.mark.parametrize( ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "expected_cube"), [ - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true", 1.0 * u.ct, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false"), # fill_value has a unit # TODO: test unit not aligned?? ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. @@ -1419,26 +1419,26 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, @pytest.mark.parametrize( - ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "fill_in_place", "expected_cube"), + ("ndc", "fill_value", "uncertainty_fill_value", "unmask", "expected_cube"), [ - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit", 1.0 * u.ct, 0.1* u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true", 1.0 * u.ct, 0.1* u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true"), - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false"), # fill_value has a unit + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false"), # when it changes the cube in place: its data, uncertainty; it does not unmask the mask. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0, 0.1, True, "ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_true"), + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_true", 1.0 * u.ct, 0.1* u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false"), # fill_value has a unit #TODO: test unit not aligned?? - ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. + ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. # TODO: are there more test cases needed? ], indirect=("ndc", "expected_cube") ) -def test_fill_masked_fill_in_place_false(ndc, fill_value, uncertainty_fill_value, unmask, fill_in_place, expected_cube): +def test_fill_masked_fill_in_place_false(ndc, fill_value, uncertainty_fill_value, unmask, expected_cube): # this time, fill_in_placve is wrong, meaning: I should compare the expected cube with the cube saved in the new place # perform the fill_masked method on the fixture, using parametrized as parameters. - filled_cube = ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place) + filled_cube = ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place=False) helpers.assert_cubes_equal(filled_cube, expected_cube) From c8a86a5d0dd24f07dd3c51b437f009d4fe19fa29 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 9 Apr 2025 22:18:00 +0100 Subject: [PATCH 84/90] Fixed coverage issue by adding more test cases. --- ndcube/conftest.py | 12 ++++++++++++ ndcube/tests/test_ndcube.py | 27 +++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 84f767135..d07726833 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -646,6 +646,18 @@ def ndcube_2d_ln_lt_uncert(wcs_2d_lt_ln): return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) +@pytest.fixture +def ndcube_2d_ln_lt_mask(wcs_2d_lt_ln): + shape = (10, 12) + data_cube = data_nd(shape) + mask = np.zeros(shape, dtype=bool) + mask[1, 1] = True + mask[2, 0] = True + mask[3, 3] = True + mask[4:6, :4] = True + return NDCube(data_cube, wcs=wcs_2d_lt_ln, mask=mask) + + @pytest.fixture def ndcube_2d_ln_lt_mask_uncert(wcs_2d_lt_ln): shape = (10, 12) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 5277ca642..25c010d62 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -1415,7 +1415,7 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, # perform the fill_masked method on the fixture, using parametrized as parameters. ndc.fill_masked(fill_value, unmask=unmask, uncertainty_fill_value=uncertainty_fill_value, fill_in_place=True) - helpers.assert_cubes_equal(ndc, expected_cube) + helpers.assert_cubes_equal(ndc, expected_cube, check_uncertainty_values=True) @pytest.mark.parametrize( @@ -1432,13 +1432,32 @@ def test_fill_masked_fill_in_place_true(ndc, fill_value, uncertainty_fill_value, ("ndcube_2d_ln_lt_mask_uncert_unit_mask_false", 1.0, 0.1 * u.ct, False, "ndcube_2d_ln_lt_mask_uncert_unit_mask_false") # no change. - # TODO: are there more test cases needed? + # TODO: are there more test cases needed? yes: when uncertainty fill is not None but ndc's uncertainty is None. ], indirect=("ndc", "expected_cube") ) def test_fill_masked_fill_in_place_false(ndc, fill_value, uncertainty_fill_value, unmask, expected_cube): - # this time, fill_in_placve is wrong, meaning: I should compare the expected cube with the cube saved in the new place + # compare the expected cube with the cube saved in the new place # perform the fill_masked method on the fixture, using parametrized as parameters. filled_cube = ndc.fill_masked(fill_value, uncertainty_fill_value, unmask, fill_in_place=False) - helpers.assert_cubes_equal(filled_cube, expected_cube) + helpers.assert_cubes_equal(filled_cube, expected_cube, check_uncertainty_values=True) + +@pytest.mark.parametrize( + ("ndc", "fill_value", "uncertainty_fill_value", "unmask"), + [ + # cube has no uncertainty but uncertainty_fill_value has an uncertainty + ("ndcube_2d_ln_lt_mask", 1.0, 0.1, False), + ("ndcube_2d_ln_lt_mask", 1.0, 0.1 * u.ct, True), + ], + indirect=("ndc",) +) +def test_fill_masked_ndc_uncertainty_none(ndc, fill_value, uncertainty_fill_value, unmask): + assert ndc.uncertainty is None + with pytest.raises(TypeError,match="Cannot fill uncertainty as uncertainty is None."): + ndc.fill_masked( + fill_value, + unmask=unmask, + uncertainty_fill_value=uncertainty_fill_value, + fill_in_place=True + ) From e60eb244238951295b91f87be12421d8469c7b85 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Wed, 9 Apr 2025 22:32:35 +0100 Subject: [PATCH 85/90] Exclude defensive assertions from coverage test. --- ndcube/tests/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 54cabb351..045beae0c 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -137,9 +137,9 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncerta assert np.allclose(test_input.uncertainty.array, expected_cube.uncertainty.array), \ f"Expected uncertainty: {expected_cube.uncertainty}, but got: {test_input.uncertainty.array}" elif test_input.uncertainty is None: - assert expected_cube.uncertainty is None, "Test uncertainty should not be None." + assert expected_cube.uncertainty is None, "Test uncertainty should not be None." # pragma: no cover elif expected_cube.uncertainty is None: - assert test_input.uncertainty is None, "Test uncertainty should be None." + assert test_input.uncertainty is None, "Test uncertainty should be None." # pragma: no cover elif test_input.uncertainty: assert test_input.uncertainty.array.shape == expected_cube.uncertainty.array.shape assert np.all(test_input.shape == expected_cube.shape) From 4983a54b967ca374750b621cbec47a939968afc0 Mon Sep 17 00:00:00 2001 From: JunyanHuo <59020618+PCJY@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:36:26 +0100 Subject: [PATCH 86/90] Update ndcube/tests/helpers.py assert_cubes_equal(), check masks equal or not, in two scenarios Co-authored-by: DanRyanIrish --- ndcube/tests/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 045beae0c..764f25d06 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -124,7 +124,10 @@ def assert_metas_equal(test_input, expected_output): def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncertainty_values=False): assert isinstance(test_input, type(expected_cube)) - assert np.all(test_input.mask == expected_cube.mask) + if isinstance(test_input.mask, bool): + assert test_input.mask is expected_cube.mask + else: + assert np.all(test_input.mask == expected_cube.mask) if check_data: np.testing.assert_array_equal(test_input.data, expected_cube.data) assert_wcs_are_equal(test_input.wcs, expected_cube.wcs) From d40e8879e7493453ca5543d95474e78e3c5c7e89 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Fri, 11 Apr 2025 16:22:45 +0100 Subject: [PATCH 87/90] Changed code for Single-Bool-True-Mask case. --- ndcube/conftest.py | 8 +++----- ndcube/ndcube.py | 6 +++++- ndcube/tests/helpers.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index d07726833..a83e1068c 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -721,29 +721,27 @@ def ndcube_2d_ln_lt_mask_uncert_unit_mask_true(wcs_2d_lt_ln): unit = u.ct data_cube = data_nd(shape) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) - mask = np.ones(shape, dtype=bool) + mask = True return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) @pytest.fixture def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_true(wcs_2d_lt_ln): - shape = (2, 3) unit = u.ct data_cube = np.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) - mask = np.zeros(shape, dtype=bool) + mask = False return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) @pytest.fixture def ndcube_2d_ln_lt_mask_uncert_unit_mask_true_expected_unmask_false(wcs_2d_lt_ln): - shape = (2, 3) unit = u.ct data_cube = np.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) - mask = np.ones(shape, dtype=bool) + mask = True return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index f7e3dce0c..a744cc3a5 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -1364,7 +1364,11 @@ def fill_masked(self, fill_value, uncertainty_fill_value=None, unmask=False, fil new_uncertainty = copy.deepcopy(self.uncertainty) new_mask = False if unmask else copy.deepcopy(self.mask) # self.mask still exists. - masked = False if (self.mask is None or self.mask is False or not self.mask.any()) else True + masked = ( + False if self.mask is None or self.mask is False + else self.mask is True if isinstance(self.mask, bool) + else self.mask.any() + ) if masked: idx_mask = slice(None) if self.mask is True else self.mask # Ensure indexing mask can index the data array. if hasattr(fill_value, "unit"): diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index 764f25d06..233095140 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -124,8 +124,8 @@ def assert_metas_equal(test_input, expected_output): def assert_cubes_equal(test_input, expected_cube, check_data=True, check_uncertainty_values=False): assert isinstance(test_input, type(expected_cube)) - if isinstance(test_input.mask, bool): - assert test_input.mask is expected_cube.mask + if isinstance(test_input.mask, bool) and isinstance(expected_cube.mask, bool): + assert test_input.mask == expected_cube.mask else: assert np.all(test_input.mask == expected_cube.mask) if check_data: From 80708080051cdb11304c5568fed23a94ab6f3271 Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 14 Apr 2025 10:39:34 +0100 Subject: [PATCH 88/90] Change fixture. --- ndcube/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index efacf0488..99eb95ce5 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -694,12 +694,11 @@ def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false(wcs_ @pytest.fixture def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true(wcs_2d_lt_ln): - shape = (2, 3) unit = u.ct data_cube = np.array([[1.0, 1.0, 2.0], [3.0, 4.0, 5.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) - mask = np.zeros(shape, dtype=bool) + mask = False return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) From 9c7aa1434877785e654a6e92631614c23e46b6bf Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 14 Apr 2025 10:39:34 +0100 Subject: [PATCH 89/90] Change fixture. --- ndcube/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ndcube/conftest.py b/ndcube/conftest.py index a83e1068c..357122b4a 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -706,12 +706,11 @@ def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_false(wcs_ @pytest.fixture def ndcube_2d_ln_lt_mask_uncert_unit_one_maskele_true_expected_unmask_true(wcs_2d_lt_ln): - shape = (2, 3) unit = u.ct data_cube = np.array([[1.0, 1.0, 2.0], [3.0, 4.0, 5.0]]) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) - mask = np.zeros(shape, dtype=bool) + mask = False return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask, unit=unit) From 6ac02e301941c51d12a96b66afd0f7ec5013720d Mon Sep 17 00:00:00 2001 From: JunyanHuo Date: Mon, 21 Apr 2025 20:16:22 +0100 Subject: [PATCH 90/90] Removed the operation_ignore_mask argument and all the logics using it. --- ndcube/ndcube.py | 38 +++----------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 84c620adf..f59defe77 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -966,10 +966,9 @@ def _new_instance(self, **kwargs): def __neg__(self): return self._new_instance(data=-self.data) - def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): + def add(self, value, handle_mask=np.logical_and): """ - Users are allowed to choose whether they want operation_ignores_mask to be True or False, - and are allowed to choose whether they want handle_mask to be AND / OR . + Users are allowed to choose whether they want handle_mask to be AND / OR . """ kwargs = {} @@ -986,7 +985,7 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): self_unmasked = self.mask is None or self.mask is False or not self.mask.any() value_unmasked = value.mask is None or value.mask is False or not value.mask.any() - if (self_unmasked and value_unmasked) or operation_ignores_mask is True: + if (self_unmasked and value_unmasked): # addition kwargs["data"] = self.data + value_data @@ -1007,37 +1006,6 @@ def add(self, value, operation_ignores_mask=True, handle_mask=np.logical_and): kwargs["uncertainty"] = new_uncertainty else: new_uncertainty = None - else: - # TODO - # When there is a mask, that is when the two new added parameters (OIM and HM) come into the picture. - # Conditional statements to permutate the two different scenarios (when it does not ignore the mask). - kwargs["data"] = self.data + value_data - self_data, value_data = self.data, value.data # May require a copy - self_mask, value_mask = self.mask, value.mask # May require handling/converting of cases when masks aren't boolean arrays but are None, True, or False. - if not operation_ignores_mask: - no_op_value = 0 # Value to set masked values since we are doing addition. (Would need to be 1 if we were doing multiplication.) - if (self_mask is True and value_mask is False): - idx = np.logical_and(self_mask, np.logical_not(value_mask)) - self_data[idx] = no_op_value - elif (self_mask is False and value_mask is True): - idx = np.logical_and(value_mask, np.logical_not(self_mask)) - value_data[idx] = no_op_value - elif (self_mask is True and value_mask is True): - idx = np.logical_and(self_mask, value_mask) - self_data[idx] = no_op_value - value_data[idx] = no_op_value - - #self_data[idx], value_data[idx] = ?, ? # Handle case when both values are masked here. # We are yet to decide the best behaviour here. - # if both are F, no operation of setting the values to be 0 needs to be done. - else: - pass # If operation ignores mask, nothing needs to be done. This line not needed in actual code. Only here for clarity. - - # Perform addition - new_data = self_data + value_data - # Calculate new mask. - new_mask = handle_mask(self_mask, value_mask) if handle_mask else None - kwargs["data"] = new_data - kwargs["mask"] = new_mask elif hasattr(value, 'unit'): if isinstance(value, u.Quantity):