Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | 128x | import { BaseDisplaySet } from './BaseDisplaySet';
import { ImageStackDisplaySet } from './ImageStackDisplaySet';
import { isEcgInstance } from './isEcgInstance';
import { isVideoInstance } from './isVideoInstance';
import { isWsiInstance } from './isWsiInstance';
import type { IDisplaySet } from './IDisplaySet';
import type { InstanceGroup, ViewportTypeHint } from './types';
import {
getPreferredViewportType,
getViewportTypesForGroup,
} from './viewportTypes';
export type CreateDisplaySetFromGroupOptions = {
displaySetId?: string;
imageIds?: Iterable<string>;
/** 0-based index of this group among the series' split groups. */
splitNumber?: number;
descriptionName?: string;
};
/**
* Resolved data fields that custom attributes must never overwrite. They are
* declared `readonly` on the display set, but `readonly` is erased at runtime,
* so the constructor-assigned fields stay writable - a consumer split rule
* returning e.g. `{ imageIds: [...] }` would otherwise clobber the resolved ids
* and break the underlying-vs-frame invariant the viewports rely on.
*/
const RESERVED_ATTRIBUTE_KEYS = new Set<string>([
'imageIds',
'underlyingImageIds',
'instances',
'displaySetId',
]);
/**
* Returns true unless `key` resolves to a read-only accessor (getter without a
* setter) somewhere on the display set's prototype chain, so custom attributes
* never clobber a computed getter.
*/
function isAssignable(target: object, key: string): boolean {
let obj: object | null = target;
while (obj) {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
if (descriptor.get || descriptor.set) {
return typeof descriptor.set === 'function';
}
return descriptor.writable !== false;
}
obj = Object.getPrototypeOf(obj);
}
return true;
}
/**
* Runs the matched rule's `customAttributes` (if any) and spreads the returned
* attributes flat onto the display set (shared attributes are declared on
* IDisplaySet). A `viewportTypes` key in the returned attributes overrides the
* rule's default viewport types; `preferredViewportType` is kept in sync
* afterwards. Reserved data fields (see {@link RESERVED_ATTRIBUTE_KEYS}) and
* keys backed by a read-only accessor on the display set are skipped rather than
* overridden.
*/
function applyCustomAttributes(
displaySet: IDisplaySet,
group: InstanceGroup,
viewportTypes: readonly ViewportTypeHint[],
options: CreateDisplaySetFromGroupOptions
): void {
const { instances, matchedRule } = group;
const first = instances[0];
if (!matchedRule.customAttributes || !first) {
return;
}
const sopClassUids = [
...new Set(instances.map((i) => i.SOPClassUID).filter(Boolean)),
];
const isMultiFrame = Number(first.NumberOfFrames) > 1;
const attributes = matchedRule.customAttributes(
{ instance: first, isMultiFrame, sopClassUids, viewportTypes },
{
instances,
splitNumber: options.splitNumber,
descriptionName: options.descriptionName,
}
);
if (!attributes) {
return;
}
for (const [key, value] of Object.entries(attributes)) {
if (RESERVED_ATTRIBUTE_KEYS.has(key)) {
continue;
}
if (isAssignable(displaySet, key)) {
(displaySet as unknown as Record<string, unknown>)[key] = value;
}
}
// Keep the preferred viewport attribute consistent if customAttributes
// overrode the allowed viewport types.
displaySet.preferredViewportType = getPreferredViewportType(
displaySet.viewportTypes
);
}
/**
* Builds cornerstone display set metadata for an instance group.
*/
export function createDisplaySetFromGroup(
group: InstanceGroup,
options: CreateDisplaySetFromGroupOptions = {}
): IDisplaySet {
const viewportTypes = getViewportTypesForGroup(group);
const { instances } = group;
// A single series can split into multiple display sets (e.g. the DWI
// mixed-b-value split), so the default id folds in the 0-based `splitNumber`
// to stay unique within a series rather than collapsing every split to the
// bare SeriesInstanceUID. This is the same value callers pass to a viewport as
// `displaySetId` - the metadata id and the viewport/registry id are one.
const baseDisplaySetId =
instances[0]?.SeriesInstanceUID ??
`display-set-${instances[0]?.imageId ?? 'unknown'}`;
const displaySetId =
options.displaySetId ??
(options.splitNumber
? `${baseDisplaySetId}:${options.splitNumber}`
: baseDisplaySetId);
const first = instances[0];
let displaySet: IDisplaySet;
if (
first &&
(isVideoInstance(first) || isEcgInstance(first) || isWsiInstance(first))
) {
const imageIds = instances.map((i) => i.imageId).filter(Boolean);
displaySet = new BaseDisplaySet({
displaySetId,
viewportTypes,
instances,
imageIds: options.imageIds ?? imageIds,
underlyingImageIds: imageIds,
});
} else {
displaySet = ImageStackDisplaySet.fromInstances(instances, {
displaySetId,
viewportTypes,
imageIds: options.imageIds,
});
}
applyCustomAttributes(displaySet, group, viewportTypes, options);
return displaySet;
}
|