This commit is contained in:
mario
2025-03-07 13:47:44 +07:00
commit c4efec5a14
3358 changed files with 303774 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
export default (study, extraData) =>
Math.max(...(extraData?.displaySets?.map?.(ds => ds.numImageFrames ?? 0) || [0]));

View File

@@ -0,0 +1 @@
export default (study, extraData) => extraData?.displaySets?.length;

View File

@@ -0,0 +1,33 @@
/**
* This function extracts an attribute from the already matched display sets, and
* compares it to the attribute in the current display set, and indicates if they match.
* From 'this', it uses:
* `sameAttribute` as the attribute name to look for
* `sameDisplaySetId` as the display set id to look for
* From `options`, it looks for
*/
export default function (displaySet, options) {
const { sameAttribute, sameDisplaySetId } = this;
if (!sameAttribute) {
console.log('sameAttribute not defined in', this);
return `sameAttribute not defined in ${this.id}`;
}
if (!sameDisplaySetId) {
console.log('sameDisplaySetId not defined in', this);
return `sameDisplaySetId not defined in ${this.id}`;
}
const { displaySetMatchDetails, displaySets } = options;
const match = displaySetMatchDetails.get(sameDisplaySetId);
if (!match) {
console.log('No match for display set', sameDisplaySetId);
return false;
}
const { displaySetInstanceUID } = match;
const altDisplaySet = displaySets.find(it => it.displaySetInstanceUID == displaySetInstanceUID);
if (!altDisplaySet) {
console.log('No display set found with', displaySetInstanceUID, 'in', displaySets);
return false;
}
const testValue = altDisplaySet[sameAttribute];
return testValue === displaySet[sameAttribute];
}

View File

@@ -0,0 +1,80 @@
/**
* Coding values is a map of simple string coding values to a set of
* attributes associated with the coding value.
*
* The simple string is in the format `<codingSchemeDesignator>:<codingValue>`
* That allows extracting the DICOM attributes from the designator/value, and
* allows for passing around the simple string.
* The additional attributes contained in the object include:
* * text - this is the coding scheme text display value, and may be language specific
* * type - this defines a named type, typically 'site'. Different names can be used
* to allow setting different findingSites values in order to define a hierarchy.
* * color - used to apply annotation color
* It is also possible to define additional attributes here, used by custom
* extensions.
*
* See https://dicom.nema.org/medical/dicom/current/output/html/part16.html
* for definitions of SCT and other code values.
*/
const codingValues = {
id: 'codingValues',
// Sites
'SCT:69536005': {
text: 'Head',
type: 'site',
},
'SCT:45048000': {
text: 'Neck',
type: 'site',
},
'SCT:818981001': {
text: 'Abdomen',
type: 'site',
},
'SCT:816092008': {
text: 'Pelvis',
type: 'site',
},
// Findings
'SCT:371861004': {
text: 'Mild intimal coronary irregularities',
color: 'green',
},
'SCT:194983005': {
text: 'Aortic insufficiency',
color: 'darkred',
},
'SCT:399232001': {
text: '2-chamber',
},
'SCT:103340004': {
text: 'SAX',
},
'SCT:91134007': {
text: 'MV',
},
'SCT:122972007': {
text: 'PV',
},
// Orientations
'SCT:24422004': {
text: 'Axial',
color: '#000000',
type: 'orientation',
},
'SCT:81654009': {
text: 'Coronal',
color: '#000000',
type: 'orientation',
},
'SCT:30730003': {
text: 'Sagittal',
color: '#000000',
type: 'orientation',
},
};
export default codingValues;

View File

@@ -0,0 +1,26 @@
const codeMenuItem = {
id: '@ohif/contextMenuAnnotationCode',
/** Applies the code value setup for this item */
transform: function (customizationService) {
const { code: codeRef } = this;
if (!codeRef) {
throw new Error(`item ${this} has no code ref`);
}
const codingValues = customizationService.get('codingValues');
const code = codingValues[codeRef];
return {
...this,
codeRef,
code: { ref: codeRef, ...code },
label: code.text,
commands: [
{
commandName: 'updateMeasurement',
},
],
};
},
};
export default codeMenuItem;

View File

