Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
073aadb
fix bug the fill only produces 1 instead of label. Was binary, instea…
robert-graf May 7, 2026
e8a78a0
negative padding, resample_from_to can now padd instead of resampte i…
robert-graf May 7, 2026
94a1dee
bump up numpy version
robert-graf May 7, 2026
b3131a1
bump up scipy
robert-graf May 7, 2026
7978776
x
robert-graf May 7, 2026
77346b5
change versions
robert-graf May 7, 2026
badbca8
numpy 2.0 support for 3.11 and above
robert-graf May 7, 2026
206306d
no upper limit for scikit-image
robert-graf May 7, 2026
dc607be
Major seep-up, by prevent copying.
robert-graf May 11, 2026
d2269cf
remove python build-ins from toml
robert-graf May 13, 2026
93e08c2
remove ants (now optional)
robert-graf May 13, 2026
8194e1c
prevent error when spineps does not find a Dense
robert-graf May 13, 2026
a4d7902
added PET export
robert-graf May 13, 2026
ab06471
x
robert-graf May 13, 2026
1b09f8e
Merge branch 'development_robert' of github.com:Hendrik-code/TPTBox i…
robert-graf May 13, 2026
e852059
Merge branch 'main' into development_robert
robert-graf May 13, 2026
9d46729
Merge branch 'development_robert' of https://github.com/Hendrik-code/…
robert-graf May 13, 2026
585760c
exclude bias_field test, because pyants is now optional
robert-graf May 13, 2026
0f647d9
forgot inplace boolean
robert-graf May 13, 2026
f39b443
add get_outpaths_spineps to cannonical imports
robert-graf May 13, 2026
6d6654d
minor bug fixes
robert-graf May 20, 2026
9025061
add PDF export support
robert-graf May 20, 2026
de3a52a
add option to set ramp path in stitching.
robert-graf May 20, 2026
a9b6e6e
Merge branch 'development_robert' of github.com:Hendrik-code/TPTBox i…
robert-graf May 20, 2026
e169b4e
add print outs
robert-graf May 20, 2026
f70abba
Merge branch 'development_robert' of https://github.com/Hendrik-code/…
robert-graf May 20, 2026
877d733
ruff
robert-graf May 20, 2026
b8382f2
Merge branch 'development_robert' of github.com:Hendrik-code/TPTBox i…
robert-graf May 20, 2026
b5fad17
ruff
robert-graf May 20, 2026
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
4 changes: 3 additions & 1 deletion TPTBox/core/bids_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@
"localizer",
"difference",
"labels",
"report",
"pet",
]
# https://bids-specification.readthedocs.io/en/stable/appendices/entity-table.html
formats_relaxed = [*formats, "t2", "t1", "t2c", "t1c", "mr", "snapshot", "t1dixon", "dwi", "ctb"]
Expand Down Expand Up @@ -221,7 +223,7 @@
"OPT": "Ophthalmic Tomography",
"OPV": "Ophthalmic Visual Field",
"OSS": "Optical Surface Scan",
"OT": "Other ",
"OT": "Other",
"PLAN": "Plan",
"PR": "Presentation State",
"PT": "Positron emission tomography (PET)",
Expand Down
2 changes: 1 addition & 1 deletion TPTBox/core/bids_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ def get_changed_bids(
auto_add_run_id=False,
additional_folder: str | None = None,
dataset_path: str | None = None,
make_parent=True,
make_parent=False,
non_strict_mode=False,
):
ds = dataset_path if dataset_path is not None else self.get_path_decomposed()[0]
Expand Down
98 changes: 90 additions & 8 deletions TPTBox/core/dicom/dicom_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,68 @@ def dicom_to_nifti_multiframe(ds, nii_path):
return nii_path


def _convert_to_nifti(dicom_out_path, nii_path):
def _export_pdf_from_dicom(dcm_path, out_pdf):
assert len(dcm_path) == 1, dcm_path
ds = dcm_path[0]

# verify modality / SOP class
if ds.Modality.upper() != "PDF":
raise ValueError("Not a PDF DICOM")

if "EncapsulatedDocument" not in ds:
raise ValueError("No embedded PDF found")

pdf_bytes = ds.EncapsulatedDocument

out_pdf = Path(out_pdf)
out_pdf.write_bytes(pdf_bytes)


def _collect_text(ds, txt_lines: list[str] | None = None):
if txt_lines is None:
txt_lines = []

def _help_collect_text(content_sequence, level: int = 0):
for item in content_sequence:
prefix = " " * level

concept = ""

if hasattr(item, "ConceptNameCodeSequence"):
try:
concept = item.ConceptNameCodeSequence[0].CodeMeaning
except Exception:
pass

value = None

