Skip to content

Commit c932efe

Browse files
authored
chore: 🤖 Add progressive imaging loading to PT/CT and VR examples (#78)
1 parent cf5e5fe commit c932efe

File tree

3 files changed

+220
-134
lines changed

3 files changed

+220
-134
lines changed

‎examples/VTKFusionExample.js‎

Lines changed: 168 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -270,44 +270,51 @@ function createPET3dPipeline(imageData, petColorMapId) {
270270
return actor;
271271
}
272272

273-
function createStudyImageIds(baseUrl, studySearchOptions) {
273+
async function createStudyImageIds(baseUrl, studySearchOptions) {
274274
const SOP_INSTANCE_UID = '00080018';
275275
const SERIES_INSTANCE_UID = '0020000E';
276276

277277
const client = new api.DICOMwebClient({ url });
278278

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+
);
297302

298-
return imageId;
303+
if (
304+
seriesInstanceUID === ctSeriesInstanceUID ||
305+
seriesInstanceUID === petSeriesInstanceUID
306+
) {
307+
instancesToRetrieve.push({
308+
studyInstanceUID,
309+
seriesInstanceUID,
310+
sopInstanceUID,
299311
});
312+
}
300313

301-
resolve(imageIds);
302-
}, reject);
314+
return imageId;
303315
});
304-
}
305-
306-
function loadDataset(imageIds, displaySetInstanceUid) {
307-
const imageDataObject = getImageData(imageIds, displaySetInstanceUid);
308316

309-
loadImageData(imageDataObject);
310-
return imageDataObject;
317+
return imageIds;
311318
}
312319

313320
class VTKFusionExample extends Component {
@@ -318,54 +325,91 @@ class VTKFusionExample extends Component {
318325
petColorMapId: 'hsv',
319326
};
320327

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+
321367
async componentDidMount() {
322368
const imageIdPromise = createStudyImageIds(url, searchInstanceOptions);
323369

324-
this.components = {};
370+
this.components = [];
325371

326372
const imageIds = await imageIdPromise;
373+
327374
let ctImageIds = imageIds.filter(imageId =>
328375
imageId.includes(ctSeriesInstanceUID)
329376
);
330-
//ctImageIds = ctImageIds.slice(0, ctImageIds.length / 4)
331377

332378
let petImageIds = imageIds.filter(imageId =>
333379
imageId.includes(petSeriesInstanceUID)
334380
);
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-
];
343381

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+
);
345392

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;
349395

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);
355398

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+
);
364407

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,
369413
});
370414
}
371415

@@ -461,61 +505,75 @@ class VTKFusionExample extends Component {
461505
);
462506
});
463507

508+
const { percentCompleteCT, percentCompletePT } = this.state;
509+
510+
const progressString = `Progress: CT: ${percentCompleteCT}% PET: ${percentCompletePT}%`;
511+
464512
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>
493532
</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>
505559
</div>
506560
</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>
519577
</div>
520578
</div>
521579
);

0 commit comments

Comments
 (0)