@@ -270,44 +270,51 @@ function createPET3dPipeline(imageData, petColorMapId) {
270
270
return actor ;
271
271
}
272
272
273
- function createStudyImageIds ( baseUrl , studySearchOptions ) {
273
+ async function createStudyImageIds ( baseUrl , studySearchOptions ) {
274
274
const SOP_INSTANCE_UID = '00080018' ;
275
275
const SERIES_INSTANCE_UID = '0020000E' ;
276
276
277
277
const client = new api . DICOMwebClient ( { url } ) ;
278
278
279
- return new Promise ( ( resolve , reject ) => {
280
- client . retrieveStudyMetadata ( studySearchOptions ) . then ( instances => {
281
- const imageIds = instances . map ( metaData => {
282
- const imageId =
283
- `wadors:` +
284
- baseUrl +
285
- '/studies/' +
286
- studyInstanceUID +
287
- '/series/' +
288
- metaData [ SERIES_INSTANCE_UID ] . Value [ 0 ] +
289
- '/instances/' +
290
- metaData [ SOP_INSTANCE_UID ] . Value [ 0 ] +
291
- '/frames/1' ;
292
-
293
- cornerstoneWADOImageLoader . wadors . metaDataManager . add (
294
- imageId ,
295
- metaData
296
- ) ;
279
+ const instances = await client . retrieveStudyMetadata ( studySearchOptions ) ;
280
+
281
+ const instancesToRetrieve = [ ] ;
282
+
283
+ const imageIds = instances . map ( instanceMetaData => {
284
+ const seriesInstanceUID = instanceMetaData [ SERIES_INSTANCE_UID ] . Value [ 0 ] ;
285
+ const sopInstanceUID = instanceMetaData [ SOP_INSTANCE_UID ] . Value [ 0 ] ;
286
+
287
+ const imageId =
288
+ `wadors:` +
289
+ baseUrl +
290
+ '/studies/' +
291
+ studyInstanceUID +
292
+ '/series/' +
293
+ seriesInstanceUID +
294
+ '/instances/' +
295
+ sopInstanceUID +
296
+ '/frames/1' ;
297
+
298
+ cornerstoneWADOImageLoader . wadors . metaDataManager . add (
299
+ imageId ,
300
+ instanceMetaData
301
+ ) ;
297
302
298
- return imageId ;
303
+ if (
304
+ seriesInstanceUID === ctSeriesInstanceUID ||
305
+ seriesInstanceUID === petSeriesInstanceUID
306
+ ) {
307
+ instancesToRetrieve . push ( {
308
+ studyInstanceUID,
309
+ seriesInstanceUID,
310
+ sopInstanceUID,
299
311
} ) ;
312
+ }
300
313
301
- resolve ( imageIds ) ;
302
- } , reject ) ;
314
+ return imageId ;
303
315
} ) ;
304
- }
305
-
306
- function loadDataset ( imageIds , displaySetInstanceUid ) {
307
- const imageDataObject = getImageData ( imageIds , displaySetInstanceUid ) ;
308
316
309
- loadImageData ( imageDataObject ) ;
310
- return imageDataObject ;
317
+ return imageIds ;
311
318
}
312
319
313
320
class VTKFusionExample extends Component {
@@ -318,54 +325,91 @@ class VTKFusionExample extends Component {
318
325
petColorMapId : 'hsv' ,
319
326
} ;
320
327
328
+ loadDataset ( imageIds , displaySetInstanceUid , modality ) {
329
+ const imageDataObject = getImageData ( imageIds , displaySetInstanceUid ) ;
330
+
331
+ const percentageCompleteStateName = `percentComplete${ modality } ` ;
332
+
333
+ loadImageData ( imageDataObject ) ;
334
+
335
+ const { insertPixelDataPromises } = imageDataObject ;
336
+
337
+ const numberOfFrames = insertPixelDataPromises . length ;
338
+
339
+ // TODO -> Maybe the component itself should do this.
340
+ insertPixelDataPromises . forEach ( promise => {
341
+ promise . then ( numberProcessed => {
342
+ const percentComplete = Math . floor (
343
+ ( numberProcessed * 100 ) / numberOfFrames
344
+ ) ;
345
+
346
+ if ( this . state . percentComplete !== percentComplete ) {
347
+ const stateUpdate = { } ;
348
+
349
+ stateUpdate [ percentageCompleteStateName ] = percentComplete ;
350
+
351
+ this . setState ( stateUpdate ) ;
352
+ }
353
+
354
+ if ( percentComplete % 20 === 0 ) {
355
+ this . rerenderAll ( ) ;
356
+ }
357
+ } ) ;
358
+ } ) ;
359
+
360
+ Promise . all ( insertPixelDataPromises ) . then ( ( ) => {
361
+ this . rerenderAll ( ) ;
362
+ } ) ;
363
+
364
+ return imageDataObject ;
365
+ }
366
+
321
367
async componentDidMount ( ) {
322
368
const imageIdPromise = createStudyImageIds ( url , searchInstanceOptions ) ;
323
369
324
- this . components = { } ;
370
+ this . components = [ ] ;
325
371
326
372
const imageIds = await imageIdPromise ;
373
+
327
374
let ctImageIds = imageIds . filter ( imageId =>
328
375
imageId . includes ( ctSeriesInstanceUID )
329
376
) ;
330
- //ctImageIds = ctImageIds.slice(0, ctImageIds.length / 4)
331
377
332
378
let petImageIds = imageIds . filter ( imageId =>
333
379
imageId . includes ( petSeriesInstanceUID )
334
380
) ;
335
- petImageIds = petImageIds . slice ( 0 , petImageIds . length / 4 ) ;
336
-
337
- const ctImageDataObject = loadDataset ( ctImageIds , 'ctDisplaySet' ) ;
338
- const petImageDataObject = loadDataset ( petImageIds , 'petDisplaySet' ) ;
339
- const promises = [
340
- ...ctImageDataObject . insertPixelDataPromises ,
341
- ...petImageDataObject . insertPixelDataPromises ,
342
- ] ;
343
381
344
- // TODO -> We could stream this ala 2D but its not done yet, so wait.
382
+ const ctImageDataObject = this . loadDataset (
383
+ ctImageIds ,
384
+ 'ctDisplaySet' ,
385
+ 'CT'
386
+ ) ;
387
+ const petImageDataObject = this . loadDataset (
388
+ petImageIds ,
389
+ 'petDisplaySet' ,
390
+ 'PT'
391
+ ) ;
345
392
346
- Promise . all ( promises ) . then ( ( ) => {
347
- const ctImageData = ctImageDataObject . vtkImageData ;
348
- const petImageData = petImageDataObject . vtkImageData ;
393
+ const ctImageData = ctImageDataObject . vtkImageData ;
394
+ const petImageData = petImageDataObject . vtkImageData ;
349
395
350
- const ctVol = createCT2dPipeline ( ctImageData ) ;
351
- const petVol = createPET2dPipeline (
352
- petImageData ,
353
- this . state . petColorMapId
354
- ) ;
396
+ const ctVol = createCT2dPipeline ( ctImageData ) ;
397
+ const petVol = createPET2dPipeline ( petImageData , this . state . petColorMapId ) ;
355
398
356
- const ctVolVR = createCT3dPipeline (
357
- ctImageData ,
358
- this . state . ctTransferFunctionPresetId
359
- ) ;
360
- const petVolVR = createPET3dPipeline (
361
- petImageData ,
362
- this . state . petColorMapId
363
- ) ;
399
+ const ctVolVR = createCT3dPipeline (
400
+ ctImageData ,
401
+ this . state . ctTransferFunctionPresetId
402
+ ) ;
403
+ const petVolVR = createPET3dPipeline (
404
+ petImageData ,
405
+ this . state . petColorMapId
406
+ ) ;
364
407
365
- this . setState ( {
366
- volumes : [ ctVol , petVol ] ,
367
- volumeRenderingVolumes : [ ctVolVR , petVolVR ] ,
368
- } ) ;
408
+ this . setState ( {
409
+ volumes : [ ctVol , petVol ] ,
410
+ volumeRenderingVolumes : [ ctVolVR , petVolVR ] ,
411
+ percentCompleteCT : 0 ,
412
+ percentCompletePT : 0 ,
369
413
} ) ;
370
414
}
371
415
@@ -461,61 +505,75 @@ class VTKFusionExample extends Component {
461
505
) ;
462
506
} ) ;
463
507
508
+ const { percentCompleteCT, percentCompletePT } = this . state ;
509
+
510
+ const progressString = `Progress: CT: ${ percentCompleteCT } % PET: ${ percentCompletePT } %` ;
511
+
464
512
return (
465
- < div className = "row" >
466
- < div className = "col-xs-12" >
467
- < h1 > Image Fusion</ h1 >
468
- < p >
469
- This example demonstrates how to use both the 2D and 3D components
470
- to display multiple volumes simultaneously. A PET volume is overlaid
471
- on a CT volume and controls are provided to update the CT Volume
472
- Rendering presets (manipulating scalar opacity, gradient opacity,
473
- RGB transfer function, etc...) and the PET Colormap (i.e. RGB
474
- Transfer Function).
475
- </ p >
476
- < p >
477
- Images are retrieved via DICOMWeb from a publicly available server
478
- and constructed into < code > vtkImageData</ code > volumes before they
479
- are provided to the component. When each slice arrives, its pixel
480
- data is dumped into the proper location in the volume array.
481
- </ p >
482
- </ div >
483
- < div className = "col-xs-12" >
484
- < div >
485
- < label htmlFor = "select_PET_colormap" > PET Colormap: </ label >
486
- < select
487
- id = "select_PET_colormap"
488
- value = { this . state . petColorMapId }
489
- onChange = { this . handleChangePETColorMapId }
490
- >
491
- { petColorMapPresetOptions }
492
- </ select >
513
+ < div >
514
+ < div className = "row" >
515
+ < div className = "col-xs-12" >
516
+ < h1 > Image Fusion </ h1 >
517
+
518
+ < p >
519
+ This example demonstrates how to use both the 2D and 3D components
520
+ to display multiple volumes simultaneously. A PET volume is
521
+ overlaid on a CT volume and controls are provided to update the CT
522
+ Volume Rendering presets (manipulating scalar opacity, gradient
523
+ opacity, RGB transfer function, etc...) and the PET Colormap (i.e.
524
+ RGB Transfer Function).
525
+ </ p >
526
+ < p >
527
+ Images are retrieved via DICOMWeb from a publicly available server
528
+ and constructed into < code > vtkImageData</ code > volumes before they
529
+ are provided to the component. When each slice arrives, its pixel
530
+ data is dumped into the proper location in the volume array.
531
+ </ p >
493
532
</ div >
494
- < div >
495
- < label htmlFor = "select_CT_xfer_fn" >
496
- CT Transfer Function Preset (for Volume Rendering):{ ' ' }
497
- </ label >
498
- < select
499
- id = "select_CT_xfer_fn"
500
- value = { this . state . ctTransferFunctionPresetId }
501
- onChange = { this . handleChangeCTTransferFunction }
502
- >
503
- { ctTransferFunctionPresetOptions }
504
- </ select >
533
+ < div className = "col-xs-12" >
534
+ < div >
535
+ < label htmlFor = "select_PET_colormap" > PET Colormap: </ label >
536
+ < select
537
+ id = "select_PET_colormap"
538
+ value = { this . state . petColorMapId }
539
+ onChange = { this . handleChangePETColorMapId }
540
+ >
541
+ { petColorMapPresetOptions }
542
+ </ select >
543
+ </ div >
544
+ < div >
545
+ < label htmlFor = "select_CT_xfer_fn" >
546
+ CT Transfer Function Preset (for Volume Rendering):{ ' ' }
547
+ </ label >
548
+ < select
549
+ id = "select_CT_xfer_fn"
550
+ value = { this . state . ctTransferFunctionPresetId }
551
+ onChange = { this . handleChangeCTTransferFunction }
552
+ >
553
+ { ctTransferFunctionPresetOptions }
554
+ </ select >
555
+ </ div >
556
+ </ div >
557
+ < div className = "col-xs-12" >
558
+ < h5 > { progressString } </ h5 >
505
559
</ div >
506
560
</ div >
507
- < hr />
508
- < div className = "col-xs-12 col-sm-6" >
509
- < View2D
510
- volumes = { this . state . volumes }
511
- onCreated = { this . saveComponentReference ( 0 ) }
512
- />
513
- </ div >
514
- < div className = "col-xs-12 col-sm-6" >
515
- < View3D
516
- volumes = { this . state . volumeRenderingVolumes }
517
- onCreated = { this . saveComponentReference ( 1 ) }
518
- />
561
+
562
+ < div className = "row" >
563
+ < hr />
564
+ < div className = "col-xs-12 col-sm-6" >
565
+ < View2D
566
+ volumes = { this . state . volumes }
567
+ onCreated = { this . saveComponentReference ( 0 ) }
568
+ orientation = { { sliceNormal : [ 1 , 0 , 0 ] , viewUp : [ 0 , 0 , 1 ] } }
569
+ />
570
+ </ div >
571
+ < div className = "col-xs-12 col-sm-6" >
572
+ < View3D
573
+ volumes = { this . state . volumeRenderingVolumes }
574
+ onCreated = { this . saveComponentReference ( 1 ) }
575
+ />
576
+ </ div >
519
577
</ div >
520
578
</ div >
521
579
) ;
0 commit comments