-
Notifications
You must be signed in to change notification settings - Fork 6
NiFTi Conversion APIs #223
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
base: main
Are you sure you want to change the base?
Conversation
c836114 to
608e8d1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for converting and serving DICOM studies, series, and images as NIfTI files via WADO-RS endpoints. It introduces NIfTI conversion capabilities by adding new API views, renderers, and utility functions while extending the Python client with methods to retrieve NIfTI files.
- Implements WADO-RS NIfTI endpoints at study, series, and image levels with corresponding multipart renderer
- Adds comprehensive NIfTI retrieval utilities with asynchronous processing and temporary file management
- Extends the Python client with NIfTI retrieval methods and multipart response handling
Reviewed Changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pyproject.toml | Added requests-toolbelt dependency for multipart decoding |
| example.env | Added DICOM_SAMPLE_DIR environment variable configuration |
| docker-compose.base.yml | Added DICOM_SAMPLE_DIR environment variable to container |
| adit/settings/base.py | Added DICOM_SAMPLE_DIR setting for sample DICOM files path |
| adit/dicom_web/views.py | Implemented three new NIfTI retrieval API views for study/series/image levels |
| adit/dicom_web/utils/wadors_utils.py | Added comprehensive NIfTI conversion utilities and helper classes |
| adit/dicom_web/urls.py | Added URL patterns for NIfTI endpoints |
| adit/dicom_web/tests/acceptance/test_wadors.py | Added acceptance tests for NIfTI retrieval functionality |
| adit/dicom_web/renderers.py | Refactored multipart rendering and added NIfTI-specific renderer |
| adit-client/adit_client/client.py | Added NIfTI retrieval methods with multipart response handling |
cbb4d5d to
38cfc80
Compare
5e57123 to
f41d341
Compare
| ) | ||
| except (NoValidDicomError, NoSpatialDataError): | ||
| # These exceptions are expected for series that cannot be converted to NIfTI | ||
| # For example, non-image series (e.g., SR documents) or series without spatial data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you remember the Claude link I sent you? Please only ignore exceptions for series where we know that they don't contain any images. Otherwise also log a warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting that we skip running the conversion on series without images? That seems like a lot of overhead just to avoid attempting conversion. I'm also not sure what the best approach for identifying those series in advance would be.
Personally, I'd rather try converting every series and just catch the ones without image data using the NoValidDicomError and NoSpatialDataError exceptions.
Is there something I'm overlooking here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I suggest not logging anything for series we know that those don't contain images. And log something for other series when they don't contain images (because we would expect that those contain images).
| # For .nii.gz files, strip the .nii part from the base_name | ||
| actual_base = os.path.splitext(base_name)[0] | ||
| file_pairs.setdefault(actual_base, {}).update({"nifti": filename}) | ||
| elif ext == ".nii": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about bval/bvec files? Aditya asked about those in the group meeting (do you remember?). And we should also log a warning if other files are created than we expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extended for bvac/bval files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The warning (or maybe info?!) for other files is still missing (an additional else block)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not expect any other files (maybe perhaps .txt file). But yes, logging this doesn't hurt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is precisely what assert is for; to prove your expectation. So assert it then.
…rt_message from DICOMWebClient for iter methods with patching
| yield bval_filename, BytesIO(bval_content) | ||
|
|
||
| # Finally yield the bvec file if it exists (diffusion b-vectors) | ||
| if "bvec" in files: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if clauses look very redundant, why not a for loop like this:
for file_type in ["json", "nifti", ...]:
filename = files[file_type]
...|
|
||
| class DcmExitCode(IntEnum): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this DcmToNiftiExitCode? Makes it more clear what exit code it is.
| ] | ||
|
|
||
| # The base directory of the project (the root of the repository) | ||
| BASE_PATH = Path(__file__).resolve(strict=True).parent.parent.parent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BASE_PATH is already set in base. And as you see in Line 1/3 everything is imported from there.
| pass | ||
|
|
||
|
|
||
| class DicomConversionError(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would here also prefer DcmToNiftiConversionError as it's clearer. Also why is this in core? It's only used in dicom_web!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this in core?! It's part of dicom_web!
| self.query_ds = QueryDataset.from_dict(query) | ||
| self.operator = DicomOperator(source_server) | ||
|
|
||
| def create_fetch_task( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this still called create_fetch_task? What task does it create? Also, please provide a valid return type as this is a bit strange function. I still wonder if it is not better to keep this method sync and then just use sync_to_async where it is really used. Would also get rid off those redundant sync_to_async calls.
| except Exception as e: | ||
| # For all other unexpected errors, log and propagate the error | ||
| logger.error(f"Error during DICOM to NIfTI conversion: {e}") | ||
| raise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary for the same reasons as mentioned in this comment: #223 (comment)
Only logging and re-raising doesn't make much sense. Not handled exceptions are logged anyway.
bccd322 to
0295bcc
Compare
| line-length = 100 | ||
| lint.select = ["E", "F", "I", "DJ"] | ||
|
|
||
| [tool.ruff.lint.isort] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why that!? I prefer the default order. This PR is not about changing the style guide.
Pull Request Overview
This PR adds support for converting and serving DICOM studies, series, and images as NIfTI files via WADO-RS endpoints.
requests-toolbeltdependency for multipart decoding in the client.wado_retrieve_niftiutility, new DRF renderers, views, and URL routes for NIfTI retrieval.retrieve_nifti_studyand updates acceptance tests.Reviewed Changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
requests-toolbeltdependencyRetrieveNifti*APIViewclasseswado_retrieve_niftiutilityretrieve_nifti_studymethod inAditClientCloses #113