* Filter segs and fetch correct metadata. * Initiate seg loading logic on series load into viewport. Need to write. * Automatic consumption of SEG functional. * Pull in master. * Fix issues after pulling in master. * Clean up old segmentation handlers. * WIP * WIP * Poppulate viewport with metadata. * Show hide/segmentation in react-vtkjs-viewport. * feat: 🎸 View segmentations in vtkjs viewport * Filter segs and fetch correct metadata. * Initiate seg loading logic on series load into viewport. Need to write. * Automatic consumption of SEG functional. * Pull in master. * Fix issues after pulling in master. * Clean up old segmentation handlers. * WIP * WIP * Poppulate viewport with metadata. * Show hide/segmentation in react-vtkjs-viewport. * feat: 🎸 View segmentations in vtkjs viewport * Rebase on master. * format format format * add basic extension parts * Updated yarn lock * Fetch derived/referenced displaysets in sidepanel * Add buttons, expose seriesDescription, write notes for Danny. * Add growcut tool extention. -> Move to a seperate repo. * Change some text fields. * expose dialog provider * hide draggable area * No pointer events for now; need to revisit * Don't filter seg from the outset for now * fiddling about * fix path to series data * fix logic to determine next available labelmapindex * Ability to swtich between labelmaps * Return a promise so we can make sure our labelmap is loaded before pulling metadata * Better UI to toggle labelmaps * Fix accidental untoggle of active labelmap for ui * Remove brush tool from cornerstone extension * Add cstools in preInit for seg extension * Add buttons for seg magic * Hide the measurements panel for now * Slightly better seg/scissor icons * Add eraser * ability to switch segments * seg color / switcher * Fix vtk labelmap cache. * sidepanel should scroll if there's overflow * Use a fancier range slider * Slightly prettier buttons * Push in progress * A bit more reactive; handle no-meta * Update to vtk.js 11.14.0 and react-vtkjs-viewport 0.6.0 * chore: kickstart preview * Fix conflict issues * Refactor panel * Isolate increment functions * Wip segment table * Hide selector and add scroll to segmentation * Increase width of segment description * Hide not used items * Add new select for segmentations * Align ui * Fix default segmentation * Fix broken import test * Get measurements panel back and add spacing to grouped buttons * Hide brush and selector and refresh when switching segmentations * Add pointer events back and fix broken tests for measurement table * Update segment list when switching humbnails * CR Update: Minor UI adjustments * CR Update: Set active segment * Move components to components folder * Add new settings ui * Integrate configuration * Improve styles * Float numbers * Improve css * Refresh settings menu if side panel changed * Fix settings rendering logic and remove comments * Extract components * Remove not used tools * Remove non used extension * Use style variables and remove comments * Remove non used dep from lock * Remove tools * Remove segmentation from toolbar * Add todo and brushradius from config * Fix packagejson growcut import * Remove webpack config already defined in parent * Update package json description * Change component name to british english to us * Improve prop types * Rename cornerstone label map event * Scope css * Extract and add doc to helper * Update jsdocs for getDerivedDatasets * Add proptypes and cr updates * Update tablistitem proptypes * config is readonly, rollback destructuring * Remove event for side panel * Add jsdocs for util * Extract getimageid to studymetada * Use ohif log instead of console * Remove specific style from side panel * Fix default props for brush radius * Descructure configuration props from settings component to avoid invalid props * Update proptypes to be required (settings) * Update readme * Remove commandsmodule * Get configuration module inside the component * Set selected segment * Rename extension and use modality to filter * Fix for failing tests (#1423) * hello world * Finish rebase for Igor. * Re-fix cornerstoneTools version that got messed up in merge. * Fix reactivity of isDisabled, write implementation for the SEG panel. * Nuke yarn.lock to stack CST to 4.12.0 * Remove debugger statements. * fix: bump cornerstone-tools version to address toFixed issue * test: force tests for MISTER^MR to navigate to study directly * test: remove eraser tool test * test: count is off by one * test: don't save SR state for the time being * test: disable more tests Co-authored-by: James A. Petts <jamesapetts@gmail.com> Co-authored-by: dannyrb <danny.ri.brown@gmail.com> Co-authored-by: igoroctaviano] <igoroctaviano@users.noreply.github.com> Co-authored-by: Mirna Silva <mirna.silva@radicalimaging.com>
This commit is contained in:
78 files changed
+3071
-1962
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import guid from '../utils/guid.js';
|
||||
import OHIFError from './OHIFError';
|
||||
import { Vector3 } from 'cornerstone-math';
|
||||
|
||||
const OBJECT = 'object';
|
||||
|
||||
@@ -63,6 +64,60 @@ class ImageSet {
|
||||
sortBy(sortingCallback) {
|
||||
return this.images.sort(sortingCallback);
|
||||
}
|
||||
|
||||
sortByImagePositionPatient() {
|
||||
const images = this.images;
|
||||
const referenceImagePositionPatient = _getImagePositionPatient(images[0]);
|
||||
|
||||
const refIppVec = new Vector3(
|
||||
referenceImagePositionPatient[0],
|
||||
referenceImagePositionPatient[1],
|
||||
referenceImagePositionPatient[2]
|
||||
);
|
||||
|
||||
const ImageOrientationPatient = _getImageOrientationPatient(images[0]);
|
||||
|
||||
const scanAxisNormal = new Vector3(
|
||||
ImageOrientationPatient[0],
|
||||
ImageOrientationPatient[1],
|
||||
ImageOrientationPatient[2]
|
||||
).cross(
|
||||
new Vector3(
|
||||
ImageOrientationPatient[3],
|
||||
ImageOrientationPatient[4],
|
||||
ImageOrientationPatient[5]
|
||||
)
|
||||
);
|
||||
|
||||
const distanceImagePairs = images.map(function(image) {
|
||||
const ippVec = new Vector3(..._getImagePositionPatient(image));
|
||||
const positionVector = refIppVec.clone().sub(ippVec);
|
||||
const distance = positionVector.dot(scanAxisNormal);
|
||||
|
||||
return {
|
||||
distance,
|
||||
image,
|
||||
};
|
||||
});
|
||||
|
||||
distanceImagePairs.sort(function(a, b) {
|
||||
return b.distance - a.distance;
|
||||
});
|
||||
|
||||
const sortedImages = distanceImagePairs.map(a => a.image);
|
||||
|
||||
images.sort(function(a, b) {
|
||||
return sortedImages.indexOf(a) - sortedImages.indexOf(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _getImagePositionPatient(image) {
|
||||
return image.getData().metadata.ImagePositionPatient;
|
||||
}
|
||||
|
||||
function _getImageOrientationPatient(image) {
|
||||
return image.getData().metadata.ImageOrientationPatient;
|
||||
}
|
||||
|
||||
export default ImageSet;
|
||||
@@ -35,6 +35,12 @@ export class StudyMetadata extends Metadata {
|
||||
writable: false,
|
||||
value: [],
|
||||
},
|
||||
_derivedDisplaySets: {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: [],
|
||||
},
|
||||
_firstSeries: {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
@@ -121,7 +127,11 @@ export class StudyMetadata extends Metadata {
|
||||
);
|
||||
if (displaySet) {
|
||||
displaySet.sopClassModule = true;
|
||||
displaySets.push(displaySet);
|
||||
|
||||
displaySet.isDerived
|
||||
? this._addDerivedDisplaySet(displaySet)
|
||||
: displaySets.push(displaySet);
|
||||
|
||||
return displaySets;
|
||||
}
|
||||
}
|
||||
@@ -186,9 +196,73 @@ export class StudyMetadata extends Metadata {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of series to be placed in the Study Metadata
|
||||
* The series that appear in the Study Metadata must represent
|
||||
* imaging modalities.
|
||||
* Adds the displaySets to the studies list of derived displaySets.
|
||||
* @param {object} displaySet The displaySet to append to the derived displaysets list.
|
||||
*/
|
||||
_addDerivedDisplaySet(displaySet) {
|
||||
this._derivedDisplaySets.push(displaySet);
|
||||
// --> Perhaps that logic should exist in the extension sop class handler and this be a dumb list.
|
||||
// TODO -> Get x Modality by referencedSeriesInstanceUid, FoR, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of derived datasets in the study, filtered by the given filter.
|
||||
* @param {object} filter An object containing search filters
|
||||
* @param {object} filter.modality
|
||||
* @param {object} filter.referencedSeriesInstanceUID
|
||||
* @param {object} filter.referencedFrameOfReferenceUID
|
||||
* @return {Array} filtered derived display sets
|
||||
*/
|
||||
getDerivedDatasets(filter) {
|
||||
const {
|
||||
modality,
|
||||
referencedSeriesInstanceUID,
|
||||
referencedFrameOfReferenceUID,
|
||||
} = filter;
|
||||
let filteredDerivedDisplaySets = this._derivedDisplaySets;
|
||||
|
||||
if (modality) {
|
||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||
displaySet => displaySet.Modality === Modality
|
||||
);
|
||||
}
|
||||
|
||||
if (referencedSeriesInstanceUID) {
|
||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||
displaySet => {
|
||||
if (!displaySet.metadata.ReferencedSeriesSequence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ReferencedSeriesSequence = Array.isArray(
|
||||
displaySet.metadata.ReferencedSeriesSequence
|
||||
)
|
||||
? displaySet.metadata.ReferencedSeriesSequence
|
||||
: [displaySet.metadata.ReferencedSeriesSequence];
|
||||
|
||||
return ReferencedSeriesSequence.some(
|
||||
ReferencedSeries =>
|
||||
ReferencedSeries.SeriesInstanceUID === referencedSeriesInstanceUID
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (referencedFrameOfReferenceUID) {
|
||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||
displaySet =>
|
||||
displaySet.ReferencedFrameOfReferenceUID ===
|
||||
ReferencedFrameOfReferenceUID
|
||||
);
|
||||
}
|
||||
|
||||
return filteredDerivedDisplaySets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of displaySets to be placed in the Study Metadata
|
||||
* The displaySets that appear in the Study Metadata must represent
|
||||
* imaging modalities. A series may be split into one or more displaySets.
|
||||
*
|
||||
* Furthermore, for drag/drop functionality,
|
||||
* it is easiest if the stack objects also contain information about
|
||||
@@ -482,6 +556,22 @@ export class StudyMetadata extends Metadata {
|
||||
return series;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first image id given display instance uid.
|
||||
* @return {string} The image id.
|
||||
*/
|
||||
getFirstImageId(displaySetInstanceUID) {
|
||||
try {
|
||||
const displaySet = this.findDisplaySet(
|
||||
displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
return displaySet.images[0].getImageId();
|
||||
} catch (error) {
|
||||
console.error('Failed to retrieve image metadata');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first instance of the current study retaining a consistent result across multiple calls.
|
||||
* @return {InstanceMetadata} An instance of the InstanceMetadata class or null if it does not exist.
|
||||
@@ -625,6 +715,10 @@ const makeDisplaySet = (series, instances) => {
|
||||
|
||||
imageSet.isReconstructable = isReconstructable.value;
|
||||
|
||||
if (shallSort && imageSet.isReconstructable) {
|
||||
imageSet.sortByImagePositionPatient();
|
||||
}
|
||||
|
||||
if (isReconstructable.missingFrames) {
|
||||
// TODO -> This is currently unused, but may be used for reconstructing
|
||||
// Volumes with gaps later on.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import DICOMWeb from '../../../DICOMWeb';
|
||||
|
||||
/**
|
||||
* Function to get series sequence (sequence of pepeating items where each
|
||||
* item includes the attributes of one or more series) based on a given sopInstance.
|
||||
*
|
||||
* @param {Object} instance The sop instance
|
||||
* @returns {Promise} Referenced series sequence
|
||||
*/
|
||||
const getReferencedSeriesSequence = instance => {
|
||||
const referencedSeriesSequenceRaw = instance['00081115'];
|
||||
|
||||
const referencedSeriesSequence = [];
|
||||
|
||||
if (referencedSeriesSequenceRaw && referencedSeriesSequenceRaw.Value) {
|
||||
referencedSeriesSequenceRaw.Value.forEach(referencedSeries => {
|
||||
const referencedSeriesInstanceUID = DICOMWeb.getString(
|
||||
referencedSeries['0020000E']
|
||||
);
|
||||
|
||||
const referencedInstanceSequenceRaw = referencedSeries['0008114A'];
|
||||
const referencedInstanceSequence = [];
|
||||
|
||||
referencedInstanceSequenceRaw.Value.forEach(referencedInstance => {
|
||||
referencedInstanceSequence.push({
|
||||
referencedSOPClassUID: DICOMWeb.getString(
|
||||
referencedInstance['00081150']
|
||||
),
|
||||
referencedSOPInstanceUID: DICOMWeb.getString(
|
||||
referencedInstance['00081155']
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
referencedSeriesSequence.push({
|
||||
referencedSeriesInstanceUID,
|
||||
referencedInstanceSequence,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return referencedSeriesSequence;
|
||||
};
|
||||
|
||||
export default getReferencedSeriesSequence;
|
||||
@@ -2,6 +2,7 @@ import DICOMWeb from '../../../DICOMWeb';
|
||||
import metadataProvider from '../../../classes/MetadataProvider';
|
||||
import getWADORSImageId from '../../../utils/getWADORSImageId';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import getReferencedSeriesSequence from './getReferencedSeriesSequence';
|
||||
|
||||
/**
|
||||
* Create a plain JS object that describes a study (a study descriptor object)
|
||||
@@ -23,7 +24,10 @@ function createStudy(server, aSopInstance) {
|
||||
PatientSize: DICOMWeb.getNumber(aSopInstance['00101020']),
|
||||
PatientWeight: DICOMWeb.getNumber(aSopInstance['00101030']),
|
||||
AccessionNumber: DICOMWeb.getString(aSopInstance['00080050']),
|
||||
StudyTime: DICOMWeb.getString(aSopInstance['00080030']),
|
||||
StudyDate: DICOMWeb.getString(aSopInstance['00080020']),
|
||||
FrameOfReferenceUID: DICOMWeb.getString(aSopInstance['00200052']),
|
||||
ReferencedSeriesSequence: getReferencedSeriesSequence(aSopInstance),
|
||||
modalities: DICOMWeb.getString(aSopInstance['00080061']), // TODO -> Rename this.. it'll take a while to not mess this one up.
|
||||
StudyDescription: DICOMWeb.getString(aSopInstance['00081030']),
|
||||
NumberOfStudyRelatedInstances: DICOMWeb.getString(aSopInstance['00201208']),
|
||||
@@ -186,7 +190,7 @@ async function makeSOPInstance(server, study, instance) {
|
||||
*/
|
||||
async function addInstancesToStudy(server, study, sopInstanceList) {
|
||||
return Promise.all(
|
||||
sopInstanceList.map(function(sopInstance) {
|
||||
sopInstanceList.map(function (sopInstance) {
|
||||
return makeSOPInstance(server, study, sopInstance);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import studyMetadataManager from './studyMetadataManager';
|
||||
import writeScript from './writeScript.js';
|
||||
import DicomLoaderService from './dicomLoaderService.js';
|
||||
import b64toBlob from './b64toBlob.js';
|
||||
import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js';
|
||||
import * as urlUtil from './urlUtil';
|
||||
import makeCancelable from './makeCancelable';
|
||||
import hotkeys from './hotkeys';
|
||||
@@ -24,6 +25,7 @@ const utils = {
|
||||
studyMetadataManager,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
loadAndCacheDerivedDisplaySets,
|
||||
makeCancelable,
|
||||
hotkeys,
|
||||
};
|
||||
@@ -40,6 +42,7 @@ export {
|
||||
studyMetadataManager,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
loadAndCacheDerivedDisplaySets,
|
||||
makeCancelable,
|
||||
hotkeys,
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('Top level exports', () => {
|
||||
'b64toBlob',
|
||||
'StackManager',
|
||||
'studyMetadataManager',
|
||||
'loadAndCacheDerivedDisplaySets',
|
||||
'DicomLoaderService',
|
||||
'urlUtil',
|
||||
'makeCancelable',
|
||||
|
||||
@@ -167,8 +167,8 @@ function _getSpacingIssue(spacing, averageSpacing) {
|
||||
function _getPerpendicularDistance(a, b) {
|
||||
return Math.sqrt(
|
||||
Math.pow(a[0] - b[0], 2) +
|
||||
Math.pow(a[1] - b[1], 2) +
|
||||
Math.pow(a[2] - b[2], 2)
|
||||
Math.pow(a[1] - b[1], 2) +
|
||||
Math.pow(a[2] - b[2], 2)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import studyMetadataManager from './studyMetadataManager';
|
||||
|
||||
/**
|
||||
* Study schema
|
||||
*
|
||||
* @typedef {Object} Study
|
||||
* @property {Array} seriesList -
|
||||
* @property {Object} seriesMap -
|
||||
* @property {Object} seriesLoader -
|
||||
* @property {string} wadoUriRoot -
|
||||
* @property {string} wadoRoot -
|
||||
* @property {string} qidoRoot -
|
||||
* @property {string} patientName -
|
||||
* @property {string} patientId -
|
||||
* @property {number} patientAge -
|
||||
* @property {number} patientSize -
|
||||
* @property {number} patientWeight -
|
||||
* @property {string} accessionNumber -
|
||||
* @property {string} studyDate -
|
||||
* @property {string} studyTime -
|
||||
* @property {string} modalities -
|
||||
* @property {string} studyDescription -
|
||||
* @property {string} imageCount -
|
||||
* @property {string} studyInstanceUid -
|
||||
* @property {string} institutionName -
|
||||
* @property {Array} displaySets -
|
||||
*/
|
||||
|
||||
/**
|
||||
* Factory function to load and cache derived display sets.
|
||||
*
|
||||
* @param {object} referencedDisplaySet Display set
|
||||
* @param {string} referencedDisplaySet.displaySetInstanceUid Display set instance uid
|
||||
* @param {string} referencedDisplaySet.seriesDate
|
||||
* @param {string} referencedDisplaySet.seriesTime
|
||||
* @param {string} referencedDisplaySet.seriesInstanceUid
|
||||
* @param {string} referencedDisplaySet.seriesNumber
|
||||
* @param {string} referencedDisplaySet.seriesDescription
|
||||
* @param {number} referencedDisplaySet.numImageFrames
|
||||
* @param {string} referencedDisplaySet.frameRate
|
||||
* @param {string} referencedDisplaySet.modality
|
||||
* @param {boolean} referencedDisplaySet.isMultiFrame
|
||||
* @param {number} referencedDisplaySet.instanceNumber
|
||||
* @param {boolean} referencedDisplaySet.isReconstructable
|
||||
* @param {string} referencedDisplaySet.studyInstanceUid
|
||||
* @param {Array} referencedDisplaySet.sopClassUids
|
||||
* @param {Study[]} studies Collection of studies
|
||||
* @returns void
|
||||
*/
|
||||
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
|
||||
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
|
||||
if (!studyMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: SeriesInstanceUID,
|
||||
});
|
||||
|
||||
if (!derivedDisplaySets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter by type
|
||||
const displaySetsPerModality = {};
|
||||
|
||||
derivedDisplaySets.forEach(displaySet => {
|
||||
const Modality = displaySet.Modality;
|
||||
|
||||
if (displaySetsPerModality[Modality] === undefined) {
|
||||
displaySetsPerModality[Modality] = [];
|
||||
}
|
||||
|
||||
displaySetsPerModality[Modality].push(displaySet);
|
||||
});
|
||||
|
||||
// For each type, see if any are loaded, if not load the most recent.
|
||||
Object.keys(displaySetsPerModality).forEach(key => {
|
||||
const displaySets = displaySetsPerModality[key];
|
||||
const isLoaded = displaySets.some(displaySet => displaySet.isLoaded);
|
||||
|
||||
if (isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find most recent and load it.
|
||||
let recentDateTime = 0;
|
||||
let recentDisplaySet;
|
||||
|
||||
displaySets.forEach(displaySet => {
|
||||
const dateTime = Number(
|
||||
`${displaySet.SeriesDate}${displaySet.SeriesTime}`
|
||||
);
|
||||
if (dateTime > recentDateTime) {
|
||||
recentDateTime = dateTime;
|
||||
recentDisplaySet = displaySet;
|
||||
}
|
||||
});
|
||||
|
||||
recentDisplaySet.load(referencedDisplaySet, studies);
|
||||
});
|
||||
};
|
||||
|
||||
export default loadAndCacheDerivedDisplaySets;
|
||||
@@ -0,0 +1,3 @@
|
||||
const reconstructableModalities = ['MR', 'CT', 'PT', 'NM'];
|
||||
|
||||
export default reconstructableModalities;
|
||||
Reference in new issue
Block a user