Skip to content

Commit

Permalink
generate label based on patient id and study count in xnat project
Browse files Browse the repository at this point in the history
  • Loading branch information
p-j-smith committed Dec 16, 2024
1 parent 8a4a793 commit 04532e7
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 4 deletions.
62 changes: 60 additions & 2 deletions pixl_core/src/core/uploader/_xnat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@
from __future__ import annotations

import os
from io import BytesIO
from typing import TYPE_CHECKING, BinaryIO, Optional
from zipfile import ZIP_DEFLATED, ZipFile

import xnat
import xnat.datatypes
import xnat.type_hints
from pydicom import dcmread

from core.uploader.base import Uploader

Expand Down Expand Up @@ -77,6 +82,28 @@ def _upload_dicom_image(
zip_content = get_study_zip_archive(study_id)
self.upload_to_xnat(zip_content, study_tags)

def _split_zip_by_modality(self, zip_content: BinaryIO) -> dict[str, BinaryIO]:
"""Split a zip file by modality."""
zip_content_by_modality = {}
with ZipFile(zip_content) as zipped_study:
for file_info in zipped_study.infolist():
with zipped_study.open(file_info) as file:
dataset = dcmread(file)
modality = dataset.Modality
patient_id = dataset.PatientID
label = f"{patient_id}_{modality}"
if label not in zip_content_by_modality:
zip_content_by_modality[label] = BytesIO()
with ZipFile(

Check warning on line 97 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L87-L97

Added lines #L87 - L97 were not covered by tests
zip_content_by_modality[label], "a", compression=ZIP_DEFLATED
) as zipped_modality:
zipped_modality.writestr(file_info.filename, file.read())

Check warning on line 100 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L100

Added line #L100 was not covered by tests

for zipped_modality in zip_content_by_modality.values():
zipped_modality.seek(0)

Check warning on line 103 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L102-L103

Added lines #L102 - L103 were not covered by tests

return zip_content_by_modality

Check warning on line 105 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L105

Added line #L105 was not covered by tests

def upload_to_xnat(
self,
zip_content: BinaryIO,
Expand All @@ -87,17 +114,48 @@ def upload_to_xnat(
user=self.user,
password=self.password,
) as session:
experiment = self._get_experiment_label(

Check warning on line 117 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L117

Added line #L117 was not covered by tests
session=session,
patient_id=study_tags.patient_id,
)

session.services.import_(
data=zip_content,
overwrite=self.overwrite,
destination=self.destination,
destination=self.project_slug,
project=self.project_slug,
subject=study_tags.patient_id,
experiment=study_tags.pseudo_anon_image_id,
experiment=experiment,
content_type="application/zip",
import_handler="DICOM-zip",
)

def _get_experiment_label(
self,
session: xnat.XNATSession,
patient_id: str,
) -> str:
"""
Create a unique experiment label based on the PatientID and number of existing DICOM studies
for the patient.
"""
project: xnat.mixin.ProjectData = session.projects[self.project_slug]
try:
subject: xnat.mixin.SubjectData = project.subjects[patient_id]
except KeyError:
n_archive_experiments = 0

Check warning on line 146 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L142-L146

Added lines #L142 - L146 were not covered by tests
else:
n_archive_experiments = len(subject.experiments)

Check warning on line 148 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L148

Added line #L148 was not covered by tests

n_prearchive_experiments = len(

Check warning on line 150 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L150

Added line #L150 was not covered by tests
session.prearchive.find(
project=self.project_slug,
subject=patient_id,
)
)
n_experiments = n_archive_experiments + n_prearchive_experiments
return f"{patient_id}_{n_experiments + 1}"

Check warning on line 157 in pixl_core/src/core/uploader/_xnat.py

View check run for this annotation

Codecov / codecov/patch

pixl_core/src/core/uploader/_xnat.py#L156-L157

Added lines #L156 - L157 were not covered by tests

def upload_parquet_files(self, parquet_export: ParquetExport) -> None: # noqa: ARG002
msg = "XNATUploader does not support parquet files"
raise NotImplementedError(msg)
5 changes: 3 additions & 2 deletions pixl_dcmd/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
)
from pytest_pixl.dicom import generate_dicom_dataset
from pytest_pixl.helpers import run_subprocess
from conftest import ids_for_parameterised_test

if typing.TYPE_CHECKING:
from core.project_config.pixl_config_model import PixlConfig
Expand Down Expand Up @@ -191,7 +190,9 @@ def test_anonymise_and_validate_as_external_user(


@pytest.mark.parametrize(
("yaml_file"), PROJECT_CONFIGS_DIR.glob("*.yaml"), ids=ids_for_parameterised_test
("yaml_file"),
PROJECT_CONFIGS_DIR.glob("*.yaml"),
ids=lambda yaml_file: str(yaml_file.stem),
)
def test_anonymise_and_validate_dicom(caplog, request, yaml_file) -> None:
"""
Expand Down

0 comments on commit 04532e7

Please sign in to comment.