Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow loading multiple series with DICOMweb #122

Merged
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make some arguments optional for `WsiDicomWebClient.create_client()`.
- Removed `WsiDicomFileClient` since it is no longer needed.
- Fetching multiple frames in one request instead of one request per frame when using DICOM Web.
- Allow multiple series UIDs to be passed to `WsiDicom.open_web()`.
- Loosen UID matching to just `study_instance`.
- Require equality of 'TotalPixelMatrixOriginSequence' when matching datasets.

## [0.12.0] - 2023-10-04

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ client = WsiDicomWebClient.create_client(
slide = WsiDicom.open_web(
client,
"study uid to open",
"series uid top open"
"series uid to open" or ["series uid 1 to open", "series uid 2 to open"]
)
```

Expand Down
3 changes: 3 additions & 0 deletions wsidicom/instance/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,14 @@ def matches_instance(self, other_dataset: "WsiDataset") -> bool:
bool
True if same instance.
"""

return (
self.uids == other_dataset.uids
and self.image_size == other_dataset.image_size
and self.tile_size == other_dataset.tile_size
and self.tile_type == other_dataset.tile_type
and (getattr(self, 'TotalPixelMatrixOriginSequence', None) ==
getattr(other_dataset, 'TotalPixelMatrixOriginSequence', None))
)

psavery marked this conversation as resolved.
Show resolved Hide resolved
def matches_series(self, uids: SlideUids, tile_size: Optional[Size] = None) -> bool:
Expand Down
10 changes: 5 additions & 5 deletions wsidicom/uid.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ def __eq__(self, other: "SlideUids") -> bool:

def matches(self, other: "SlideUids") -> bool:
if settings.strict_uid_check:
return self == other
return (
self.study_instance == other.study_instance
and self.frame_of_reference == other.frame_of_reference
)

return (
self.study_instance == other.study_instance
and self.series_instance == other.series_instance
)
return self.study_instance == other.study_instance


@dataclass
Expand Down
63 changes: 36 additions & 27 deletions wsidicom/web/wsidicom_web_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import List, Union
from typing import Iterable, List, Union

from pydicom.uid import UID

Expand All @@ -33,7 +33,7 @@ def __init__(
self,
client: WsiDicomWebClient,
study_uid: Union[str, UID],
series_uid: Union[str, UID],
series_uids: Union[str, UID, Iterable[Union[str, UID]]],
requested_transfer_syntax: UID,
):
"""Create a WsiDicomWebSource.
Expand All @@ -44,42 +44,51 @@ def __init__(
Client use for DICOMWeb communication.
study_uid: Union[str, UID]
Study UID of DICOM WSI to open.
series_uid: Union[str, UID]
Series UID of DICOM WSI top open.
series_uids: Union[str, UID, Iterable[Union[str, UID]]]
Series UIDs of DICOM WSI top open.
requested_transfer_syntax: UID
Transfer syntax to request for image data, for example
UID("1.2.840.10008.1.2.4.50") for JPEGBaseline8Bit.

"""
if not isinstance(study_uid, UID):
study_uid = UID(study_uid)
if not isinstance(series_uid, UID):
series_uid = UID(series_uid)

if isinstance(series_uids, (str, UID)):
series_uids = [series_uids]

self._level_instances: List[WsiInstance] = []
self._label_instances: List[WsiInstance] = []
self._overview_instances: List[WsiInstance] = []
for instance_uid in client.get_wsi_instances(study_uid, series_uid):
dataset = client.get_instance(study_uid, series_uid, instance_uid)
if not WsiDataset.is_supported_wsi_dicom(
dataset, requested_transfer_syntax
):
continue
dataset = WsiDataset(dataset)
image_data = WsiDicomWebImageData(
client, dataset, requested_transfer_syntax
)
instance = WsiInstance(dataset, image_data)
if instance.image_type == ImageType.VOLUME:
self._level_instances.append(instance)
elif instance.image_type == ImageType.LABEL:
self._label_instances.append(instance)
elif instance.image_type == ImageType.OVERVIEW:
self._overview_instances.append(instance)
self._annotation_instances: List[AnnotationInstance] = []
for instance_uid in client.get_ann_instances(study_uid, series_uid):
instance = client.get_instance(study_uid, series_uid, instance_uid)
annotation_instance = AnnotationInstance.open_dataset(instance)
self._annotation_instances.append(annotation_instance)

for series_uid in series_uids:
if not isinstance(series_uid, UID):
series_uid = UID(series_uid)

for instance_uid in client.get_wsi_instances(study_uid, series_uid):
dataset = client.get_instance(study_uid, series_uid, instance_uid)
if not WsiDataset.is_supported_wsi_dicom(
dataset, requested_transfer_syntax
):
continue
dataset = WsiDataset(dataset)
image_data = WsiDicomWebImageData(
client, dataset, requested_transfer_syntax
)
instance = WsiInstance(dataset, image_data)
if instance.image_type == ImageType.VOLUME:
self._level_instances.append(instance)
elif instance.image_type == ImageType.LABEL:
self._label_instances.append(instance)
elif instance.image_type == ImageType.OVERVIEW:
self._overview_instances.append(instance)

for instance_uid in client.get_ann_instances(study_uid, series_uid):
instance = client.get_instance(study_uid, series_uid, instance_uid)
annotation_instance = AnnotationInstance.open_dataset(instance)
self._annotation_instances.append(annotation_instance)

try:
self._base_dataset = next(
instance.dataset
Expand Down
8 changes: 4 additions & 4 deletions wsidicom/wsidicom.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def open_web(
cls,
client: WsiDicomWebClient,
study_uid: Union[str, UID],
series_uid: Union[str, UID],
series_uids: Union[str, UID, Iterable[Union[str, UID]]],
requested_transfer_syntax: UID = JPEGBaseline8Bit,
label: Optional[Union[PILImage, str, Path]] = None,
) -> "WsiDicom":
Expand All @@ -133,8 +133,8 @@ def open_web(
Configured DICOM web client.
study_uid: Union[str, UID]
Study uid of wsi to open.
series_uid: Union[str, UID]
Series uid of wsi to open
series_uids: Union[str, UID, Iterable[Union[str, UID]]]
Series uids of wsi to open
transfer_syntax: UID
Transfer syntax to request for image data, for example
UID("1.2.840.10008.1.2.4.50") for JPEGBaseline8Bit.
Expand All @@ -147,7 +147,7 @@ def open_web(
WsiDicom created from WSI DICOM instances in study-series.
"""
source = WsiDicomWebSource(
client, study_uid, series_uid, requested_transfer_syntax
client, study_uid, series_uids, requested_transfer_syntax
)
return cls(source, label, True)

Expand Down