fix: Initial sort not consistent (#5224)
* fix: Initial sort not consistent * Add better sort criteria * Remove sorting changes * Start replacing screen shots * Update bun lock for version * Leave default sort for test mode * fix: Add consistent sorting for same series splits * PR comments * docs: add notes for series/display set sort * Updated example docs * Update sortVector to be compareSameStudy * Add unit test * PR comments
This commit is contained in:
parent
14bc99124d
commit
77f9f8e1c4
@ -67,7 +67,7 @@ function getDisplaySetInfo(instances) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeDisplaySet = instances => {
|
const makeDisplaySet = (instances, index) => {
|
||||||
// Need to sort the instances in order to get a consistent instance/thumbnail
|
// Need to sort the instances in order to get a consistent instance/thumbnail
|
||||||
sortStudyInstances(instances);
|
sortStudyInstances(instances);
|
||||||
const instance = instances[0];
|
const instance = instances[0];
|
||||||
@ -190,7 +190,7 @@ function getDisplaySetsFromSeries(instances) {
|
|||||||
// into their own specific display sets. Place the rest of each
|
// into their own specific display sets. Place the rest of each
|
||||||
// series into another display set.
|
// series into another display set.
|
||||||
const stackableInstances = [];
|
const stackableInstances = [];
|
||||||
instances.forEach(instance => {
|
instances.forEach((instance, instanceIndex) => {
|
||||||
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
||||||
if (!isImage(instance.SOPClassUID) && !instance.Rows) {
|
if (!isImage(instance.SOPClassUID) && !instance.Rows) {
|
||||||
return;
|
return;
|
||||||
@ -198,7 +198,7 @@ function getDisplaySetsFromSeries(instances) {
|
|||||||
|
|
||||||
let displaySet;
|
let displaySet;
|
||||||
if (isMultiFrame(instance)) {
|
if (isMultiFrame(instance)) {
|
||||||
displaySet = makeDisplaySet([instance]);
|
displaySet = makeDisplaySet([instance], instanceIndex);
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
numImageFrames: instance.NumberOfFrames,
|
numImageFrames: instance.NumberOfFrames,
|
||||||
@ -207,7 +207,7 @@ function getDisplaySetsFromSeries(instances) {
|
|||||||
});
|
});
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
} else if (isSingleImageModality(instance.Modality)) {
|
} else if (isSingleImageModality(instance.Modality)) {
|
||||||
displaySet = makeDisplaySet([instance]);
|
displaySet = makeDisplaySet([instance], instanceIndex);
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
instanceNumber: instance.InstanceNumber,
|
instanceNumber: instance.InstanceNumber,
|
||||||
@ -220,7 +220,7 @@ function getDisplaySetsFromSeries(instances) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (stackableInstances.length) {
|
if (stackableInstances.length) {
|
||||||
const displaySet = makeDisplaySet(stackableInstances);
|
const displaySet = makeDisplaySet(stackableInstances, displaySets.length);
|
||||||
displaySet.setAttribute('studyInstanceUid', instances[0].StudyInstanceUID);
|
displaySet.setAttribute('studyInstanceUid', instances[0].StudyInstanceUID);
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import getStudies from './studiesList';
|
|
||||||
import { DicomMetadataStore, log, utils, Enums } from '@ohif/core';
|
import { DicomMetadataStore, log, utils, Enums } from '@ohif/core';
|
||||||
|
import getStudies from './studiesList';
|
||||||
import isSeriesFilterUsed from '../../utils/isSeriesFilterUsed';
|
import isSeriesFilterUsed from '../../utils/isSeriesFilterUsed';
|
||||||
|
|
||||||
const { getSplitParam } = utils;
|
const { seriesSortCriteria, getSplitParam } = utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the route.
|
* Initialize the route.
|
||||||
@ -14,7 +14,12 @@ const { getSplitParam } = utils;
|
|||||||
* @returns array of subscriptions to cancel
|
* @returns array of subscriptions to cancel
|
||||||
*/
|
*/
|
||||||
export async function defaultRouteInit(
|
export async function defaultRouteInit(
|
||||||
{ servicesManager, studyInstanceUIDs, dataSource, filters, appConfig }: withAppTypes,
|
{
|
||||||
|
servicesManager,
|
||||||
|
studyInstanceUIDs,
|
||||||
|
dataSource,
|
||||||
|
filters,
|
||||||
|
}: withAppTypes & { studyInstanceUIDs?: string[] },
|
||||||
hangingProtocolId,
|
hangingProtocolId,
|
||||||
stageIndex
|
stageIndex
|
||||||
) {
|
) {
|
||||||
@ -27,13 +32,17 @@ export async function defaultRouteInit(
|
|||||||
*/
|
*/
|
||||||
function applyHangingProtocol() {
|
function applyHangingProtocol() {
|
||||||
const displaySets = displaySetService.getActiveDisplaySets();
|
const displaySets = displaySetService.getActiveDisplaySets();
|
||||||
|
// The display sets are not necessarily in load order, even though the
|
||||||
|
// series got started in load order, so re-sort them before hanging
|
||||||
|
const sortCriteria = seriesSortCriteria.default;
|
||||||
|
|
||||||
if (!displaySets || !displaySets.length) {
|
if (!displaySets || !displaySets.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const sortedDisplaySets = [...displaySets].sort(sortCriteria);
|
||||||
|
|
||||||
// Gets the studies list to use
|
// Gets the studies list to use
|
||||||
const studies = getStudies(studyInstanceUIDs, displaySets);
|
const studies = getStudies(studyInstanceUIDs, sortedDisplaySets);
|
||||||
|
|
||||||
// study being displayed, and is thus the "active" study.
|
// study being displayed, and is thus the "active" study.
|
||||||
const activeStudy = studies[0];
|
const activeStudy = studies[0];
|
||||||
|
|||||||
@ -464,12 +464,17 @@ export default class DisplaySetService extends PubSubService {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param sortFn function to sort the display sets
|
* @param sortFn function to sort the display sets
|
||||||
* @param direction direction to sort the display sets
|
* @param direction direction to sort the display sets. Ascending means
|
||||||
|
* increasing in value, which will typically put the lowest series numbers
|
||||||
|
* first, with low priority display sets last with newest first.
|
||||||
|
* The meaning of this flag may change to leave the image/non-image display
|
||||||
|
* set sorting alone and only affect sorting within groups, or have additional
|
||||||
|
* values for specific changes to the sort.
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
public sortDisplaySets(
|
public sortDisplaySets(
|
||||||
sortFn: (a: DisplaySet, b: DisplaySet) => number,
|
sortFn: (a: DisplaySet, b: DisplaySet) => number,
|
||||||
direction: string,
|
direction: 'ascending' | 'descending' = 'ascending',
|
||||||
suppressEvent = false
|
suppressEvent = false
|
||||||
): void {
|
): void {
|
||||||
this.activeDisplaySets.sort(sortFn);
|
this.activeDisplaySets.sort(sortFn);
|
||||||
|
|||||||
@ -78,6 +78,12 @@ export type DisplaySet = {
|
|||||||
isLoaded?: boolean;
|
isLoaded?: boolean;
|
||||||
isHydrated?: boolean;
|
isHydrated?: boolean;
|
||||||
isRehydratable?: boolean;
|
isRehydratable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the comparison function (for sort) to use when comparing display
|
||||||
|
* sets that are coming from same series instanceUID.
|
||||||
|
*/
|
||||||
|
compareSameSeries?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DisplaySetSeriesMetadataInvalidatedEvent = {
|
export type DisplaySetSeriesMetadataInvalidatedEvent = {
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { useSystem } from '../contextProviders/SystemProvider';
|
import { useSystem } from '../contextProviders/SystemProvider';
|
||||||
|
import { seriesSortCriteria } from './sortStudy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab properties that drive which tab group is used for thumbnail display.
|
* Tab properties that drive which tab group is used for thumbnail display.
|
||||||
*/
|
*/
|
||||||
export type TabProp = {
|
export type TabProp = {
|
||||||
name: string,
|
name: string;
|
||||||
label: string,
|
label: string;
|
||||||
studies: any[],
|
studies: any[];
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of tab properties with studies presorted depending on tab mod.
|
* Collection of tab properties with studies presorted depending on tab mod.
|
||||||
@ -36,7 +37,7 @@ export function createStudyBrowserTabs(
|
|||||||
recentTimeframeMS = 31536000000
|
recentTimeframeMS = 31536000000
|
||||||
): TabsProps {
|
): TabsProps {
|
||||||
const { servicesManager } = useSystem();
|
const { servicesManager } = useSystem();
|
||||||
const { displaySetService } = servicesManager.services;
|
const { displaySetService, customizationService } = servicesManager.services;
|
||||||
|
|
||||||
const shouldSortBySeriesUID = process.env.TEST_ENV === 'true';
|
const shouldSortBySeriesUID = process.env.TEST_ENV === 'true';
|
||||||
const primaryStudies = [];
|
const primaryStudies = [];
|
||||||
@ -48,17 +49,16 @@ export function createStudyBrowserTabs(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// sort them by seriesInstanceUID
|
// sort them by seriesInstanceUID
|
||||||
let sortedDisplaySets;
|
const sortCriteria = shouldSortBySeriesUID
|
||||||
if (shouldSortBySeriesUID) {
|
? seriesSortCriteria.compareSeriesUID
|
||||||
sortedDisplaySets = displaySetsForStudy.sort((a, b) => {
|
: (customizationService.getCustomization('sortingCriteria') as (a, b) => number);
|
||||||
const displaySetA = displaySetService.getDisplaySetByUID(a.displaySetInstanceUID);
|
const sortedDisplaySets = displaySetsForStudy.sort((a, b) => {
|
||||||
const displaySetB = displaySetService.getDisplaySetByUID(b.displaySetInstanceUID);
|
const displaySetA = displaySetService.getDisplaySetByUID(a.displaySetInstanceUID);
|
||||||
|
const displaySetB = displaySetService.getDisplaySetByUID(b.displaySetInstanceUID);
|
||||||
|
return sortCriteria(displaySetA, displaySetB);
|
||||||
|
});
|
||||||
|
|
||||||
return displaySetA.SeriesInstanceUID.localeCompare(displaySetB.SeriesInstanceUID);
|
// return displaySetA.SeriesInstanceUID.localeCompare(displaySetB.SeriesInstanceUID);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sortedDisplaySets = displaySetsForStudy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabStudy = Object.assign({}, study, {
|
const tabStudy = Object.assign({}, study, {
|
||||||
displaySets: sortedDisplaySets,
|
displaySets: sortedDisplaySets,
|
||||||
|
|||||||
44
platform/core/src/utils/sortStudy.test.js
Normal file
44
platform/core/src/utils/sortStudy.test.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { compareSeriesUID, addSameSeriesCompare, compare } from './sortStudy';
|
||||||
|
|
||||||
|
addSameSeriesCompare('default', (a,b) => compare(a.default,b.default), 5);
|
||||||
|
const altCompare = 'altCompare'
|
||||||
|
addSameSeriesCompare(altCompare, (a,b) => compare(a.altCompare,b.altCompare), 3);
|
||||||
|
|
||||||
|
const ds1 = {
|
||||||
|
SeriesInstanceUID: '1',
|
||||||
|
default: 'ds1',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ds2 = {
|
||||||
|
...ds1,
|
||||||
|
default: 'ds2',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ds3 = {
|
||||||
|
...ds2,
|
||||||
|
altCompare: 3,
|
||||||
|
compareSameSeries: altCompare,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ds4 = {
|
||||||
|
...ds1,
|
||||||
|
altCompare:4,
|
||||||
|
compareSameSeries: altCompare,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ds5 = {
|
||||||
|
...ds1,
|
||||||
|
SeriesInstanceUID: '3',
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('sortStudy', () => {
|
||||||
|
test('compareSameSeries',() => {
|
||||||
|
const initial = [ds5, ds4,ds3,ds2,ds1];
|
||||||
|
initial.sort(compareSeriesUID);
|
||||||
|
expect(initial[0]).toBe(ds1);
|
||||||
|
expect(initial[1]).toBe(ds2);
|
||||||
|
expect(initial[2]).toBe(ds3);
|
||||||
|
expect(initial[3]).toBe(ds4);
|
||||||
|
expect(initial[4]).toBe(ds5);
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -3,19 +3,77 @@ import isLowPriorityModality from './isLowPriorityModality';
|
|||||||
import calculateScanAxisNormal from './calculateScanAxisNormal';
|
import calculateScanAxisNormal from './calculateScanAxisNormal';
|
||||||
import areAllImageOrientationsEqual from './areAllImageOrientationsEqual';
|
import areAllImageOrientationsEqual from './areAllImageOrientationsEqual';
|
||||||
|
|
||||||
const compareSeriesDateTime = (a, b) => {
|
export const compare = (a, b) => {
|
||||||
const seriesDateA = Date.parse(`${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`);
|
if (a == b) return 0;
|
||||||
const seriesDateB = Date.parse(`${b.seriesDate ?? b.SeriesDate} ${b.seriesTime ?? b.SeriesTime}`);
|
if (!a && b) return -1;
|
||||||
return seriesDateA - seriesDateB;
|
if (!b && a) return 1;
|
||||||
|
if (a < b) return -1;
|
||||||
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultSeriesSort = (a, b) => {
|
type CompareSameSeries = {
|
||||||
|
priority: number;
|
||||||
|
compare: (a, b) => number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapCompareSameSeries = new Map<string, CompareSameSeries>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a comparison for same series display sets.
|
||||||
|
* Supply null for compareF to delete the function.
|
||||||
|
*/
|
||||||
|
export function addSameSeriesCompare(name: string, compareF: (a, b) => number, priority: number) {
|
||||||
|
if (!compareF) {
|
||||||
|
mapCompareSameSeries.delete(name);
|
||||||
|
} else {
|
||||||
|
mapCompareSameSeries.set(name, { compare: compareF, priority });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the "series" sort is used on display sets, it is possible to get the
|
||||||
|
* same series twice. This method compares two display sets from the same series
|
||||||
|
*
|
||||||
|
* If both display sets have the same compareSameSeries name, then the
|
||||||
|
* function registered for that name will be used.
|
||||||
|
*
|
||||||
|
* If they differ, then the priority between the two functions will be used.
|
||||||
|
*
|
||||||
|
* Otherwise, the instance compare will be used on the default instance.
|
||||||
|
*
|
||||||
|
* This provides a configurable well defined sorting order.
|
||||||
|
*/
|
||||||
|
export const compareSameSeriesDisplaySet = (a, b) => {
|
||||||
|
const { compareSameSeries: compareAName = 'default' } = a;
|
||||||
|
const { compareSameSeries: compareBName = 'default' } = b;
|
||||||
|
const compareA = mapCompareSameSeries.get(compareAName);
|
||||||
|
const compareB = mapCompareSameSeries.get(compareBName);
|
||||||
|
if (compareA && compareB) {
|
||||||
|
const compareValue =
|
||||||
|
compareA === compareB
|
||||||
|
? compareA.compare(a, b)
|
||||||
|
: compare(compareA.priority, compareB.priority);
|
||||||
|
if (!compareValue) {
|
||||||
|
return compareValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sortByInstanceNumber(a.instance, b.instance);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const compareSeriesUID = (a, b) =>
|
||||||
|
compare(a.SeriesInstanceUID, b.SeriesInstanceUID) || compareSameSeriesDisplaySet(a, b);
|
||||||
|
|
||||||
|
export const compareSeriesDateTime = (a, b) => {
|
||||||
|
// Natural order of string is good enough here
|
||||||
|
const seriesDateA = `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`;
|
||||||
|
const seriesDateB = `${b.seriesDate ?? b.SeriesDate} ${b.seriesTime ?? b.SeriesTime}`;
|
||||||
|
return compare(seriesDateA, seriesDateB) || compareSeriesUID(a, b);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultSeriesSort = (a, b) => {
|
||||||
const seriesNumberA = a.SeriesNumber ?? a.seriesNumber;
|
const seriesNumberA = a.SeriesNumber ?? a.seriesNumber;
|
||||||
const seriesNumberB = b.SeriesNumber ?? b.seriesNumber;
|
const seriesNumberB = b.SeriesNumber ?? b.seriesNumber;
|
||||||
if (seriesNumberA === seriesNumberB) {
|
return compare(seriesNumberA, seriesNumberB) || compareSeriesDateTime(a, b);
|
||||||
return compareSeriesDateTime(a, b);
|
|
||||||
}
|
|
||||||
return seriesNumberA - seriesNumberB;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,14 +82,14 @@ const defaultSeriesSort = (a, b) => {
|
|||||||
* @param {Object} firstSeries
|
* @param {Object} firstSeries
|
||||||
* @param {Object} secondSeries
|
* @param {Object} secondSeries
|
||||||
*/
|
*/
|
||||||
function seriesInfoSortingCriteria(firstSeries, secondSeries) {
|
export function seriesInfoSortingCriteria(firstSeries, secondSeries) {
|
||||||
const aLowPriority = isLowPriorityModality(firstSeries.Modality ?? firstSeries.modality);
|
const aLowPriority = isLowPriorityModality(firstSeries.Modality ?? firstSeries.modality);
|
||||||
const bLowPriority = isLowPriorityModality(secondSeries.Modality ?? secondSeries.modality);
|
const bLowPriority = isLowPriorityModality(secondSeries.Modality ?? secondSeries.modality);
|
||||||
|
|
||||||
if (aLowPriority) {
|
if (aLowPriority) {
|
||||||
// Use the reverse sort order for low priority modalities so that the
|
// Use the reverse sort order for low priority modalities so that the
|
||||||
// most recent one comes up first as usually that is the one of interest.
|
// most recent one comes up first as usually that is the one of interest.
|
||||||
return bLowPriority ? defaultSeriesSort(secondSeries, firstSeries) : 1;
|
return bLowPriority ? compareSeriesDateTime(secondSeries, firstSeries) : 1;
|
||||||
} else if (bLowPriority) {
|
} else if (bLowPriority) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -39,44 +97,50 @@ function seriesInfoSortingCriteria(firstSeries, secondSeries) {
|
|||||||
return defaultSeriesSort(firstSeries, secondSeries);
|
return defaultSeriesSort(firstSeries, secondSeries);
|
||||||
}
|
}
|
||||||
|
|
||||||
const seriesSortCriteria = {
|
export const seriesSortCriteria = {
|
||||||
default: seriesInfoSortingCriteria,
|
default: seriesInfoSortingCriteria,
|
||||||
seriesInfoSortingCriteria,
|
seriesInfoSortingCriteria,
|
||||||
|
compareSameSeries: compareSameSeriesDisplaySet,
|
||||||
|
compareSeriesDateTime,
|
||||||
|
compareSeriesUID,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortByInstanceNumber = (a, b) => {
|
/**
|
||||||
// Sort by InstanceNumber (0020,0013)
|
* Compares two instances first by instance number, and then by
|
||||||
|
* sop and frame numbers.
|
||||||
|
* Handles undefined values for use with display set comparison.
|
||||||
|
*/
|
||||||
|
export const sortByInstanceNumber = (a, b) => {
|
||||||
|
if (!a || !b) {
|
||||||
|
return (!a && !b && 0) || (!a && -1) || 1;
|
||||||
|
}
|
||||||
const aInstance = parseInt(a.InstanceNumber) || 0;
|
const aInstance = parseInt(a.InstanceNumber) || 0;
|
||||||
const bInstance = parseInt(b.InstanceNumber) || 0;
|
const bInstance = parseInt(b.InstanceNumber) || 0;
|
||||||
if (aInstance !== bInstance) {
|
if (aInstance !== bInstance) {
|
||||||
return (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0);
|
return (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0);
|
||||||
}
|
}
|
||||||
// Fallback rule to enable consistent sorting
|
return compare(a.SOPInstanceUID, b.SOPInstanceUID) || compare(a.frameNumber, b.frameNumber);
|
||||||
if (a.SOPInstanceUID === b.SOPInstanceUID) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return a.SOPInstanceUID < b.SOPInstanceUID ? -1 : 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const instancesSortCriteria = {
|
export const instancesSortCriteria = {
|
||||||
default: sortByInstanceNumber,
|
default: sortByInstanceNumber,
|
||||||
sortByInstanceNumber,
|
sortByInstanceNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortingCriteria = {
|
export const sortingCriteria = {
|
||||||
seriesSortCriteria,
|
seriesSortCriteria,
|
||||||
instancesSortCriteria,
|
instancesSortCriteria,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts given series (given param is modified)
|
* Sorts given series or display sets
|
||||||
* The default criteria is based on series number in ascending order.
|
* The default criteria is based on series number in ascending order.
|
||||||
*
|
*
|
||||||
* @param {Array} series List of series
|
* @param series - List of series (modified in place)
|
||||||
* @param {function} seriesSortingCriteria method for sorting
|
* @param seriesSortingCriteria - method for sorting
|
||||||
* @returns {Array} sorted series object
|
* @returns sorted series object
|
||||||
*/
|
*/
|
||||||
const sortStudySeries = (
|
export const sortStudySeries = (
|
||||||
series,
|
series,
|
||||||
seriesSortingCriteria = seriesSortCriteria.default,
|
seriesSortingCriteria = seriesSortCriteria.default,
|
||||||
sortFunction = null
|
sortFunction = null
|
||||||
@ -96,7 +160,7 @@ const sortStudySeries = (
|
|||||||
* @param {function} instancesSortingCriteria method for sorting
|
* @param {function} instancesSortingCriteria method for sorting
|
||||||
* @returns {Array} sorted instancesList object
|
* @returns {Array} sorted instancesList object
|
||||||
*/
|
*/
|
||||||
const sortStudyInstances = (
|
export const sortStudyInstances = (
|
||||||
instancesList,
|
instancesList,
|
||||||
instancesSortingCriteria = instancesSortCriteria.default
|
instancesSortingCriteria = instancesSortCriteria.default
|
||||||
) => {
|
) => {
|
||||||
@ -113,7 +177,7 @@ const sortStudyInstances = (
|
|||||||
* @param {function} [instancesSortingCriteria = instancesSortCriteria.default] method for sorting instances
|
* @param {function} [instancesSortingCriteria = instancesSortCriteria.default] method for sorting instances
|
||||||
* @returns {Object} sorted study object
|
* @returns {Object} sorted study object
|
||||||
*/
|
*/
|
||||||
export default function sortStudy(
|
export function sortStudy(
|
||||||
study,
|
study,
|
||||||
deepSort = true,
|
deepSort = true,
|
||||||
seriesSortingCriteria = seriesSortCriteria.default,
|
seriesSortingCriteria = seriesSortCriteria.default,
|
||||||
@ -134,7 +198,7 @@ export default function sortStudy(
|
|||||||
return study;
|
return study;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidForPositionSort(images): boolean {
|
export function isValidForPositionSort(images): boolean {
|
||||||
if (images.length <= 1) {
|
if (images.length <= 1) {
|
||||||
return false; // No need to sort if there's only one image
|
return false; // No need to sort if there's only one image
|
||||||
}
|
}
|
||||||
@ -161,7 +225,7 @@ function isValidForPositionSort(images): boolean {
|
|||||||
*
|
*
|
||||||
* @returns images - reference to images after sorting
|
* @returns images - reference to images after sorting
|
||||||
*/
|
*/
|
||||||
const sortImagesByPatientPosition = images => {
|
export const sortImagesByPatientPosition = images => {
|
||||||
const referenceImagePositionPatient = images[0].ImagePositionPatient;
|
const referenceImagePositionPatient = images[0].ImagePositionPatient;
|
||||||
const imageOrientationPatient = images[0].ImageOrientationPatient;
|
const imageOrientationPatient = images[0].ImageOrientationPatient;
|
||||||
|
|
||||||
@ -188,13 +252,4 @@ const sortImagesByPatientPosition = images => {
|
|||||||
return images;
|
return images;
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export default sortStudy;
|
||||||
sortStudy,
|
|
||||||
sortStudySeries,
|
|
||||||
sortStudyInstances,
|
|
||||||
sortingCriteria,
|
|
||||||
seriesSortCriteria,
|
|
||||||
instancesSortCriteria,
|
|
||||||
isValidForPositionSort,
|
|
||||||
sortImagesByPatientPosition,
|
|
||||||
};
|
|
||||||
|
|||||||
68
platform/docs/docs/development/notes-requirements.md
Normal file
68
platform/docs/docs/development/notes-requirements.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
sidebar_position: 14
|
||||||
|
sidebar_label: Notes and Requirements
|
||||||
|
title: Notes and Requirements for general OHIF behaviour
|
||||||
|
summary: Specifies some of the expected behavior of OHIF generally
|
||||||
|
---
|
||||||
|
|
||||||
|
# Notes and Requirements
|
||||||
|
|
||||||
|
This document just lists general notes and requirements for how OHIF behaves.
|
||||||
|
The plan is to break this document down into a new sub-category once there
|
||||||
|
are sufficient notes/requirements.
|
||||||
|
|
||||||
|
|
||||||
|
## Series and Display Set Sorting `sortStudy.ts`
|
||||||
|
|
||||||
|
Often a user will want to see a sorted list of series, or more generally
|
||||||
|
a sorted list of display sets. Series are the original data and can be split
|
||||||
|
up into several display sets, although they are the same general sort of concept
|
||||||
|
|
||||||
|
For example, an MR series might contain both T1 and T2 echos, and the T2 echo
|
||||||
|
should occur after the T1. Or, a single series might contain 4 mammography views:
|
||||||
|
`LCC`, `RCC`, `LMLO`, `RMLO` with all the `CC` views shown first, and within
|
||||||
|
that all the left views first for a given sub-type of CC view.
|
||||||
|
|
||||||
|
To allow controlling that, the `sortStudy` can register sort functions
|
||||||
|
that user used when two display sets come from the same series. Between
|
||||||
|
those display sets, the registration also registers a default ordering
|
||||||
|
for that compare function. Thus, the registration might look like:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
addSameSeriesCompare('mammographyCompare', mammographyCompare, 5)
|
||||||
|
addSameSeriesCompare('mrT1T2Compare', mrT1T2Compare, 7);
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, the display set for mr and mammography need to set the field `compareSameSeries`
|
||||||
|
to the value `mammographyCompare`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
makeDisplaySet
|
||||||
|
...
|
||||||
|
displaySet = {
|
||||||
|
...,
|
||||||
|
compareSameSeries: 'mammographyCompare',
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specifying Sort Order from Series Split
|
||||||
|
|
||||||
|
The series split rules (`getSopClassHandlerModule`) can specify a custom order
|
||||||
|
of display sets for the same series by adding a `sortVector`
|
||||||
|
to the display set created. Display sets which match on series instance uid
|
||||||
|
are then compared using the sort vector. The first element is the general sort
|
||||||
|
order for this type of value among all other sort types, and must be numeric.
|
||||||
|
The remainder of the values in the vector should be consistent for all
|
||||||
|
sort vectors whose first value is the same value.
|
||||||
|
|
||||||
|
For example, the mammography sort vector might have a primary value of '25',
|
||||||
|
and then use the next three values for `view type`, `sub type` and `side`.
|
||||||
|
It might also be true that "both" side views sort before everything and would be assigned
|
||||||
|
a value less than `25` here.
|
||||||
|
|
||||||
|
```
|
||||||
|
// LCC view
|
||||||
|
[25, 'CC', 'L', 'XO']
|
||||||
|
// BCC view
|
||||||
|
[24, 'CC', 'B']
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user