You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
6.0 KiB

import os
from typing import Optional
import cv2
import numpy as np
import pydicom
def read_dicom(path: str) -> np.ndarray:
ds = pydicom.dcmread(path)
img = ds.pixel_array
if getattr(ds, "PhotometricInterpretation", "") == "MONOCHROME1":
img = np.max(img) - img
img = img.astype(np.float32)
img -= img.min()
if img.max() > 0:
img /= img.max()
img = (img * 255.0).clip(0, 255).astype(np.uint8)
if img.ndim == 2:
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
elif img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
return img
def save_secondary_capture(
original_dicom_path: str,
img_bgr: np.ndarray,
out_path: str,
series_description: str = "RG-SCOLIOSIS-CONTOUR",
overlay_text: Optional[str] = None,
):
import pydicom
from pydicom.uid import generate_uid
ds = pydicom.dcmread(original_dicom_path)
img_draw = img_bgr.copy()
if overlay_text:
cv2.putText(
img_draw,
overlay_text,
(20, 40),
cv2.FONT_HERSHEY_SIMPLEX,
1.0,
(0, 255, 0),
2,
)
img_rgb = cv2.cvtColor(img_draw, cv2.COLOR_BGR2RGB)
rows, cols = img_rgb.shape[:2]
ds.SOPClassUID = "1.2.840.10008.5.1.4.1.1.7"
ds.SOPInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.Modality = "OT"
ds.SeriesDescription = series_description
if "SeriesNumber" in ds:
try:
ds.SeriesNumber = int(ds.SeriesNumber) + 1000
except Exception:
ds.SeriesNumber = 1000
else:
ds.SeriesNumber = 1000
ds.Rows = rows
ds.Columns = cols
ds.SamplesPerPixel = 3
ds.PhotometricInterpretation = "RGB"
ds.PlanarConfiguration = 0
ds.BitsAllocated = 8
ds.BitsStored = 8
ds.HighBit = 7
ds.PixelRepresentation = 0
ds.PixelData = img_rgb.tobytes()
os.makedirs(os.path.dirname(out_path), exist_ok=True)
ds.save_as(out_path, write_like_original=False)
def save_dicom_sr(
original_dicom_path: str,
description_text: str,
conclusion_text: str,
out_path: str,
series_description: str = "SCOLIOSIS_SR",
extra_struct: Optional[dict] = None,
):
import datetime as _dt
import pydicom
from pydicom.dataset import Dataset, FileDataset
from pydicom.uid import ExplicitVRLittleEndian, generate_uid
src = pydicom.dcmread(original_dicom_path)
file_meta = Dataset()
file_meta.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.88.11"
file_meta.MediaStorageSOPInstanceUID = generate_uid()
file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
file_meta.ImplementationClassUID = generate_uid()
ds = FileDataset(out_path, {}, file_meta=file_meta, preamble=b"\0" * 128)
for tag in (
"PatientName",
"PatientID",
"PatientBirthDate",
"PatientSex",
"StudyInstanceUID",
"StudyID",
"AccessionNumber",
"StudyDate",
"StudyTime",
):
if tag in src:
ds[tag] = src[tag]
ds.SOPClassUID = file_meta.MediaStorageSOPClassUID
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.SeriesInstanceUID = generate_uid()
ds.Modality = "SR"
ds.SeriesDescription = series_description
ds.SeriesNumber = 2000
ds.InstanceNumber = 1
if "SpecificCharacterSet" in src:
ds.SpecificCharacterSet = src.SpecificCharacterSet
else:
ds.SpecificCharacterSet = "ISO_IR 192"
now = _dt.datetime.now()
ds.ContentDate = now.strftime("%Y%m%d")
ds.ContentTime = now.strftime("%H%M%S")
root = Dataset()
root.ValueType = "CONTAINER"
root.ContinuityOfContent = "SEPARATE"
root.ConceptNameCodeSequence = [Dataset()]
root.ConceptNameCodeSequence[0].CodeValue = "11528-7"
root.ConceptNameCodeSequence[0].CodingSchemeDesignator = "LN"
root.ConceptNameCodeSequence[0].CodeMeaning = "Imaging Report"
desc_item = Dataset()
desc_item.ValueType = "TEXT"
desc_item.ConceptNameCodeSequence = [Dataset()]
desc_item.ConceptNameCodeSequence[0].CodeValue = "121070"
desc_item.ConceptNameCodeSequence[0].CodingSchemeDesignator = "DCM"
desc_item.ConceptNameCodeSequence[0].CodeMeaning = "Findings"
desc_item.TextValue = str(description_text)
concl_item = Dataset()
concl_item.ValueType = "TEXT"
concl_item.ConceptNameCodeSequence = [Dataset()]
concl_item.ConceptNameCodeSequence[0].CodeValue = "121106"
concl_item.ConceptNameCodeSequence[0].CodingSchemeDesignator = "DCM"
concl_item.ConceptNameCodeSequence[0].CodeMeaning = "Summary"
concl_item.TextValue = str(conclusion_text)
content_items = [desc_item, concl_item]
if extra_struct:
def _text_item(code, meaning, value, scheme="DCM"):
item = Dataset()
item.ValueType = "TEXT"
item.ConceptNameCodeSequence = [Dataset()]
item.ConceptNameCodeSequence[0].CodeValue = code
item.ConceptNameCodeSequence[0].CodingSchemeDesignator = scheme
item.ConceptNameCodeSequence[0].CodeMeaning = meaning
item.TextValue = str(value)
return item
if "degree_class" in extra_struct:
content_items.append(_text_item("SCL-DEGREE", "Scoliosis degree class (1-4, 0=none)", extra_struct["degree_class"]))
if "scoliosis_type" in extra_struct:
content_items.append(_text_item("SCL-TYPE", "Scoliosis type", extra_struct["scoliosis_type"]))
if "main_cobb" in extra_struct:
content_items.append(_text_item("SCL-MAIN-COBB", "Main Cobb angle, deg", f"{extra_struct['main_cobb']:.1f}"))
if "prob_scoliosis" in extra_struct:
content_items.append(_text_item("SCL-PROB", "Probability of scoliosis (0-1)", f"{extra_struct['prob_scoliosis']:.2f}"))
root.ContentSequence = content_items
ds.ContentSequence = [root]
ds.CompletionFlag = "COMPLETE"
ds.VerificationFlag = "UNVERIFIED"
os.makedirs(os.path.dirname(out_path), exist_ok=True)
ds.save_as(out_path, write_like_original=False)