Skip to content

Commit

Permalink
update code syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
LennyN95 committed Jul 15, 2024
1 parent c070af3 commit ddc3e9a
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 86 deletions.
101 changes: 45 additions & 56 deletions src/pydcmqi/segimage.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from __future__ import annotations

import json
import os
import shutil
import subprocess
import tempfile
from collections import OrderedDict
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any

import numpy as np
import SimpleITK as sitk
Expand All @@ -17,7 +16,7 @@
# --== helper and utility functions ==--


def get_min_max_values(image: sitk.Image) -> Tuple[float, float]:
def get_min_max_values(image: sitk.Image) -> tuple[float, float]:
filter = sitk.MinimumMaximumImageFilter()
filter.Execute(image)
return filter.GetMinimum(), filter.GetMaximum()
Expand All @@ -28,7 +27,9 @@ def _path(path: str | Path) -> Path:
return Path(path)
if isinstance(path, Path):
return path
raise ValueError("Invalid path type.")

msg = "Invalid path type."
raise ValueError(msg)


# --== class definitions ==--
Expand Down Expand Up @@ -58,7 +59,7 @@ def fromDict(d: TripletDict) -> Triplet:
return Triplet(d["CodeMeaning"], d["CodeValue"], d["CodingSchemeDesignator"])