@@ -0,0 +1,100 @@
const findingsContextMenu = {
id: 'measurementsContextMenu',
customizationType: 'ohif.contextMenu',
menus: [
{
id: 'forExistingMeasurement',
// selector restricts context menu to when there is nearbyToolData
selector: ({ nearbyToolData }) => !!nearbyToolData,
items: [
{
customizationType: 'ohif.contextSubMenu',
label: 'Site',
actionType: 'ShowSubMenu',
subMenu: 'siteSelectionSubMenu',
},
{
customizationType: 'ohif.contextSubMenu',
label: 'Finding',
actionType: 'ShowSubMenu',
subMenu: 'findingSelectionSubMenu',
},
{
// customizationType is implicit here in the configuration setup
label: 'Delete Measurement',
commands: [
{
commandName: 'deleteMeasurement',
},
],
},
{
label: 'Add Label',
commands: [
{
commandName: 'setMeasurementLabel',
},
],
},
// The example below shows how to include a delegating sub-menu,
// Only available on the @ohif/mnGrid hanging protocol
// To demonstrate, select the 3x1 layout from the protocol menu
// and right click on a measurement.
{
label: 'IncludeSubMenu',
selector: ({ protocol }) => protocol?.id === '@ohif/mnGrid',
delegating: true,
subMenu: 'orientationSelectionSubMenu',
},
],
},
{
id: 'orientationSelectionSubMenu',
selector: ({ nearbyToolData }) => false,
items: [
{
customizationType: '@ohif/contextMenuAnnotationCode',
code: 'SCT:24422004',
},
{
customizationType: '@ohif/contextMenuAnnotationCode',
code: 'SCT:81654009',
},
],
},
{
id: 'findingSelectionSubMenu',
selector: ({ nearbyToolData }) => false,
items: [
{
customizationType: '@ohif/contextMenuAnnotationCode',
code: 'SCT:371861004',
},
{
customizationType: '@ohif/contextMenuAnnotationCode',
code: 'SCT:194983005',
},
],
},
{
id: 'siteSelectionSubMenu',
selector: ({ nearbyToolData }) => !!nearbyToolData,
items: [
{
customizationType: '@ohif/contextMenuAnnotationCode',
code: 'SCT:69536005',
},
{
customizationType: '@ohif/contextMenuAnnotationCode',
code: 'SCT:45048000',
},
],
},
],
};
export default findingsContextMenu;

View File

@@ -0,0 +1,5 @@
import codingValues from './codingValues';
import contextMenuCodeItem from './contextMenuCodeItem';
import findingsContextMenu from './findingsContextMenu';
export { codingValues, contextMenuCodeItem, findingsContextMenu };

View File

@@ -0,0 +1,14 @@
import { codingValues, contextMenuCodeItem, findingsContextMenu } from './custom-context-menu';
export default function getCustomizationModule() {
return [
{
name: 'custom-context-menu',
value: [codingValues, contextMenuCodeItem, findingsContextMenu],
},
{
name: 'contextMenuCodeItem',
value: [contextMenuCodeItem],
},
];
}

View File

@@ -0,0 +1,17 @@
import hpMN from './hpMN';
const hangingProtocols = [
{
name: '@ohif/hp-extension.mn',
protocol: hpMN,
},
];
/**
* Registers a single study hanging protocol which can be referenced as
* `@ohif/hp-exgtension.mn`, that has initial layouts which show images
* only display sets, up to a 2x2 view.
*/
export default function getHangingProtocolModule() {
return hangingProtocols;
}

View File

