Skip to content

Commit c8ceab1

Browse files
committed
Remove logic to fix metadata
1 parent df88355 commit c8ceab1

1 file changed

Lines changed: 25 additions & 148 deletions

File tree

src/torchio/datasets/ct_rate.py

Lines changed: 25 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
from __future__ import annotations
22

3-
import ast
43
import enum
54
import multiprocessing
6-
import shutil
75
from pathlib import Path
86
from typing import TYPE_CHECKING
97
from typing import Literal
108
from typing import Union
119

12-
import numpy as np
13-
import SimpleITK as sitk
14-
from tqdm.auto import tqdm
15-
from tqdm.contrib.concurrent import process_map
1610
from tqdm.contrib.concurrent import thread_map
1711

1812
from ..data.dataset import SubjectsDataset
@@ -43,8 +37,8 @@ class MetadataIndexColumn(str, enum.Enum):
4337
class CtRate(SubjectsDataset):
4438
"""CT-RATE dataset.
4539
46-
This class provides access to
47-
`CT-RATE <https://huggingface.co/datasets/ibrahimhamamci/CT-RATE>`_,
40+
This class helps loading the `CT-RATE dataset
41+
<https://huggingface.co/datasets/ibrahimhamamci/CT-RATE>`_,
4842
which contains chest CT scans with associated radiology reports and
4943
abnormality labels.
5044
@@ -53,12 +47,14 @@ class CtRate(SubjectsDataset):
5347
Args:
5448
root: Root directory where the dataset has been downloaded.
5549
split: Dataset split to use, either ``'train'`` or ``'validation'``.
56-
token: Hugging Face token for accessing gated repositories. Alternatively,
57-
login using `huggingface-cli login` to cache the token.
5850
num_subjects: Optional limit on the number of subjects to load (useful for
59-
testing). If ``None``, all subjects in the split are loaded.
51+
debugging). If ``None``, all subjects in the split are loaded.
6052
report_key: Key to use for storing radiology reports in the Subject metadata.
61-
sizes: List of image sizes (in pixels) to include. Default: [512, 768, 1024].
53+
sizes: List of image sizes (in-plane, in voxels) to include.
54+
load_fixed: If ``True``, load the files with fixed spatial metadata
55+
added in `this pull request
56+
<https://huggingface.co/datasets/ibrahimhamamci/CT-RATE/discussions/85>`_.
57+
Otherwise, load the original files with incorrect spatial metadata.
6258
**kwargs: Additional arguments for SubjectsDataset.
6359
6460
Examples:
@@ -88,26 +84,32 @@ class CtRate(SubjectsDataset):
8884
'Bronchiectasis',
8985
'Interlobular septal thickening',
9086
]
87+
REPORT_KEYS = [
88+
'ClinicalInformation_EN',
89+
'Findings_EN',
90+
'Impressions_EN',
91+
'Technique_EN',
92+
]
9193

9294
def __init__(
9395
self,
9496
root: TypePath,
9597
split: TypeSplit = 'train',
9698
*,
97-
token: str | None = None,
9899
num_subjects: int | None = None,
99100
report_key: str = 'report',
100101
sizes: list[int] | None = None,
102+
load_fixed: bool = True,
101103
**kwargs,
102104
):
103105
self._root_dir = Path(root)
104-
self._token = token
105106
self._num_subjects = num_subjects
106107
self._report_key = report_key
107108
self._sizes = self._SIZES if sizes is None else sizes
108109

109110
self._split = self._parse_split(split)
110111
self.metadata = self._get_metadata()
112+
self._load_fixed = load_fixed
111113
subjects_list = self._get_subjects_list(self.metadata)
112114
super().__init__(subjects_list, **kwargs)
113115

@@ -313,7 +315,11 @@ def _instantiate_image(self, image_row: pd.Series) -> ScalarImage:
313315
"""
314316
image_dict = image_row.to_dict()
315317
filename = image_dict[self._FILENAME_KEY]
316-
image_path = self._root_dir / self._get_image_path(filename)
318+
relative_image_path = self._get_image_path(
319+
filename,
320+
load_fixed=self._load_fixed,
321+
)
322+
image_path = self._root_dir / relative_image_path
317323
report_dict = self._extract_report_dict(image_dict)
318324
image_dict[self._report_key] = report_dict
319325
image = ScalarImage(image_path, **image_dict)
@@ -332,19 +338,13 @@ def _extract_report_dict(self, subject_dict: dict[str, str]) -> dict[str, str]:
332338
Note:
333339
This method modifies the input subject_dict by removing the report keys.
334340
"""
335-
report_keys = [
336-
'ClinicalInformation_EN',
337-
'Findings_EN',
338-
'Impressions_EN',
339-
'Technique_EN',
340-
]
341341
report_dict = {}
342-
for key in report_keys:
342+
for key in self.REPORT_KEYS:
343343
report_dict[key] = subject_dict.pop(key)
344344
return report_dict
345345

346346
@staticmethod
347-
def _get_image_path(filename: str) -> Path:
347+
def _get_image_path(filename: str, load_fixed: bool) -> Path:
348348
"""Construct the relative path to an image file within the dataset structure.
349349
350350
Parses the filename to determine the hierarchical directory structure
@@ -363,131 +363,8 @@ def _get_image_path(filename: str) -> Path:
363363
parts = filename.split('_')
364364
base_dir = 'dataset'
365365
split_dir = parts[0]
366+
if load_fixed:
367+
split_dir = f'{split_dir}_fixed'
366368
level1 = f'{parts[0]}_{parts[1]}'
367369
level2 = f'{level1}_{parts[2]}'
368370
return Path(base_dir, split_dir, level1, level2, filename)
369-
370-
@staticmethod
371-
def _fix_image(image: ScalarImage, out_path: Path, *, force: bool = False) -> None:
372-
"""Fix the spatial metadata of a CT-RATE image file.
373-
374-
The original NIfTI files in the CT-RATE dataset have incorrect spatial
375-
metadata. This method reads the image, fixes the spacing, origin, and
376-
orientation based on the metadata provided in the CSV, and applies the correct
377-
rescaling to convert to Hounsfield units.
378-
379-
Args:
380-
in_path: The path to the image file to fix.
381-
out_path: The path where the fixed image will be saved.
382-
383-
Note:
384-
This method overwrites the original file with the fixed version.
385-
The fixed image is stored as INT16 with proper HU values.
386-
"""
387-
# Adapted from https://huggingface.co/datasets/ibrahimhamamci/CT-RATE/blob/main/download_scripts/fix_metadata.py
388-
if not force and out_path.exists():
389-
return
390-
spacing_x, spacing_y = map(float, ast.literal_eval(image['XYSpacing']))
391-
spacing_z = image['ZSpacing']
392-
image_sitk = sitk.ReadImage(str(image.path))
393-
image_sitk.SetSpacing((spacing_x, spacing_y, spacing_z))
394-
395-
image_sitk.SetOrigin(ast.literal_eval(image['ImagePositionPatient']))
396-
397-
orientation = ast.literal_eval(image['ImageOrientationPatient'])
398-
row_cosine, col_cosine = orientation[:3], orientation[3:6]
399-
z_cosine = np.cross(row_cosine, col_cosine).tolist()
400-
image_sitk.SetDirection(row_cosine + col_cosine + z_cosine)
401-
402-
RescaleIntercept = image['RescaleIntercept']
403-
RescaleSlope = image['RescaleSlope']
404-
adjusted_hu = image_sitk * RescaleSlope + RescaleIntercept
405-
cast_int16 = sitk.Cast(adjusted_hu, sitk.sitkInt16)
406-
407-
out_path.parent.mkdir(parents=True, exist_ok=True)
408-
sitk.WriteImage(cast_int16, str(out_path))
409-
return cast_int16
410-
411-
def _copy_not_images(self, out_dir: Path) -> None:
412-
"""Copy all files from the root directory except the images."""
413-
for path in self._root_dir.iterdir():
414-
if path.name == 'dataset':
415-
for subdirectory in path.iterdir():
416-
if subdirectory.name in ['train', 'valid']:
417-
continue
418-
print(
419-
f'Copying {subdirectory} to {out_dir / subdirectory.relative_to(self._root_dir)}'
420-
)
421-
shutil.copytree(
422-
subdirectory,
423-
out_dir / subdirectory.relative_to(self._root_dir),
424-
dirs_exist_ok=True,
425-
)
426-
elif path.name.startswith('.'):
427-
continue
428-
elif path.is_dir():
429-
print(f'Copying {path} to {out_dir / path.name}')
430-
shutil.copytree(
431-
path,
432-
out_dir / path.name,
433-
dirs_exist_ok=True,
434-
)
435-
else:
436-
print(f'Copying {path} to {out_dir / path.name}')
437-
shutil.copy(path, out_dir / path.name)
438-
439-
def fix_metadata(
440-
self,
441-
out_dir: str | Path,
442-
parallelism: TypeParallelism = None,
443-
) -> CtRate:
444-
"""Fix the metadata of all images in the dataset.
445-
446-
Reads each image, applies the correct spatial metadata, and saves the fixed
447-
image to the specified output directory.
448-
449-
Args:
450-
out_dir: The directory where the fixed images will be saved.
451-
"""
452-
out_dir = Path(out_dir)
453-
out_dir.mkdir(parents=True, exist_ok=True)
454-
# self._copy_not_images(out_dir)
455-
images = []
456-
out_paths = []
457-
for subject in self.dry_iter():
458-
for image in subject.get_images():
459-
out_path = out_dir / image.path.relative_to(self._root_dir)
460-
images.append(image)
461-
out_paths.append(out_path)
462-
if parallelism == 'thread':
463-
thread_map(
464-
self._fix_image,
465-
images,
466-
out_paths,
467-
max_workers=multiprocessing.cpu_count(),
468-
desc='Fixing metadata',
469-
)
470-
elif parallelism == 'process':
471-
process_map(
472-
self._fix_image,
473-
images,
474-
out_paths,
475-
max_workers=multiprocessing.cpu_count(),
476-
desc='Fixing metadata',
477-
)
478-
else:
479-
zipped = zip(images, out_paths)
480-
with tqdm(total=len(images), desc='Fixing metadata') as pbar:
481-
for image, out_path in zipped:
482-
pbar.set_description(f'Fixing {image.path.name}')
483-
self._fix_image(image, out_path)
484-
pbar.update(1)
485-
new_dataset = CtRate(
486-
out_dir,
487-
split=self._split,
488-
token=self._token,
489-
num_subjects=self._num_subjects,
490-
report_key=self._report_key,
491-
sizes=self._sizes,
492-
)
493-
return new_dataset

0 commit comments

Comments
 (0)