diff --git a/pyobs/images/image.py b/pyobs/images/image.py index 786bdc94..2ca54c76 100644 --- a/pyobs/images/image.py +++ b/pyobs/images/image.py @@ -11,6 +11,7 @@ from numpy.typing import NDArray from pyobs.utils.fits import FilenameFormatter +import pyobs.utils.exceptions as exc MetaClass = TypeVar("MetaClass") @@ -244,7 +245,7 @@ def to_bytes(self) -> bytes: def write_catalog(self, f: Any, *args: Any, **kwargs: Any) -> None: """Write catalog to file object.""" - if self.catalog is None: + if self._catalog is None: return hdu = table_to_hdu(self._catalog) @@ -364,7 +365,13 @@ def get_meta_safe(self, meta_class: Type[MetaClass], default: Optional[MetaClass return default @property - def data(self) -> Optional[NDArray[Any]]: + def data(self) -> NDArray[Any]: + if self._data is None: + raise exc.ImageError("No data found in image.") + return self._data + + @property + def safe_data(self) -> Optional[NDArray[Any]]: return self._data @data.setter @@ -372,7 +379,13 @@ def data(self, val: Optional[NDArray[Any]]): self._data = val @property - def header(self) -> Optional[fits.Header]: + def header(self) -> fits.Header: + if self._header is None: + raise exc.ImageError("No header found in image.") + return self._header + + @property + def safe_header(self) -> Optional[fits.Header]: return self._header @header.setter @@ -380,7 +393,13 @@ def header(self, val: Optional[fits.Header]): self._header = val @property - def mask(self) -> Optional[NDArray[Any]]: + def mask(self) -> NDArray[Any]: + if self._mask is None: + raise exc.ImageError("No mask found in image.") + return self._mask + + @property + def safe_mask(self) -> Optional[NDArray[Any]]: return self._mask @mask.setter @@ -388,7 +407,13 @@ def mask(self, val: Optional[NDArray[Any]]): self._mask = val @property - def uncertainty(self) -> Optional[NDArray[Any]]: + def uncertainty(self) -> NDArray[Any]: + if self._uncertainty is None: + raise exc.ImageError("No uncertainties found in image.") + return self._uncertainty + + @property + def safe_uncertainty(self) -> Optional[NDArray[Any]]: return self._uncertainty @uncertainty.setter @@ -396,7 +421,13 @@ def uncertainty(self, val: Optional[NDArray[Any]]): self._uncertainty = val @property - def catalog(self) -> Optional[Table]: + def catalog(self) -> Table: + if self._catalog is None: + raise exc.ImageError("No catalog found in image.") + return self._catalog + + @property + def safe_catalog(self) -> Optional[Table]: return self._catalog @catalog.setter @@ -404,7 +435,13 @@ def catalog(self, val: Optional[Table]): self._catalog = val @property - def raw(self) -> Optional[NDArray[Any]]: + def raw(self) -> NDArray[Any]: + if self._raw is None: + raise exc.ImageError("No raw data found in image.") + return self._raw + + @property + def safe_raw(self) -> Optional[NDArray[Any]]: return self._raw @raw.setter @@ -412,7 +449,9 @@ def raw(self, val: Optional[NDArray[Any]]): self._raw = val @property - def meta(self) -> Optional[Dict[Any, Any]]: + def meta(self) -> Dict[Any, Any]: + if self._meta is None: + self._meta: Dict[Any, Any] = {} return self._meta @meta.setter diff --git a/pyobs/images/processors/_daobackgroundremover.py b/pyobs/images/processors/_daobackgroundremover.py index 454f4f24..0aa85258 100644 --- a/pyobs/images/processors/_daobackgroundremover.py +++ b/pyobs/images/processors/_daobackgroundremover.py @@ -30,7 +30,7 @@ def _estimate_background(self, image: Image) -> npt.NDArray[float]: filter_size=self._filter_size, sigma_clip=self._sigma_clip, bkg_estimator=self._bkg_estimator, - mask=image.mask, + mask=image.safe_mask, ) return bkg.background diff --git a/pyobs/images/processors/astrometry/_dotnet_request_builder.py b/pyobs/images/processors/astrometry/_dotnet_request_builder.py index 384dc751..94d06387 100644 --- a/pyobs/images/processors/astrometry/_dotnet_request_builder.py +++ b/pyobs/images/processors/astrometry/_dotnet_request_builder.py @@ -54,8 +54,6 @@ def _build_request_data(self) -> None: def __call__(self, image: Image) -> _DotNetRequest: # set catalog and header - if image.catalog is None: - raise exc.ImageError("No catalog found in image.") self._catalog = image.catalog[["x", "y", "flux", "peak"]].to_pandas() self._header = image.header diff --git a/pyobs/images/processors/detection/daophot.py b/pyobs/images/processors/detection/daophot.py index 4f5d5778..fe3feafd 100644 --- a/pyobs/images/processors/detection/daophot.py +++ b/pyobs/images/processors/detection/daophot.py @@ -65,7 +65,7 @@ async def __call__(self, image: Image) -> Image: Image with attached catalog. """ - if image.data is None: + if image.safe_data is None: log.warning("No data found in image.") return image diff --git a/pyobs/images/processors/detection/pysep.py b/pyobs/images/processors/detection/pysep.py index 2b7cbb45..fc7f084e 100644 --- a/pyobs/images/processors/detection/pysep.py +++ b/pyobs/images/processors/detection/pysep.py @@ -23,16 +23,22 @@ class SepSourceDetection(SourceDetection): __module__ = "pyobs.images.processors.detection" _CATALOG_KEYS = [ - "x", "y", + "x", + "y", "peak", "flux", "fwhm", - "a", "b", "theta", + "a", + "b", + "theta", "ellipticity", "tnpix", "kronrad", - "fluxrad25", "fluxrad50", "fluxrad75", - "xwin", "ywin", + "fluxrad25", + "fluxrad50", + "fluxrad75", + "xwin", + "ywin", ] def __init__( @@ -77,7 +83,7 @@ async def __call__(self, image: Image) -> Image: Image with attached catalog. """ - if image.data is None: + if image.safe_data is None: log.warning("No data found in image.") return image @@ -104,7 +110,7 @@ async def __call__(self, image: Image) -> Image: @staticmethod def _get_mask_or_default(image: Image) -> np.ndarray: - if image.mask is not None: + if image.safe_mask is not None: return image.mask return np.zeros(image.data.shape, dtype=bool) diff --git a/pyobs/images/processors/exptime/star.py b/pyobs/images/processors/exptime/star.py index 6846351f..cf5971e7 100644 --- a/pyobs/images/processors/exptime/star.py +++ b/pyobs/images/processors/exptime/star.py @@ -51,7 +51,7 @@ async def _calc_exp_time(self, image: Image) -> float: self._image = copy(image) last_exp_time = image.header["EXPTIME"] - if self._image.catalog is None: + if self._image.safe_catalog is None: log.info("No catalog found in image.") return last_exp_time diff --git a/pyobs/images/processors/misc/smooth.py b/pyobs/images/processors/misc/smooth.py index 7b1a4539..5a1741c4 100644 --- a/pyobs/images/processors/misc/smooth.py +++ b/pyobs/images/processors/misc/smooth.py @@ -54,7 +54,7 @@ async def __call__(self, image: Image) -> Image: """ output_image = image.copy() - if output_image.data is None: + if output_image.safe_data is None: log.warning("No data found in image.") return image diff --git a/pyobs/images/processors/misc/softbin.py b/pyobs/images/processors/misc/softbin.py index eeb8a9ff..1feb5cea 100644 --- a/pyobs/images/processors/misc/softbin.py +++ b/pyobs/images/processors/misc/softbin.py @@ -37,12 +37,12 @@ async def __call__(self, image: Image) -> Image: """ output_image = image.copy() - if output_image.data is None: + if output_image.safe_data is None: log.warning("No data found in image.") return image output_image.data = self._reshape_image(output_image.data) - if output_image.data is None: + if output_image.safe_data is None: log.warning("No data found in image after reshaping.") return image diff --git a/pyobs/images/processors/offsets/brighteststar.py b/pyobs/images/processors/offsets/brighteststar.py index dce13abd..c55a6c39 100644 --- a/pyobs/images/processors/offsets/brighteststar.py +++ b/pyobs/images/processors/offsets/brighteststar.py @@ -37,7 +37,7 @@ async def __call__(self, image: Image) -> Image: """ # get catalog and sort by flux - cat = image.catalog + cat = image.safe_catalog if cat is None or len(cat) < 1: log.warning("No catalog found in image.") return image diff --git a/pyobs/images/processors/offsets/nstar.py b/pyobs/images/processors/offsets/nstar.py index a56a41e7..371f6132 100644 --- a/pyobs/images/processors/offsets/nstar.py +++ b/pyobs/images/processors/offsets/nstar.py @@ -128,12 +128,6 @@ async def _boxes_from_ref(self, image: Image, star_box_size: int) -> List: # run pipeline on 1st image img = await self.run_pipeline(image) - # check data and catalog - if img.data is None: - raise ValueError("No data found in image.") - if img.catalog is None: - raise ValueError("No catalog found in image.") - # do photometry and get catalog sources = self._fits2numpy(img.catalog) @@ -273,7 +267,7 @@ def _calculate_offsets(self, image: Image) -> Tuple[Optional[float], Optional[fl """ # data? - if image.data is None: + if image.safe_data is None: return None, None # calculate offset for each star diff --git a/pyobs/images/processors/offsets/projected.py b/pyobs/images/processors/offsets/projected.py index 73cea460..0b457b2b 100644 --- a/pyobs/images/processors/offsets/projected.py +++ b/pyobs/images/processors/offsets/projected.py @@ -82,10 +82,6 @@ def _process(self, image: Image) -> Tuple[npt.NDArray[float], npt.NDArray[float] # get image data and header data, hdr = image.data, image.header - # no data? - if data is None: - raise ValueError("Image contains no data.") - # trimsec if "TRIMSEC" in hdr: m = re.match(r"\[([0-9]+):([0-9]+),([0-9]+):([0-9]+)\]", hdr["TRIMSEC"]) diff --git a/pyobs/images/processors/photometry/photutil.py b/pyobs/images/processors/photometry/photutil.py index 8070aeaa..4356a07b 100644 --- a/pyobs/images/processors/photometry/photutil.py +++ b/pyobs/images/processors/photometry/photutil.py @@ -66,7 +66,7 @@ async def __call__(self, image: Image) -> Image: return image # fetch catalog - if image.catalog is None: + if image.safe_catalog is None: log.warning("No catalog in image.") return image sources = image.catalog.copy() @@ -96,7 +96,8 @@ async def __call__(self, image: Image) -> Image: # do photometry phot = await loop.run_in_executor( - None, partial(aperture_photometry, image.data, aperture, mask=image.mask, error=image.uncertainty) + None, + partial(aperture_photometry, image.data, aperture, mask=image.safe_mask, error=image.safe_uncertainty), ) # calc flux diff --git a/pyobs/images/processors/photometry/pysep.py b/pyobs/images/processors/photometry/pysep.py index c559ada6..bb765079 100644 --- a/pyobs/images/processors/photometry/pysep.py +++ b/pyobs/images/processors/photometry/pysep.py @@ -67,18 +67,15 @@ def _photometry(image: Image) -> Image: from pyobs.images.processors.detection import SepSourceDetection # check data - if image.data is None: + if image.safe_data is None: log.warning("No data found in image.") return image - if image.catalog is None: + if image.safe_catalog is None: log.warning("No catalog found in image.") return image - # no mask? - mask = image.mask if image.mask is not None else np.ones(image.data.shape, dtype=bool) - # remove background - data, bkg = SepSourceDetection.remove_background(image.data, mask) + data, bkg = SepSourceDetection.remove_background(image.data, image.safe_mask) # fetch catalog sources = image.catalog.copy() @@ -93,7 +90,13 @@ def _photometry(image: Image) -> Image: for diameter in [1, 2, 3, 4, 5, 6, 7, 8]: if image.pixel_scale is not None: flux, fluxerr, flag = sep.sum_circle( - data, x, y, diameter / 2.0 / image.pixel_scale, mask=image.mask, err=image.uncertainty, gain=gain + data, + x, + y, + diameter / 2.0 / image.pixel_scale, + mask=image.safe_mask, + err=image.safe_uncertainty, + gain=gain, ) sources["fluxaper{0}".format(diameter)] = flux sources["fluxerr{0}".format(diameter)] = fluxerr diff --git a/pyobs/modules/camera/basecamera.py b/pyobs/modules/camera/basecamera.py index 24640667..96ac0760 100644 --- a/pyobs/modules/camera/basecamera.py +++ b/pyobs/modules/camera/basecamera.py @@ -246,7 +246,7 @@ async def __expose(self, exposure_time: float, image_type: ImageType, broadcast: self._exposure = ExposureInfo(start=datetime.datetime.utcnow(), exposure_time=exposure_time) try: image = await self._expose(exposure_time, open_shutter, abort_event=self.expose_abort) - if image is None or image.data is None: + if image is None or image.safe_data is None: raise exc.GrabImageError("Could not take image.") except exc.PyObsError: diff --git a/pyobs/modules/image/seeing.py b/pyobs/modules/image/seeing.py index 68bd825f..6557d912 100644 --- a/pyobs/modules/image/seeing.py +++ b/pyobs/modules/image/seeing.py @@ -79,7 +79,7 @@ async def process_new_image_event(self, event: Event, sender: str) -> bool: return False # get catalog - cat = image.catalog + cat = image.safe_catalog if cat is None: # no catalog found in file return False diff --git a/pyobs/utils/focusseries/photometry.py b/pyobs/utils/focusseries/photometry.py index c316e09b..5c4874c5 100644 --- a/pyobs/utils/focusseries/photometry.py +++ b/pyobs/utils/focusseries/photometry.py @@ -45,7 +45,7 @@ def analyse_image(self, image: Image, focus_value: float) -> None: self._source_detection(image) # filter - sources = image.catalog + sources = image.safe_catalog if sources is None: return sources = sources[sources["ellipticity"] < 0.1] diff --git a/pyobs/utils/focusseries/projection.py b/pyobs/utils/focusseries/projection.py index 61e48107..44feb811 100644 --- a/pyobs/utils/focusseries/projection.py +++ b/pyobs/utils/focusseries/projection.py @@ -47,7 +47,7 @@ def analyse_image(self, image: Image, focus_value: float) -> None: """ # clean data - if image.data is None: + if image.safe_data is None: return data = self._clean(image.data) diff --git a/tests/images/processors/test_removebackground.py b/tests/images/processors/test_removebackground.py index 46981b18..2ecb7d34 100644 --- a/tests/images/processors/test_removebackground.py +++ b/tests/images/processors/test_removebackground.py @@ -49,4 +49,4 @@ def test_call_const_background(mocker): image = Image(data=np.ones((20, 20))) output_image = remover(image) - np.testing.assert_array_equal(output_image.data, np.zeros((20, 20))) \ No newline at end of file + np.testing.assert_array_equal(output_image.data, np.zeros((20, 20))) diff --git a/tests/images/test_image.py b/tests/images/test_image.py index 904f51bf..06c02c88 100644 --- a/tests/images/test_image.py +++ b/tests/images/test_image.py @@ -24,20 +24,28 @@ def assert_array_equal_or_none(check_value, value): def assert_equal_image_params(image, data=None, mask=None, uncertainty=None, catalog=None, raw=None, meta={}): - assert_array_equal_or_none(data, image.data) - assert_array_equal_or_none(mask, image.mask) - assert_array_equal_or_none(uncertainty, image.uncertainty) + assert_array_equal_or_none(data, image.safe_data) + assert_array_equal_or_none(mask, image.safe_mask) + assert_array_equal_or_none(uncertainty, image.safe_uncertainty) if catalog is None: - assert image.catalog is None + assert image.safe_catalog is None else: assert_array_equal_or_none(catalog.as_array(), image.catalog.as_array()) - assert_array_equal_or_none(raw, image.raw) + assert_array_equal_or_none(raw, image.safe_raw) assert image.meta == meta def assert_equal_image(image, other): - assert_equal_image_params(image, other.data, other.mask, other.uncertainty, other.catalog, other.raw, other.meta) + assert_equal_image_params( + image, + other.safe_data, + other.safe_mask, + other.safe_uncertainty, + other.safe_catalog, + other.safe_raw, + other.meta, + ) @pytest.fixture()