src_app_dataController.js
import {ListenerHandler} from '../utils/listen';
import {mergeObjects} from '../utils/operator';
// doc imports
/* eslint-disable no-unused-vars */
import {Image} from '../image/image';
import {AnnotationGroup} from '../image/annotationGroup';
/* eslint-enable no-unused-vars */
/**
* DICOM data: meta and possible image.
*/
export class DicomData {
/**
* DICOM meta data.
*
* @type {object}
*/
meta;
/**
* Image extracted from meta data.
*
* @type {Image|undefined}
*/
image;
/**
* Annotattion group extracted from meta data.
*
* @type {AnnotationGroup|undefined}
*/
annotationGroup;
/**
* @param {object} meta The DICOM meta data.
*/
constructor(meta) {
this.meta = meta;
}
}
/*
* DicomData controller.
*/
export class DataController {
/**
* List of DICOM data.
*
* @type {Object<string, DicomData>}
*/
#dataList = {};
/**
* Distinct data loaded counter.
*
* @type {number}
*/
#dataIdCounter = -1;
/**
* Listener handler.
*
* @type {ListenerHandler}
*/
#listenerHandler = new ListenerHandler();
/**
* Get the next data id.
*
* @returns {string} The data id.
*/
getNextDataId() {
++this.#dataIdCounter;
return this.#dataIdCounter.toString();
}
/**
* Get the list of ids in the data storage.
*
* @returns {string[]} The list of data ids.
*/
getDataIds() {
return Object.keys(this.#dataList);
}
/**
* Reset the class: empty the data storage.
*/
reset() {
this.#dataList = {};
}
/**
* Get a data at a given index.
*
* @param {string} dataId The data id.
* @returns {DicomData|undefined} The DICOM data.
*/
get(dataId) {
return this.#dataList[dataId];
}
/**
* Get the list of dataIds that contain the input UIDs.
*
* @param {string[]} uids A list of UIDs.
* @returns {string[]} The list of dataIds that contain the UIDs.
*/
getDataIdsFromSopUids(uids) {
const res = [];
// check input
if (typeof uids === 'undefined' ||
uids.length === 0) {
return res;
}
const keys = Object.keys(this.#dataList);
for (const key of keys) {
if (typeof this.#dataList[key].image !== 'undefined' &&
this.#dataList[key].image.containsImageUids(uids)) {
res.push(key);
}
}
return res;
}
/**
* Set the image at a given index.
*
* @param {string} dataId The data id.
* @param {Image} image The image to set.
*/
setImage(dataId, image) {
this.#dataList[dataId].image = image;
/**
* Data image set event.
*
* @event DataController#dataimageset
* @type {object}
* @property {string} type The event type.
* @property {Array} value The event value, first element is the image.
* @property {string} dataid The data id.
*/
this.#fireEvent({
type: 'dataimageset',
value: [image],
dataid: dataId
});
// listen to image change
image.addEventListener('imagecontentchange', this.#getFireEvent(dataId));
image.addEventListener('imagegeometrychange', this.#getFireEvent(dataId));
}
/**
* Add a new data.
*
* @param {string} dataId The data id.
* @param {DicomData} data The data.
*/
add(dataId, data) {
if (typeof this.#dataList[dataId] !== 'undefined') {
throw new Error('Data id already used in storage: ' + dataId);
}
// store the new image
this.#dataList[dataId] = data;
/**
* Data add event.
*
* @event DataController#dataadd
* @type {object}
* @property {string} type The event type.
* @property {string} dataid The data id.
*/
this.#fireEvent({
type: 'dataadd',
dataid: dataId
});
// listen to image change
if (typeof data.image !== 'undefined') {
data.image.addEventListener(
'imagecontentchange', this.#getFireEvent(dataId));
data.image.addEventListener(
'imagegeometrychange', this.#getFireEvent(dataId));
}
if (typeof data.annotationGroup !== 'undefined') {
data.annotationGroup.addEventListener(
'annotationadd', this.#getFireEvent(dataId));
data.annotationGroup.addEventListener(
'annotationupdate', this.#getFireEvent(dataId));
data.annotationGroup.addEventListener(
'annotationremove', this.#getFireEvent(dataId));
}
}
/**
* Remove a data from the list.
*
* @param {string} dataId The data id.
*/
remove(dataId) {
if (typeof this.#dataList[dataId] !== 'undefined') {
// stop listeners
const image = this.#dataList[dataId].image;
if (typeof image !== 'undefined') {
image.removeEventListener(
'imagecontentchange', this.#getFireEvent(dataId));
image.removeEventListener(
'imagegeometrychange', this.#getFireEvent(dataId));
}
const annotationGroup = this.#dataList[dataId].annotationGroup;
if (typeof annotationGroup !== 'undefined') {
annotationGroup.removeEventListener(
'annotationadd', this.#getFireEvent(dataId));
annotationGroup.removeEventListener(
'annotationupdate', this.#getFireEvent(dataId));
annotationGroup.removeEventListener(
'annotationremove', this.#getFireEvent(dataId));
}
// remove data from list
delete this.#dataList[dataId];
/**
* Data remove event.
*
* @event DataController#dataremove
* @type {object}
* @property {string} type The event type.
* @property {string} dataid The data id.
*/
this.#fireEvent({
type: 'dataremove',
dataid: dataId
});
}
}
/**
* Update the current data.
*
* @param {string} dataId The data id.
* @param {DicomData} data The data.
*/
update(dataId, data) {
if (typeof this.#dataList[dataId] === 'undefined') {
throw new Error('Cannot find data to update: ' + dataId);
}
const dataToUpdate = this.#dataList[dataId];
// add slice to current image
if (typeof dataToUpdate.image !== 'undefined' &&
typeof data.image !== 'undefined'
) {
dataToUpdate.image.appendSlice(data.image);
}
// update meta data
// TODO add time support
let idKey = '';
if (typeof data.meta['00020010'] !== 'undefined') {
// dicom case, use 'InstanceNumber'
idKey = '00200013';
} else {
idKey = 'imageUid';
}
dataToUpdate.meta = mergeObjects(
dataToUpdate.meta,
data.meta,
idKey,
'value');
/**
* Data udpate event.
*
* @event DataController#dataupdate
* @type {object}
* @property {string} type The event type.
* @property {string} dataid The data id.
*/
this.#fireEvent({
type: 'dataupdate',
dataid: dataId
});
}
/**
* Add an event listener to this class.
*
* @param {string} type The event type.
* @param {Function} callback The function associated with the provided
* event type, will be called with the fired event.
*/
addEventListener(type, callback) {
this.#listenerHandler.add(type, callback);
}
/**
* Remove an event listener from this class.
*
* @param {string} type The event type.
* @param {Function} callback The function associated with the provided
* event type.
*/
removeEventListener(type, callback) {
this.#listenerHandler.remove(type, callback);
}
/**
* Fire an event: call all associated listeners with the input event object.
*
* @param {object} event The event to fire.
*/
#fireEvent = (event) => {
this.#listenerHandler.fireEvent(event);
};
/**
* Get a fireEvent function that adds the input data id
* to the event value.
*
* @param {string} dataId The data id.
* @returns {Function} A fireEvent function.
*/
#getFireEvent(dataId) {
return (event) => {
event.dataid = dataId;
this.#fireEvent(event);
};
}
} // DataController class