Skip to content

Commit 1e0d400

Browse files
committed
feat(dicom): use custom itk_wasm_rt_study in write-rt-struct
Write additional metadata.
1 parent 1ba076a commit 1e0d400

14 files changed

+768
-933
lines changed

packages/dicom/dcmtk/CMakeLists.txt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,9 @@ if (EMSCRIPTEN OR WASI)
223223
set(HAVE_CMAKE_SIZEOF_ULONG TRUE CACHE STRING "Have size of unsigned long")
224224
set(HAVE_CMAKE_SIZEOF_ULONGLONG TRUE CACHE STRING "Have size of unsigned long long")
225225
endif()
226-
# set(PLM_GIT_REPOSITORY "https://github.com/InsightSoftwareConsortium/plastimatch")
227-
# set(PLM_GIT_TAG "5036f97b213d37a0a07dd232c827fb5f35049ae4")
228-
# set(PLM_GIT_REPOSITORY "/home/matt/src/plastimatch")
229-
set(PLM_GIT_REPOSITORY "https://github.com/thewtex/plastimatch")
230-
set(PLM_GIT_TAG "origin/itk-wasm")
226+
set(PLM_GIT_REPOSITORY "https://github.com/InsightSoftwareConsortium/plastimatch")
227+
# plastimatch-2024-10-30-5036f97-itk-wasm-2025-02-03
228+
set(PLM_GIT_TAG "a8e899092d52f6c7146592730c5dcd8e9331200b")
231229
option(PLM_CONFIG_ENABLE_DCMTK "Enable DCMTK support" ON)
232230
option(PLM_CONFIG_ENABLE_OPENMP "Enable OpenMP support" OFF)
233231
option(PLM_CONFIG_ENABLE_OPENCL "Enable OpenCL support" OFF)
@@ -253,7 +251,11 @@ FetchContent_Declare(
253251
)
254252
FetchContent_MakeAvailable(plastimatch_lib)
255253

256-
add_executable(write-rt-struct write-rt-struct.cxx)
254+
add_executable(write-rt-struct
255+
write-rt-struct.cxx
256+
itk_wasm_rt_study.cxx
257+
itk_wasm_dcmtk_rt_study.cxx
258+
)
257259
target_link_libraries(write-rt-struct PUBLIC ${ITK_LIBRARIES} plmbase)
258260

259261
if (NOT WASI) # currently crashes on exit
@@ -267,7 +269,15 @@ add_test(
267269
NAME write-rt-struct-synth-lung-1
268270
COMMAND write-rt-struct
269271
${CMAKE_CURRENT_SOURCE_DIR}/../test/data/input/rt-struct/synth-lung-1.cxt
270-
${CMAKE_CURRENT_BINARY_DIR}/synth-lung-1
272+
${CMAKE_CURRENT_BINARY_DIR}/synth-lung-1/rtss.dcm
273+
)
274+
275+
add_test(
276+
NAME write-rt-struct-synth-lung-1-metadata
277+
COMMAND write-rt-struct
278+
${CMAKE_CURRENT_SOURCE_DIR}/../test/data/input/rt-struct/synth-lung-1.cxt
279+
${CMAKE_CURRENT_BINARY_DIR}/synth-lung-1/rtss-added-metadata.dcm
280+
--dicom-metadata ${CMAKE_CURRENT_SOURCE_DIR}/../test/data/input/rt-struct/dicom-metadata.json
271281
)
272282

