feat: cornerstone3D stack and volume viewports (#2787)

* feat: cs3d working stack viewport and tools (#19)

* Squashed everything

* fix weird react issue

* fix eslint / prettier stuff

* thumbnails work now with cpu rendering

* feat: use new loadImageToCanvas in cs3d

* make jump to measurement work

* fix active thumbnail

* fix measurement delete

* fix window level presets

* remove segmentation and sync groups for now

* fix the dicom pdf and dicom video

* fix cornerstone window assignment for cypress

* apply review comments

Co-authored-by: Erik <erik.sweed@gmail.com>

* feat: cs3d tools and toolGroups (#20)

* add more tools to work with cs3d mode

* fix hotkeys

* add stack manager usage for stach viewports

* add image scrollbar

* wip viewport overlay

* fix toAnnotation schema for tools

* fix the unnecessary size change that triggered resize

* hanging protocol improvement to allow unmatched errors

* study description matching for hanging protocol

* fix the displaysetOptions to work

* fix handle the active tool when a new viewport is added

* fix separate toolGroups for mode

* apply review comments

* apply review comments

* yarn lock

* feat: overlay component (#21)

* fix default displayset options

* add viewport overlay

* apply review comments

* feat: loading and orientation indicators (#22)

* add loading indicator

* add orientation marker initial work

* apply review comments

* fix: orientation markers (#23)

* finished the orientation markers

* fix various broken cypress tests

* apply review comments

* update yarn lock

* feat: re-working measurement tracking mode with cornerstone3d (#2805)

* feat: cs3d working stack viewport and tools (#19)

* Squashed everything

* fix weird react issue

* fix eslint / prettier stuff

* thumbnails work now with cpu rendering

* feat: use new loadImageToCanvas in cs3d

* make jump to measurement work

* fix active thumbnail

* fix measurement delete

* fix window level presets

* remove segmentation and sync groups for now

* fix the dicom pdf and dicom video

* fix cornerstone window assignment for cypress

* apply review comments

Co-authored-by: Erik <erik.sweed@gmail.com>

* feat: Add Measurement tracking mode with cs3D (#2789)

* feat: first render for cornerstone3d tracked viewport

* make tool active work

* wip for SR extension

* renamed dicom sr to cornerstone dicom sr

* remove cornerstone from dicom pdf and video

* move dicom sr logic to sr extension

* feat: Add hydration for the length tool

* fix SR display tool for length using cs3d

* fix default config

* fix: various bugs with sr viewport and tracking

* fix promptying to continue tracking for when SR is created

* feat: add keep trackign of unique identifiers

* fix hydration for same imageIds

* feat: Add SR toolGroup creation on modeEnter

* feat: remove the need for separate mapper for SR hydration

* add SR display for ellipse

* handle hydration of elliptical ROI tool

* remove cornerstone extension

* add arrow mapping

* feat: Add ArrowAnnotate SR display and hydration

* apply review comments

* apply review comments

* move viewport labels to the viewportData

* apply review comments

* fix: integration cypress tests with cornerstone3D and add CINE tool (#2795)

* fix: integration cypress tests with cornerstone3D

* revert to addOrUpdate as it makes more sense

* fix local drag and drop

* fix tests

* move dicomLoaderService to cornerstone extension

* fix various import bugs

* fix bug for local PT series

* fix various unit tests

* bump cs3d versions

* add angle and magnify tool

* bump dependencies to avoid broken peerDeps

* feat: add initial work for capture using cs3d

* feat: show annotations on the image capture

* feat: add svg layer export

* feat: Add CINE Tool

* feat: remove unnecessary viewport rendering for cine state changes

Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>

* docs: modify and improve documentation (#2800)

* cleanup docs versionings

* feat: Add all version explanations

* version docs for 3.0

* wip for changing docs

* wip for updated docs

* add utility module documentation

* fix demo with nohoisting of history

* add slides and video to resources

* apply review comments

* fix: drag and drop SR into SR viewport (#2803)

* update yarn lock

Co-authored-by: Erik <erik.sweed@gmail.com>

* update pathnames to match v3-stable

* update the e2e pathname

* fix: various bugs with regard to tracking workflow (#2811)

* fix: various issues with measurement panel

* fix: update default tool style for annotations

* fix: annotatoin label getting removed

* feat: Add backward compatibility for SR hydration with legacy cornerstone

* fix: cursors and ellipse ROI max style

* fix: ArrowAnnotate SRDisplay

* apply review comments

* bump package versions

* fix: bugin rehydration of SR

* fix: e2e tests

* fix active viewport thickness and arrowTool ui

* add readme for measurement tracking

* use uploaded image for readme

* add back images

* try to fix e2e test

* fix: window level presets hotkeys

* Update README.md

* update yarn lock

* feat: volume api and TMTV mode (#2817)

* feat: volumeAPI and TMTV mode

* feat: use cs3d cache service to obtain viewportData

* wip for volume api

* wip: fusion viewport

* feat: add blend mode option

* wip for image scrollbar

* fix drag and drop thumbnail

* wip for image scrollbar for voluems

* fix: element mismatch bug for scroll

* feat: Add image scrollbar to volumes

* feat: add syncGroups to volume api

* feat: add tmtv mode initial setup

* feat: add initial image options for the stack viewports

* feat: Add custom load strategy for volume viewports via HP

* apply review comments

fix: Jump presets cs3d (#2812)

* feat: Add JumpPreset to OHIF for Cornerstone3D

* fix: accessing viewport service from servicesManager

feat: volume API and TMTV mode (#2814)

* fix: do not display overlays on mip viewports

* feat: add optional disableCommands for toggle buttons

* fix: toggleCrossharis

* feat: config the crosshairs

* feat: add PetSUV Panel for changing metadata

* feat: initial work for the rectangleROIThreshold panel

* wip: measurement service

* roi threshold working

* feat: Add displayText to segmentations

* feat: add remove segmentation

* feat: add csv export

* feat: add RT export for annotations in tmtv mode

* fix: fusion to use pt in tmtv mode and measuremet mappings

* fix: various bugs

* apply review comments

* apply review comments

* feat: add fusion color maps

* add readme to tmtv mode

* Update README.md

* fix: try to fix build

* fix unit tests

* Update README.md

* feat: add about to the panel

* fix: changing strategy in roi panel

* apply review comments

* update package versions

* wip for stackPrefetch

* fix: cornerstone3d hydration and renaming (#2818)

* update readme

* renamed cornerstone extension

* wip for renaming cornerstone3D variables

* wip for renaming cornerstone3D variables

* wip for fixing bugs for SR viewport

* fix: jumpToMeasurement and initial label after hydration

* fix: fileName capitalization

* feat: use the new prefetch stack in the cs3d (#2820)

* feat: use the new prefetch stack in the cs3d

* use viewport scroll api for stack viewport

* fix cine stop when scrollbar changes

* feat: use new prefetch events

* fix loading state to not show repeatedly

* fix: various bugs for tmtv mode thresholding and new icons (#2823)

* feat: make tmtv mode available in worklist

* feat: add new icons for tmtv mode

* feat: add fusion color icon

* fix: parallel scale calculation

* fix: bump Cornerstone to get large image support working

* fix: Fix issues with CPU viewport flipping, including config files

* fix: bump cornerstone to fix magnify tool

* fix: Bump Cornerstone version to fix resetCamera issue in StackViewport

* ci: Add _headers file to enable CORS headers for Netlify Drag/Drop deploys

* fix: WADO-URI was not working. PET Metadata was coming from the wrong place. Fixed some minor React errors

* feat(OHIF):Allow modes and extensions to be added after commpile time. (#2838)

Also works with the compile time add that the existing cli uses, so that both build types work.
Eric and I agreed this doesn't change existing functionality, but is almost entirely build issues/fixes.

* bump: dependency versions to fix hydration bugs (#2848)

* bump: dcmjs version to fix hydration bugs

* try to fix tests

* bump dependency versions

Co-authored-by: Alireza <ar.sedghi@gmail.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
authored and GitHub committed 2022-07-27 12:39:04 -04:00
1 parent 86dcaf6293
commit bd1c38c00c
668 files changed
+41669 -13410

No files matched your search

-136
View File
@@ -1,136 +0,0 @@
let stackMap = {};
let configuration = {};
const stackUpdatedCallbacks = [];
/**
* Loop through the current series and add metadata to the
* Cornerstone meta data provider. This will be used to fill information
* into the viewport overlays, and to calculate reference lines and orientation markers
* @param {Object} stackMap stackMap object
* @param {Object} displaySet The set of images to make the stack from
* @return {Array} Array with image IDs
*/
function createAndAddStack(
stackMap,
displaySet,
dataSource,
stackUpdatedCallbacks
) {
const {
images,
displaySetInstanceUID,
StudyInstanceUID,
frameRate,
isClip,
initialImageIdIndex,
} = displaySet;
if (!images) {
return;
}
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
const stack = {
StudyInstanceUID,
displaySetInstanceUID,
imageIds,
frameRate,
isClip,
initialImageIdIndex,
};
stackMap[displaySetInstanceUID] = stack;
return stack;
}
configuration = {
createAndAddStack,
};
/**
* This object contains all the functions needed for interacting with the stack manager.
* Generally, findStack is the only function used. If you want to know when new stacks
* come in, you can register a callback with addStackUpdatedCallback.
*/
const StackManager = {
/**
* Removes all current stacks
*/
clearStacks() {
stackMap = {};
},
/**
* Create a stack from an image set, as well as add in the metadata on a per image bases.
* @param displaySet The set of images to make the stack from
* @return {Array} Array with image IDs
*/
makeAndAddStack(displaySet, dataSource) {
return configuration.createAndAddStack(
stackMap,
displaySet,
dataSource,
stackUpdatedCallbacks
);
},
/**
* Find a stack from the currently created stacks.
* @param displaySetInstanceUID The UID of the stack to find.
* @returns {*} undefined if not found, otherwise the stack object is returned.
*/
findStack(displaySetInstanceUID) {
return stackMap[displaySetInstanceUID];
},
/**
* Find a stack or create one if it has not been created yet
* @param displaySet The set of images to make the stack from
* @return {Array} Array with image IDs
*/
findOrCreateStack(displaySet, dataSource) {
let stack = this.findStack(displaySet.displaySetInstanceUID);
if (!stack || !stack.imageIds) {
stack = this.makeAndAddStack(displaySet, dataSource);
}
return stack;
},
/**
* Gets the underlying map of displaySetInstanceUID to stack object.
* WARNING: Do not change this object. It directly affects the manager.
* @returns {{}} map of displaySetInstanceUID -> stack.
*/
getAllStacks() {
return stackMap;
},
/**
* Adds in a callback to be called on a stack being added / updated.
* @param callback must accept at minimum one argument,
* which is the stack that was added / updated.
*/
addStackUpdatedCallback(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be provided as a function');
}
stackUpdatedCallbacks.push(callback);
},
/**
* Return configuration
*/
getConfiguration() {
return configuration;
},
/**
* Set configuration, in order to provide compatibility
* with other systems by overriding this functions
* @param {Object} config object with functions to be overrided
*
* For now, only makeAndAddStack can be overrided
*/
setConfiguration(config) {
configuration = config;
},
};
export { StackManager };
export default StackManager;
+21
View File
@@ -0,0 +1,21 @@
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
export default debounce;
@@ -1,205 +0,0 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import { api } from 'dicomweb-client';
import DICOMWeb from '../DICOMWeb';
import errorHandler from '../errorHandler';
const getImageId = imageObj => {
if (!imageObj) {
return;
}
return typeof imageObj.getImageId === 'function'
? imageObj.getImageId()
: imageObj.url;
};
const findImageIdOnStudies = (studies, displaySetInstanceUID) => {
const study = studies.find(study => {
const displaySet = study.displaySets.some(
displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID
);
return displaySet;
});
const { series = [] } = study;
const { instances = [] } = series[0] || {};
const instance = instances[0];
return getImageId(instance);
};
const someInvalidStrings = strings => {
const stringsArray = Array.isArray(strings) ? strings : [strings];
const emptyString = string => !string;
let invalid = stringsArray.some(emptyString);
return invalid;
};
const getImageInstance = dataset => {
return dataset && dataset.images && dataset.images[0];
};
const getImageInstanceId = imageInstance => {
return getImageId(imageInstance);
};
const fetchIt = (url, headers = DICOMWeb.getAuthorizationHeader()) => {
return fetch(url, headers).then(response => response.arrayBuffer());
};
const cornerstoneRetriever = imageId => {
return cornerstone.loadAndCacheImage(imageId).then(image => {
return image && image.data && image.data.byteArray.buffer;
});
};
const wadorsRetriever = (
url,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
headers = DICOMWeb.getAuthorizationHeader(),
errorInterceptor = errorHandler.getHTTPErrorHandler()
) => {
const config = {
url,
headers,
errorInterceptor,
};
const dicomWeb = new api.DICOMwebClient(config);
return dicomWeb.retrieveInstance({
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
});
};
const getImageLoaderType = imageId => {
const loaderRegExp = /^\w+\:/;
const loaderType = loaderRegExp.exec(imageId);
return (
(loaderRegExp.lastIndex === 0 &&
loaderType &&
loaderType[0] &&
loaderType[0].replace(':', '')) ||
''
);
};
class DicomLoaderService {
getLocalData(dataset, studies) {
if (dataset && dataset.localFile) {
// Use referenced imageInstance
const imageInstance = getImageInstance(dataset);
let imageId = getImageInstanceId(imageInstance);
// or Try to get it from studies
if (someInvalidStrings(imageId)) {
imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUID);
}
if (!someInvalidStrings(imageId)) {
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
}
}
getDataByImageType(dataset) {
const imageInstance = getImageInstance(dataset);
if (imageInstance) {
const imageId = getImageInstanceId(imageInstance);
let getDicomDataMethod = fetchIt;
const loaderType = getImageLoaderType(imageId);
switch (loaderType) {
case 'dicomfile':
getDicomDataMethod = cornerstoneRetriever.bind(this, imageId);
break;
case 'wadors':
const url = imageInstance.getData().wadoRoot;
const studyInstanceUID = imageInstance.getStudyInstanceUID();
const seriesInstanceUID = imageInstance.getSeriesInstanceUID();
const sopInstanceUID = imageInstance.getSOPInstanceUID();
const invalidParams = someInvalidStrings([
url,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
]);
if (invalidParams) {
return;
}
getDicomDataMethod = wadorsRetriever.bind(
this,
url,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID
);
break;
case 'wadouri':
// Strip out the image loader specifier
imageId = imageId.substring(imageId.indexOf(':') + 1);
if (someInvalidStrings(imageId)) {
return;
}
getDicomDataMethod = fetchIt.bind(this, imageId);
break;
}
return getDicomDataMethod();
}
}
getDataByDatasetType(dataset) {
const {
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
authorizationHeaders,
wadoRoot,
wadoUri,
} = dataset;
// Retrieve wadors or just try to fetch wadouri
if (!someInvalidStrings(wadoRoot)) {
return wadorsRetriever(
wadoRoot,
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
authorizationHeaders
);
} else if (!someInvalidStrings(wadoUri)) {
return fetchIt(wadoUri, { headers: authorizationHeaders });
}
}
*getLoaderIterator(dataset, studies) {
yield this.getLocalData(dataset, studies);
yield this.getDataByImageType(dataset);
yield this.getDataByDatasetType(dataset);
}
findDicomDataPromise(dataset, studies) {
const loaderIterator = this.getLoaderIterator(dataset, studies);
// it returns first valid retriever method.
for (const loader of loaderIterator) {
if (loader) {
return loader;
}
}
// in case of no valid loader
throw new Error('Invalid dicom data loader');
}
}
const dicomLoaderService = new DicomLoaderService();
export default dicomLoaderService;
@@ -0,0 +1,115 @@
import { DicomMetadataStore } from '../services/DicomMetadataStore/DicomMetadataStore';
export default function downloadCSVReport(measurementData) {
if (measurementData.length === 0) {
// Prevent download of report with no measurements.
return;
}
const columns = [
'Patient ID',
'Patient Name',
'StudyInstanceUID',
'SeriesInstanceUID',
'SOPInstanceUID',
'Label',
];
const reportMap = {};
measurementData.forEach(measurement => {
const {
referenceStudyUID,
referenceSeriesUID,
getReport,
uid,
} = measurement;
if (!getReport) {
console.warn('Measurement does not have a getReport function');
return;
}
const seriesMetadata = DicomMetadataStore.getSeries(
referenceStudyUID,
referenceSeriesUID
);
const commonRowItems = _getCommonRowItems(measurement, seriesMetadata);
const report = getReport(measurement);
reportMap[uid] = {
report,
commonRowItems,
};
});
// get columns names inside the report from each measurement and
// add them to the rows array (this way we can add columns for any custom
// measurements that may be added in the future)
Object.keys(reportMap).forEach(id => {
const { report } = reportMap[id];
report.columns.forEach(column => {
if (!columns.includes(column)) {
columns.push(column);
}
});
});
const results = _mapReportsToRowArray(reportMap, columns);
let csvContent =
'data:text/csv;charset=utf-8,' +
results.map(res => res.join(',')).join('\n');
_createAndDownloadFile(csvContent);
}
function _mapReportsToRowArray(reportMap, columns) {
const results = [columns];
Object.keys(reportMap).forEach(id => {
const { report, commonRowItems } = reportMap[id];
const row = [];
// For commonRowItems, find the correct index and add the value to the
// correct row in the results array
Object.keys(commonRowItems).forEach(key => {
const index = columns.indexOf(key);
const value = commonRowItems[key];
row[index] = value;
});
// For each annotation data, find the correct index and add the value to the
// correct row in the results array
report.columns.forEach((column, index) => {
const colIndex = columns.indexOf(column);
const value = report.values[index];
row[colIndex] = value;
});
results.push(row);
});
return results;
}
function _getCommonRowItems(measurement, seriesMetadata) {
const firstInstance = seriesMetadata.instances[0];
return {
'Patient ID': firstInstance.PatientID, // Patient ID
'Patient Name': firstInstance.PatientName.Alphabetic, // PatientName
StudyInstanceUID: measurement.referenceStudyUID, // StudyInstanceUID
SeriesInstanceUID: measurement.referenceSeriesUID, // SeriesInstanceUID
SOPInstanceUID: measurement.SOPInstanceUID, // SOPInstanceUID
Label: measurement.label || '', // Label
};
}
function _createAndDownloadFile(csvContent) {
const encodedUri = encodeURI(csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', 'MeasurementReport.csv');
document.body.appendChild(link);
link.click();
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Removes the data loader scheme from the imageId
*
* @param {string} imageId Image ID
* @returns {string} imageId without the data loader scheme
* @memberof Cache
*/
export default function imageIdToURI(imageId) {
const colonIndex = imageId.indexOf(':');
return imageId.substring(colonIndex + 1);
}
+13 -7
View File
@@ -1,11 +1,9 @@
import ObjectPath from './objectPath';
import StackManager from './StackManager.js';
import absoluteUrl from './absoluteUrl';
import guid from './guid';
import sortBy from './sortBy.js';
import sortBySeriesDate from './sortBySeriesDate.js';
import writeScript from './writeScript.js';
import DicomLoaderService from './dicomLoaderService.js';
import b64toBlob from './b64toBlob.js';
//import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js';
import urlUtil from './urlUtil';
@@ -22,9 +20,13 @@ import progressTrackingUtils from './progressTrackingUtils';
import isLowPriorityModality from './isLowPriorityModality';
import { isImage } from './isImage';
import isDisplaySetReconstructable from './isDisplaySetReconstructable';
import imageIdToURI from './imageIdToURI';
import debounce from './debounce';
import roundNumber from './roundNumber';
import downloadCSVReport from './downloadCSVReport';
// Commented out unused functionality.
// Need to implement new mechanism for dervived displaySets using the displaySetManager.
// Need to implement new mechanism for derived displaySets using the displaySetManager.
const utils = {
guid,
@@ -36,9 +38,8 @@ const utils = {
formatDate,
formatPN,
b64toBlob,
StackManager,
DicomLoaderService,
urlUtil,
imageIdToURI,
//loadAndCacheDerivedDisplaySets,
makeDeferred,
makeCancelable,
@@ -51,6 +52,9 @@ const utils = {
isLowPriorityModality,
isImage,
isDisplaySetReconstructable,
debounce,
roundNumber,
downloadCSVReport,
};
export {
@@ -61,8 +65,6 @@ export {
formatDate,
writeScript,
b64toBlob,
StackManager,
DicomLoaderService,
urlUtil,
//loadAndCacheDerivedDisplaySets,
makeDeferred,
@@ -76,6 +78,10 @@ export {
isLowPriorityModality,
isImage,
isDisplaySetReconstructable,
imageIdToURI,
debounce,
roundNumber,
downloadCSVReport,
};
export default utils;
+4 -2
View File
@@ -10,14 +10,16 @@ describe('Top level exports', () => {
'sortBySeriesDate',
'isLowPriorityModality',
'writeScript',
'debounce',
'downloadCSVReport',
'imageIdToURI',
'roundNumber',
'b64toBlob',
'StackManager',
'formatDate',
'formatPN',
//'loadAndCacheDerivedDisplaySets',
'isDisplaySetReconstructable',
'isImage',
'DicomLoaderService',
'urlUtil',
'makeDeferred',
'makeCancelable',
@@ -1,3 +1,9 @@
import toNumber from './toNumber';
// TODO: Is 10% a reasonable spacingTolerance for spacing?
const spacingTolerance = 0.2;
const iopTolerance = 0.01;
/**
* Checks if a series is reconstructable to a 3D volume.
*
@@ -8,7 +14,7 @@ export default function isDisplaySetReconstructable(instances) {
return { value: false };
}
const firstInstance = instances[0].getData().metadata;
const firstInstance = instances[0];
const Modality = firstInstance.Modality;
const isMultiframe = firstInstance.NumberOfFrames > 1;
@@ -35,19 +41,21 @@ function processMultiframe(instance) {
}
function processSingleframe(instances) {
const firstImage = instances[0].getData().metadata;
const firstImageRows = firstImage.Rows;
const firstImageColumns = firstImage.Columns;
const firstImageSamplesPerPixel = firstImage.SamplesPerPixel;
const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
const firstImagePositionPatient = firstImage.ImagePositionPatient;
const firstImage = instances[0];
const firstImageRows = toNumber(firstImage.Rows);
const firstImageColumns = toNumber(firstImage.Columns);
const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel);
const firstImageOrientationPatient = toNumber(
firstImage.ImageOrientationPatient
);
const firstImagePositionPatient = toNumber(firstImage.ImagePositionPatient);
// Can't reconstruct if we:
// -- Have a different dimensions within a displaySet.
// -- Have a different number of components within a displaySet.
// -- Have different orientations within a displaySet.
for (let i = 1; i < instances.length; i++) {
const instance = instances[i].getData().metadata;
const instance = instances[i];
const {
Rows,
Columns,
@@ -55,11 +63,13 @@ function processSingleframe(instances) {
ImageOrientationPatient,
} = instance;
const imageOrientationPatient = toNumber(ImageOrientationPatient);
if (
Rows !== firstImageRows ||
Columns !== firstImageColumns ||
SamplesPerPixel !== firstImageSamplesPerPixel ||
!_isSameOrientation(ImageOrientationPatient, firstImageOrientationPatient)
!_isSameOrientation(imageOrientationPatient, firstImageOrientationPatient)
) {
return { value: false };
}
@@ -71,8 +81,9 @@ function processSingleframe(instances) {
// If spacing is on a uniform grid but we are missing frames,
// Allow reconstruction, but pass back the number of missing frames.
if (instances.length > 2) {
const lastIpp = instances[instances.length - 1].getData().metadata
.ImagePositionPatient;
const lastIpp = toNumber(
instances[instances.length - 1].ImagePositionPatient
);
// We can't reconstruct if we are missing ImagePositionPatient values
if (!firstImagePositionPatient || !lastIpp) {
@@ -86,11 +97,12 @@ function processSingleframe(instances) {
let previousImagePositionPatient = firstImagePositionPatient;
for (let i = 1; i < instances.length; i++) {
const instance = instances[i].getData().metadata;
const { ImagePositionPatient } = instance;
const instance = instances[i];
// Todo: get metadata from OHIF.MetadataProvider
const imagePositionPatient = toNumber(instance.ImagePositionPatient);
const spacingBetweenFrames = _getPerpendicularDistance(
ImagePositionPatient,
imagePositionPatient,
previousImagePositionPatient
);
const spacingIssue = _getSpacingIssue(
@@ -108,7 +120,7 @@ function processSingleframe(instances) {
}
}
previousImagePositionPatient = ImagePositionPatient;
previousImagePositionPatient = imagePositionPatient;
}
}
@@ -127,10 +139,6 @@ function _isSameOrientation(iop1, iop2) {
);
}
// TODO: Is 10% a reasonable spacingTolerance for spacing?
const spacingTolerance = 0.1;
const iopTolerance = 0.01;
/**
* Checks for spacing issues.
*
+5
View File
@@ -0,0 +1,5 @@
function _round(value, decimals) {
return Number(value.toFixed(decimals));
}
export default _round;
+13
View File
@@ -0,0 +1,13 @@
/**
* Returns the values as an array of javascript numbers
*
* @param val - The javascript object for the specified element in the metadata
* @returns {*}
*/
export default function toNumber(val) {
if (Array.isArray(val)) {
return val.map(v => (v !== undefined ? Number(v) : v));
} else {
return val !== undefined ? Number(val) : val;
}
}