From 69092bf9571ff61123c51a7ea8fce4e21280a344 Mon Sep 17 00:00:00 2001 From: James Kerns Date: Thu, 26 Oct 2023 16:20:26 -0500 Subject: [PATCH 1/2] various fixes --- pylinac/core/image.py | 25 ++++++++++++++++--------- pylinac/core/utilities.py | 2 +- tests_basic/core/test_image.py | 7 +++++++ tests_basic/utils.py | 6 +++--- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/pylinac/core/image.py b/pylinac/core/image.py index 06b9fe312..cb8a006a9 100644 --- a/pylinac/core/image.py +++ b/pylinac/core/image.py @@ -1050,14 +1050,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 @@ -1086,7 +1084,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. diff --git a/pylinac/core/utilities.py b/pylinac/core/utilities.py index 4bd8c7f67..8517b3ea6 100644 --- a/pylinac/core/utilities.py +++ b/pylinac/core/utilities.py @@ -173,7 +173,7 @@ 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))) diff --git a/tests_basic/core/test_image.py b/tests_basic/core/test_image.py index 3a3ddd32e..aa815d314 100644 --- a/tests_basic/core/test_image.py +++ b/tests_basic/core/test_image.py @@ -551,6 +551,13 @@ def test_save_tiff(self): png_array = np.asarray(pimg) assert_array_almost_equal(png_array, xim.array) + def test_overflow(self): + # 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) + class TestLinacDicomImage(TestCase): def test_normal_image(self): diff --git a/tests_basic/utils.py b/tests_basic/utils.py index 0623bb7db..fa2e6ca74 100644 --- a/tests_basic/utils.py +++ b/tests_basic/utils.py @@ -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) From d52a067403f14bd9db215481571a20cfdf96deaa Mon Sep 17 00:00:00 2001 From: James Kerns Date: Fri, 27 Oct 2023 09:07:58 -0500 Subject: [PATCH 2/2] more over/under error fixes --- pylinac/core/array_utils.py | 9 +++++++-- pylinac/core/image.py | 6 +++--- pylinac/core/utilities.py | 2 +- tests_basic/core/test_image.py | 3 +++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pylinac/core/array_utils.py b/pylinac/core/array_utils.py index 9c9c2db97..e4ebe5aad 100644 --- a/pylinac/core/array_utils.py +++ b/pylinac/core/array_utils.py @@ -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 diff --git a/pylinac/core/image.py b/pylinac/core/image.py index 4d1e0a0bd..918dc096b 100644 --- a/pylinac/core/image.py +++ b/pylinac/core/image.py @@ -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 @@ -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 diff --git a/pylinac/core/utilities.py b/pylinac/core/utilities.py index 8517b3ea6..fd58141e2 100644 --- a/pylinac/core/utilities.py +++ b/pylinac/core/utilities.py @@ -178,7 +178,7 @@ def decode_binary( 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") diff --git a/tests_basic/core/test_image.py b/tests_basic/core/test_image.py index aa815d314..be1e384b0 100644 --- a/tests_basic/core/test_image.py +++ b/tests_basic/core/test_image.py @@ -552,11 +552,14 @@ def test_save_tiff(self): 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):