273283
endif()
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
#include "itk_wasm_dcmtk_rt_study.h"
19+
20+
#include "dcmtk_rt_study_p.h"
21+
22+
#include "plmbase_config.h"
23+
#include <stdlib.h>
24+
#include <stdio.h>
25+
#include "dcmtk_config.h"
26+
#include "dcmtk/ofstd/ofstream.h"
27+
#include "dcmtk/dcmdata/dctk.h"
28+
29+
#include "dcmtk_file.h"
30+
#include "dcmtk_metadata.h"
31+
#include "dcmtk_module.h"
32+
#include "dcmtk_rt_study.h"
33+
#include "dcmtk_rt_study_p.h"
34+
#include "dcmtk_series.h"
35+
#include "dcmtk_slice_data.h"
36+
#include "dcmtk_util.h"
37+
#include "file_util.h"
38+
#include "logfile.h"
39+
#include "metadata.h"
40+
#include "plm_uid_prefix.h"
41+
#include "plm_version.h"
42+
#include "print_and_exit.h"
43+
#include "rtss.h"
44+
#include "rtss_contour.h"
45+
#include "rtss_roi.h"
46+
#include "string_util.h"
47+
48+
Itk_wasm_dcmtk_rt_study::Itk_wasm_dcmtk_rt_study ()
49+
{
50+
}
51+
52+
Itk_wasm_dcmtk_rt_study::~Itk_wasm_dcmtk_rt_study ()
53+
{
54+
}
55+
56+
void
57+
Itk_wasm_dcmtk_rt_study::save_rtss (const char * fname, const ItkWasmRtStudyMetadata & metadata)
58+
{
59+
// Modified:
60+
// rt_study.cxx Rt_study::save_dicom
61+
this->d_ptr->rt_study_metadata->generate_new_rtstruct_instance_uid ();
62+
63+
// Modified:
64+
// rtds_dcmtk.cxx Rt_study::rtss_save
65+
66+
OFCondition ofc;
67+
Rtss::Pointer& rtss = this->d_ptr->rtss;
68+
Metadata::Pointer rtstruct_metadata;
69+
if (this->d_ptr->rt_study_metadata) {
70+
rtstruct_metadata = d_ptr->rt_study_metadata->get_rtstruct_metadata ();
71+
}
72+
73+
/* Prepare structure set with slice uids */
74+
const Slice_list *slice_list = d_ptr->rt_study_metadata->get_slice_list ();
75+
rtss->apply_slice_list (slice_list);
76+
77+
/* Prepare dcmtk */
78+
DcmFileFormat fileformat;
79+
DcmDataset *dataset = fileformat.getDataset();
80+
81+
/* Add entries for common modules */
82+
Dcmtk_module::set_sop_common (dataset);
83+
84+
/* GCS FIX, remove below code, use common modules instead */
85+
86+
/* ----------------------------------------------------------------- */
87+
/* Part 1 -- General header */
88+
/* ----------------------------------------------------------------- */
89+
dataset->putAndInsertOFStringArray(DCM_InstanceCreationDate,
90+
d_ptr->rt_study_metadata->get_study_date());
91+
dataset->putAndInsertOFStringArray(DCM_InstanceCreationTime,
92+
d_ptr->rt_study_metadata->get_study_time());
93+
dataset->putAndInsertOFStringArray(DCM_InstanceCreatorUID,
94+
PlmUidPrefix::getInstance().get().c_str());
95+
dataset->putAndInsertString (DCM_SOPClassUID, UID_RTStructureSetStorage);
96+
dcmtk_put (dataset, DCM_SOPInstanceUID,
97+
d_ptr->rt_study_metadata->get_rtstruct_instance_uid());
98+
dataset->putAndInsertOFStringArray (DCM_StudyDate,
99+
d_ptr->rt_study_metadata->get_study_date());
100+
dataset->putAndInsertOFStringArray (DCM_StudyTime,
101+
d_ptr->rt_study_metadata->get_study_time());
102+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata,
103+
DCM_StudyDescription, "");
104+
105+
dataset->putAndInsertOFStringArray (DCM_AccessionNumber, "");
106+
dataset->putAndInsertOFStringArray (DCM_Modality, "RTSTRUCT");
107+
dataset->putAndInsertString (DCM_Manufacturer, metadata.manufacturer.c_str());
108+
dataset->putAndInsertString (DCM_InstitutionName, "");
109+
dataset->putAndInsertString (DCM_ReferringPhysicianName, "");
110+
dataset->putAndInsertString (DCM_StationName, "");
111+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata,
112+
DCM_SeriesDescription, "");
113+
dataset->putAndInsertString (DCM_ManufacturerModelName, metadata.manufacturerModelName.c_str());
114+
115+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata, DCM_PatientName, "");
116+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata, DCM_PatientID, "");
117+
dataset->putAndInsertString (DCM_PatientBirthDate, "");
118+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata, DCM_PatientSex, "O");
119+
dataset->putAndInsertString (DCM_SoftwareVersions,
120+
PLASTIMATCH_VERSION_STRING);
121+
122+
dataset->putAndInsertString (DCM_StudyInstanceUID,
123+
d_ptr->rt_study_metadata->get_study_uid().c_str());
124+
dataset->putAndInsertString (DCM_SeriesInstanceUID,
125+
d_ptr->rt_study_metadata->get_rtstruct_series_uid());
126+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata, DCM_StudyID, "10001");
127+
dcmtk_copy_from_metadata (dataset, rtstruct_metadata, DCM_SeriesNumber, "1");
128+
dataset->putAndInsertString (DCM_InstanceNumber, "1");
129+
dataset->putAndInsertString (DCM_StructureSetLabel, "AutoSS");
130+
dataset->putAndInsertString (DCM_StructureSetName, "AutoSS");
131+
dataset->putAndInsertOFStringArray (DCM_StructureSetDate,
132+
d_ptr->rt_study_metadata->get_study_date());
133+
dataset->putAndInsertOFStringArray (DCM_StructureSetTime,
134+
d_ptr->rt_study_metadata->get_study_time());
135+
136+
/* ----------------------------------------------------------------- */
137+
/* Part 2 -- UID's for CT series */
138+
/* ----------------------------------------------------------------- */
139+
DcmSequenceOfItems *rfor_seq = 0;
140+
DcmItem *rfor_item = 0;
141+
dataset->findOrCreateSequenceItem (
142+
DCM_ReferencedFrameOfReferenceSequence, rfor_item, -2);
143+
rfor_item->putAndInsertString (DCM_FrameOfReferenceUID,
144+
d_ptr->rt_study_metadata->get_frame_of_reference_uid().c_str());
145+
dataset->findAndGetSequence (
146+
DCM_ReferencedFrameOfReferenceSequence, rfor_seq);
147+
DcmItem *rtrstudy_item = 0;
148+
rfor_item->findOrCreateSequenceItem (
149+
DCM_RTReferencedStudySequence, rtrstudy_item, -2);
150+
rtrstudy_item->putAndInsertString (
151+
DCM_ReferencedSOPClassUID,
152+
UID_RETIRED_StudyComponentManagementSOPClass);
153+
rtrstudy_item->putAndInsertString (
154+
DCM_ReferencedSOPInstanceUID,
155+
d_ptr->rt_study_metadata->get_study_uid().c_str());
156+
DcmItem *rtrseries_item = 0;
157+
rtrstudy_item->findOrCreateSequenceItem (
158+
DCM_RTReferencedSeriesSequence, rtrseries_item, -2);
159+
rtrseries_item->putAndInsertString (
160+
DCM_SeriesInstanceUID, d_ptr->rt_study_metadata->get_ct_series_uid());
161+
162+
for (int k = 0; k < d_ptr->rt_study_metadata->num_slices(); k++) {
163+
DcmItem *ci_item = 0;
164+
rtrseries_item->findOrCreateSequenceItem (
165+
DCM_ContourImageSequence, ci_item, -2);
166+
ci_item->putAndInsertString (
167+
DCM_ReferencedSOPClassUID, UID_CTImageStorage);
168+
ci_item->putAndInsertString (
169+
DCM_ReferencedSOPInstanceUID,
170+
d_ptr->rt_study_metadata->get_slice_uid (k));
171+
}
172+
173+
/* ----------------------------------------------------------------- */
174+
/* Part 3 -- Structure info */
175+
/* ----------------------------------------------------------------- */
176+
for (size_t i = 0; i < rtss->num_structures; i++) {
177+
DcmItem *ssroi_item = 0;
178+
std::string tmp;
179+
dataset->findOrCreateSequenceItem (
180+
DCM_StructureSetROISequence, ssroi_item, -2);
181+
tmp = string_format ("%d", rtss->slist[i]->id);
182+
ssroi_item->putAndInsertString (DCM_ROINumber, tmp.c_str());
183+
ssroi_item->putAndInsertString (DCM_ReferencedFrameOfReferenceUID,
184+
d_ptr->rt_study_metadata->get_frame_of_reference_uid().c_str());
185+
ssroi_item->putAndInsertString (DCM_ROIName,
186+
rtss->slist[i]->name.c_str());
187+
if (metadata.roiMetadata.size() > i) {
188+
ssroi_item->putAndInsertString (DCM_ROIGenerationAlgorithm,
189+
metadata.roiMetadata[i].roiGenerationAlgorithm.c_str());
190+
ssroi_item->putAndInsertString (DCM_ROIDescription,
191+
metadata.roiMetadata[i].roiDescription.c_str());
192+
}
193+
else {
194+
ssroi_item->putAndInsertString (DCM_ROIGenerationAlgorithm, "");
195+
}
196+
}
197+
198+
/* ----------------------------------------------------------------- */
199+
/* Part 4 -- Contour info */
200+
/* ----------------------------------------------------------------- */
201+
for (size_t i = 0; i < rtss->num_structures; i++) {
202+
Rtss_roi *curr_structure = rtss->slist[i];
203+
DcmItem *roic_item = 0;
204+
dataset->findOrCreateSequenceItem (
205+
DCM_ROIContourSequence, roic_item, -2);
206+
std::string tmp = curr_structure->get_dcm_color_string ();
207+
roic_item->putAndInsertString (DCM_ROIDisplayColor, tmp.c_str());
208+
for (size_t j = 0; j < curr_structure->num_contours; j++) {
209+
Rtss_contour *curr_contour = curr_structure->pslist[j];
210+
if (curr_contour->num_vertices <= 0) continue;
211+
212+
#if defined (commentout)
213+
/* GCS 2013-07-02: DICOM standard allows contours without
214+
an associated slice UID. Maybe this bug is now
215+
fixed in XiO??? */
216+
/* GE -> XiO transfer does not work if contour does not have
217+
corresponding slice uid */
218+
if (curr_contour->ct_slice_uid.empty()) {
219+
printf ("Warning: Omitting contour (%ld,%ld)\n",
220+
(long) i, (long) j);
221+
continue;
222+
}
223+
#endif
224+
225+
/* Add item to ContourSequence */
226+
DcmItem *c_item = 0;
227+
roic_item->findOrCreateSequenceItem (
228+
DCM_ContourSequence, c_item, -2);
229+
230+
/* ContourImageSequence */
231+
if (curr_contour->ct_slice_uid != "") {
232+
DcmItem *ci_item = 0;
233+
c_item->findOrCreateSequenceItem (
234+
DCM_ContourImageSequence, ci_item, -2);
235+
ci_item->putAndInsertString (DCM_ReferencedSOPClassUID,
236+
UID_CTImageStorage);
237+
ci_item->putAndInsertString (DCM_ReferencedSOPInstanceUID,
238+
curr_contour->ct_slice_uid.c_str());
239+
}
240+
241+
/* ContourGeometricType */
242+
c_item->putAndInsertString (DCM_ContourGeometricType,
243+
"CLOSED_PLANAR");
244+
245+
/* NumberOfContourPoints */
246+
tmp = string_format ("%d", curr_contour->num_vertices);
247+
c_item->putAndInsertString (DCM_NumberOfContourPoints, tmp.c_str());
248+
249+
/* ContourData */
250+
tmp = string_format ("%.8g\\%.8g\\%.8g",
251+
curr_contour->x[0],
252+
curr_contour->y[0],
253+
curr_contour->z[0]);
254+
for (size_t k = 1; k < curr_contour->num_vertices; k++) {
255+
std::string tmp2 = string_format ("\\%.8g\\%.8g\\%.8g",
256+
curr_contour->x[k],
257+
curr_contour->y[k],
258+
curr_contour->z[k]);
259+
tmp += tmp2;
260+
}
261+
c_item->putAndInsertString (DCM_ContourData, tmp.c_str());
262+
}
263+
264+
tmp = string_format ("%d", (int) curr_structure->id);
265+
roic_item->putAndInsertString (DCM_ReferencedROINumber, tmp.c_str());
266+
}
267+
268+
/* ----------------------------------------------------------------- */
269+
/* Part 5 -- More structure info */
270+
/* ----------------------------------------------------------------- */
271+
for (size_t i = 0; i < rtss->num_structures; i++) {
272+
Rtss_roi *curr_structure = rtss->slist[i];
273+
std::string tmp;
274+
275+
/* RTROIObservationsSequence */
276+
DcmItem *rtroio_item = 0;
277+
dataset->findOrCreateSequenceItem (
278+
DCM_RTROIObservationsSequence, rtroio_item, -2);
279+
280+
/* ObservationNumber */
281+
tmp = string_format ("%d", (int) curr_structure->id);
282+
rtroio_item->putAndInsertString (DCM_ObservationNumber, tmp.c_str());
283+
/* ReferencedROINumber */
284+
rtroio_item->putAndInsertString (DCM_ReferencedROINumber, tmp.c_str());
285+
if (metadata.roiMetadata.size() > i) {
286+
rtroio_item->putAndInsertString (DCM_RTROIInterpretedType,
287+
metadata.roiMetadata[i].rtRoiInterpretedType.c_str());
288+
}
289+
else {
290+
rtroio_item->putAndInsertString (DCM_RTROIInterpretedType, "");
291+
}
292+
/* ROIInterpreter */
293+
rtroio_item->putAndInsertString (DCM_ROIInterpreter, "");
294+
///* ROIPhysicalProperty */
295+
if(rtss->slist[i]->rsp_value > -1.0)
296+
{
297+
DcmItem *rsp_item = NULL;
298+
if (rtroio_item->findOrCreateSequenceItem(
299+
DCM_ROIPhysicalPropertiesSequence, rsp_item, -2).good())
300+
{
301+
rsp_item->putAndInsertString(DCM_ROIPhysicalProperty, "REL_STOP_RATIO");
302+
rsp_item->putAndInsertString(DCM_ROIPhysicalPropertyValue, std::to_string(rtss->slist[i]->rsp_value).c_str());
303+
}
304+
}
305+
}
306+
307+
/* ----------------------------------------------------------------- */
308+
/* Write the output file */
309+
/* ----------------------------------------------------------------- */
310+
make_parent_directories (fname);
311+
312+
ofc = fileformat.saveFile (fname, EXS_LittleEndianExplicit);
313+
if (ofc.bad()) {
314+
print_and_exit ("Error: cannot write DICOM RTSTRUCT (%s)\n",
315+
ofc.text());
316+
}
317+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
#ifndef _itk_wasm_dcmtk_rt_study_h_
19+
#define _itk_wasm_dcmtk_rt_study_h_
20+
21+
#include "dcmtk_rt_study.h"
22+
23+
#include "itk_wasm_rt_study_metadata.h"
24+
25+
class PLMBASE_API Itk_wasm_dcmtk_rt_study: public Dcmtk_rt_study {
26+
public:
27+
Itk_wasm_dcmtk_rt_study ();
28+
~Itk_wasm_dcmtk_rt_study ();
29+
30+
void save_rtss (const char *fname, const ItkWasmRtStudyMetadata& metadata);
31+
protected:
32+
};
33+
34+
#endif

0 commit comments

Comments
 (0)