@staticmethod
def fromTuple(t: Tuple[str, str, str]) -> Triplet:
def fromTuple(t: tuple[str, str, str]) -> Triplet:
"""
Create a LabeledTriplet from a tuple.
Expand Down Expand Up @@ -183,9 +184,6 @@ def validate(data: dict) -> bool:
or Triplet.fromDict(data["AnatomicRegionModifierSequence"]).valid,
]

print("required_fields", required_fields)
print("optional_fields", optional_fields)

return all(required_fields) and all(optional_fields)

def getConfigData(self, bypass_validation: bool = False) -> dict:
Expand All @@ -194,8 +192,7 @@ def getConfigData(self, bypass_validation: bool = False) -> dict:

@property
def data(self) -> SegmentDict:
d = self._bake_data()
return d
return self._bake_data()

def __getitem__(self, key: str) -> Any:
return self._data[key]
Expand Down Expand Up @@ -229,11 +226,11 @@ def description(self, description: str) -> None:
self._data["SegmentDescription"] = description

@property
def rgb(self) -> Tuple[int]:
def rgb(self) -> tuple[int, int, int]:
return tuple(self._data["recommendedDisplayRGBValue"])

@rgb.setter
def rgb(self, rgb: Tuple[int]) -> None:
def rgb(self, rgb: tuple[int, int, int]) -> None:
self._data["recommendedDisplayRGBValue"] = list(rgb)

@property
Expand All @@ -260,7 +257,7 @@ def segmentAlgorithmType(self) -> str:
def segmentAlgorithmType(self, segmentAlgorithmType: str) -> None:
self._data["SegmentAlgorithmType"] = segmentAlgorithmType

def _triplet_setter(self, key: str, value: Union[Tuple[str, str, str], Triplet]):
def _triplet_setter(self, key: str, value: tuple[str, str, str] | Triplet):
if isinstance(value, tuple):
value = Triplet.fromTuple(value)
assert isinstance(value, Triplet)
Expand All @@ -271,19 +268,15 @@ def segmentedPropertyCategory(self) -> Triplet:
return self._triplet_factory("SegmentedPropertyCategoryCodeSequence")

@segmentedPropertyCategory.setter
def segmentedPropertyCategory(
self, value: Union[Tuple[str, str, str], Triplet]
) -> None:
def segmentedPropertyCategory(self, value: tuple[str, str, str] | Triplet) -> None:
self._triplet_setter("SegmentedPropertyCategoryCodeSequence", value)

@property
def segmentedPropertyType(self) -> Triplet:
return self._triplet_factory("SegmentedPropertyTypeCodeSequence")

@segmentedPropertyType.setter
def segmentedPropertyType(
self, value: Union[Tuple[str, str, str], Triplet]
) -> None:
def segmentedPropertyType(self, value: tuple[str, str, str] | Triplet) -> None:
self._triplet_setter("SegmentedPropertyTypeCodeSequence", value)

@property
Expand All @@ -292,7 +285,7 @@ def segmentedPropertyTypeModifier(self) -> Triplet:

@segmentedPropertyTypeModifier.setter
def segmentedPropertyTypeModifier(
self, value: Union[Tuple[str, str, str], Triplet]
self, value: tuple[str, str, str] | Triplet
) -> None:
self._triplet_setter("SegmentedPropertyTypeModifierCodeSequence", value)

Expand All @@ -305,7 +298,7 @@ def anatomicRegion(self) -> Triplet:
return self._triplet_factory("AnatomicRegionSequence")

@anatomicRegion.setter
def anatomicRegion(self, value: Union[Tuple[str, str, str], Triplet]) -> None:
def anatomicRegion(self, value: tuple[str, str, str] | Triplet) -> None:
self._triplet_setter("AnatomicRegionSequence", value)

@property
Expand All @@ -317,9 +310,7 @@ def anatomicRegionModifier(self) -> Triplet:
return self._triplet_factory("AnatomicRegionModifierSequence")

@anatomicRegionModifier.setter
def anatomicRegionModifier(
self, value: Union[Tuple[str, str, str], Triplet]
) -> None:
def anatomicRegionModifier(self, value: tuple[str, str, str] | Triplet) -> None:
self._triplet_setter("AnatomicRegionModifierSequence", value)

@property
Expand All @@ -329,11 +320,11 @@ def hasAnatomicRegionModifier(self) -> bool:

class Segment:
def __init__(self) -> None:
self.path: Optional[Path] = None
self.path: Path | None = None
self.data = SegmentData()

self._cached_itk: Optional[sitk.Image] = None
self._cached_numpy: Optional[np.ndarray] = None
self._cached_itk: sitk.Image | None = None
self._cached_numpy: np.ndarray | None = None

@property
def config(self) -> dict:
Expand All @@ -352,7 +343,7 @@ def labelID(self, labelID: int) -> None:
self.data.labelID = labelID

def setFile(
self, path: Union[str, Path], labelID: int, diable_sanity_check: bool = False
self, path: str | Path, labelID: int, diable_sanity_check: bool = False
) -> None:
# make sure path is a Path object
path = _path(path)
Expand All @@ -365,12 +356,10 @@ def setFile(
if not path.is_file():
raise ValueError(f"Path is not a file: {path}")

# check file has as many labels as expected
try:
image = sitk.ReadImage(str(path))
except Exception:
raise ValueError(f"Could not read image: {path}")
# read image
image = sitk.ReadImage(str(path))

# check file has as many labels as expected
if image.GetNumberOfComponentsPerPixel() != 1:
raise ValueError(
f"Image must have only one component per pixel: {path}"
Expand Down Expand Up @@ -414,8 +403,8 @@ def isMultiLabel(self) -> bool:
def isLabel(self, label: int) -> bool:
return label in np.unique(self.numpy)

def isLabelSet(self, labels: List[int]) -> bool:
return all([self.isLabel(l) for l in labels])
def isLabelSet(self, labels: list[int]) -> bool:
return all(self.isLabel(label) for label in labels)

def isLabelRange(self, start: int, end: int) -> bool:
return self.isLabelSet(list(range(start, end + 1)))
Expand All @@ -424,7 +413,7 @@ def isLabelRange(self, start: int, end: int) -> bool:
def binary(self) -> np.ndarray:
return self.numpy == self.labelID

def saveAsBinary(self, path: Union[str, Path]) -> None:
def saveAsBinary(self, path: str | Path) -> None:
# make sure path is a Path object
path = _path(path)

Expand Down Expand Up @@ -523,15 +512,15 @@ def asdict(self) -> SegImageDict:

class SegImageFiles:
def __init__(self) -> None:
self._dicomseg: Optional[Path] = None
self._config: Optional[Path] = None
self._dicomseg: Path | None = None
self._config: Path | None = None

@property
def dicomseg(self) -> Optional[Path]:
def dicomseg(self) -> Path | None:
return self._dicomseg

@property
def config(self) -> Optional[Path]:
def config(self) -> Path | None:
return self._config


Expand All @@ -543,7 +532,7 @@ def reset(cls) -> None:
cls.verbose = False

def __init__(
self, verbose: Optional[bool] = None, tmp_dir: Optional[Union[Path, str]] = None
self, verbose: bool | None = None, tmp_dir: Path | str | None = None
) -> None:
# set verbose
if verbose is not None:
Expand All @@ -566,13 +555,13 @@ def __init__(
self.loaded = False
self.files = SegImageFiles()

self._config: Optional[dict] = None
self._segments: List[Segment] = []
self._config: dict | None = None
self._segments: list[Segment] = []

def load(
self,
dicomseg_file: Union[Path, str],
output_dir: Optional[Union[Path, str]] = None,
dicomseg_file: Path | str,
output_dir: Path | str | None = None,
) -> bool:
print(f"Converting file: {dicomseg_file} into {output_dir}.")

Expand Down Expand Up @@ -621,7 +610,7 @@ def _import(
config_file = output_dir / "pydcmqi-meta.json"

# load the config file
with open(config_file) as f:
with Path.open(config_file) as f:
self._config = json.load(f)

# load data
Expand All @@ -631,7 +620,7 @@ def _import(
# load each segmentation as item
for i, s in enumerate(self._config["segmentAttributes"]):
# find generated export file
f = os.path.join(output_dir, f"pydcmqi-{i+1}.nii.gz")
f = output_dir / f"pydcmqi-{i+1}.nii.gz"

# load all configs from segment definition
for config in s:
Expand All @@ -650,9 +639,9 @@ def _import(

def write(
self,
output_file: Union[str, Path],
dicom_dir: Union[str, Path],
export_config_to_file: Optional[Union[str, Path]] = None,
output_file: str | Path,
dicom_dir: str | Path,
export_config_to_file: str | Path | None = None,
allow_overwrite: bool = False,
) -> None:
# make sure the output file is a Path object
Expand Down Expand Up @@ -693,7 +682,7 @@ def write(

# store in _debug_test_meta.json
meta_tmp_file = Path(self.tmp_dir) / "_debug_test_meta.json"
with open(meta_tmp_file, "w") as f:
with Path.open(meta_tmp_file, "w") as f:
json.dump(config, f, indent=2)

# export config file if requested
Expand Down Expand Up @@ -733,7 +722,7 @@ def config(self) -> SegImageDict:
raise ValueError(f"Segment {s} has no file specified.")

# sort segments by files
f2s: Dict[str, List[Segment]] = {}
f2s: dict[str, list[Segment]] = {}
for s in self._segments:
p = str(s.path)
if p not in f2s:
Expand All @@ -743,7 +732,7 @@ def config(self) -> SegImageDict:
# sort the segments by their labelID
f2s = {k: sorted(v, key=lambda x: x.labelID) for k, v in f2s.items()}

# order the dicionary by it's keys
# order the dictionary by it's keys
of2s = OrderedDict(sorted(f2s.items()))

# check that for all files
Expand All @@ -765,15 +754,15 @@ def config(self) -> SegImageDict:
return config

@property
def segmentation_files(self) -> List[Path]:
return sorted(set([s.path for s in self._segments]))
def segmentation_files(self) -> list[Path]:
return sorted({s.path for s in self._segments})

@config.setter
def config(self, config: SegImageDict) -> None:
self.data.setConfigData(config)

@property
def segments(self) -> List[Segment]:
def segments(self) -> list[Segment]:
return self._segments

def add_segment(self, segment: Segment) -> None:
Expand Down
6 changes: 3 additions & 3 deletions src/pydcmqi/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import List, TypedDict
from typing import TypedDict


class TripletDict(TypedDict):
Expand All @@ -15,7 +15,7 @@ class SegmentDict(TypedDict):
SegmentDescription: str
SegmentAlgorithmName: str
SegmentAlgorithmType: str
recommendedDisplayRGBValue: List[int]
recommendedDisplayRGBValue: list[int]
SegmentedPropertyCategoryCodeSequence: TripletDict
SegmentedPropertyTypeCodeSequence: TripletDict

Expand All @@ -29,4 +29,4 @@ class SegImageDict(TypedDict):
InstanceNumber: str
SeriesDescription: str
SeriesNumber: str
segmentAttributes: List[List[SegmentDict]]
segmentAttributes: list[list[SegmentDict]]
Loading

0 comments on commit ddc3e9a

Please sign in to comment.