feat: Start using group filtering to define measurements table layout (#4501)
This commit is contained in:
1 parent
bc73de9710
commit
82440e88d5
16 files changed
+357
-210
No files matched your search
@@ -161,6 +161,16 @@ export class CommandsManager {
|
||||
* Run one or more commands with specified extra options.
|
||||
* Returns the result of the last command run.
|
||||
*
|
||||
* Example commands to run are:
|
||||
* * 'updateMeasurement'
|
||||
* * `{commandName: 'displayWhatever'}`
|
||||
* * `['updateMeasurement', {commandName: 'displayWhatever'}]`
|
||||
* * `{ commands: 'updateMeasurement' }`
|
||||
* * `{ commands: ['updateMeasurement', {commandName: 'displayWhatever'}]}`
|
||||
*
|
||||
* Note how the various styles can be mixed, simplifying the declaration of
|
||||
* sets of commands.
|
||||
*
|
||||
* @param toRun - A specification of one or more commands,
|
||||
* typically an object of { commandName, commandOptions, context }
|
||||
* or an array of such objects. It can also be a single commandName as string
|
||||
@@ -169,7 +179,7 @@ export class CommandsManager {
|
||||
* the commandOptions specified in the base.
|
||||
*/
|
||||
public run(
|
||||
toRun: Command | Commands | Command[] | string | undefined,
|
||||
toRun: Command | Commands | (Command | string)[] | string | undefined,
|
||||
options?: Record<string, unknown>
|
||||
): unknown {
|
||||
if (!toRun) {
|
||||
|
||||
@@ -597,7 +597,10 @@ export default class ExtensionManager extends PubSubService {
|
||||
}
|
||||
|
||||
Object.keys(definitions).forEach(commandName => {
|
||||
const commandDefinition = definitions[commandName];
|
||||
let commandDefinition = definitions[commandName];
|
||||
if (typeof commandDefinition === 'function') {
|
||||
commandDefinition = { commandFn: commandDefinition };
|
||||
}
|
||||
const commandHasContextThatDoesNotExist =
|
||||
commandDefinition.context && !this._commandsManager.getContext(commandDefinition.context);
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ const VALUE_TYPES = {
|
||||
ROI_THRESHOLD_MANUAL: 'value_type::roiThresholdManual',
|
||||
};
|
||||
|
||||
export type MeasurementFilter = (measurement) => boolean;
|
||||
|
||||
/**
|
||||
* MeasurementService class that supports source management and measurement management.
|
||||
* Sources can be any library that can provide "annotations" (e.g. cornerstone-tools, cornerstone, etc.)
|
||||
@@ -108,7 +110,7 @@ class MeasurementService extends PubSubService {
|
||||
public static REGISTRATION = {
|
||||
name: 'measurementService',
|
||||
altName: 'MeasurementService',
|
||||
create: ({ configuration = {} }) => {
|
||||
create: _options => {
|
||||
return new MeasurementService();
|
||||
},
|
||||
};
|
||||
@@ -120,10 +122,11 @@ class MeasurementService extends PubSubService {
|
||||
private measurements = new Map();
|
||||
private unmappedMeasurements = new Map();
|
||||
|
||||
private sources = {};
|
||||
private mappings = {};
|
||||
|
||||
constructor() {
|
||||
super(EVENTS);
|
||||
this.sources = {};
|
||||
this.mappings = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,12 +167,16 @@ class MeasurementService extends PubSubService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all measurements.
|
||||
* Gets measurements, optionally filtered by the filter
|
||||
* function.
|
||||
*
|
||||
* @return {Measurement[]} Array of measurements
|
||||
*/
|
||||
getMeasurements() {
|
||||
return [...this.measurements.values()];
|
||||
public getMeasurements(filter?: MeasurementFilter) {
|
||||
const measurements = [...this.measurements.values()];
|
||||
return filter
|
||||
? measurements.filter(measurement => filter.call(this, measurement))
|
||||
: measurements;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,11 +589,20 @@ class MeasurementService extends PubSubService {
|
||||
});
|
||||
}
|
||||
|
||||
clearMeasurements() {
|
||||
/**
|
||||
* Clears measurements that match the filter, defaulting to all of them.
|
||||
* That allows, for example, clearing all of a single studies measurements
|
||||
* without needing to clear other measurements.
|
||||
*/
|
||||
public clearMeasurements(filter?: MeasurementFilter) {
|
||||
// Make a copy of the measurements
|
||||
const measurements = [...this.measurements.values(), ...this.unmappedMeasurements.values()];
|
||||
this.unmappedMeasurements.clear();
|
||||
this.measurements.clear();
|
||||
const toClear = this.getMeasurements(filter);
|
||||
const unmappedClear = filter
|
||||
? [...this.unmappedMeasurements.values()].filter(filter)
|
||||
: this.unmappedMeasurements;
|
||||
const measurements = [...toClear, ...unmappedClear];
|
||||
unmappedClear.forEach(measurement => this.unmappedMeasurements.delete(measurement.uid));
|
||||
toClear.forEach(measurement => this.measurements.delete(measurement.uid));
|
||||
this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED, { measurements });
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ describe('Top level exports', () => {
|
||||
'progressTrackingUtils',
|
||||
'uuidv4',
|
||||
'addAccessors',
|
||||
'MeasurementFilters',
|
||||
].sort();
|
||||
|
||||
const exports = Object.keys(utils.default).sort();
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import { splitComma, getSplitParam } from './splitComma';
|
||||
import { createStudyBrowserTabs } from './createStudyBrowserTabs';
|
||||
import { sopClassDictionary } from './sopClassDictionary';
|
||||
import * as MeasurementFilters from './measurementFilters';
|
||||
|
||||
// Commented out unused functionality.
|
||||
// Need to implement new mechanism for derived displaySets using the displaySetManager.
|
||||
@@ -84,6 +85,7 @@ const utils = {
|
||||
getSplitParam,
|
||||
generateAcceptHeader,
|
||||
createStudyBrowserTabs,
|
||||
MeasurementFilters,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -117,6 +119,7 @@ export {
|
||||
getSplitParam,
|
||||
generateAcceptHeader,
|
||||
createStudyBrowserTabs,
|
||||
MeasurementFilters,
|
||||
};
|
||||
|
||||
export default utils;
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Returns a filter function which filters for measurements belonging to both
|
||||
* the study and series.
|
||||
*/
|
||||
export function filterMeasurementsBySeriesUID(selectedSeries: string[]) {
|
||||
return measurement => selectedSeries.includes(measurement.referenceSeriesUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true for measurements include referencedImageId (coplanar with an image)
|
||||
*/
|
||||
export function filterPlanarMeasurement(measurement) {
|
||||
return measurement?.referencedImageId;
|
||||
}
|
||||
|
||||
/** A filter that always returns true */
|
||||
export function filterAny(_measurement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** A filter that excludes everything */
|
||||
export function filterNone(_measurement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the measurements which are found in any of the specified
|
||||
* filters. Strings will be looked up by name.
|
||||
*/
|
||||
export function filterOr(...filters) {
|
||||
return function (item) {
|
||||
for (let filter of filters) {
|
||||
if (typeof filter === 'string') {
|
||||
filter = this[filter];
|
||||
}
|
||||
if (typeof filter !== 'function') {
|
||||
continue;
|
||||
}
|
||||
if (filter.call(this, item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for additional findings, that is, measurements with
|
||||
* a value of type point, and having a referenced image
|
||||
*/
|
||||
export function filterAdditionalFindings(measurementService) {
|
||||
const { POINT } = measurementService.VALUE_TYPES;
|
||||
return dm => dm.type === POINT && dm.referencedImageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that applies the second filter unless the first filter would
|
||||
* include the given measurement.
|
||||
* That is, (!filterUnless) && filterThen
|
||||
*/
|
||||
export function filterUnless(filterUnless, filterThen) {
|
||||
return item => (filterUnless(item) ? false : filterThen(item));
|
||||
}
|
||||
|
||||
const isString = s => typeof s === 'string' || s instanceof String;
|
||||
|
||||
/**
|
||||
* Returns true if all the filters return true.
|
||||
* Any filter can be a string name of a filter on the "this" object
|
||||
* called on the final filter call.
|
||||
*/
|
||||
export function filterAnd(...filters) {
|
||||
return function (item) {
|
||||
for (const filter of filters) {
|
||||
if (isString(filter)) {
|
||||
if (!this[filter](item)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!filter.call(this, item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that returns true if none of the filters supplied return true.
|
||||
* Any filter supplied can be a name, in which case hte filter will be retrieved
|
||||
* from "this" object on the call.
|
||||
*
|
||||
* For example, for filterNot("otherFilterName"), if that is called on
|
||||
* `{ otherFilterName: filterNone }`
|
||||
* then otherFilterName will be called, returning false in this case and
|
||||
* filterNot will return true.
|
||||
*
|
||||
*
|
||||
*/
|
||||
export function filterNot(...filters) {
|
||||
if (filters.length !== 1) {
|
||||
return filterAnd.apply(null, filters.map(filterNot));
|
||||
}
|
||||
const [filter] = filters;
|
||||
if (isString(filter)) {
|
||||
return function (item) {
|
||||
return !this[filter](item);
|
||||
};
|
||||
}
|
||||
return item => !filter(item);
|
||||
}
|
||||
Reference in new issue
Block a user