feat(URL): add param for initial series and sop uids to display (#3265)
* feat: Allow navigating to a specified series and sop instance This was a feature in OHIF v2, so adding it to v3, albeit with new parameters. feat: Allow comma separated as well as repeated args params * docs * Test fixes * feat: Navigate to SOP selected - PR fixes * Updated docs * PR fixes
This commit is contained in:
parent
226244a26c
commit
50ed96ff73
@ -22,8 +22,8 @@ import {
|
||||
retrieveStudyMetadata,
|
||||
deleteStudyMetadataPromise,
|
||||
} from './retrieveStudyMetadata.js';
|
||||
import StaticWadoClient from './utils/StaticWadoClient.js';
|
||||
import getDirectURL from '../utils/getDirectURL.js';
|
||||
import StaticWadoClient from './utils/StaticWadoClient';
|
||||
import getDirectURL from '../utils/getDirectURL';
|
||||
|
||||
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
||||
|
||||
@ -90,7 +90,9 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
const implementation = {
|
||||
initialize: ({ params, query }) => {
|
||||
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params;
|
||||
const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs');
|
||||
const queryStudyInstanceUIDs = utils.splitComma(
|
||||
query.getAll('StudyInstanceUIDs')
|
||||
);
|
||||
|
||||
const StudyInstanceUIDs =
|
||||
(queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) ||
|
||||
|
||||
@ -9,18 +9,19 @@ import { api } from 'dicomweb-client';
|
||||
*/
|
||||
export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
static studyFilterKeys = {
|
||||
StudyInstanceUID: '0020000D',
|
||||
PatientName: '00100010',
|
||||
studyinstanceuid: '0020000D',
|
||||
patientname: '00100010',
|
||||
'00100020': 'mrn',
|
||||
StudyDescription: '00081030',
|
||||
StudyDate: '00080020',
|
||||
ModalitiesInStudy: '00080061',
|
||||
AccessionNumber: '00080050',
|
||||
studydescription: '00081030',
|
||||
studydate: '00080020',
|
||||
modalitiesinstudy: '00080061',
|
||||
accessionnumber: '00080050',
|
||||
};
|
||||
|
||||
static seriesFilterKeys = {
|
||||
SeriesInstanceUID: '0020000E',
|
||||
SeriesNumber: '00200011',
|
||||
seriesinstanceuid: '0020000E',
|
||||
seriesnumber: '00200011',
|
||||
modality: '00080060',
|
||||
};
|
||||
|
||||
constructor(qidoConfig) {
|
||||
@ -37,15 +38,18 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
async searchForStudies(options) {
|
||||
if (!this.staticWado) return super.searchForStudies(options);
|
||||
|
||||
let searchResult = await super.searchForStudies(options);
|
||||
const searchResult = await super.searchForStudies(options);
|
||||
const { queryParams } = options;
|
||||
|
||||
if (!queryParams) return searchResult;
|
||||
|
||||
const lowerParams = this.toLowerParams(queryParams);
|
||||
const filtered = searchResult.filter(study => {
|
||||
for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) {
|
||||
if (
|
||||
!this.filterItem(
|
||||
key,
|
||||
queryParams,
|
||||
lowerParams,
|
||||
study,
|
||||
StaticWadoClient.studyFilterKeys
|
||||
)
|
||||
@ -61,16 +65,18 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
async searchForSeries(options) {
|
||||
if (!this.staticWado) return super.searchForSeries(options);
|
||||
|
||||
let searchResult = await super.searchForSeries(options);
|
||||
const searchResult = await super.searchForSeries(options);
|
||||
const { queryParams } = options;
|
||||
if (!queryParams) return searchResult;
|
||||
const filtered = searchResult.filter(study => {
|
||||
const lowerParams = this.toLowerParams(queryParams);
|
||||
|
||||
const filtered = searchResult.filter(series => {
|
||||
for (const key of Object.keys(StaticWadoClient.seriesFilterKeys)) {
|
||||
if (
|
||||
!this.filterItem(
|
||||
key,
|
||||
queryParams,
|
||||
study,
|
||||
lowerParams,
|
||||
series,
|
||||
StaticWadoClient.seriesFilterKeys
|
||||
)
|
||||
) {
|
||||
@ -136,13 +142,13 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
/**
|
||||
* Filters the return list by the query parameters.
|
||||
*
|
||||
* @param {*} key
|
||||
* @param {*} queryParams
|
||||
* @param anyCaseKey - a possible search key
|
||||
* @param queryParams -
|
||||
* @param {*} study
|
||||
* @param {*} sourceFilterMap
|
||||
* @returns
|
||||
*/
|
||||
filterItem(key, queryParams, study, sourceFilterMap) {
|
||||
filterItem(key: string, queryParams, study, sourceFilterMap) {
|
||||
const altKey = sourceFilterMap[key] || key;
|
||||
if (!queryParams) return true;
|
||||
const testValue = queryParams[key] || queryParams[altKey];
|
||||
@ -153,6 +159,15 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
return this.compareDateRange(testValue, valueElem.Value[0]);
|
||||
}
|
||||
const value = valueElem.Value;
|
||||
return this.compareValues(testValue, value) && true;
|
||||
return this.compareValues(testValue, value);
|
||||
}
|
||||
|
||||
/** Converts the query parameters to lower case query parameters */
|
||||
toLowerParams(queryParams: Record<string, unknown>): Record<string, unknown> {
|
||||
const lowerParams = {};
|
||||
Object.entries(queryParams).forEach(([key, value]) => {
|
||||
lowerParams[key.toLowerCase()] = value;
|
||||
});
|
||||
return lowerParams;
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ const defaultProtocol = {
|
||||
hasUpdatedPriorsInformation: false,
|
||||
name: 'Default',
|
||||
createdDate: '2021-02-23T19:22:08.894Z',
|
||||
modifiedDate: '2021-02-23T19:22:08.894Z',
|
||||
modifiedDate: '2023-04-01',
|
||||
availableTo: {},
|
||||
editableBy: {},
|
||||
protocolMatchingRules: [],
|
||||
@ -33,8 +33,6 @@ const defaultProtocol = {
|
||||
},
|
||||
displaySetSelectors: {
|
||||
defaultDisplaySetId: {
|
||||
// Unused currently
|
||||
imageMatchingRules: [],
|
||||
// Matches displaysets, NOT series
|
||||
seriesMatchingRules: [
|
||||
// Try to match series with images by default, to prevent weird display
|
||||
@ -45,6 +43,15 @@ const defaultProtocol = {
|
||||
greaterThan: { value: 0 },
|
||||
},
|
||||
},
|
||||
// This display set will select the specified items by preference
|
||||
// It has no affect if nothing is specified in the URL.
|
||||
{
|
||||
attribute: 'isDisplaySetFromUrl',
|
||||
weight: 10,
|
||||
constraint: {
|
||||
equals: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
// Can be used to select matching studies
|
||||
// studyMatchingRules: [],
|
||||
@ -65,7 +72,13 @@ const defaultProtocol = {
|
||||
viewportOptions: {
|
||||
viewportType: 'stack',
|
||||
toolGroupId: 'default',
|
||||
// initialImageOptions: {
|
||||
// This will specify the initial image options index if it matches in the URL
|
||||
// and will otherwise not specify anything.
|
||||
initialImageOptions: {
|
||||
custom: 'sopInstanceLocation',
|
||||
},
|
||||
// Other options for initialImageOptions, which can be included in the default
|
||||
// custom attribute, or can be provided directly.
|
||||
// index: 180,
|
||||
// preset: 'middle', // 'first', 'last', 'middle'
|
||||
// },
|
||||
|
||||
@ -133,7 +133,6 @@ function modeFactory() {
|
||||
const {
|
||||
toolGroupService,
|
||||
syncGroupService,
|
||||
toolbarService,
|
||||
segmentationService,
|
||||
cornerstoneViewportService,
|
||||
} = servicesManager.services;
|
||||
|
||||
@ -30,6 +30,10 @@ class ImageSet {
|
||||
writable: false,
|
||||
value: guid(), // Unique ID of the instance
|
||||
});
|
||||
|
||||
this.instances = images;
|
||||
this.instance = images[0];
|
||||
this.StudyInstanceUID = this.instance?.StudyInstanceUID;
|
||||
}
|
||||
|
||||
getUID() {
|
||||
@ -56,7 +60,7 @@ class ImageSet {
|
||||
}
|
||||
}
|
||||
|
||||
getNumImages = () => this.images.length
|
||||
getNumImages = () => this.images.length;
|
||||
|
||||
getImage(index) {
|
||||
return this.images[index];
|
||||
|
||||
@ -6,6 +6,10 @@ import IDisplaySet from '../DisplaySetService/IDisplaySet';
|
||||
import { CommandsManager } from '../../classes';
|
||||
import ServicesManager from '../ServicesManager';
|
||||
import * as HangingProtocol from '../../types/HangingProtocol';
|
||||
import {
|
||||
isDisplaySetFromUrl,
|
||||
sopInstanceLocation,
|
||||
} from './isDisplaySetFromUrl';
|
||||
|
||||
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
|
||||
|
||||
@ -81,6 +85,14 @@ export default class HangingProtocolService extends PubSubService {
|
||||
// we can add more advanced checking here
|
||||
callback: displaySet => displaySet.isReconstructable ?? false,
|
||||
},
|
||||
isDisplaySetFromUrl: {
|
||||
name: 'Checks if the display set is as specified in the URL',
|
||||
callback: isDisplaySetFromUrl,
|
||||
},
|
||||
sopInstanceLocation: {
|
||||
name: 'Gets the position of the specified sop instance',
|
||||
callback: sopInstanceLocation,
|
||||
},
|
||||
};
|
||||
listeners = {};
|
||||
registeredImageLoadStrategies = {};
|
||||
@ -648,6 +660,39 @@ export default class HangingProtocolService extends PubSubService {
|
||||
return viewportsToUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a computed options value, or a copy of the options
|
||||
* This allows computing values such as the initial image index to use
|
||||
* based on custom attribute functions, the same as the validators.
|
||||
* Computing individual values is something that can be declared statically
|
||||
* as long as the named functions are provided ahead of time, which is much
|
||||
* simpler than recomputing the entire protocol.
|
||||
*/
|
||||
public getComputedOptions(
|
||||
options: Record<string, unknown>,
|
||||
displaySetUIDs: string[]
|
||||
) {
|
||||
const computed = { ...options };
|
||||
let displaySets;
|
||||
for (const key in computed) {
|
||||
const value = computed[key];
|
||||
if (!value) continue;
|
||||
if (value.custom) {
|
||||
if (!displaySets) {
|
||||
displaySets = this.displaySets.filter(
|
||||
displaySet =>
|
||||
displaySetUIDs.indexOf(displaySet.displaySetInstanceUID) !== -1
|
||||
);
|
||||
}
|
||||
computed[key] = this.customAttributeRetrievalCallbacks[
|
||||
value.custom
|
||||
].callback.call(computed, displaySets);
|
||||
if (computed[key] === undefined) computed[key] = computed.defaultValue;
|
||||
}
|
||||
}
|
||||
return computed;
|
||||
}
|
||||
|
||||
/**
|
||||
* It applied the protocol to the current studies and display sets based on the
|
||||
* protocolId that is provided.
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { getSplitParam } from '../../utils';
|
||||
|
||||
/** Indicates if the given display set is the one specified in the
|
||||
* displaySet parameter in the URL
|
||||
* The parameters are:
|
||||
* initialSeriesInstanceUID
|
||||
* initialSOPInstanceUID
|
||||
*/
|
||||
const isDisplaySetFromUrl = (displaySet): boolean => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const initialSeriesInstanceUID = getSplitParam(
|
||||
'initialseriesinstanceuid',
|
||||
params
|
||||
);
|
||||
const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid', params);
|
||||
if (!initialSeriesInstanceUID && !initialSOPInstanceUID) return false;
|
||||
const isSeriesMatch =
|
||||
!initialSeriesInstanceUID ||
|
||||
initialSeriesInstanceUID.some(
|
||||
seriesUID => displaySet.SeriesInstanceUID === seriesUID
|
||||
);
|
||||
const isSopMatch =
|
||||
!initialSOPInstanceUID ||
|
||||
displaySet.instances?.some?.(instance =>
|
||||
initialSOPInstanceUID.some(sopUID => sopUID === instance.SOPInstanceUID)
|
||||
);
|
||||
return isSeriesMatch && isSopMatch;
|
||||
};
|
||||
|
||||
/** Returns the index location of the requested image, or the defaultValue in this.
|
||||
* Returns undefined to fallback to the defaultValue
|
||||
*/
|
||||
function sopInstanceLocation(displaySets) {
|
||||
const displaySet = displaySets[0];
|
||||
if (!displaySet) return;
|
||||
const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid');
|
||||
if (!initialSOPInstanceUID) return;
|
||||
|
||||
const index = displaySet.instances.findIndex(instance =>
|
||||
initialSOPInstanceUID.includes(instance.SOPInstanceUID)
|
||||
);
|
||||
// Need to return in the initial position specified format.
|
||||
return index === -1 ? undefined : { index };
|
||||
}
|
||||
|
||||
export { isDisplaySetFromUrl, sopInstanceLocation };
|
||||
@ -70,8 +70,8 @@ export type ConstraintValue =
|
||||
| boolean
|
||||
| []
|
||||
| {
|
||||
value: string | number | boolean | [];
|
||||
};
|
||||
value: string | number | boolean | [];
|
||||
};
|
||||
|
||||
export type Constraint = {
|
||||
// This value exactly
|
||||
@ -135,19 +135,27 @@ export type SyncGroup = {
|
||||
target?: boolean;
|
||||
};
|
||||
|
||||
/** Declares a custom option, that is a computed type value */
|
||||
export type CustomOptionAttribute<T> = {
|
||||
custom: string;
|
||||
defaultValue?: T;
|
||||
};
|
||||
|
||||
export type CustomOption<T> = CustomOptionAttribute<T> | T;
|
||||
|
||||
export type initialImageOptions = {
|
||||
index?: number;
|
||||
preset?: string; // todo: type more
|
||||
};
|
||||
|
||||
export type ViewportOptions = {
|
||||
toolGroupId?: string;
|
||||
viewportType?: string;
|
||||
toolGroupId?: CustomOption<string>;
|
||||
viewportType?: CustomOption<string>;
|
||||
id?: string;
|
||||
orientation?: string;
|
||||
orientation?: CustomOption<string>;
|
||||
viewportId?: string;
|
||||
initialImageOptions?: initialImageOptions;
|
||||
syncGroups?: SyncGroup[];
|
||||
initialImageOptions?: CustomOption<initialImageOptions>;
|
||||
syncGroups?: CustomOption<SyncGroup>[];
|
||||
customViewportProps?: Record<string, unknown>;
|
||||
// Set to true to allow non-matching drag and drop or options provided
|
||||
// from options.displaySetSelectorsMap
|
||||
|
||||
@ -33,6 +33,7 @@ import {
|
||||
seriesSortCriteria,
|
||||
} from './sortStudy';
|
||||
import { subscribeToNextViewportGridChange } from './subscribeToNextViewportGridChange';
|
||||
import { splitComma, getSplitParam } from './splitComma';
|
||||
|
||||
// Commented out unused functionality.
|
||||
// Need to implement new mechanism for derived displaySets using the displaySetManager.
|
||||
@ -71,6 +72,8 @@ const utils = {
|
||||
roundNumber,
|
||||
downloadCSVReport,
|
||||
subscribeToNextViewportGridChange,
|
||||
splitComma,
|
||||
getSplitParam,
|
||||
};
|
||||
|
||||
export {
|
||||
@ -100,6 +103,8 @@ export {
|
||||
debounce,
|
||||
roundNumber,
|
||||
downloadCSVReport,
|
||||
splitComma,
|
||||
getSplitParam,
|
||||
};
|
||||
|
||||
export default utils;
|
||||
|
||||
@ -13,6 +13,8 @@ describe('Top level exports', () => {
|
||||
'sortStudyInstances',
|
||||
'sortStudySeries',
|
||||
'sortingCriteria',
|
||||
'splitComma',
|
||||
'getSplitParam',
|
||||
'isLowPriorityModality',
|
||||
'writeScript',
|
||||
'debounce',
|
||||
|
||||
31
platform/core/src/utils/splitComma.ts
Normal file
31
platform/core/src/utils/splitComma.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/** Splits a list of stirngs by commas within the strings */
|
||||
const splitComma = (strings: string[]): string[] => {
|
||||
if (!strings) return null;
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
const comma = strings[i].indexOf(',');
|
||||
if (comma !== -1) {
|
||||
const splits = strings[i].split(/,/);
|
||||
strings.splice(i, 1, ...splits);
|
||||
}
|
||||
}
|
||||
return strings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of the comma split parameters from the given URL search params
|
||||
* @param lowerCaseKey - lower case search parameter value
|
||||
* @param params - URLSearchParams
|
||||
* @returns Array of comma split items matching, or null
|
||||
*/
|
||||
const getSplitParam = (
|
||||
lowerCaseKey: string,
|
||||
params = new URLSearchParams(window.location.search)
|
||||
): string[] => {
|
||||
return splitComma(
|
||||
[...params]
|
||||
.find(([key, value]) => key.toLowerCase() === lowerCaseKey && value)
|
||||
?.slice?.(1)
|
||||
);
|
||||
};
|
||||
|
||||
export { splitComma, getSplitParam };
|
||||
@ -110,17 +110,46 @@ You can open more than one study in the Viewer by adding the `StudyInstanceUIDs`
|
||||
:::tip
|
||||
|
||||
You can ues this feature to open a current and prior study in the Viewer.
|
||||
Read more in the [Hanging Protocol Module](../platform/extensions/modules/hpModule.md#matching-on-prior-study-with-uid) section
|
||||
Read more in the [Hanging Protocol Module](../platform/extensions/modules/hpModule.md#matching-on-prior-study-with-uid) section. You can also use commas to separate
|
||||
values.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
### SeriesInstanceUIDs
|
||||
### SeriesInstanceUID and initialSeriesInstanceUID
|
||||
|
||||
Sometimes you need to only open a specific series in a study, you can do that by
|
||||
Sometimes you need to only retrieve a specific series in a study, you can do
|
||||
that by providing series level QIDO query parameters in the URL such as
|
||||
SeriesInstanceUID or SeriesNumber. This does NOT work with instance or study
|
||||
level parameters. For example:
|
||||
|
||||
```js
|
||||
/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1&SeriesInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125095748.1
|
||||
http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1&SeriesInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125113545.4
|
||||
```
|
||||
|
||||
This will only open the viewer with one series (one displaySet).
|
||||
This will only open the viewer with one series (one displaySet) loaded, and no
|
||||
queries made for any other series.
|
||||
|
||||
Alternatively, sometimes you want to just open the study on a specified series
|
||||
and/or display a particular sop instance, which you can accomplish using:
|
||||
`initialSeriesInstanceUID` and/or `initialSOPInstanceUID`
|
||||
to select the series to open on, but allowing other
|
||||
series to be present in the study browser panel. This is the same behaviour
|
||||
as in OHIF 2.0, albeit on different URL parameters. For example:
|
||||
|
||||
```js
|
||||
http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1&initialSeriesInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125113545.4
|
||||
```
|
||||
|
||||
Note that you can combine these, if you want to load a specific set of series
|
||||
plus show an initial one as the first one selected, for example:
|
||||
|
||||
```js
|
||||
http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1&SeriesInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125113545.4&initialSopInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125113546.1
|
||||
```
|
||||
|
||||
### hangingProtocolId
|
||||
|
||||
You can select the initial hanging protocol to apply by using the
|
||||
hangingProtocolId parameter. The selected parameter must be available in a
|
||||
hangingProtocolModule registration, but does not have to be active.
|
||||
|
||||
@ -218,7 +218,8 @@ the display set is the same as the other viewports, but the
|
||||
from the display set selector which isn't already filling a view.
|
||||
|
||||
## Custom Attribute
|
||||
In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance,
|
||||
In some situations, you might want to match based on a custom
|
||||
attribute and not the DICOM tags. For instance,
|
||||
if you have assigned a `timepointId` to each study, and you want to match based on it.
|
||||
Good news is that, in `OHIF-v3` you can define you custom attribute and use it for matching.
|
||||
|
||||
@ -294,3 +295,18 @@ function modeFactory() {
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Attributes for Viewport Options
|
||||
|
||||
The custom attributes can also be used for viewport options. This example,
|
||||
from the default hanging protocol navigates the image to the image
|
||||
specified in the URL:
|
||||
|
||||
```javascript
|
||||
viewportOptions: {
|
||||
initialImageOptions: {
|
||||
// custom attribute name is selected by 'custom'
|
||||
custom: 'sopInstanceLocation',
|
||||
// This is the value returned if the above doesn't return anything
|
||||
defaultValue: { index: 5 },
|
||||
```
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
describe('OHIF HP', () => {
|
||||
beforeEach(() => {
|
||||
const beforeSetup = () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
|
||||
'&hangingProtocolId=@ohif/hp-extension.mn'
|
||||
@ -7,15 +7,19 @@ describe('OHIF HP', () => {
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
});
|
||||
};
|
||||
|
||||
it('Should display 3 up', () => {
|
||||
beforeSetup();
|
||||
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
it('Should navigate next/previous stage', () => {
|
||||
beforeSetup();
|
||||
|
||||
cy.get('body').type(',');
|
||||
cy.wait(250);
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
@ -28,4 +32,19 @@ describe('OHIF HP', () => {
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
|
||||
it('Should navigate to display set specified', () => {
|
||||
// This filters by series instance UID, meaning there will only be 1 thumbnail
|
||||
// It applies the initial SOP instance, navigating to that image
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
|
||||
'&SeriesInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125113545.4&initialSopInstanceUID=1.3.6.1.4.1.25403.345050719074.3824.20170125113546.1'
|
||||
);
|
||||
cy.expectMinimumThumbnails(1);
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
|
||||
// The specified series/sop UID's are index 101, so ensure that image is displayed
|
||||
cy.get('@viewportInfoTopRight').should('contains.text', 'I:6');
|
||||
});
|
||||
});
|
||||
|
||||
@ -99,9 +99,10 @@ function ViewerViewportGrid(props) {
|
||||
return {
|
||||
displaySetInstanceUIDs: displaySetUIDsToHang,
|
||||
displaySetOptions: displaySetUIDsToHangOptions,
|
||||
viewportOptions: {
|
||||
...viewportOptions,
|
||||
},
|
||||
viewportOptions: hangingProtocolService.getComputedOptions(
|
||||
viewportOptions,
|
||||
displaySetUIDsToHang
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -279,8 +279,10 @@ export default function ModeRoute({
|
||||
const filters =
|
||||
Array.from(query.keys()).reduce(
|
||||
(acc: Record<string, string>, val: string) => {
|
||||
if (val !== 'StudyInstanceUIDs') {
|
||||
if (['seriesInstanceUID', 'SeriesInstanceUID'].includes(val)) {
|
||||
const lowerVal = val.toLowerCase();
|
||||
if (lowerVal !== 'studyinstanceuids') {
|
||||
// Not sure why the case matters here - it doesn't in the URL
|
||||
if (lowerVal === 'seriesinstanceuid') {
|
||||
return {
|
||||
...acc,
|
||||
seriesInstanceUID: query.get(val),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user