Replies: 2 comments 3 replies
-
Do you still need help with this? You could easily duplicate individual slice DICOM files; the hard part is in updating the SliceLocation across all the slices, or in adapting the new studyset to any associated dose/contours, etc. Can you give more detail on what the use-case is here? |
Beta Was this translation helpful? Give feedback.
1 reply
-
Here's a script I use to extend CT datasets by copying slices at either the superior or inferior end: #!/usr/bin/env python
"""Search a directory for DICOM CT Image Storage datasets and extend it in a
given direction (either superiorly or inferiorly) and generate new and
consistent UIDs for the following top-level elements:
* (0020,000D) Study Instance UID (with --study)
* (0020,000E) Series Instance UID
* (0008,0018) SOP Instance UID
* (0020,0052) Frame of Reference UID (with --force-for)
The study will be extended by copying the most superior or inferior slice.
"""
from __future__ import print_function
import argparse
from copy import deepcopy
import os
import shutil
import sys
from pydicom import dcmread
from pydicom.uid import generate_uid, PYDICOM_ROOT_UID, UID
from pydicom._storage_sopclass_uids import CTImageStorage
def setup_argparse():
"""Setup the CLI argument parser.
Returns
-------
parser : argparse.ArgumentParser
The CLI argument parser object.
"""
parser = argparse.ArgumentParser(
description=(
"Search a directory for DICOM CT Image Storage datasets, "
"extend it either superiorly or inferiorly and generate new and "
"consistent UIDs for the extended data"
),
)
# Required arguments
parser.add_argument(
'dir',
type=str,
help="The path to the directory containing the CT study",
)
parser.add_argument(
'n',
type=int,
help=(
"The number of times the most inferior/superior slice will "
"be copied"
)
)
# Extension direction
direction = parser.add_mutually_exclusive_group(required=True)
direction.add_argument(
'--inf',
action='store_true',
help="Extend the study in the inferior direction",
)
direction.add_argument(
'--sup',
action='store_true',
help="Extend the study in the superior direction",
)
# Optional arguments
parser.add_argument(
'-o',
type=str,
help=(
"The path to the directory where the extended CT study should be "
"written"
),
default="ext"
)
parser.add_argument(
'--org-root',
type=str,
help=(
"Override the <org root> section of the UIDs with the "
"supplied value (default is the pydicom root UID "
"'1.2.826.0.1.3680043.8.498.')"
),
default=PYDICOM_ROOT_UID,
)
#parser.add_argument(
# '--uid-length',
# type=int,
# help=(
# "Limit the generated UID lengths to the specified number of "
# "characters (maximum 64)"
# ),
# default=62,
#)
parser.add_argument(
'--study',
action='store_true',
help=(
"Generate new UIDs at the Study level rather than the Series "
"level (i.e. change (0020,000D) 'Study Instance UID', (0020,000E) "
"'Series Instance UID', (0008,0018) 'SOP Instance UID' and "
"(0020,0052) 'Frame Of Reference UID')"
),
default=False,
)
parser.add_argument(
'--force-for',
action='store_true',
help="Force a new (0020,0052) 'Frame of Reference UID' value",
default=False,
)
return parser
def search_directory(fdir):
"""Search a directory for CT Image Storage datasets."""
# Check that all the CT Images belong to the same Study/Series/FoR
data = {}
for fname in os.listdir(fdir):
fdir = os.path.abspath(fdir)
fpath = os.path.join(fdir, fname)
if os.path.isfile(fpath):
try:
ds = dcmread(fpath, force=True)
except IOError as exc:
continue
try:
study_uid = ds.StudyInstanceUID
series_uid = ds.SeriesInstanceUID
sop_uid = ds.SOPInstanceUID
iop = ds.ImageOrientationPatient
sop_class = ds.SOPClassUID
except Exception as exc:
continue
# Only Axial CT slices are supported
if sop_class != CTImageStorage:
continue
if iop != [1, 0, 0, 0, 1, 0]:
continue
data.setdefault(study_uid, {})
if series_uid not in data[study_uid]:
series = Series()
data[study_uid][series_uid] = series
try:
series.description = ds.SeriesDescription
except Exception as exc:
series.description = 'no description'
series.sop_instance_uids.append(sop_uid)
series.fpath.append(fpath)
if not data:
sys.exit("No suitable CT studies available")
# List all available studies
all_studies = sorted(data.keys())
print('The following studies were found in {}'.format(fdir))
ii = 1
for study in all_studies:
print('Study UID {}'.format(study))
for series in sorted(data[study].keys()):
series_cls = data[study][series]
n_slices = len(series_cls)
series_cls.app_index = str(ii)
desc = series_cls.description
print(' {}. {} ({} images)'.format(ii, desc, n_slices))
ii += 1
index = input(
'Please enter the index number of the series to extend, or 0 to exit: '
)
if index == '0':
sys.exit()
for study in all_studies:
for series in data[study]:
series_cls = data[study][series]
if series_cls.app_index == index:
return series_cls
raise ValueError(
"Invalid index number"
)
def extend(series, extend_sup, nr, level='series', force_for=False):
"""
Parameters
----------
series : list of Series
The CT series to extend.
extend_sup : bool
If True then extend the series superiorly, else inferiorly.
nr : int
The number of slices to extend the series by.
"""
# DICOM coordinates:
# X: increases to the LHS of the patient
# Y: increases to the POSTERIOR side of the patient
# Z: increases to the SUPERIOR side of the patient
# Read datasets
datasets = [dcmread(fp, force=True) for fp in series.fpath]
# Sorted from most superior to least
datasets = sorted(
datasets, key=lambda x: x.ImagePositionPatient[2], reverse=True
)
# The dataset to copy
if extend_sup:
copy_ds = datasets[0]
else:
copy_ds = datasets[-1]
# Need to change:
# Series Instance UID, SOP Instance UID
# if --study: StudyInstanceUID, FrameOfReferenceUID
# if --force-for: FrameOfReferenceUID
#
# (0018,9327) TablePosition, float, required if Frame Type is ORIGINAL
# (0020,0013) InstanceNumber, int starting at 1
# (0020,0032) ImagePositionPatient
# (0020,1041) SliceLocation
# Determine slice spacing
ipp = [ds.ImagePositionPatient[2] for ds in datasets]
delta_position = [ii - jj for ii, jj in zip(ipp[:-1], ipp[1:])]
offsets = list(set(delta_position))
if len(offsets) != 1:
print(
"Unable to continue as spacing between the CT images is "
"inconsistent"
)
sys.exit()
slice_spacing = offsets[0]
for ii in range(nr):
ds = deepcopy(copy_ds)
delta = (ii + 1) * slice_spacing
if not extend_sup:
delta *= -1.0
if 'TablePosition' in ds:
ds.TablePosition = ds.TablePosition + delta
ds.ImagePositionPatient[2] = ds.ImagePositionPatient[2] + delta
ds.SliceLocation = ds.SliceLocation + delta
datasets.append(ds)
if level == 'study':
# We use 62 character UIDs as some programs don't like full length
new_study = generate_uid()[:62]
force_for = True
new_series = generate_uid()[:62]
if force_for:
new_for = generate_uid()[:62]
# Update Instance Number and UIDs
datasets = sorted(datasets, key=lambda x: x.ImagePositionPatient[2], reverse=True)
for ii, ds in enumerate(datasets):
ds.InstanceNumber = ii + 1
if level == 'study':
ds.StudyInstanceUID = new_study
ds.SeriesInstanceUID = new_series
ds.SOPInstanceUID = generate_uid()[:62]
if force_for:
ds.FrameOfReferenceUID = new_for
return datasets
def write_extended(extended, fdir):
if not os.path.exists(fdir):
os.makedirs(fdir, exist_ok=True)
# Write filename based on acquisition number
for ds in extended:
fname = "{:0>5d}".format(ds.InstanceNumber)
fpath = os.path.join(fdir, fname)
ds.save_as(fpath, write_like_original=False)
print("Extended study saved to: {}".format(fdir))
class Series(object):
def __init__(self):
self.sop_instance_uids = []
self.description = 'no description'
self.fpath = []
self.app_index = None
def __len__(self):
return len(self.sop_instance_uids)
if __name__ == "__main__":
args = setup_argparse().parse_args()
series = search_directory(args.dir)
level = 'series'
if args.study:
level = 'study'
extended = extend(series, args.sup, args.n, level, args.force_for)
if args.o == 'ext':
fdir = os.path.join(os.path.abspath(args.dir), 'ext')
else:
fdir = args.o
write_extended(extended, fdir) |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I have 22 slices, I want to replicate the specific slice number and save it as a new DICOM file using Pydicom For example, I want to replicate slice numbers 3 and 4 one time then my total slices will be 24 to be saved in the new DICOM file.
The following code I write to read All DICOM files in a folder,
How can i process to add slices and Save Dicom
Beta Was this translation helpful? Give feedback.
All reactions