@@ -0,0 +1,239 @@
import { Types } from '@ohif/core';
const viewport0a = {
viewportOptions: {
viewportId: 'viewportA',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
};
const viewport1b = {
viewportOptions: {
viewportId: 'viewportB',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 1,
id: 'defaultDisplaySetId',
},
],
};
const viewport2c = {
viewportOptions: {
viewportId: 'viewportC',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 2,
id: 'defaultDisplaySetId',
},
],
};
const viewport3d = {
viewportOptions: {
viewportId: 'viewportD',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 3,
id: 'defaultDisplaySetId',
},
],
};
const viewport4e = {
viewportOptions: {
viewportId: 'viewportE',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 4,
id: 'defaultDisplaySetId',
},
],
};
const viewport5f = {
viewportOptions: {
viewportId: 'viewportF',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 5,
id: 'defaultDisplaySetId',
},
],
};
const viewport3a = {
viewportOptions: {
viewportId: 'viewportA',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 3,
id: 'defaultDisplaySetId',
},
],
};
const viewport2b = {
viewportOptions: {
viewportId: 'viewportB',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 2,
id: 'defaultDisplaySetId',
},
],
};
const viewport1c = {
viewportOptions: {
viewportId: 'viewportC',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 1,
id: 'defaultDisplaySetId',
},
],
};
const viewport0d = {
viewportOptions: {
viewportId: 'viewportD',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 0,
id: 'defaultDisplaySetId',
},
],
};
const viewportStructure = {
layoutType: 'grid',
properties: {
rows: 2,
columns: 2,
},
};
const viewportStructure32 = {
layoutType: 'grid',
properties: {
rows: 2,
columns: 3,
},
};
/**
* This hanging protocol is a test hanging protocol used to apply various
* layouts in different positions for display, re-using earlier names in
* various orders.
*/
const hpTestSwitch: Types.HangingProtocol.Protocol = {
hasUpdatedPriorsInformation: false,
id: '@ohif/mnTestSwitch',
description: 'Has various hanging protocol grid layouts',
name: 'Test Switch',
protocolMatchingRules: [
{
id: 'OneOrMoreSeries',
weight: 25,
attribute: 'numberOfDisplaySetsWithImages',
constraint: {
greaterThan: 0,
},
},
],
toolGroupIds: ['default'],
displaySetSelectors: {
defaultDisplaySetId: {
seriesMatchingRules: [
{
attribute: 'numImageFrames',
constraint: {
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,
},
},
],
},
},
defaultViewport: {
viewportOptions: {
viewportType: 'stack',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: -1,
},
],
},
stages: [
{
name: '2x2 0a1b2c3d',
viewportStructure,
viewports: [viewport0a, viewport1b, viewport2c, viewport3d],
},
{
name: '3x2 0a1b4e2c3d5f',
viewportStructure: viewportStructure32,
// Note the following structure simply preserves the viewportId for
// a given screen position
viewports: [viewport0a, viewport1b, viewport4e, viewport2c, viewport3d, viewport5f],
},
{
name: '2x2 1c0d3a2b',
viewportStructure,
viewports: [viewport1c, viewport0d, viewport3a, viewport2b],
},
{
name: '2x2 3a2b1c0d',
viewportStructure,
viewports: [viewport3a, viewport2b, viewport1c, viewport0d],
},
],
numberOfPriorsReferenced: -1,
};
export default hpTestSwitch;

View File

@@ -0,0 +1,5 @@
import packageJson from '../package.json';
const id = packageJson.name;
export { id };

View File

@@ -0,0 +1,64 @@
import { Types } from '@ohif/core';
import { id } from './id';
import hpTestSwitch from './hpTestSwitch';
import getCustomizationModule from './getCustomizationModule';
// import {setViewportZoomPan, storeViewportZoomPan } from './custom-viewport/setViewportZoomPan';
import sameAs from './custom-attribute/sameAs';
import numberOfDisplaySets from './custom-attribute/numberOfDisplaySets';
import maxNumImageFrames from './custom-attribute/maxNumImageFrames';
/**
* The test extension provides additional behaviour for testing various
* customizations and settings for OHIF.
*/
const testExtension: Types.Extensions.Extension = {
/**
* Only required property. Should be a unique value across all extensions.
*/
id,
/** Register additional behaviour:
* * HP custom attribute seriesDescriptions to retrieve an array of all series descriptions
* * HP custom attribute numberOfDisplaySets to retrieve the number of display sets
* * HP custom attribute numberOfDisplaySetsWithImages to retrieve the number of display sets containing images
* * HP custom attribute to return a boolean true, when the attribute sameAttribute has the same
* value as another series description in an already matched display set selector named with the value
* in `sameDisplaySetId`
*/
preRegistration: ({ servicesManager }: Types.Extensions.ExtensionParams) => {
const { hangingProtocolService } = servicesManager.services;
hangingProtocolService.addCustomAttribute(
'numberOfDisplaySets',
'Number of displays sets',
numberOfDisplaySets
);
hangingProtocolService.addCustomAttribute(
'maxNumImageFrames',
'Maximum of number of image frames',
maxNumImageFrames
);
hangingProtocolService.addCustomAttribute(
'sameAs',
'Match an attribute in an existing display set',
sameAs
);
},
/** Registers some customizations */
getCustomizationModule,
getHangingProtocolModule: () => {
return [
// Create a MxN hanging protocol available by default
{
name: hpTestSwitch.id,
protocol: hpTestSwitch,
},
];
},
};
export default testExtension;