Skip to content

Commit

Permalink
Merged in refactor/misc_fixes (pull request #282)
Browse files Browse the repository at this point in the history
numpy overflow/underflow errors

Approved-by: Randy Taylor
  • Loading branch information
jrkerns committed Oct 30, 2023
2 parents aefb1a9 + d52a067 commit e7df976
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 19 deletions.
9 changes: 7 additions & 2 deletions pylinac/core/array_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,15 @@ def convert_to_dtype(array: np.array, dtype: type[np.dtype]) -> np.array:
"""
# original array info
old_dtype_info = get_dtype_info(array.dtype)
relative_values = array.astype(float) / old_dtype_info.max
# float range is so large that it's better to normalize
if isinstance(old_dtype_info, np.finfo):
relative_values = stretch(relative_values, min=0, max=1)
relative_values = stretch(array, min=0, max=1)
else:
# we have an int-like array.
# the float conversion is to avoid integer division
# this can help when the values are very small
# we will cast back to int later
relative_values = array.astype(float) / old_dtype_info.max
# new array info
dtype_info = get_dtype_info(dtype)
dtype_range = dtype_info.max - dtype_info.min
Expand Down
31 changes: 19 additions & 12 deletions pylinac/core/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import matplotlib.pyplot as plt
import numpy as np
import pydicom
import scipy.ndimage.filters as spf
import scipy.ndimage as spf
from PIL import Image as pImage
from PIL.PngImagePlugin import PngInfo
from PIL.TiffTags import TAGS
Expand Down Expand Up @@ -347,8 +347,8 @@ def _is_dicom(path: str | Path | io.BytesIO | ImageLike | np.ndarray) -> bool:
def _is_image_file(path: str | Path) -> bool:
"""Whether the file is a readable image file via Pillow."""
try:
pImage.open(path)
return True
with pImage.open(path):
return True
except:
return False

Expand Down Expand Up @@ -1051,14 +1051,12 @@ def _parse_compressed_bytes(
xim, int, num_values=img_width + 1
)
diffs = self._get_diffs(lookup_table, xim)
for diff, idx in zip(
np.asarray(diffs, dtype=np.int16),
range(img_width + 1, img_width * img_height),
):
left = a[idx - 1]
above = a[idx - img_width]
upper_left = a[idx - img_width - 1]
a[idx] = diff + left + above - upper_left
for diff, idx in zip(diffs, range(img_width + 1, img_width * img_height)):
# intermediate math can cause overflow errors. Use float for intermediate, then back to int
left = float(a[idx - 1])
above = float(a[idx - img_width])
upper_left = float(a[idx - img_width - 1])
a[idx] = np.asarray(diff + left + above - upper_left, dtype=dtype)
return a.reshape((img_height, img_width))

@staticmethod
Expand Down Expand Up @@ -1087,7 +1085,16 @@ def _get_diffs(lookup_table: np.ndarray, xim: BinaryIO):
diffs[start + 1 : stop] = vals
if stop != byte_changes[-1]:
diffs[stop] = decode_binary(xim, LOOKUP_CONVERSION[lookup_table[stop]])
return diffs
return np.asarray(diffs, dtype=float)

@property
def dpmm(self) -> float:
"""The dots/mm value of the XIM images. The value appears to be in cm in the file."""
if self.properties["PixelWidth"] != self.properties["PixelHeight"]:
raise ValueError(
"The XIM image does not have the same pixel height and width"
)
return 1 / (10 * self.properties["PixelHeight"])

def save_as(self, file: str, format: str | None = None) -> None:
"""Save the image to a NORMAL format. PNG is highly suggested. Accepts any format supported by Pillow.
Expand Down
4 changes: 2 additions & 2 deletions pylinac/core/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ def decode_binary(
ssize = struct.calcsize("i") * num_values
output = np.asarray(struct.unpack("i" * num_values, f.read(ssize)))
if len(output) == 1:
output = int(output)
output = int(np.squeeze(output))
elif dtype == float:
ssize = struct.calcsize("f") * num_values
output = np.asarray(struct.unpack("f" * num_values, f.read(ssize)))
if len(output) == 1:
output = float(output)
output = float(np.squeeze(output))
else:
raise TypeError(f"datatype '{dtype}' was not valid")

Expand Down
10 changes: 10 additions & 0 deletions tests_basic/core/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,16 @@ def test_save_tiff(self):
png_array = np.asarray(pimg)
assert_array_almost_equal(png_array, xim.array)

def test_overflow(self):
old_settings = np.geterr()
# set overflow to cause errors
# shouldn't raise
np.seterr(all="raise")
xim_path = get_file_from_cloud_test_repo(["IsoCal-kV-08.xim"])
XIM(xim_path)
# reset to old settings
np.seterr(**old_settings)


class TestLinacDicomImage(TestCase):
def test_normal_image(self):
Expand Down
6 changes: 3 additions & 3 deletions tests_basic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ def get_file_from_cloud_test_repo(path: List[str], force: bool = False) -> str:
"""Get a single file from GCP storage. Returns the path to disk it was downloaded to"""
local_filename = osp.join(osp.dirname(__file__), LOCAL_TEST_DIR, *path)
if osp.isfile(local_filename) and not force:
print(
f"Local file found: {local_filename}@{hashlib.md5(open(local_filename, 'rb').read()).hexdigest()}"
)
with open(local_filename, "rb") as f:
file_hash = hashlib.md5(f.read()).hexdigest()
print(f"Local file found: {local_filename}@{file_hash}")
return local_filename
with access_gcp() as client:
bucket = client.bucket(GCP_BUCKET_NAME)
Expand Down

0 comments on commit e7df976

Please sign in to comment.