comparison OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp @ 2174:2410a171ebfb

refactoring using DicomWebDataset and OrthancNativeDataset
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 22 Oct 2024 21:52:34 +0200
parents 8e3c403cc643
children 43ef60388fa2
comparison
equal deleted inserted replaced
2172:239fb2c893c1 2174:2410a171ebfb
25 25
26 #include "../Scene2D/ColorTextureSceneLayer.h" 26 #include "../Scene2D/ColorTextureSceneLayer.h"
27 #include "../Scene2D/FloatTextureSceneLayer.h" 27 #include "../Scene2D/FloatTextureSceneLayer.h"
28 #include "GeometryToolbox.h" 28 #include "GeometryToolbox.h"
29 #include "ImageToolbox.h" 29 #include "ImageToolbox.h"
30 #include "OrthancDatasets/DicomDatasetReader.h"
31 #include "OrthancDatasets/DicomWebDataset.h"
32 #include "OrthancDatasets/OrthancNativeDataset.h"
30 33
31 #include <Images/Image.h> 34 #include <Images/Image.h>
32 #include <Images/ImageProcessing.h> 35 #include <Images/ImageProcessing.h>
33 #include <Logging.h> 36 #include <Logging.h>
34 #include <OrthancException.h> 37 #include <OrthancException.h>
229 if (!dicom.HasTag(Orthanc::DICOM_TAG_INSTANCE_NUMBER) || 232 if (!dicom.HasTag(Orthanc::DICOM_TAG_INSTANCE_NUMBER) ||
230 !dicom.GetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER).ParseInteger32(instanceNumber_)) 233 !dicom.GetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER).ParseInteger32(instanceNumber_))
231 { 234 {
232 instanceNumber_ = 0; 235 instanceNumber_ = 0;
233 } 236 }
234 237 }
238
239
240 void DicomInstanceParameters::InjectSequenceTags(const IDicomDataset& dataset)
241 {
242 /**
243 * Use DICOM tag "SequenceOfUltrasoundRegions" (0018,6011) in
244 * order to derive the pixel spacing on ultrasound (US) images
245 **/
246
247 static const Orthanc::DicomTag DICOM_TAG_SEQUENCE_OF_ULTRASOUND_REGIONS(0x0018, 0x6011);
248 static const Orthanc::DicomTag DICOM_TAG_PHYSICAL_UNITS_X_DIRECTION(0x0018, 0x6024);
249 static const Orthanc::DicomTag DICOM_TAG_PHYSICAL_UNITS_Y_DIRECTION(0x0018, 0x6026);
250 static const Orthanc::DicomTag DICOM_TAG_PHYSICAL_DELTA_X(0x0018, 0x602c);
251 static const Orthanc::DicomTag DICOM_TAG_PHYSICAL_DELTA_Y(0x0018, 0x602e);
252
253 DicomDatasetReader reader(dataset);
254
255 size_t size;
256
257 if (!data_.hasPixelSpacing_ &&
258 dataset.GetSequenceSize(size, Orthanc::DicomPath(DICOM_TAG_SEQUENCE_OF_ULTRASOUND_REGIONS)) &&
259 size == 1)
260 {
261 int directionX, directionY;
262 double deltaX, deltaY;
263
264 if (reader.GetIntegerValue(directionX, Orthanc::DicomPath(DICOM_TAG_SEQUENCE_OF_ULTRASOUND_REGIONS,
265 0, DICOM_TAG_PHYSICAL_UNITS_X_DIRECTION)) &&
266 reader.GetIntegerValue(directionY, Orthanc::DicomPath(DICOM_TAG_SEQUENCE_OF_ULTRASOUND_REGIONS,
267 0, DICOM_TAG_PHYSICAL_UNITS_Y_DIRECTION)) &&
268 reader.GetDoubleValue(deltaX, Orthanc::DicomPath(DICOM_TAG_SEQUENCE_OF_ULTRASOUND_REGIONS,
269 0, DICOM_TAG_PHYSICAL_DELTA_X)) &&
270 reader.GetDoubleValue(deltaY, Orthanc::DicomPath(DICOM_TAG_SEQUENCE_OF_ULTRASOUND_REGIONS,
271 0, DICOM_TAG_PHYSICAL_DELTA_Y)) &&
272 directionX == 0x0003 && // Centimeters
273 directionY == 0x0003) // Centimeters
274 {
275 // Scene coordinates are expressed in millimeters => multiplication by 10
276 SetPixelSpacing(10.0 * deltaX, 10.0 * deltaY);
277 }
278 }
279
280
281 /**
282 * New in Stone Web viewer 2.2: Deal with Philips multiframe
283 * (cf. mail from Tomas Kenda on 2021-08-17). This cannot be done
284 * in LoadSeriesDetailsFromInstance, as the "Per Frame Functional
285 * Groups Sequence" is not available at that point.
286 **/
235 287
236 static const Orthanc::DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE(0x5200, 0x9230); 288 static const Orthanc::DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE(0x5200, 0x9230);
237 static const Orthanc::DicomTag DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE(0x0028, 0x9132); 289 static const Orthanc::DicomTag DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE(0x0028, 0x9132);
238 290
239 const Orthanc::DicomValue* frames = dicom.TestAndGetValue(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE); 291 if (dataset.GetSequenceSize(size, Orthanc::DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE)))
240 if (frames != NULL && 292 {
241 hasNumberOfFrames_ && 293 data_.perFrameWindowing_.resize(data_.numberOfFrames_);
242 frames->IsSequence())
243 {
244 /**
245 * New in Stone Web viewer 2.2: Deal with Philips multiframe
246 * (cf. mail from Tomas Kenda on 2021-08-17). This cannot be done
247 * in LoadSeriesDetailsFromInstance, as the "Per Frame Functional Groups Sequence"
248 * is not available at that point.
249 **/
250
251 const Json::Value& sequence = frames->GetSequenceContent();
252
253 perFrameWindowing_.resize(numberOfFrames_);
254 294
255 // This corresponds to "ParsedDicomFile::GetDefaultWindowing()" 295 // This corresponds to "ParsedDicomFile::GetDefaultWindowing()"
256 for (Json::ArrayIndex i = 0; i < sequence.size(); i++) 296 for (size_t i = 0; i < size; i++)
257 { 297 {
258 if (i < numberOfFrames_ && 298 size_t tmp;
259 sequence[i].isMember(DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE.Format())) 299 double center, width;
300
301 if (dataset.GetSequenceSize(tmp, Orthanc::DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
302 DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE)) &&
303 tmp == 1 &&
304 reader.GetDoubleValue(center, Orthanc::DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
305 DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE, 0,
306 Orthanc::DICOM_TAG_WINDOW_CENTER)) &&
307 reader.GetDoubleValue(width, Orthanc::DicomPath(DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE, i,
308 DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE, 0,
309 Orthanc::DICOM_TAG_WINDOW_WIDTH)))
260 { 310 {
261 const Json::Value& v = sequence[i][DICOM_TAG_FRAME_VOI_LUT_SEQUENCE_ATTRIBUTE.Format()]; 311 data_.perFrameWindowing_[i] = Windowing(center, width);
262
263 static const char* KEY_VALUE = "Value";
264
265 if (v.isMember(KEY_VALUE) &&
266 v[KEY_VALUE].type() == Json::arrayValue &&
267 v[KEY_VALUE].size() >= 1 &&
268 v[KEY_VALUE][0].isMember(Orthanc::DICOM_TAG_WINDOW_CENTER.Format()) &&
269 v[KEY_VALUE][0].isMember(Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()) &&
270 v[KEY_VALUE][0][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()].isMember(KEY_VALUE) &&
271 v[KEY_VALUE][0][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()].isMember(KEY_VALUE))
272 {
273 const Json::Value& scenter = v[KEY_VALUE][0][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()][KEY_VALUE];
274 const Json::Value& swidth = v[KEY_VALUE][0][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()][KEY_VALUE];
275
276 double center, width;
277 if (scenter.isString() &&
278 swidth.isString() &&
279 Orthanc::SerializationToolbox::ParseDouble(center, scenter.asString()) &&
280 Orthanc::SerializationToolbox::ParseDouble(width, swidth.asString()))
281 {
282 perFrameWindowing_[i] = Windowing(center, width);
283 }
284 else if (scenter.isNumeric() &&
285 swidth.isNumeric())
286 {
287 perFrameWindowing_[i] = Windowing(scenter.asDouble(), swidth.asDouble());
288 }
289 }
290 } 312 }
291 } 313 }
292 } 314 }
315 }
316
317
318 DicomInstanceParameters::DicomInstanceParameters(const DicomInstanceParameters& other) :
319 data_(other.data_),
320 tags_(other.tags_->Clone())
321 {
322 }
323
324
325 DicomInstanceParameters::DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
326 data_(dicom),
327 tags_(dicom.Clone())
328 {
329 OrthancNativeDataset dataset(dicom);
330 InjectSequenceTags(dataset);
293 } 331 }
294 332
295 333
296 double DicomInstanceParameters::GetSliceThickness() const 334 double DicomInstanceParameters::GetSliceThickness() const
297 { 335 {
812 } 850 }
813 851
814 852
815 void DicomInstanceParameters::EnrichUsingDicomWeb(const Json::Value& dicomweb) 853 void DicomInstanceParameters::EnrichUsingDicomWeb(const Json::Value& dicomweb)
816 { 854 {
817 /** 855 DicomWebDataset dataset(dicomweb);
818 * Use DICOM tag "SequenceOfUltrasoundRegions" (0018,6011) in 856 InjectSequenceTags(dataset);
819 * order to derive the pixel spacing on ultrasound (US) images
820 **/
821
822 if (!data_.hasPixelSpacing_)
823 {
824 const Json::Value* region = LookupDicomWebSingleValue(dicomweb, "00186011", "SQ");
825 if (region != NULL)
826 {
827 const Json::Value* physicalUnitsXDirection = LookupDicomWebSingleValue(*region, "00186024", "US");
828 const Json::Value* physicalUnitsYDirection = LookupDicomWebSingleValue(*region, "00186026", "US");
829 const Json::Value* physicalDeltaX = LookupDicomWebSingleValue(*region, "0018602C", "FD");
830 const Json::Value* physicalDeltaY = LookupDicomWebSingleValue(*region, "0018602E", "FD");
831
832 if (physicalUnitsXDirection != NULL &&
833 physicalUnitsYDirection != NULL &&
834 physicalDeltaX != NULL &&
835 physicalDeltaY != NULL &&
836 physicalUnitsXDirection->type() == Json::intValue &&
837 physicalUnitsYDirection->type() == Json::intValue &&
838 physicalUnitsXDirection->asInt() == 0x0003 && // Centimeters
839 physicalUnitsYDirection->asInt() == 0x0003 && // Centimeters
840 physicalDeltaX->isNumeric() &&
841 physicalDeltaY->isNumeric())
842 {
843 // Scene coordinates are expressed in millimeters => multiplication by 10
844 SetPixelSpacing(10.0 * physicalDeltaX->asDouble(),
845 10.0 * physicalDeltaY->asDouble());
846 }
847 }
848 }
849 } 857 }
850 858
851 859
852 CoordinateSystem3D DicomInstanceParameters::GetMultiFrameGeometry() const 860 CoordinateSystem3D DicomInstanceParameters::GetMultiFrameGeometry() const
853 { 861 {