diff --git a/autofit/database/model/array.py b/autofit/database/model/array.py index af42438e5..a02a95360 100644 --- a/autofit/database/model/array.py +++ b/autofit/database/model/array.py @@ -169,6 +169,14 @@ def header(self): def header(self, header): self._header = header.tostring() + @property + def has_data(self): + """ + Whether this HDU carries an array payload. A data-less HDU stores no + shape, which is how it is distinguished from one holding an array. + """ + return self._shape is not None + @property def hdu(self): from astropy.io import fits @@ -176,7 +184,7 @@ def hdu(self): type_ = fits.PrimaryHDU if self.is_primary else fits.ImageHDU return type_( - self.array, + self.array if self.has_data else None, self.header, ) @@ -185,7 +193,18 @@ def hdu(self, hdu): from astropy.io import fits self.is_primary = isinstance(hdu, fits.PrimaryHDU) - self.array = hdu.data + + # A data-less HDU is legitimate and routine: the first HDU of a + # multi-extension FITS is conventionally an empty PrimaryHDU, and + # `AggregateFITS` emits exactly that. Leave the array columns null + # rather than dereferencing `hdu.data.dtype` on None. + if hdu.data is None: + self._dtype = None + self._shape = None + self.bytes = None + else: + self.array = hdu.data + self.header = hdu.header @property diff --git a/test_autofit/database/test_file_types.py b/test_autofit/database/test_file_types.py index 8a162e822..8500a79cf 100644 --- a/test_autofit/database/test_file_types.py +++ b/test_autofit/database/test_file_types.py @@ -54,3 +54,34 @@ def test_hdu(hdu, hdu_array): loaded = db_hdu.hdu assert (loaded.data == hdu_array).all() assert loaded.header == hdu.header + + +def test_hdu_without_data(): + """ + A data-less HDU must round-trip. The first HDU of a multi-extension FITS is + conventionally an empty `PrimaryHDU` — `AggregateFITS` emits exactly that — + so dereferencing `hdu.data.dtype` here broke every such scrape. + """ + db_hdu = db.HDU(name="test", hdu=fits.PrimaryHDU()) + + assert not db_hdu.has_data + + loaded = db_hdu.hdu + assert isinstance(loaded, fits.PrimaryHDU) + assert loaded.data is None + + +def test_set_fits_with_empty_primary_hdu(fit, hdu_array): + """ + The shape the database scrape actually meets: an empty `PrimaryHDU` + followed by named image extensions. + """ + image_hdu = fits.ImageHDU(hdu_array) + image_hdu.header["EXTNAME"] = "MODEL_IMAGE" + + fit.set_fits("test", fits.HDUList([fits.PrimaryHDU(), image_hdu])) + + loaded = fit.get_fits("test") + assert len(loaded) == 2 + assert loaded[0].data is None + assert (loaded[1].data == hdu_array).all()