-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[IDC-1994] Sort series list by SeriesNumber, and sort by same SeriesNumber by date/time. #2010
Changes from all commits
db502c1
e96e9a2
a1fb788
d83e036
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,8 @@ import { api } from 'dicomweb-client'; | |
// - createStacks | ||
import { isImage } from '../../utils/isImage'; | ||
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable'; | ||
import isLowPriorityModality from '../../utils/isLowPriorityModality'; | ||
import errorHandler from '../../errorHandler'; | ||
import isLowPriorityModality from '../../utils/isLowPriorityModality'; | ||
|
||
export class StudyMetadata extends Metadata { | ||
constructor(data, uid) { | ||
|
@@ -308,14 +308,10 @@ export class StudyMetadata extends Metadata { | |
series | ||
); | ||
|
||
displaySets.push(...displaySetsForSeries); | ||
displaySetsForSeries.forEach(ds => this._insertDisplaySet(ds)); | ||
}); | ||
|
||
return sortDisplaySetList(displaySets); | ||
} | ||
|
||
sortDisplaySets() { | ||
sortDisplaySetList(this._displaySets); | ||
return this._displaySets; | ||
} | ||
|
||
/** | ||
|
@@ -346,33 +342,17 @@ export class StudyMetadata extends Metadata { | |
this.addDisplaySet(displaySet); | ||
}); | ||
|
||
this.sortDisplaySets(); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Set display sets | ||
* @param {Array} displaySets Array of display sets (ImageSet[]) | ||
*/ | ||
setDisplaySets(displaySets) { | ||
if (Array.isArray(displaySets) && displaySets.length > 0) { | ||
// TODO: This is weird, can we just switch it to writable: true? | ||
this._displaySets.splice(0); | ||
|
||
displaySets.forEach(displaySet => this.addDisplaySet(displaySet)); | ||
this.sortDisplaySets(); | ||
} | ||
} | ||
|
||
/** | ||
* 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 || displaySet.sopClassModule) { | ||
this._displaySets.push(displaySet); | ||
this._insertDisplaySet(displaySet); | ||
return true; | ||
} | ||
return false; | ||
|
@@ -393,6 +373,96 @@ export class StudyMetadata extends Metadata { | |
} | ||
} | ||
|
||
/** | ||
* Insert the displaySet so that the list has an increasing SeriesNumber, | ||
* with the most recent series first for displaySets with the same SeriesNumber. | ||
* | ||
* If the displaySet is low priority, the same logic is applied, but is sorted within a sub list | ||
* At the end of the list, where all low priority data is found. | ||
*/ | ||
_insertDisplaySet(displaySet) { | ||
const { SeriesNumber } = displaySet; | ||
const displaySets = this._displaySets; | ||
let insertIndex = displaySets.length; | ||
let firstIndexWithSameSeriesNumber; | ||
|
||
// If low priority, start search from next low priority. | ||
if (isLowPriorityModality(displaySet.Modality)) { | ||
let startingIndex; | ||
|
||
// Find where the first low priority displaySet is. | ||
for (let i = 0; i < displaySets.length; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. optimization, so not blocking: we can cache this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or we can maintain these as separate lists internally, and then provide "displaysets" by joining the two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good points, we can keep further optimisations in mind if speed becomes an issue (maybe with multiple hundred SRs?), but I think we've already spent enough IDC time sorting the list. |
||
if (isLowPriorityModality(displaySets[i].Modality)) { | ||
startingIndex = i; | ||
break; | ||
} | ||
} | ||
|
||
if (!startingIndex) { | ||
startingIndex = displaySets.length; | ||
} | ||
|
||
// Find the correct SeriesNumber location to insert within the low priority | ||
// Modality displaySets | ||
for (let i = startingIndex; i < displaySets.length; i++) { | ||
if ( | ||
displaySets[i].SeriesNumber === SeriesNumber && | ||
!firstIndexWithSameSeriesNumber | ||
) { | ||
firstIndexWithSameSeriesNumber = i; | ||
} | ||
|
||
if (displaySets[i].SeriesNumber > SeriesNumber) { | ||
insertIndex = i; | ||
break; | ||
} | ||
} | ||
} else { | ||
// Find correct SeriesNumber to insert or where the low priority modalities start. | ||
for (let i = 0; i < displaySets.length; i++) { | ||
if ( | ||
displaySets[i].SeriesNumber === SeriesNumber && | ||
!firstIndexWithSameSeriesNumber | ||
) { | ||
firstIndexWithSameSeriesNumber = i; | ||
} | ||
|
||
if ( | ||
displaySets[i].SeriesNumber > SeriesNumber || | ||
isLowPriorityModality(displaySets[i].Modality) | ||
) { | ||
insertIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// If we have multiple displaySets with the same series number, find the insert position based on | ||
// SeriesDate and SeriesTime. | ||
if (firstIndexWithSameSeriesNumber !== undefined) { | ||
// If no SeriesDate, is just a placeholder displaySet, just insert anywhere, it will be re-added later. | ||
if (displaySet.SeriesDate) { | ||
const seriesDateTime = `${displaySet.SeriesDate}${displaySet.SeriesTime}`; | ||
|
||
for (let i = firstIndexWithSameSeriesNumber; i < insertIndex; i++) { | ||
const displaySetI = displaySets[i]; | ||
|
||
if ( | ||
displaySetI.SeriesDate && | ||
`${displaySetI.SeriesDate}${displaySetI.SeriesTime}` < | ||
seriesDateTime | ||
) { | ||
insertIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
this._displaySets.splice(insertIndex, 0, displaySet); | ||
this.displaySets = this._displaySets; | ||
} | ||
|
||
/** | ||
* Search the associated display sets using the supplied callback as criteria. The callback is passed | ||
* two arguments: display set (an ImageSet instance) and index (the integer | ||
|
@@ -548,49 +618,6 @@ export class StudyMetadata extends Metadata { | |
return this._series.indexOf(series); | ||
} | ||
|
||
/** | ||
* It sorts the series based on display sets order. Each series must be an instance | ||
* of SeriesMetadata and each display sets must be an instance of ImageSet. | ||
* Useful example of usage: | ||
* Study data provided by backend does not sort series at all and client-side | ||
* needs series sorted by the same criteria used for sorting display sets. | ||
*/ | ||
sortSeriesByDisplaySets() { | ||
// Object for mapping display sets' index by SeriesInstanceUID | ||
const displaySetsMapping = {}; | ||
|
||
// Loop through each display set to create the mapping | ||
this.forEachDisplaySet((displaySet, index) => { | ||
if (!(displaySet instanceof ImageSet)) { | ||
throw new OHIFError( | ||
`StudyMetadata::sortSeriesByDisplaySets display set at index ${index} is not an instance of ImageSet` | ||
); | ||
} | ||
|
||
// In case of multiframe studies, just get the first index occurence | ||
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) { | ||
displaySetsMapping[displaySet.SeriesInstanceUID] = index; | ||
} | ||
}); | ||
|
||
// Clone of actual series | ||
const actualSeries = this.getSeries(); | ||
|
||
actualSeries.forEach((series, index) => { | ||
if (!(series instanceof SeriesMetadata)) { | ||
throw new OHIFError( | ||
`StudyMetadata::sortSeriesByDisplaySets series at index ${index} is not an instance of SeriesMetadata` | ||
); | ||
} | ||
|
||
// Get the new series index | ||
const seriesIndex = displaySetsMapping[series.getSeriesInstanceUID()]; | ||
|
||
// Update the series object with the new series position | ||
this._series[seriesIndex] = series; | ||
}); | ||
} | ||
|
||
/** | ||
* Compares the current study instance with another one. | ||
* @param {StudyMetadata} study An instance of the StudyMetadata class. | ||
|
@@ -865,53 +892,3 @@ function _getDisplaySetFromSopClassModule( | |
} | ||
return displaySet; | ||
} | ||
|
||
/** | ||
* Sort series primarily by Modality (i.e., series with references to other | ||
* series like SEG, KO or PR are grouped in the end of the list) and then by | ||
* series number: | ||
* | ||
* -------- | ||
* | CT #3 | | ||
* | CT #4 | | ||
* | CT #5 | | ||
* -------- | ||
* | SEG #1 | | ||
* | SEG #2 | | ||
* -------- | ||
* | ||
* @param {*} a - DisplaySet | ||
* @param {*} b - DisplaySet | ||
*/ | ||
|
||
function seriesSortingCriteria(a, b) { | ||
const isLowPriorityA = isLowPriorityModality(a.Modality); | ||
const isLowPriorityB = isLowPriorityModality(b.Modality); | ||
if (!isLowPriorityA && isLowPriorityB) { | ||
return -1; | ||
} | ||
if (isLowPriorityA && !isLowPriorityB) { | ||
return 1; | ||
} | ||
return sortBySeriesNumber(a, b); | ||
} | ||
|
||
/** | ||
* Sort series by series number. Series with low | ||
* @param {*} a - DisplaySet | ||
* @param {*} b - DisplaySet | ||
*/ | ||
function sortBySeriesNumber(a, b) { | ||
const seriesNumberAIsGreaterOrUndefined = | ||
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber); | ||
|
||
return seriesNumberAIsGreaterOrUndefined ? 1 : -1; | ||
} | ||
|
||
/** | ||
* Sorts a list of display set objects | ||
* @param {Array} list A list of display sets to be sorted | ||
*/ | ||
function sortDisplaySetList(list) { | ||
return list.sort(seriesSortingCriteria); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be nice to pull this bit out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? pull this out where?