fix: Enhance multiframe instance handling and improve instance valida… (#5956)
fix: Enhance multiframe instance handling and improve instance validation - Updated `getDisplaySetInfo` to ensure local file frame imageIds resolve correctly, falling back to the source multiframe instance when necessary. - Improved instance filtering in `isDisplaySetReconstructable` to handle undefined instances more robustly. - Modified `App` prop types to make `routerBasename` optional and added new props for loading indicators and data sources. - Refactored `CinePlayer` component to replace Button with a div for better accessibility and interaction.
This commit is contained in:
parent
3069d33c99
commit
f5a66b9eea
@ -48,12 +48,20 @@ function getDisplaySetInfo(instances) {
|
|||||||
let firstTimePointInstances;
|
let firstTimePointInstances;
|
||||||
|
|
||||||
if (instances[0].NumberOfFrames > 1 && timePoints.length > 1) {
|
if (instances[0].NumberOfFrames > 1 && timePoints.length > 1) {
|
||||||
// handle multiframe dynamic volume
|
// Handle multiframe dynamic volumes. Local file frame imageIds do not
|
||||||
firstTimePointInstances = timePoints[0].map(imageId => metaData.get('instance', imageId));
|
// always resolve to a frame-level instance object, so keep resolved
|
||||||
|
// entries and fall back to the source multiframe instance when needed.
|
||||||
|
firstTimePointInstances = timePoints[0]
|
||||||
|
.map(imageId => metaData.get('instance', imageId))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (!firstTimePointInstances.length) {
|
||||||
|
firstTimePointInstances = [instances[0]];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// O(n) to convert it into a map and O(1) to find each instance
|
// O(n) to convert it into a map and O(1) to find each instance
|
||||||
instances.forEach(instance => instancesMap.set(instance.imageId, instance));
|
instances.forEach(instance => instancesMap.set(instance.imageId, instance));
|
||||||
firstTimePointInstances = timePoint.map(imageId => instancesMap.get(imageId));
|
firstTimePointInstances = timePoint.map(imageId => instancesMap.get(imageId)).filter(Boolean);
|
||||||
}
|
}
|
||||||
displaySetInfo = isDisplaySetReconstructable(firstTimePointInstances, appConfig);
|
displaySetInfo = isDisplaySetReconstructable(firstTimePointInstances, appConfig);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -184,12 +184,16 @@ App.propTypes = {
|
|||||||
config: PropTypes.oneOfType([
|
config: PropTypes.oneOfType([
|
||||||
PropTypes.func,
|
PropTypes.func,
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
routerBasename: PropTypes.string.isRequired,
|
routerBasename: PropTypes.string,
|
||||||
oidc: PropTypes.array,
|
oidc: PropTypes.array,
|
||||||
whiteLabeling: PropTypes.object,
|
whiteLabeling: PropTypes.object,
|
||||||
extensions: PropTypes.array,
|
extensions: PropTypes.array,
|
||||||
|
showLoadingIndicator: PropTypes.bool,
|
||||||
|
showStudyList: PropTypes.bool,
|
||||||
|
modes: PropTypes.array,
|
||||||
|
dataSources: PropTypes.array,
|
||||||
}),
|
}),
|
||||||
]).isRequired,
|
]),
|
||||||
/* Extensions that are "bundled" or "baked-in" to the application.
|
/* Extensions that are "bundled" or "baked-in" to the application.
|
||||||
* These would be provided at build time as part of they entry point. */
|
* These would be provided at build time as part of they entry point. */
|
||||||
defaultExtensions: PropTypes.array,
|
defaultExtensions: PropTypes.array,
|
||||||
|
|||||||
@ -11,12 +11,14 @@ const iopTolerance = 0.01;
|
|||||||
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
|
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
|
||||||
*/
|
*/
|
||||||
export default function isDisplaySetReconstructable(instances, appConfig) {
|
export default function isDisplaySetReconstructable(instances, appConfig) {
|
||||||
if (!instances.length) {
|
const definedInstances = instances?.filter(Boolean) || [];
|
||||||
|
|
||||||
|
if (!definedInstances.length) {
|
||||||
return { value: false };
|
return { value: false };
|
||||||
}
|
}
|
||||||
const firstInstance = instances[0];
|
const firstInstance = definedInstances[0];
|
||||||
|
|
||||||
const isMultiframe = firstInstance.NumberOfFrames > 1;
|
const isMultiframe = (firstInstance.NumberOfFrames || 0) > 1;
|
||||||
|
|
||||||
if (appConfig) {
|
if (appConfig) {
|
||||||
const rows = toNumber(firstInstance.Rows);
|
const rows = toNumber(firstInstance.Rows);
|
||||||
@ -30,16 +32,16 @@ export default function isDisplaySetReconstructable(instances, appConfig) {
|
|||||||
// in favor of the calculation by metadata (orientation and positions)
|
// in favor of the calculation by metadata (orientation and positions)
|
||||||
|
|
||||||
// Can't reconstruct if we only have one image.
|
// Can't reconstruct if we only have one image.
|
||||||
if (!isMultiframe && instances.length === 1) {
|
if (!isMultiframe && definedInstances.length === 1) {
|
||||||
return { value: false };
|
return { value: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't reconstruct if all instances don't have the ImagePositionPatient.
|
// Can't reconstruct if all instances don't have the ImagePositionPatient.
|
||||||
if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) {
|
if (!isMultiframe && !definedInstances.every(instance => instance.ImagePositionPatient)) {
|
||||||
return { value: false };
|
return { value: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedInstances = sortInstancesByPosition(instances);
|
const sortedInstances = sortInstancesByPosition(definedInstances);
|
||||||
|
|
||||||
return isMultiframe ? processMultiframe(sortedInstances[0]) : processSingleframe(sortedInstances);
|
return isMultiframe ? processMultiframe(sortedInstances[0]) : processSingleframe(sortedInstances);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,9 +103,10 @@ const CinePlayer: React.FC<CinePlayerProps> = ({
|
|||||||
onOpenChange={setPopoverOpen}
|
onOpenChange={setPopoverOpen}
|
||||||
>
|
>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<div
|
||||||
variant="ghost"
|
role="button"
|
||||||
className="h-full border-none bg-transparent p-0 hover:bg-transparent"
|
tabIndex={0}
|
||||||
|
className="h-full cursor-pointer border-none bg-transparent p-0"
|
||||||
>
|
>
|
||||||
<Numeric.Container
|
<Numeric.Container
|
||||||
mode="stepper"
|
mode="stepper"
|
||||||
@ -129,7 +130,7 @@ const CinePlayer: React.FC<CinePlayerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</Numeric.NumberStepper>
|
</Numeric.NumberStepper>
|
||||||
</Numeric.Container>
|
</Numeric.Container>
|
||||||
</Button>
|
</div>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
side="bottom"
|
side="bottom"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user