[WIP] Moving new Hanging-Protocol bootstrap from Nucleus to OHIF Viewers
This commit is contained in:
parent
a159c105b0
commit
7fa581e52b
@ -5,7 +5,6 @@ import { Random } from 'meteor/random';
|
||||
import { $ } from 'meteor/jquery';
|
||||
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
import 'meteor/ohif:viewerbase';
|
||||
|
||||
/**
|
||||
@ -373,7 +372,7 @@ Template.protocolEditor.events({
|
||||
text: 'Are you sure you would like to remove this Protocol? This cannot be reversed.'
|
||||
};
|
||||
|
||||
Viewerbase.dialogUtils.showConfirmDialog(() => {
|
||||
OHIF.viewerbase.showConfirmDialog(() => {
|
||||
// Remove the Protocol
|
||||
HP.ProtocolStore.removeProtocol(selectedProtocol.id);
|
||||
|
||||
|
||||
@ -165,9 +165,22 @@ var sortByScore = function(arr) {
|
||||
};
|
||||
|
||||
HP.ProtocolEngine = class ProtocolEngine {
|
||||
constructor(LayoutManager, studies) {
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} LayoutManager Layout Manager Object
|
||||
* @param {Array} studies Array of study metadata
|
||||
* @param {Array} relatedStudies Array of related studies
|
||||
* @param {Object} studyMedadataSource Instance of StudyMetadataSource (ohif-viewerbase) Object to get study metadata
|
||||
*/
|
||||
constructor(LayoutManager, studies, relatedStudies, studyMetadataSource) {
|
||||
if (!(studyMetadataSource instanceof OHIF.viewerbase.StudyMetadataSource)) {
|
||||
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor studyMetadataSource is not an instance of StudyMetadataSource');
|
||||
}
|
||||
|
||||
this.LayoutManager = LayoutManager;
|
||||
this.studies = studies;
|
||||
this.relatedStudies = relatedStudies;
|
||||
this.studyMetadataSource = studyMetadataSource;
|
||||
|
||||
this.reset();
|
||||
|
||||
@ -252,7 +265,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
matched.forEach(function(matchedDetail) {
|
||||
var protocol = matchedDetail.protocol;
|
||||
if (!protocol) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
var protocolInCollection = MatchedProtocols.findOne({
|
||||
@ -352,6 +365,11 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
});
|
||||
}
|
||||
|
||||
getRelatedStudies(options, sorting) {
|
||||
HP.setGetRelatedStudies = getNucleusRelatedStudies();
|
||||
HP.getRelatedStudies(options, sorting);
|
||||
}
|
||||
|
||||
// Match images given a list of Studies and a Viewport's image matching reqs
|
||||
matchImages(viewport) {
|
||||
var studyMatchingRules = viewport.studyMatchingRules;
|
||||
@ -375,6 +393,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
abstractPriorValue = parseInt(abstractPriorValue, 10);
|
||||
// TODO: Restrict or clarify validators for abstractPriorValue?
|
||||
|
||||
// @TODO replace for this.relatedStudies (TypeSafeCollection of StudyMetadatas)
|
||||
var studies = StudyListStudies.find({
|
||||
patientId: currentStudy.patientId,
|
||||
studyDate: {
|
||||
@ -407,9 +426,11 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
});
|
||||
|
||||
if (!alreadyLoaded) {
|
||||
OHIF.studylist.retrieveStudyMetadata(priorStudy.studyInstanceUid).then(study => {
|
||||
this.studyMetadataSource.getByInstanceUID(priorStudy.studyInstanceUid, study => {
|
||||
study.abstractPriorValue = abstractPriorValue;
|
||||
study.displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(study);
|
||||
OHIF.viewer.Studies.insert(study);
|
||||
|
||||
this.studies.push(study);
|
||||
this.matchImages(viewport);
|
||||
this.updateViewports();
|
||||
@ -428,8 +449,8 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
|
||||
highestStudyMatchingScore = studyMatchDetails.score;
|
||||
|
||||
study.seriesList.forEach(function(series) {
|
||||
var seriesMatchDetails = HP.match(series, seriesMatchingRules);
|
||||
study.forEachSeries(function(series) {
|
||||
const seriesMatchDetails = HP.match(series, seriesMatchingRules);
|
||||
if ((seriesMatchingRules.length && !seriesMatchDetails.score) ||
|
||||
seriesMatchDetails.score < highestSeriesMatchingScore) {
|
||||
return;
|
||||
@ -437,11 +458,13 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
|
||||
highestSeriesMatchingScore = seriesMatchDetails.score;
|
||||
|
||||
series.instances.forEach(function(instance, index) {
|
||||
series.forEachInstance(function(instance, index) {
|
||||
// This tests to make sure there is actually image data in this instance
|
||||
// TODO: Change this when we add PDF and MPEG support
|
||||
// See https://ohiforg.atlassian.net/browse/LT-227
|
||||
if (!OHIF.viewerbase.isImage(instance.sopClassUid) && !instance.rows) {
|
||||
// sopClassUid = x00080016
|
||||
// rows = x00280010
|
||||
if (!OHIF.viewerbase.isImage(instance.getRawValue('x00080016')) && !instance.getRawValue('x00280010')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -463,34 +486,34 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
const totalMatchScore = instanceMatchDetails.score + seriesMatchDetails.score + studyMatchDetails.score;
|
||||
|
||||
const imageDetails = {
|
||||
studyInstanceUid: study.studyInstanceUid,
|
||||
seriesInstanceUid: series.seriesInstanceUid,
|
||||
sopInstanceUid: instance.sopInstanceUid,
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
currentImageIdIndex: index,
|
||||
matchingScore: totalMatchScore,
|
||||
matchDetails: matchDetails,
|
||||
sortingInfo: {
|
||||
score: totalMatchScore,
|
||||
study: study.studyDate + study.studyTime,
|
||||
series: parseInt(series.seriesNumber), // TODO: change for seriesDateTime
|
||||
instance: parseInt(instance.instanceNumber) // TODO: change for acquisitionTime
|
||||
study: instance.getRawValue('x00080020') + instance.getRawValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030
|
||||
series: parseInt(instance.getRawValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011
|
||||
instance: parseInt(instance.getRawValue('x00200013')) // TODO: change for acquisitionTime InstanceNumber = x00200013
|
||||
}
|
||||
};
|
||||
|
||||
// Filter imageSet function: filter by InstanceUid
|
||||
const filterImageFn = (image) => {
|
||||
return image.getData().sopInstanceUid === instance.sopInstanceUid;
|
||||
const filterImageSetFn = (imageSet, sopInstanceUid) => {
|
||||
// @TODO: remove getData() here
|
||||
const found = imageSet.getData().sopInstanceUid === instance.sopInstanceUid;
|
||||
return found;
|
||||
};
|
||||
|
||||
// Find the displaySet
|
||||
const displaySet = study.displaySets.find(ds => {
|
||||
return ds.images.filter(filterImageFn).length > 0;
|
||||
});
|
||||
const displaySet = study.getDisplaySets().find(ds => ds.images.filter(imageSet => filterImageSetFn));
|
||||
|
||||
// If the instance was found, set the displaySet ID
|
||||
if (displaySet) {
|
||||
imageDetails.displaySetInstanceUid = displaySet.displaySetInstanceUid;
|
||||
imageDetails.imageId = OHIF.viewerbase.getImageId(instance);
|
||||
imageDetails.imageId = instance.getImageId();
|
||||
}
|
||||
|
||||
if ((totalMatchScore > highestImageMatchingScore) || !bestMatch) {
|
||||
|
||||
@ -8,5 +8,11 @@ const Studies = new TypeSafeCollection();
|
||||
// Make it publicly available on "OHIF.viewer" namespace...
|
||||
OHIF.viewer.Studies = Studies;
|
||||
|
||||
// Create main StudyMetadataList collection which will be used across the entire viewer...
|
||||
const StudyMetadataList = new TypeSafeCollection();
|
||||
|
||||
// Make it publicly available on "OHIF.viewer" namespace...
|
||||
OHIF.viewer.StudyMetadataList = StudyMetadataList;
|
||||
|
||||
// Subscriptions...
|
||||
Meteor.subscribe('studyImportStatus');
|
||||
|
||||
@ -28,85 +28,93 @@ Template.viewerMain.onCreated(() => {
|
||||
|
||||
Template.viewerMain.onRendered(() => {
|
||||
const instance = Template.instance();
|
||||
const { studies } = instance.data;
|
||||
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
||||
|
||||
HP.ProtocolStore.onReady(() => {
|
||||
const { studies, currentTimepointId, measurementApi, timepointIds } = instance.data;
|
||||
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
||||
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
||||
|
||||
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
||||
// @TODO: move this logic to the viewers to break dependency of ohif-hanging-protocols package and avoid circurlar dependencies
|
||||
// HP.ProtocolStore.onReady(() => {
|
||||
// const { studies, currentTimepointId, measurementApi, timepointIds } = instance.data;
|
||||
|
||||
// Default actions for Associated Studies
|
||||
if(currentTimepointId) {
|
||||
// Follow-up studies: same as the first measurement in the table
|
||||
// Baseline studies: target-tool
|
||||
if(studies[0]) {
|
||||
let activeTool;
|
||||
// In follow-ups, get the baseline timepointId
|
||||
const timepointId = timepointIds.find(id => id !== currentTimepointId);
|
||||
// // Default actions for Associated Studies
|
||||
// if(currentTimepointId) {
|
||||
// // Follow-up studies: same as the first measurement in the table
|
||||
// // Baseline studies: target-tool
|
||||
// if(studies[0]) {
|
||||
// let activeTool;
|
||||
// // In follow-ups, get the baseline timepointId
|
||||
// const timepointId = timepointIds.find(id => id !== currentTimepointId);
|
||||
|
||||
// Follow-up studies
|
||||
if(studies[0].timepointType === 'followup' && timepointId) {
|
||||
const measurementTools = OHIF.measurements.MeasurementApi.getConfiguration().measurementTools;
|
||||
// // Follow-up studies
|
||||
// if(studies[0].timepointType === 'followup' && timepointId) {
|
||||
// const measurementTools = OHIF.measurements.MeasurementApi.getConfiguration().measurementTools;
|
||||
|
||||
// Create list of measurement tools
|
||||
const measurementTypes = measurementTools.map(
|
||||
tool => {
|
||||
const { id, cornerstoneToolType } = tool;
|
||||
return {
|
||||
id,
|
||||
cornerstoneToolType
|
||||
}
|
||||
}
|
||||
);
|
||||
// // Create list of measurement tools
|
||||
// const measurementTypes = measurementTools.map(
|
||||
// tool => {
|
||||
// const { id, cornerstoneToolType } = tool;
|
||||
// return {
|
||||
// id,
|
||||
// cornerstoneToolType
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
// Iterate over each measurment tool to find the first baseline
|
||||
// measurment. If so, stops the loop and prevent fetching from all
|
||||
// collections
|
||||
measurementTypes.every(({id, cornerstoneToolType}) => {
|
||||
// Get measurement
|
||||
if(measurementApi[id]) {
|
||||
const measurement = measurementApi[id].findOne({ timepointId });
|
||||
// // Iterate over each measurment tool to find the first baseline
|
||||
// // measurment. If so, stops the loop and prevent fetching from all
|
||||
// // collections
|
||||
// measurementTypes.every(({id, cornerstoneToolType}) => {
|
||||
// // Get measurement
|
||||
// if(measurementApi[id]) {
|
||||
// const measurement = measurementApi[id].findOne({ timepointId });
|
||||
|
||||
// Found a measurement, save tool and stop loop
|
||||
if(measurement) {
|
||||
activeTool = cornerstoneToolType;
|
||||
// // Found a measurement, save tool and stop loop
|
||||
// if(measurement) {
|
||||
// activeTool = cornerstoneToolType;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// });
|
||||
// }
|
||||
|
||||
// If not set, for associated studies default is target-tool
|
||||
toolManager.setActiveTool(activeTool || 'bidirectional');
|
||||
}
|
||||
// // If not set, for associated studies default is target-tool
|
||||
// toolManager.setActiveTool(activeTool || 'bidirectional');
|
||||
// }
|
||||
|
||||
// Toggle Measurement Table
|
||||
if(instance.data.state) {
|
||||
instance.data.state.set('rightSidebar', 'measurements');
|
||||
}
|
||||
}
|
||||
// Hide as default for single study
|
||||
else {
|
||||
if(instance.data.state) {
|
||||
instance.data.state.set('rightSidebar', null);
|
||||
}
|
||||
}
|
||||
// // Toggle Measurement Table
|
||||
// if(instance.data.state) {
|
||||
// instance.data.state.set('rightSidebar', 'measurements');
|
||||
// }
|
||||
// }
|
||||
// // Hide as default for single study
|
||||
// else {
|
||||
// if(instance.data.state) {
|
||||
// instance.data.state.set('rightSidebar', null);
|
||||
// }
|
||||
// }
|
||||
|
||||
if (OHIF.viewer.isProtocolEngineDisabled === true) {
|
||||
OHIF.viewerbase.layoutManager.updateViewports();
|
||||
} else {
|
||||
// updateViewports method from current layout manager will be automatically called by the protocol engine;
|
||||
ProtocolEngine = new HP.ProtocolEngine(OHIF.viewerbase.layoutManager, studies);
|
||||
HP.setEngine(ProtocolEngine);
|
||||
}
|
||||
// if (OHIF.viewer.isProtocolEngineDisabled === true) {
|
||||
// OHIF.viewerbase.layoutManager.updateViewports();
|
||||
// } else {
|
||||
// // updateViewports method from current layout manager will be automatically called by the protocol engine;
|
||||
// const studyMetadataList = OHIF.viewer.StudyMetadataList.all();
|
||||
// const layoutManager = OHIF.viewerbase.layoutManager;
|
||||
|
||||
// const studyMetadataSource = new NucleusStudyMetadataSource;
|
||||
// ProtocolEngine = new HP.ProtocolEngine(layoutManager, studyMetadataList, [], studyMetadataSource);
|
||||
// HP.setEngine(ProtocolEngine);
|
||||
// }
|
||||
|
||||
// Enable hotkeys
|
||||
hotkeyUtils.enableHotkeys();
|
||||
// Session.set('ViewerMainReady', Random.id());
|
||||
// });
|
||||
|
||||
Session.set('ViewerMainReady', Random.id());
|
||||
});
|
||||
// Enable hotkeys
|
||||
hotkeyUtils.enableHotkeys();
|
||||
|
||||
Session.set('ViewerMainRendered', Random.id());
|
||||
});
|
||||
|
||||
Template.viewerMain.onDestroyed(() => {
|
||||
|
||||
@ -176,10 +176,6 @@ Viewerbase.dicomTagDescriptions = dicomTagDescriptions;
|
||||
import { ImageSet } from './lib/classes/ImageSet';
|
||||
Viewerbase.ImageSet = ImageSet;
|
||||
|
||||
// LayoutManager
|
||||
import { LayoutManager } from './lib/classes/LayoutManager';
|
||||
Viewerbase.LayoutManager = LayoutManager;
|
||||
|
||||
// ResizeViewportManager
|
||||
import { ResizeViewportManager } from './lib/classes/ResizeViewportManager';
|
||||
Viewerbase.ResizeViewportManager = ResizeViewportManager;
|
||||
@ -201,3 +197,7 @@ Viewerbase.OHIFError = OHIFError;
|
||||
// StackImagePositionOffsetSynchronizer
|
||||
import { StackImagePositionOffsetSynchronizer } from './lib/classes/StackImagePositionOffsetSynchronizer';
|
||||
Viewerbase.StackImagePositionOffsetSynchronizer = StackImagePositionOffsetSynchronizer;
|
||||
|
||||
// StudyMetadataSource
|
||||
import { StudyMetadataSource } from './lib/classes/StudyMetadataSource';
|
||||
Viewerbase.StudyMetadataSource = StudyMetadataSource;
|
||||
|
||||
@ -50,7 +50,9 @@ const changeTextCallback = (data, eventData, doneChangingTextCallback) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Viewerbase.helpers.isTouchDevice()) {
|
||||
// Is necessary to use Blaze object to not create
|
||||
// circular depencency with helper object (./helpers)
|
||||
if (Blaze._globalHelpers.isTouchDevice()) {
|
||||
// Center the dialog on screen on touch devices
|
||||
dialog.css({
|
||||
top: 0,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Table of contents
|
||||
In this document, some important objects are described. In the files there are comments that can help better undestand methods and properties.
|
||||
In this document, some important objects are described. In the files there are comments that can help better undestand their methods and properties.
|
||||
- [ResizeViewportManager object](#the-resize-viewport-manager-object)
|
||||
- [ImageSet object](#the-image-set-object)
|
||||
- [Layout Manager](#the-layout-manager-object)
|
||||
@ -14,7 +14,7 @@ It's only necessary to bind **handleResize** function to the window resize event
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
const ResizeViewportManager = new Viewerbase.ResizeViewportManager();
|
||||
window.addEventListener('resize', ResizeViewportManager.handleResize.bind(ResizeViewportManager));
|
||||
window.addEventListener('resize', ResizeViewportManager.getResizeHandler());
|
||||
```
|
||||
An example os its usage can be found in **ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js**.
|
||||
|
||||
@ -104,25 +104,64 @@ Each of this _div.viewportContainer_ will have some classes to help CSS specific
|
||||
|
||||
With the introduction of the new _Study Metadata API_ in which study metadata is represented by class hierarchies (using prototype-based inheritance), the usage of standard _Minimongo_ collections as a central client-side storage for this data became no longer an option. Standard _Mongo_ and _Minimongo_ collections internally _flatten_ data (in other words, data gets serialized) before storage hence no functions or prototype chains are preserved. In that scenario, when an object is restored (fetched), what is returned is actually a flattened copy of the original object with no functions or prototype (it's no longer an instance of it's original class). As an attempt to overcome this limitation a new type of collection was intruduced: the *TypeSafeCollection*.
|
||||
|
||||
The _TypeSafeCollection_ is a simple list-like collection which tries to implement an API _similar_ but not compatible with _Mongo_'s API. It supports basic features like search by attribute map and ID, retrieval by index, sorting of result sets, insertion, removal and reactive operations but, unlike _Mongo_'s API, it (still) lacks support to advanced functionality like complex search criterea or flexible sorting options.
|
||||
The `TypeSafeCollection` is a simple list-like collection which tries to implement an API _similar_ but not compatible with _Mongo_'s API. It supports basic features like search by attribute map and ID, retrieval by index, sorting of result sets, insertion, removal and reactive operations but, unlike _Mongo_'s API, it (still) lacks support to advanced functionality like complex search criterea or flexible sorting options.
|
||||
|
||||
## Implementation
|
||||
|
||||
The `TypeSafeCollection` is implemented on top of the _JavaScript_ `Array` object. Each element inserted in the collection is appended to the end of its internal array as a _key-value pair (KVP)_ object where the _key_ is a unique randomly generated ID string and the _value_ is the element itself. Once the object has been successfully stored, the generated ID (its ID) is returned to the client code and can later be used to access that specific element. At this point, an important difference to the _Minimongo_ API can be highlighted: a _TypeSafeCollection_ instance will never make any changes to the stored element (e.g., no "\_id" property will ever be assigned to the original object). Another relevant feature that is supported by this design decision is that _not only objects_ can be stored in this collections, but literally _anything_.
|
||||
|
||||
Inside the codebase, the _value_ attribute of each _KVP_ entry in the collection is refered to as _the **payload** of the entry_ since it's what really matters to the user. Hence, this term will also be used here to refer to the _value that has been stored in the collection_. That being said, we can approach another important feature of these collections: A single _payload_ cannot be stored more than once in a given collection. When an attempt of inserting a _payload_ which is already present in the collection is detected, the insert operation will fail and `null` will be returned. In that regard, the collection behaves like `Set` object not permitting a payload to be stored more than once. Strict equality is used when comparing payloads, thus cloned objects are not considered the same. This feature adds an additional garantee that a given study/series/instance will not be listed more than once (it was designed as a replacement for central study collections which were always checked for duplicates).
|
||||
|
||||
Please refer to the codebase for the full `TypeSafeCollection` API.
|
||||
|
||||
## Usage
|
||||
|
||||
In order to use _TypeSafeCollection_ the **ohif:viewerbase** package needs to be imported by the referring code and instantiated as follows.
|
||||
|
||||
Basic usage:
|
||||
In order to use the `TypeSafeCollection` class, the **ohif:viewerbase** package needs to be imported by the referring code and instantiated as follows:
|
||||
|
||||
```javascript
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase'; // i.e., Viewerbase.TypeSafeCollection
|
||||
OR
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import 'meteor/ohif:viewerbase'; // i.e., OHIF.viewerbase.TypeSafeCollection
|
||||
// The later is preferred when the client code already makes use of the "OHIF" namespace making the second
|
||||
// "import" a garantee that the ".viewerbase" namespace has been properly loaded.
|
||||
```
|
||||
|
||||
const users = new Viewerbase.TypeSafeCollection();
|
||||
let id = users.insert({
|
||||
A few usage examples:
|
||||
|
||||
```javascript
|
||||
|
||||
const Users = new OHIF.viewerbase.TypeSafeCollection();
|
||||
|
||||
[[ ... ]]
|
||||
|
||||
// Insert a User object...
|
||||
let userId = Users.insert({
|
||||
data: {
|
||||
name: 'John Doe',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
age: 45
|
||||
},
|
||||
getName() {
|
||||
return this.data.name;
|
||||
getFullName() {
|
||||
return `${this.data.firstName} ${this.data.lastName}`;
|
||||
},
|
||||
getAge() {
|
||||
return this.data.age;
|
||||
}
|
||||
});
|
||||
|
||||
[[ ... ]]
|
||||
|
||||
let theUserWeJustStored = Users.findById(userId); // ;-)
|
||||
|
||||
[[ ... ]]
|
||||
|
||||
// Retrieve a single user with "Doe" as `lastName`...
|
||||
let myUser = Users.findBy({ 'data.lastName': 'Doe' });
|
||||
// Or all users with "Doe" as `lastName`, sorted by `firstName` in ascending
|
||||
// order and using the `age` attribute to break ties in descending order...
|
||||
let myUsers = Users.findAllBy({ 'data.lastName': 'Doe' }, {
|
||||
sort: [ [ 'data.firstName', 'asc' ], [ 'data.age', 'desc' ] ]
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { OHIFError } from './OHIFError';
|
||||
|
||||
/**
|
||||
* Abstract class to fetch study metadata.
|
||||
*/
|
||||
export class StudyMetadataSource {
|
||||
|
||||
/**
|
||||
* Get study metadata for a given study info
|
||||
* @param {Object} studyInfo Study info object
|
||||
*/
|
||||
getByStudyInfo(studyInfo) {
|
||||
/**
|
||||
* Please override this method on a specialized class.
|
||||
*/
|
||||
throw new OHIFError('StudyMetadataSource::getByStudyInfo is not overriden. Please, override it in a specialized class. See OHIFStudyMetadataSource for example');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get study metadata for a study with given study InstanceUID
|
||||
* @param {String} studyInstanceUID Study InstanceUID
|
||||
*/
|
||||
getByInstanceUID(studyInstanceUID) {
|
||||
/**
|
||||
* Please override this method on a specialized class.
|
||||
*/
|
||||
throw new OHIFError('StudyMetadataSource::getByInstanceUID is not overriden. Please, override it in a specialized class. See OHIFStudyMetadataSource for example');
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,12 @@ export class TypeSafeCollection {
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update the payload associated with the given ID to be the new supplied payload.
|
||||
* @param {string} id The ID of the entry that will be updated.
|
||||
* @param {any} payload The element that will replace the previous payload.
|
||||
* @returns {boolean} Returns true if the given ID is present in the collection, false otherwise.
|
||||
*/
|
||||
updateById(id, payload) {
|
||||
let result = false,
|
||||
found = this._elementWithPayload(payload, true);
|
||||
@ -69,6 +75,12 @@ export class TypeSafeCollection {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that the given element has been changed by notifying reactive data-source observers.
|
||||
* This method is basically a means to invalidate the inernal reactive data-source.
|
||||
* @param {any} payload The element that has been altered.
|
||||
* @returns {boolean} Returns true if the element is present in the collection, false otherwise.
|
||||
*/
|
||||
update(payload) {
|
||||
let result = false,
|
||||
found = this._elementWithPayload(payload, true);
|
||||
@ -80,6 +92,12 @@ export class TypeSafeCollection {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an element in the collection. On success, the element ID (a unique string) is returned. On failure, returns null.
|
||||
* A failure scenario only happens when the given payload is already present in the collection. Note that NO exceptions are thrown!
|
||||
* @param {any} payload The element to be stored.
|
||||
* @returns {string} The ID of the inserted element or null if the element already exists...
|
||||
*/
|
||||
insert(payload) {
|
||||
let id = null,
|
||||
found = this._elementWithPayload(payload, true);
|
||||
@ -91,6 +109,10 @@ export class TypeSafeCollection {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all elements from the collection.
|
||||
* @returns {void} No meaningful value is returned.
|
||||
*/
|
||||
removeAll() {
|
||||
let all = this._elements(true),
|
||||
length = all.length;
|
||||
@ -104,6 +126,11 @@ export class TypeSafeCollection {
|
||||
this._invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove elements from the collection that match the criteria given in the property map.
|
||||
* @param {Object} propertyMap A property map that will be macthed against all collection elements.
|
||||
* @returns {Array} A list with all removed elements.
|
||||
*/
|
||||
remove(propertyMap) {
|
||||
let found = this.findAllEntriesBy(propertyMap),
|
||||
foundCount = found.length,
|
||||
@ -120,24 +147,49 @@ export class TypeSafeCollection {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ID of the given element inside the collection.
|
||||
* @param {any} payload The element being searched for.
|
||||
* @returns {string} The ID of the given element or undefined if the element is not present.
|
||||
*/
|
||||
getElementId(payload) {
|
||||
let found = this._elementWithPayload(payload);
|
||||
return found && found.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the position of the given element in the internal list returning -1 if the element is not present.
|
||||
* @param {any} payload The element being searched for.
|
||||
* @returns {number} The position of the given element in the internal list. If the element is not present -1 is returned.
|
||||
*/
|
||||
findById(id) {
|
||||
let found = this._elementWithId(id);
|
||||
return found && found.payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the position of the given element in the internal list returning -1 if the element is not present.
|
||||
* @param {any} payload The element being searched for.
|
||||
* @returns {number} The position of the given element in the internal list. If the element is not present -1 is returned.
|
||||
*/
|
||||
indexOfElement(payload) {
|
||||
return this._elements().indexOf(this._elementWithPayload(payload, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the position of the element associated with the given ID in the internal list returning -1 if the element is not present.
|
||||
* @param {string} id The index of the element.
|
||||
* @returns {number} The position of the element associated with the given ID in the internal list. If the element is not present -1 is returned.
|
||||
*/
|
||||
indexOfId(id) {
|
||||
return this._elements().indexOf(this._elementWithId(id, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list-like approach to the collection returning an element by index.
|
||||
* @param {number} index The index of the element.
|
||||
* @returns {any} If out of bounds, undefined is returned. Otherwise the element in the given position is returned.
|
||||
*/
|
||||
getElementByIndex(index) {
|
||||
let found = ((this._elements())[index >= 0 ? index : -1]);
|
||||
return found && found.payload;
|
||||
@ -149,7 +201,7 @@ export class TypeSafeCollection {
|
||||
* @param {function} callback A callback function which will define the search criteria. The callback
|
||||
* function will be passed the collection element, its ID and its index in this very order. The callback
|
||||
* shall return true when its criterea has been fulfilled.
|
||||
* @returns {Any} The matched element or undefined if not match was found.
|
||||
* @returns {any} The matched element or undefined if not match was found.
|
||||
*/
|
||||
find(callback) {
|
||||
let found;
|
||||
@ -334,7 +386,7 @@ function _getPropertyValue(targetObject, propertyName) {
|
||||
* @param {Object} propertyMap The property map whose properties will be used for comparison. Composite
|
||||
* property names (e.g., 'address.country.name') will be tested against the "resolved" properties from the target object.
|
||||
* @param {Object} targetObject The target object whose properties will be tested.
|
||||
* @returns {boolean} Returns true if the properties match, false otherwhise.
|
||||
* @returns {boolean} Returns true if the properties match, false otherwise.
|
||||
*/
|
||||
function _compareToPropertyMapStrict(propertyMap, targetObject) {
|
||||
let result = false;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Metadata } from './Metadata';
|
||||
import { dicomTagDescriptions } from '../../dicomTagDescriptions';
|
||||
import { OHIFError } from '../OHIFError';
|
||||
|
||||
const UNDEFINED = 'undefined';
|
||||
const NUMBER = 'number';
|
||||
@ -11,8 +12,43 @@ export class InstanceMetadata extends Metadata {
|
||||
constructor(data) {
|
||||
super(data);
|
||||
this._sopInstanceUID = null;
|
||||
this._imageId = null;
|
||||
// Initialize Public Properties
|
||||
this._definePublicProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define Public Properties
|
||||
* This method should only be called during initialization (inside the class constructor)
|
||||
*/
|
||||
_definePublicProperties() {
|
||||
|
||||
/**
|
||||
* Property: this.sopInstanceUID
|
||||
* Same as this.getSOPInstanceUID()
|
||||
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
||||
* sopInstanceCollection.findBy({
|
||||
* sopInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* });
|
||||
*/
|
||||
Object.defineProperty(this, 'sopInstanceUID', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
return this.getSOPInstanceUID();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the SOPInstanceUID of the current instance.
|
||||
*/
|
||||
@ -70,6 +106,7 @@ export class InstanceMetadata extends Metadata {
|
||||
/**
|
||||
* Please override this method on a specialized class.
|
||||
*/
|
||||
throw new OHIFError('InstanceMetadata::getRawValue is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,22 +134,18 @@ export class InstanceMetadata extends Metadata {
|
||||
/**
|
||||
* Please override this method
|
||||
*/
|
||||
throw new OHIFError('InstanceMetadata::tagExists is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URI for the specified resource related to this instance (if an instance).
|
||||
* @param {string} resource The name of the resource. This name cannot have any leading or
|
||||
* trailing slashes
|
||||
* @param {string|number} [optParameter] An optional parameter. This cannot have any leading or
|
||||
* trailing slashes
|
||||
* @returns {string|undefined} The constructed URI for the specified resource related to this
|
||||
* instance or undefined if this sequence is a sub-sequence (i.e.: it has no related resources of
|
||||
* its own).
|
||||
*/
|
||||
getResourceUri(resource, optParameter) {
|
||||
* Get custom image id of a sop instance
|
||||
* @return {Any} sop instance image id
|
||||
*/
|
||||
getImageId() {
|
||||
/**
|
||||
* Please override this method
|
||||
*/
|
||||
throw new OHIFError('InstanceMetadata::getImageId is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,8 +7,42 @@ export class SeriesMetadata extends Metadata {
|
||||
super(data);
|
||||
this._seriesInstanceUID = null;
|
||||
this._instances = []; // InstanceMetadata[]
|
||||
// Initialize Public Properties
|
||||
this._definePublicProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define Public Properties
|
||||
* This method should only be called during initialization (inside the class constructor)
|
||||
*/
|
||||
_definePublicProperties() {
|
||||
|
||||
/**
|
||||
* Property: this.seriesInstanceUID
|
||||
* Same as this.getSeriesInstanceUID()
|
||||
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
||||
* seriesCollection.findBy({
|
||||
* seriesInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* });
|
||||
*/
|
||||
Object.defineProperty(this, 'seriesInstanceUID', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
return this.getSeriesInstanceUID();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the SeriesInstanceUID of the current series.
|
||||
*/
|
||||
|
||||
@ -1,12 +1,92 @@
|
||||
import { Metadata } from './Metadata';
|
||||
import { SeriesMetadata } from './SeriesMetadata';
|
||||
import { ImageSet } from '../ImageSet';
|
||||
|
||||
export class StudyMetadata extends Metadata {
|
||||
|
||||
constructor(data) {
|
||||
super(data);
|
||||
this._studyInstanceUID = null;
|
||||
this._series = []; // SeriesMetadata[]
|
||||
this._displaySets = [];
|
||||
// Initialize Public Properties
|
||||
this._definePublicProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define Public Properties
|
||||
* This method should only be called during initialization (inside the class constructor)
|
||||
*/
|
||||
_definePublicProperties() {
|
||||
|
||||
/**
|
||||
* Property: this.studyInstanceUID
|
||||
* Same as this.getStudyInstanceUID()
|
||||
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
||||
* studyCollection.findBy({
|
||||
* studyInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* });
|
||||
*/
|
||||
Object.defineProperty(this, 'studyInstanceUID', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
return this.getStudyInstanceUID();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Getter for displaySets
|
||||
* @return {Array} Array of display set object
|
||||
*/
|
||||
getDisplaySets() {
|
||||
return this._displaySets.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set display sets
|
||||
* @param {Array} displaySets Array of display sets (ImageSet[])
|
||||
*/
|
||||
setDisplaySets(displaySets) {
|
||||
displaySets.forEach(displaySet => this.addDisplaySet(displaySet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single display set to the list
|
||||
* @param {Object} displaySet Display set object
|
||||
* @returns {boolean} True on success, false on failure.
|
||||
*/
|
||||
addDisplaySet(displaySet) {
|
||||
if (displaySet instanceof ImageSet) {
|
||||
this._displaySets.push(displaySet);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the supplied callback for each display set in the current study passing
|
||||
* two arguments: display set (a ImageSet instance) and index (the integer
|
||||
* index of the display set within the current study)
|
||||
* @param {function} callback The callback function which will be invoked for each display set instance.
|
||||
* @returns {undefined} Nothing is returned.
|
||||
*/
|
||||
forEachDisplaySet(callback) {
|
||||
if (Metadata.isValidCallback(callback)) {
|
||||
this._displaySets.forEach((displaySet, index) => {
|
||||
callback.call(null, displaySet, index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user