Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# AI assistant memory
memories/
148 changes: 148 additions & 0 deletions src/core/ResliceRepresentation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import vtkImageResliceMapper, {
vtkImageResliceMapper as IvtkImageResliceMapper,
} from '@kitware/vtk.js/Rendering/Core/ImageResliceMapper';
import { SlabTypes } from '@kitware/vtk.js/Rendering/Core/ImageResliceMapper/Constants';
import { forwardRef, PropsWithChildren, useRef } from 'react';
import useComparableEffect from '../utils/useComparableEffect';
import SliceRepresentation, {
SliceRepresentationProps,
} from './SliceRepresentation';

export interface ResliceRepresentationProps extends SliceRepresentationProps {
/**
* Slice plane
*/
slicePlane?: any;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a vtkPlane?

Suggested change
slicePlane?: any;
slicePlane?: vtkPlane;


/**
* Optional slice polydata
*/
slicePolyData?: any;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
slicePolyData?: any;
slicePolyData?: vtkPolyData;


/**
* Slab type
*/
slabType?: number;

/**
* Slab thickness
*/
slabThickness?: number;

/**
* Slab trapezoid integration
*/
slabTrapezoidIntegration?: boolean;
}

export default forwardRef(function ResliceRepresentation(
props: PropsWithChildren<ResliceRepresentationProps>,
fwdRef
) {
const {
slicePlane,
slicePolyData,
slabType = SlabTypes.MEAN,
slabThickness = 0.0,
slabTrapezoidIntegration = false,
mapperInstance: providedMapper,
...sliceProps
} = props;

// Create reslice mapper once and reuse it across renders.
const internalMapperRef = useRef<IvtkImageResliceMapper | null>(null);
if (!internalMapperRef.current) {
internalMapperRef.current = vtkImageResliceMapper.newInstance();
}
const mapperInstance = (providedMapper ||
internalMapperRef.current) as IvtkImageResliceMapper;

// Handle reslice-specific mapper updates
useComparableEffect(
() => {
if (!providedMapper && mapperInstance) {
trackModified(true);
mapperInstance.setSlicePlane(slicePlane);
}
},
[slicePlane, providedMapper],
([cur, prevMapped], [prev, prevProvided]) =>
cur === prev && prevMapped === prevProvided
);

useComparableEffect(
() => {
if (!providedMapper && mapperInstance) {
trackModified(true);
mapperInstance.setSlicePolyData(slicePolyData);
}
},
[slicePolyData, providedMapper],
([cur, prevMapped], [prev, prevProvided]) =>
cur === prev && prevMapped === prevProvided
);

useComparableEffect(
() => {
if (!providedMapper && mapperInstance) {
trackModified(true);
if (
slabType != null &&
slabType >= SlabTypes.MIN &&
slabType <= SlabTypes.SUM
) {
mapperInstance.setSlabType(slabType);
}
}
},
[slabType, providedMapper],
([cur, prevMapped], [prev, prevProvided]) =>
cur === prev && prevMapped === prevProvided
);

useComparableEffect(
() => {
if (!providedMapper && mapperInstance) {
trackModified(true);
if (slabThickness != null) {
mapperInstance.setSlabThickness(slabThickness);
}
}
},
[slabThickness, providedMapper],
([cur, prevMapped], [prev, prevProvided]) =>
cur === prev && prevMapped === prevProvided
);

useComparableEffect(
() => {
if (!providedMapper && mapperInstance) {
trackModified(true);
if (slabTrapezoidIntegration != null) {
mapperInstance.setSlabTrapezoidIntegration(
slabTrapezoidIntegration ? 1 : 0
);
}
}
},
[slabTrapezoidIntegration, providedMapper],
([cur, prevMapped], [prev, prevProvided]) =>
cur === prev && prevMapped === prevProvided
);

function trackModified(changed: boolean) {
if (changed) {
// Trigger render

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we leave this implementation empty for now?
Here's what I got from Claude AI:

It does nothing. But in the parent SliceRepresentation, trackModified comes from useBooleanAccumulator() and is what actually drives renderer.requestRender(). So every trackModified(true) call in the five reslice effects (slicePlane, slicePolyData, slabType, slabThickness, slabTrapezoidIntegration) is dead code with a comment promising a render that never happens.
The practical consequence: the effects do call mapperInstance.setSlabThickness(...) etc., so the mapper object is updated — but no render is requested. And because the reslice-specific props are destructured out and not forwarded to (they go into the discarded reslice locals, only ...sliceProps is passed down), a change to, say, slabThickness alone won't cause the child to re-render either. So changing those props at runtime produces no visible update until some unrelated interaction forces a render. Worth testing directly — it lines up with floryst's note that he hasn't actually tested it yet.
The fix is to thread the real trackModified/render-request mechanism through instead of shadowing it with a stub.

}
}

return (
<SliceRepresentation
{...sliceProps}
mapperInstance={mapperInstance as any}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is any needed here?
Can it be fixed by using vtkAbstractImageMapper in SliceRepresentation at line 55?
change mapperInstance?: AbstractImageMapper; to mapperInstance?: vtkAbstractImageMapper;

ref={fwdRef}
>
{props.children}
</SliceRepresentation>
);
});
16 changes: 16 additions & 0 deletions src/core/ShareDataSet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,19 @@ export function RegisterDataSet(props: RegisterDataSetProps) {

// --- //

// Store the connection function so it can be evaluated lazily when
// dataAvailable fires (i.e. after the async data load completes).
const connRef = useRef<(() => unknown) | null>(null);

const downstream = useMemo<IDownstream>(
() => ({
setInputData(obj) {
share.register(id, obj);
},
setInputConnection(conn) {
// Persist the port function; don't evaluate it yet — the upstream
// algorithm may not have data until an async operation completes.
connRef.current = conn;
downstream.setInputData(conn());
},
}),
Expand All @@ -175,10 +182,14 @@ export function RegisterDataSet(props: RegisterDataSetProps) {
const mockRepresentation = useMemo<IRepresentation>(
() => ({
dataChanged() {
// Re-evaluate the connection to capture the latest output.
if (connRef.current) share.register(id, connRef.current());
share.dispatchDataChanged(id);
dataChangedEvent.current.dispatchEvent();
},
dataAvailable() {
// Re-evaluate the connection NOW, when the data is actually ready.
if (connRef.current) share.register(id, connRef.current());
share.dispatchDataAvailable(id);
dataAvailableEvent.current.dispatchEvent();
},
Expand Down Expand Up @@ -226,3 +237,8 @@ export function UseDataSet(props: UseDataSetProps) {

return null;
}

/**
* ShareDataSet is an alias for UseDataSet for backwards compatibility
*/
export const ShareDataSet = UseDataSet;
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ export { default as PolyData } from './core/PolyData';
export type { PolyDataProps } from './core/PolyData';
export { default as Reader } from './core/Reader';
export type { ReaderProps } from './core/Reader';
export { default as ResliceRepresentation } from './core/ResliceRepresentation';
export type { ResliceRepresentationProps } from './core/ResliceRepresentation';
export {
RegisterDataSet,
ShareDataSet,
ShareDataSetRoot,
UseDataSet,
} from './core/ShareDataSet';
Expand Down
Loading
Loading