Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed Tests/images/hopper_naxis_zero.fits
Binary file not shown.
27 changes: 23 additions & 4 deletions Tests/test_file_fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def test_open() -> None:

def test_gzip1() -> None:
with Image.open("Tests/images/m13_gzip.fits") as im:
assert im.getpixel((0, 0)) == 111

assert_image_equal_tofile(im, "Tests/images/m13.fits")


Expand All @@ -36,6 +38,22 @@ def test_invalid_file() -> None:
FitsImagePlugin.FitsImageFile(invalid_file)


def test_unsupported_number_of_bits() -> None:
image_data = b"".join(
data.ljust(80, b" ")
for data in [
b"SIMPLE = T",
b"BITPIX = 128",
b"NAXIS = 1",
b"NAXIS1 = 0",
b"END",
]
)
with pytest.raises(OSError, match="Unsupported number of bits"):
with Image.open(BytesIO(image_data)):
pass


def test_truncated_fits() -> None:
# No END to headers
image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE"
Expand All @@ -44,10 +62,11 @@ def test_truncated_fits() -> None:


def test_naxis_zero() -> None:
# This test image has been manually hexedited
# to set the number of data axes to zero
with pytest.raises(ValueError):
with Image.open("Tests/images/hopper_naxis_zero.fits"):
image_data = b"".join(
data.ljust(80, b" ") for data in [b"SIMPLE = T", b"NAXIS = 0", b"END"]
).ljust(2881)
with pytest.raises(ValueError, match="No image data"):
with Image.open(BytesIO(image_data)):
pass


Expand Down
18 changes: 13 additions & 5 deletions src/PIL/FitsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,23 @@ def _parse_headers(

number_of_bits = int(headers[prefix + b"BITPIX"])
if number_of_bits == 8:
self._mode = "L"
self._mode = rawmode = "L"
elif number_of_bits == 16:
self._mode = "I;16"
rawmode = "I;16B"
elif number_of_bits == 32:
self._mode = "I"
elif number_of_bits in (-32, -64):
rawmode = "I;32B"
elif number_of_bits == -32 or (decoder_name == "raw" and number_of_bits == -64):
self._mode = "F"
rawmode = f"F;{number_of_bits * -1}BF"
else:
msg = "Unsupported number of bits"
raise OSError(msg)

args: tuple[str | int, ...]
if decoder_name == "raw":
args = (self.mode, 0, -1)
args = (rawmode, 0, -1)
else:
args = (number_of_bits,)
return decoder_name, offset, args
Expand All @@ -133,11 +139,13 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int

rows = []
offset = 0
number_of_bits = min(self.args[0] // 8, 4)
number_of_bytes = abs(self.args[0]) // 8
for y in range(self.state.ysize):
row = bytearray()
for x in range(self.state.xsize):
row += value[offset + (4 - number_of_bits) : offset + 4]
row += value[
offset + 4 - 1 : offset + (4 - number_of_bytes) - 1 : -1
]
offset += 4
rows.append(row)
self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row]))
Expand Down