for attr in ["TextValue", "CodeMeaning", "NumericValue"]:
if hasattr(item, attr):
value = getattr(item, attr)
break

if concept or value is not None:
txt_lines.append(f"{prefix}{concept}: {value}")

if hasattr(item, "ContentSequence"):
_help_collect_text(
item.ContentSequence,
level + 1,
)

if hasattr(ds, "ContentSequence"):
_help_collect_text(ds.ContentSequence)
return txt_lines


def _extract_txt_from_dicom(dcm_path, out_txt):
lines = []
for p in dcm_path:
lines = _collect_text(p, lines)
Path(out_txt).write_text("\n".join(lines))


def _extract_nii_from_dicom(dicom_out_path, nii_path):
"""
Convert DICOM files to NIfTI format and handle common conversion errors.

Expand All @@ -206,6 +267,7 @@ def _convert_to_nifti(dicom_out_path, nii_path):
FunctionTimedOut: Raised if the DICOM-to-NIfTI conversion times out.
ValueError: Raised for generic validation failures.
"""

try:
if isinstance(dicom_out_path, list):
try:
Expand All @@ -217,6 +279,7 @@ def _convert_to_nifti(dicom_out_path, nii_path):
return True
except Exception as e:
logger.on_debug("Multi-Frame DICOM did not work:", e)
## The PDF dicom lands here
convert_dicom.dicom_array_to_nifti(dicom_out_path, nii_path, True)
else:
# func_timeout(10, dicom2nifti.dicom_series_to_nifti, (dicom_out_path, nii_path, True))
Expand Down Expand Up @@ -246,6 +309,9 @@ def _convert_to_nifti(dicom_out_path, nii_path):
logger.print_error()

return False
except Exception:
print(nii_path)

return True


