Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #834 from pymedphys/structure-dedupe
- Loading branch information
Showing
12 changed files
with
904 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
310 changes: 310 additions & 0 deletions
310
examples/protyping/dicom/dedupe-contour/002-scratch.ipynb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,310 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import copy\n", | ||
"\n", | ||
"import numpy as np\n", | ||
"import matplotlib.pyplot as plt\n", | ||
"\n", | ||
"import shapely\n", | ||
"import shapely.ops\n", | ||
"\n", | ||
"import pydicom\n", | ||
"\n", | ||
"import pymedphys" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"dcm_path = pymedphys.data_path(\"dedupe-contour.dcm\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"CONTOUR_GEOMETRIC_TYPE = 'CLOSED_PLANAR'" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"dcm = pydicom.read_file(str(dcm_path), force=True)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def extract_contours_and_image_sequences(contour_sequence):\n", | ||
" contours_by_z = {}\n", | ||
" image_sequence_by_z = {}\n", | ||
"\n", | ||
" expected_contour_keys = {\n", | ||
" pydicom.tag.Tag(*tag)\n", | ||
" for tag in [(0x3006, 0x0050), (0x3006, 0x0046), (0x3006, 0x0042), (0x3006, 0x0016)]\n", | ||
" }\n", | ||
"\n", | ||
" for contour in contour_sequence:\n", | ||
" if contour.ContourGeometricType != CONTOUR_GEOMETRIC_TYPE:\n", | ||
" raise ValueError(f\"Only {CONTOUR_GEOMETRIC_TYPE} type is supported\")\n", | ||
"\n", | ||
" if set(contour.keys()) != expected_contour_keys:\n", | ||
" raise ValueError(\"Unexpected contour sequence format\")\n", | ||
"\n", | ||
" contour_data = contour.ContourData\n", | ||
"\n", | ||
" x = np.array(contour_data[0::3])\n", | ||
" y = np.array(contour_data[1::3])\n", | ||
" z = np.array(contour_data[2::3])\n", | ||
"\n", | ||
" unique_z = np.unique(z)\n", | ||
"\n", | ||
" if len(unique_z) != 1:\n", | ||
" raise ValueError(\"All z values should be equal\")\n", | ||
"\n", | ||
" z = unique_z[0]\n", | ||
" polygon = shapely.geometry.Polygon(zip(x, y))\n", | ||
"\n", | ||
" try:\n", | ||
" contours_by_z[z].append(polygon)\n", | ||
" except KeyError:\n", | ||
" contours_by_z[z] = [polygon]\n", | ||
"\n", | ||
" try:\n", | ||
" image_sequence_by_z[z].append(contour.ContourImageSequence)\n", | ||
" except KeyError:\n", | ||
" image_sequence_by_z[z] = [contour.ContourImageSequence]\n", | ||
" \n", | ||
" \n", | ||
" return contours_by_z, image_sequence_by_z" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def collapse_image_sequence(image_sequence_by_z):\n", | ||
" image_sequence_by_z_collapsed = {}\n", | ||
"\n", | ||
" expected_image_sequence_keys = {\n", | ||
" pydicom.tag.Tag(*tag)\n", | ||
" for tag in [(0x0008, 0x1150), (0x0008, 0x1155)]\n", | ||
" }\n", | ||
"\n", | ||
" for z, image_sequences in image_sequence_by_z.items():\n", | ||
" values = set()\n", | ||
"\n", | ||
" for image_sequence in image_sequences:\n", | ||
" if len(image_sequence) != 1:\n", | ||
" raise ValueError(\"Expected only one item per image sequence\")\n", | ||
"\n", | ||
" image_sequence_item = image_sequence[0]\n", | ||
"\n", | ||
" if set(image_sequence_item.keys()) != expected_image_sequence_keys:\n", | ||
" raise ValueError(\"Unexpected contour image sequence format\")\n", | ||
"\n", | ||
" values.add((\n", | ||
" image_sequence_item.ReferencedSOPClassUID,\n", | ||
" image_sequence_item.ReferencedSOPInstanceUID,\n", | ||
" ))\n", | ||
"\n", | ||
" if len(values) != 1:\n", | ||
" raise ValueError(\"Each z value should only point to one image slice\")\n", | ||
"\n", | ||
" image_sequence_by_z_collapsed[z] = image_sequences[0]\n", | ||
" \n", | ||
" return image_sequence_by_z_collapsed" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def merge_contours(contours_by_z):\n", | ||
" all_merged = {}\n", | ||
"\n", | ||
" for z, contours in contours_by_z.items():\n", | ||
" all_merged[z] = shapely.ops.unary_union(contours)\n", | ||
" \n", | ||
" return all_merged" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def get_coords_from_polygon(polygon):\n", | ||
" return polygon.exterior.coords.xy" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def get_coords_from_multipolygon(multipolygon):\n", | ||
" return [\n", | ||
" get_coords_from_polygon(item) for item in multipolygon\n", | ||
" ]" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def get_coords_from_polygon_or_multipolygon(polygon_or_multipolygon):\n", | ||
" try:\n", | ||
" return [get_coords_from_polygon(polygon_or_multipolygon)]\n", | ||
" except AttributeError:\n", | ||
" return get_coords_from_multipolygon(polygon_or_multipolygon)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def format_coords_for_dicom(all_merged):\n", | ||
" dicom_format_coords_by_z = {}\n", | ||
"\n", | ||
" for z, merged in all_merged.items():\n", | ||
" coords = get_coords_from_polygon_or_multipolygon(merged)\n", | ||
" new_contour_data = []\n", | ||
" for coord in coords:\n", | ||
" stacked_coords = np.hstack(list(zip(coord[0], coord[1], z * np.ones_like(coord[1]))))\n", | ||
" stacked_coords = np.round(stacked_coords, 1)\n", | ||
" stacked_coords = stacked_coords.tolist()\n", | ||
"\n", | ||
" new_contour_data.append(stacked_coords)\n", | ||
"\n", | ||
" dicom_format_coords_by_z[z] = new_contour_data\n", | ||
" \n", | ||
" return dicom_format_coords_by_z" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def create_new_contour_sequence(dicom_format_coords_by_z, image_sequence_by_z_collapsed):\n", | ||
" new_contour_sequence = pydicom.sequence.Sequence()\n", | ||
"\n", | ||
" for z, contour_items in dicom_format_coords_by_z.items():\n", | ||
" contour_image_sequence = image_sequence_by_z_collapsed[z]\n", | ||
" for contour_data in contour_items:\n", | ||
" new_contour_dataset = pydicom.dataset.Dataset()\n", | ||
" new_contour_dataset.ContourGeometricType = CONTOUR_GEOMETRIC_TYPE\n", | ||
" if len(contour_data) % 3 != 0:\n", | ||
" raise ValueError(\"The contour points should be divisible by 3\")\n", | ||
"\n", | ||
" new_contour_dataset.NumberOfContourPoints = len(contour_data) // 3\n", | ||
" new_contour_dataset.ContourImageSequence = contour_image_sequence\n", | ||
" new_contour_dataset.ContourData = contour_data\n", | ||
"\n", | ||
" new_contour_sequence.append(new_contour_dataset)\n", | ||
" \n", | ||
" return new_contour_sequence\n", | ||
" \n", | ||
" " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def concatenate_contours_within_sequence(roi_contour_sequence, inplace=False):\n", | ||
" contour_sequence = roi_contour_sequence.ContourSequence\n", | ||
" contours_by_z, image_sequence_by_z = extract_contours_and_image_sequences(contour_sequence)\n", | ||
" \n", | ||
" image_sequence_by_z_collapsed = collapse_image_sequence(image_sequence_by_z)\n", | ||
" all_merged = merge_contours(contours_by_z)\n", | ||
" \n", | ||
" dicom_format_coords_by_z = format_coords_for_dicom(all_merged)\n", | ||
" \n", | ||
" new_contour_sequence = create_new_contour_sequence(\n", | ||
" dicom_format_coords_by_z, image_sequence_by_z_collapsed)\n", | ||
" \n", | ||
" if not inplace:\n", | ||
" new_roi_contour_sequence = copy.copy(roi_contour_sequence)\n", | ||
" new_roi_contour_sequence.ContourSequence = new_contour_sequence\n", | ||
" \n", | ||
" return new_roi_contour_sequence\n", | ||
" else:\n", | ||
" roi_contour_sequence.ContourSequence = new_contour_sequence " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"roi_contour_sequences = dcm.ROIContourSequence\n", | ||
"\n", | ||
"for item in roi_contour_sequences:\n", | ||
" concatenate_contours_within_sequence(item, inplace=True)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"dcm.save_as('output.dcm')" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "pymedphys-master", | ||
"language": "python", | ||
"name": "pymedphys-master" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.7.0" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 4 | ||
} |
Oops, something went wrong.