Co-authored-by: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com> Co-authored-by: Chao Sui <chao.sui@curvebeamai.com> Co-authored-by: Jaseel-trenser <108331536+Jaseel-trenser@users.noreply.github.com>
144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import { vec3 } from 'gl-matrix';
|
|
import { dicomSplit } from './dicomSplit';
|
|
|
|
/**
|
|
* Combine the Per instance frame data, the shared frame data
|
|
* and the root data objects.
|
|
* The data is combined by taking nested sequence objects within
|
|
* the functional group sequences. Data that is directly contained
|
|
* within the functional group sequences, such as private creators
|
|
* will be ignored.
|
|
* This can be safely called with an undefined frame in order to handle
|
|
* single frame data. (eg frame is undefined is the same as frame===1).
|
|
*/
|
|
const combineFrameInstance = (frame, instance) => {
|
|
const {
|
|
PerFrameFunctionalGroupsSequence,
|
|
SharedFunctionalGroupsSequence,
|
|
NumberOfFrames,
|
|
ImageType,
|
|
} = instance;
|
|
|
|
instance.ImageType = dicomSplit(ImageType);
|
|
|
|
if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) {
|
|
const frameNumber = Number.parseInt(frame || 1);
|
|
|
|
// this is to fix NM multiframe datasets with position and orientation
|
|
// information inside DetectorInformationSequence
|
|
if (!instance.ImageOrientationPatient && instance.DetectorInformationSequence) {
|
|
instance.ImageOrientationPatient =
|
|
instance.DetectorInformationSequence[0].ImageOrientationPatient;
|
|
}
|
|
|
|
let ImagePositionPatientToUse = instance.ImagePositionPatient;
|
|
|
|
if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) {
|
|
let imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient;
|
|
let imageOrientationPatient = instance.ImageOrientationPatient;
|
|
|
|
imagePositionPatient = imagePositionPatient.map(it => Number(it));
|
|
imageOrientationPatient = imageOrientationPatient.map(it => Number(it));
|
|
const SpacingBetweenSlices = Number(instance.SpacingBetweenSlices);
|
|
|
|
// Calculate the position for the current frame
|
|
if (imageOrientationPatient && SpacingBetweenSlices) {
|
|
const rowOrientation = vec3.fromValues(
|
|
imageOrientationPatient[0],
|
|
imageOrientationPatient[1],
|
|
imageOrientationPatient[2]
|
|
);
|
|
|
|
const colOrientation = vec3.fromValues(
|
|
imageOrientationPatient[3],
|
|
imageOrientationPatient[4],
|
|
imageOrientationPatient[5]
|
|
);
|
|
|
|
const normalVector = vec3.cross(vec3.create(), rowOrientation, colOrientation);
|
|
|
|
const position = vec3.scaleAndAdd(
|
|
vec3.create(),
|
|
imagePositionPatient,
|
|
normalVector,
|
|
SpacingBetweenSlices * (frameNumber - 1)
|
|
);
|
|
|
|
ImagePositionPatientToUse = [position[0], position[1], position[2]];
|
|
}
|
|
}
|
|
|
|
// Cache the _parentInstance at the top level as a full copy to prevent
|
|
// setting values hard.
|
|
if (!instance._parentInstance) {
|
|
Object.defineProperty(instance, '_parentInstance', {
|
|
value: { ...instance },
|
|
});
|
|
}
|
|
const sharedInstance = createCombinedValue(
|
|
instance._parentInstance,
|
|
SharedFunctionalGroupsSequence?.[0],
|
|
'_shared'
|
|
);
|
|
const newInstance = createCombinedValue(
|
|
sharedInstance,
|
|
PerFrameFunctionalGroupsSequence?.[frameNumber - 1],
|
|
frameNumber
|
|
);
|
|
|
|
newInstance.ImagePositionPatient = ImagePositionPatientToUse ??
|
|
newInstance.ImagePositionPatient ?? [0, 0, frameNumber];
|
|
|
|
Object.defineProperty(newInstance, 'frameNumber', {
|
|
value: frameNumber,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true,
|
|
});
|
|
return newInstance;
|
|
} else {
|
|
return instance;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates a combined instance stored in the parent object which
|
|
* inherits from the parent instance the attributes in the functional groups.
|
|
* The storage key in the parent is in key
|
|
*/
|
|
function createCombinedValue(parent, functionalGroups, key) {
|
|
if (parent[key]) {
|
|
return parent[key];
|
|
}
|
|
// Exclude any proxying values
|
|
const newInstance = Object.create(parent);
|
|
Object.defineProperty(parent, key, {
|
|
value: newInstance,
|
|
writable: false,
|
|
enumerable: false,
|
|
});
|
|
if (!functionalGroups) {
|
|
return newInstance;
|
|
}
|
|
const shared = functionalGroups
|
|
? Object.values(functionalGroups)
|
|
.filter(Boolean)
|
|
.map(it => it[0])
|
|
.filter(it => typeof it === 'object')
|
|
: [];
|
|
|
|
// merge the shared first then the per frame to override
|
|
[...shared].forEach(item => {
|
|
if (item.SOPInstanceUID) {
|
|
// This sub-item is a previous value information item, so don't merge it
|
|
return;
|
|
}
|
|
Object.entries(item).forEach(([key, value]) => {
|
|
newInstance[key] = value;
|
|
});
|
|
});
|
|
return newInstance;
|
|
}
|
|
|
|
export default combineFrameInstance;
|