Expand All @@ -264,7 +330,7 @@ def _get_paths(
):
if keys is None:
keys = {}
(mri_format, keys) = extract_keys_from_json(
(mri_format, keys, ending) = extract_keys_from_json(
simp_json,
dcm_data_l,
use_session,
Expand All @@ -277,7 +343,7 @@ def _get_paths(
json_file_name, json_bids_name = _generate_bids_path(
dataset_nifti_dir, keys, mri_format, simp_json, make_subject_chunks=make_subject_chunks, parent=parent
)
nii_path = str(json_file_name).replace(".json", "") + ".nii.gz"
nii_path = str(json_file_name).replace(".json", "") + ending
return json_file_name, json_bids_name, nii_path


Expand Down Expand Up @@ -364,11 +430,17 @@ def _from_dicom_to_nii(
if exist and Path(nii_path).exists():
logger.print("already exists:", json_file_name, ltype=Log_Type.STRANGE, verbose=verbose)
return nii_path
suc = _convert_to_nifti(dcm_data_l, nii_path)
add_grid = False
if nii_path.endswith(".pdf"):
_export_pdf_from_dicom(dcm_data_l, nii_path)
elif nii_path.endswith(".txt"):
_extract_txt_from_dicom(dcm_data_l, nii_path)
else:
add_grid = _extract_nii_from_dicom(dcm_data_l, nii_path)

if suc:
if add_grid:
_add_grid_info_to_json(nii_path, json_file_name)
return nii_path if suc else None
return nii_path if add_grid else None


def _add_grid_info_to_json(nii_path: Path | str, simp_json: Path | str, force_update=False, add=True):
Expand All @@ -390,7 +462,7 @@ def _add_grid_info_to_json(nii_path: Path | str, simp_json: Path | str, force_up
return json_dict


def _find_all_files(dcm_dirs: Path | list[Path]):
def _find_all_files(dcm_dirs: Path | list[Path],verbose=False):
"""
Recursively find all DICOM directories or files in the given paths.

Expand All @@ -400,6 +472,9 @@ def _find_all_files(dcm_dirs: Path | list[Path]):
Yields:
Path: Paths to directories or individual DICOM files found during the search.
"""
if verbose:
logger.on_neutral("Start file searching")
i = 0
yield dcm_dirs
dcm_dirs = dcm_dirs if isinstance(dcm_dirs, list) else [dcm_dirs]
for dcm_dir in dcm_dirs:
Expand All @@ -408,9 +483,15 @@ def _find_all_files(dcm_dirs: Path | list[Path]):
file = ""
for file in files:
if Path(file).is_file(): # str(file).endswith(".dcm") or str(file).endswith(".ima")
if verbose:
logger.on_neutral("File ",i,end="\r")
i += 1
yield Path(root, file).absolute().parent
break
else:
if verbose:
logger.on_neutral("File ",i,end="\r")
i += 1
yield Path(root, file)
# if "." not in str(file):
# yield Path(root, file).absolute().parent
Expand Down Expand Up @@ -606,7 +687,7 @@ def extract_dicom_folder(
convert_dicom.settings.disable_validate_slice_increment()
outs = {}

for p in _find_all_files(dicom_folder):
for p in _find_all_files(dicom_folder,verbose=verbose):
dicom_path = p

if str(dicom_path).endswith(".pkl"):
Expand Down Expand Up @@ -672,6 +753,7 @@ def process_series(key, files, parts):
p, Path("/media/data/robert/datasets", "dataset-Durchleuchtung222"), False, False, validate_slice_increment=False
)


sys.exit()
# s = "/home/robert/Downloads/bein/dataset-oberschenkel/rawdata/sub-1-3-46-670589-11-2889201787-2305829596-303261238-2367429497/mr/sub-1-3-46-670589-11-2889201787-2305829596-303261238-2367429497_sequ-406_mr.nii.gz"
# nii2 = NII.load(s, False)
Expand Down
54 changes: 36 additions & 18 deletions TPTBox/core/dicom/dicom_header_to_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,15 @@ def extract_keys_from_json( # noqa: C901
def _get(key, default=None):
if key not in simp_json:
return keys.get(key, default)
return str(simp_json[key]).replace("_", "-").replace(" ", "-").replace(".", "-")
value = str(simp_json[key]).replace("_", "-").replace(" ", "-").replace(".", "-")
# remove invalid filename characters
value = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "", value)
# collapse repeated dashes
value = re.sub(r"-+", "-", value)
# strip leading/trailing dots and dashes
value = value.strip(".-")

return value

"""Extract keys from JSON based on study and series descriptions."""
#### NAKO FIXED ####
Expand All @@ -174,25 +182,28 @@ def _get(key, default=None):
series_description = _get("SeriesDescription", "unnamed")
"""Determine the MRI format based on the series description."""
if "T2_TSE" in series_description:
return "T2w", {"acq": "sag", "chunk": series_description.split("_")[-1], "sequ": simp_json["SeriesNumber"], **keys}
return "T2w", {"acq": "sag", "chunk": series_description.split("_")[-1], "sequ": simp_json["SeriesNumber"], **keys}, ".nii.gz"
elif "3D_GRE_TRA" in series_description:
return "vibe", {
"acq": "ax",
"part": dixon_mapping[series_description.split("_")[-1].lower()],
"chunk": _get("ProtocolName", "unnamed").split("_")[-1],
**keys,
}
return (
"vibe",
{
"acq": "ax",
"part": dixon_mapping[series_description.split("_")[-1].lower()],
"chunk": _get("ProtocolName", "unnamed").split("_")[-1],
**keys,
},
".nii.gz",
)
elif "ME_vibe" in series_description:
return "mevibe", {
"acq": "ax",
"part": dixon_mapping[series_description.split("_")[-1].lower()],
"sequ": simp_json["SeriesNumber"],
**keys,
}
return (
"mevibe",
{"acq": "ax", "part": dixon_mapping[series_description.split("_")[-1].lower()], "sequ": simp_json["SeriesNumber"], **keys},
".nii.gz",
)
elif "PD" in series_description:
return "pd", {"acq": "iso", **keys}
return "pd", {"acq": "iso", **keys}, ".nii.gz"
elif "T2_HASTE" in series_description:
return "T2haste", {"acq": "ax", **keys}
return "T2haste", {"acq": "ax", **keys}, ".nii.gz"
else:
raise NotImplementedError(series_description)
# GENERAL
Expand Down Expand Up @@ -268,6 +279,8 @@ def _get(key, default=None):
found = False
if modality == "ct":
mri_format = "ct"
elif modality.lower() == "pt":
mri_format = "pet"
elif modality == "xa": # Angiography
biplane = False
if "BIPLANE A" in image_type or "SINGLE A" in image_type:
Expand Down Expand Up @@ -319,9 +332,14 @@ def _get(key, default=None):
" km " in series_description.lower() or series_description.startswith("km") or series_description.endswith("km")
) and keys.get("ce") is None:
keys["ce"] = "ContrastAgent"
elif modality.lower() == "pdf":
return "report", keys, ".pdf"
elif modality.lower() == "sr":
keys["desc"] = _get("SeriesDescription", None)
return "report", keys, ".txt"
else:
raise NotImplementedError(f"modality='{modality.upper()}', ({modalities.get(modality.upper())})")
raise NotImplementedError(f"modality='{modality}', ({modalities.get(modality.upper(), 'Non Standard Modality key')})")

# ".*sub.*t1.*": "subtraktion",
# "subtraktion.*t1.*": "subtraktion",
return mri_format, keys
return mri_format, keys, ".nii.gz"
Loading
Loading