diff --git a/mu_map/file/interfile.py b/mu_map/file/interfile.py index a916dda966588edde0f8148a4ea269d03c2eab6f..2944a9be74f7c85c3f99ff03ecea303eca37944e 100644 --- a/mu_map/file/interfile.py +++ b/mu_map/file/interfile.py @@ -58,6 +58,7 @@ class Interfile: Interfile data type wrapping the header and the image into an object. """ + header: Dict[str, str] image: np.ndarray @@ -70,6 +71,7 @@ class _InterfileKeys: """ Data class defining keys for an Interfile header. """ + placeholder: str = "{_}" _dim: str = f"matrix size [{placeholder}]" @@ -88,6 +90,8 @@ class _InterfileKeys: def spacing(self, index: int) -> str: return self._spacing.replace(self.placeholder, str(index)) + + InterfileKeys = _InterfileKeys() @@ -103,7 +107,9 @@ def type_by_format(number_format: str, bytes_per_pixel: int) -> type: if number_format == "float" and bytes_per_pixel == 4: return np.float32 - raise ValueError("Unknown mapping from format {number_format} with {bytes_per_pixel} bytes to numpy type") + raise ValueError( + "Unknown mapping from format {number_format} with {bytes_per_pixel} bytes to numpy type" + ) def parse_interfile_header_str(header: str) -> Dict[str, str]: @@ -139,13 +145,17 @@ def load_interfile(filename: str) -> Interfile: Load an INTERFILE header and its image as a numpy array. :param filename: the filename of the INTERFILE header file - :return: the header as a dict and the image as a numpy array + :return: the interfile image """ header = parse_interfile_header(filename) dim_x = int(header[InterfileKeys.dim(1)]) dim_y = int(header[InterfileKeys.dim(2)]) - dim_z = int(header[InterfileKeys.dim(3)]) if InterfileKeys.dim(3) in header else int(header[InterfileKeys.n_projections]) + dim_z = ( + int(header[InterfileKeys.dim(3)]) + if InterfileKeys.dim(3) in header + else int(header[InterfileKeys.n_projections]) + ) bytes_per_pixel = int(header[InterfileKeys.bytes_per_pixel]) num_format = header[InterfileKeys.number_format] @@ -170,6 +180,16 @@ def load_interfile_img(filename: str) -> np.ndarray: def write_interfile(filename: str, interfile: Interfile): + """ + Write an interfile image. + Note that custom extensions to the filename are removed and the defaults + from this module are used (.hv for the header and .v for the image). + This method also sanitizes the header by making sure that + dimensions match the image. + + :param filename: the filename where the interfile image is written to + :param interfile: the interfile saved + """ filename = os.path.splitext(filename)[0] filename_data = f"{filename}{EXTENSION_INTERFILE_IMAGE}" filename_header = f"{filename}{EXTENSION_INTERFILE_HEADER}" @@ -195,11 +215,29 @@ if __name__ == "__main__": import argparse import math - parser = argparse.ArgumentParser(description="Modify an interfile image") - parser.add_argument("--interfile", type=str, required=True, help="the interfile input") - parser.add_argument("--out", type=str, help="the interfile output - if not set the input file will be overwritte") - parser.add_argument("--cmd", choices=["fill", "crop", "pad", "flip"], help="the modification to perform") - parser.add_argument("--param", type=int, required=True, help="the parameters for the modification [fill: value, crop & pad: target size of z dimension]") + parser = argparse.ArgumentParser( + description="Modify an interfile image", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--interfile", type=str, required=True, help="the interfile input" + ) + parser.add_argument( + "--out", + type=str, + help="the interfile output - if not set the input file will be overwritte", + ) + parser.add_argument( + "--cmd", + choices=["fill", "crop", "pad", "flip"], + help="the modification to perform", + ) + parser.add_argument( + "--param", + type=int, + required=True, + help="the parameters for the modification [fill: value, crop & pad: target size of z dimension]", + ) args = parser.parse_args() args.out = args.interfile if args.out is None else args.out @@ -211,16 +249,22 @@ if __name__ == "__main__": if args.cmd == "pad": target_size = args.param real_size = interfile.image.shape[0] - assert target_size > real_size, f"Cannot pad from a larger size {real_size} to a smaller size {target_size}" + assert ( + target_size > real_size + ), f"Cannot pad from a larger size {real_size} to a smaller size {target_size}" diff = target_size - real_size pad_lower = math.ceil(diff / 2) pad_upper = math.floor(diff / 2) - interfile.image = np.pad(interfile.image, ((pad_lower, pad_upper), (0, 0), (0, 0))) + interfile.image = np.pad( + interfile.image, ((pad_lower, pad_upper), (0, 0), (0, 0)) + ) if args.cmd == "crop": target_size = args.param real_size = interfile.image.shape[0] - assert target_size < real_size, f"Cannot crop from a smaller size {real_size} to a larger size {target_size}" + assert ( + target_size < real_size + ), f"Cannot crop from a smaller size {real_size} to a larger size {target_size}" diff = target_size / 2 center = real_size // 2 @@ -231,7 +275,3 @@ if __name__ == "__main__": interfile.image = interfile.image[:, :, ::-1] write_interfile(args.out, interfile) - - - -