Hello
I wrote the following code. My goal here is to slice a single DICOM file and establish a standard. However, the sliced DICOM does not appear the same as the original DICOM in the slicer program. The positions and spacings look different. What is the reason for this?
Thanks in advance for your answer.
import pydicom
import os
import numpy as np
from pydicom.uid import generate_uid
def calculate_geometry(ds):
shared_fg = ds.SharedFunctionalGroupsSequence[0]
pixel_measures_shared = shared_fg.PixelMeasuresSequence[0]
spacing_x = float(pixel_measures_shared.PixelSpacing[0])
spacing_y = float(pixel_measures_shared.PixelSpacing[1])
per_frame = ds.PerFrameFunctionalGroupsSequence
ipp1 = np.array(per_frame[0].PlanePositionSequence[0].ImagePositionPatient, dtype=float)
ipp2 = np.array(per_frame[1].PlanePositionSequence[0].ImagePositionPatient, dtype=float)
diff_vector = ipp2 - ipp1
iop = np.array(per_frame[0].PlaneOrientationSequence[0].ImageOrientationPatient, dtype=float)
row_cos = iop[:3]
col_cos = iop[3:]
slice_normal = np.cross(row_cos, col_cos)
slice_normal = slice_normal / np.linalg.norm(slice_normal)
spacing_between_slices = abs(np.dot(diff_vector, slice_normal))
slice_thickness = spacing_between_slices
if hasattr(per_frame[0], "PixelMeasuresSequence"):
pm = per_frame[0].PixelMeasuresSequence[0]
if hasattr(pm, "SliceThickness"):
slice_thickness = float(pm.SliceThickness)
if hasattr(pm, "SpacingBetweenSlices"):
spacing_between_slices = float(pm.SpacingBetweenSlices)
return spacing_x, spacing_y, slice_thickness, spacing_between_slices
def extract_multiframe_to_series(input_path, output_dir):
ds = pydicom.dcmread(input_path)
os.makedirs(output_dir, exist_ok=True)
frames = int(ds.NumberOfFrames)
pixel_array = ds.pixel_array
sx, sy, slice_thickness, spacing_between = calculate_geometry(ds)
new_series_uid = generate_uid()
frame_of_reference_uid = ds.FrameOfReferenceUID
print(f"Detected voxel size: {sx:.4f} × {sy:.4f} × {spacing_between:.4f} mm")
print(f"Total frames: {frames}")
print(f"New SeriesInstanceUID: {new_series_uid}")
per_frame = ds.PerFrameFunctionalGroupsSequence
for i in range(frames):
frame_fg = per_frame[i]
iop = frame_fg.PlaneOrientationSequence[0].ImageOrientationPatient
ipp = frame_fg.PlanePositionSequence[0].ImagePositionPatient
frame_pixels = pixel_array[i]
new_ds = pydicom.Dataset()
new_ds.file_meta = pydicom.Dataset()
new_ds.file_meta.TransferSyntaxUID = ds.file_meta.TransferSyntaxUID
new_ds.file_meta.MediaStorageSOPClassUID = ds.SOPClassUID
new_ds.file_meta.MediaStorageSOPInstanceUID = generate_uid()
new_ds.file_meta.ImplementationClassUID = pydicom.uid.PYDICOM_IMPLEMENTATION_UID
new_ds.PatientName = ds.get("PatientName", "Anonymous")
new_ds.PatientID = ds.get("PatientID", "ID")
new_ds.PatientBirthDate = getattr(ds, "PatientBirthDate", "")
new_ds.PatientSex = getattr(ds, "PatientSex", "")
new_ds.StudyInstanceUID = ds.StudyInstanceUID
new_ds.StudyDate = getattr(ds, "StudyDate", "")
new_ds.StudyTime = getattr(ds, "StudyTime", "")
new_ds.StudyDescription = getattr(ds, "StudyDescription", "")
new_ds.SeriesInstanceUID = new_series_uid
new_ds.SeriesNumber = 1001
new_ds.SeriesDescription = f"{getattr(ds, 'SeriesDescription', 'Tomosynthesis')} - Split"
new_ds.Modality = ds.Modality
new_ds.SOPClassUID = ds.SOPClassUID
new_ds.SOPInstanceUID = generate_uid()
new_ds.InstanceNumber = i + 1
new_ds.ImageOrientationPatient = iop
new_ds.ImagePositionPatient = ipp
new_ds.FrameOfReferenceUID = frame_of_reference_uid
new_ds.Rows = ds.Rows
new_ds.Columns = ds.Columns
new_ds.PixelSpacing = [str(sx), str(sy)]
new_ds.SliceThickness = str(slice_thickness)
new_ds.SpacingBetweenSlices = str(spacing_between)
new_ds.SamplesPerPixel = ds.SamplesPerPixel
new_ds.PhotometricInterpretation = ds.PhotometricInterpretation
new_ds.BitsAllocated = ds.BitsAllocated
new_ds.BitsStored = ds.BitsStored
new_ds.HighBit = ds.HighBit
new_ds.PixelRepresentation = ds.PixelRepresentation
if "WindowCenter" in ds:
new_ds.WindowCenter = ds.WindowCenter
new_ds.WindowWidth = ds.WindowWidth
if "WindowCenterWidthExplanation" in ds:
new_ds.WindowCenterWidthExplanation = ds.WindowCenterWidthExplanation
new_ds.PixelData = frame_pixels.tobytes()
out_path = os.path.join(output_dir, f"{i+1:04d}.dcm")
pydicom.dcmwrite(out_path, new_ds)
if __name__ == "__main__":
extract_multiframe_to_series(r"tomo.dcm", r"DICOM_SPLIT")
The metadata for the first slice of the series DICOM appears as follows.

