Skip to content
Merged
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
20 changes: 6 additions & 14 deletions PanTS-Demo/src/helpers/CornerstoneNifti2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1740,7 +1740,7 @@ export function stopCine() {
}

// ---------------------------------------------------------------------------
// Per-pane flip / rotate — like Cine, these act on whichever pane is currently
// Per-pane flip — like Cine, these act on whichever pane is currently
// "in focus" (VisualizationPage tracks that via scroll/click and passes it in).
// ---------------------------------------------------------------------------

Expand All @@ -1758,23 +1758,15 @@ export function flipPaneHorizontal(pane: CinePane): void {
}
}

// Rotates 90° clockwise from wherever the pane currently sits (cumulative — four
// clicks return to the start). NOTE: cornerstone's rotation-angle sign convention
// relative to "on-screen clockwise" isn't independently confirmed here — if a
// case turns out to visibly rotate counter-clockwise instead, flip the `+ 90`
// below to `- 90` (mod still needs the `+ 360` to stay positive in that case).
export function rotatePane90Clockwise(pane: CinePane): void {
// Mirrors the pane top-bottom. Same toggle behavior as flipPaneHorizontal: a second
// call on the same pane un-flips it, and flip() renders by itself.
export function flipPaneVertical(pane: CinePane): void {
const viewport = _getMprViewport(pane);
if (!viewport) return;
try {
const next = (viewport.getRotation() + 90) % 360;
viewport.setRotation(next);
// Unlike flip(), setRotation() only triggers a CAMERA_MODIFIED event — it
// never calls render() itself, so without this the rotation wouldn't show
// until some unrelated interaction happened to re-render the pane.
viewport.render();
viewport.flip({ flipVertical: true });
} catch (e) {
console.warn(`Rotate failed for pane "${pane}":`, e);
console.warn(`Vertical flip failed for pane "${pane}":`, e);
}
}

Expand Down
18 changes: 7 additions & 11 deletions PanTS-Demo/src/helpers/compareViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ export type CompareHandle = {
* queued, or the cells moved since the last pass), so the caller refits again. */
refit: (keepView?: boolean) => boolean;
resetView: () => void;
// Which viewport cine/flip/rotate act on — whichever pane was last clicked/scrolled.
// Which viewport cine/flip act on — whichever pane was last clicked/scrolled.
setFocusedViewport: (viewportId: string) => void;
// Dotted line in each case's other 2 panes for whichever of that case's panes was
// last focused — tracked independently per case, so both cases can show reference
// lines at once even though "focus" for cine/flip/rotate is a single global value.
// lines at once even though "focus" for cine/flip is a single global value.
setReferenceLines: (enabled: boolean) => void;
flipFocused: () => void;
rotateFocused90: () => void;
flipFocusedVertical: () => void;
startCine: (fps?: number) => boolean;
stopCine: () => void;
// Hands the primary mouse button (on BOTH cases at once) to a measurement tool or
Expand Down Expand Up @@ -1232,7 +1232,7 @@ async function buildCompare(els: CompareElements, src: CompareSources, gen: numb
eventTarget.addEventListener(tools.Enums.Events.ANNOTATION_REMOVED, annotationCases.onRemoved);
const caseKeyForAnnotation = annotationCases.caseKeyFor;

// --- Focus tracking: which viewport cine/flip/rotate act on (whichever pane was last
// --- Focus tracking: which viewport cine/flip act on (whichever pane was last
// clicked/scrolled), and which pane is each case's reference-line "source" (tracked
// independently per case so both cases can show reference lines simultaneously). ---
let focusedViewportId: string = A.ax;
Expand Down Expand Up @@ -1555,16 +1555,12 @@ async function buildCompare(els: CompareElements, src: CompareSources, gen: numb
console.warn("[compare] flip failed:", e);
}
},
rotateFocused90() {
flipFocusedVertical() {
const vp = engine.getViewport(focusedViewportId) as unknown as MprViewportLike | undefined;
if (!vp) return;
try {
const next = (vp.getRotation() + 90) % 360;
vp.setRotation(next);
// setRotation only triggers CAMERA_MODIFIED — it never calls render() itself.
vp.render();
vp?.flip({ flipVertical: true });
} catch (e) {
console.warn("[compare] rotate failed:", e);
console.warn("[compare] vertical flip failed:", e);
}
},
startCine(fps) {
Expand Down
10 changes: 5 additions & 5 deletions PanTS-Demo/src/routes/CompareViewerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
IconClick,
IconClipboardList,
IconFlipHorizontal,
IconFlipVertical,
IconGrid3x3,
IconHome,
IconLasso,
Expand All @@ -27,7 +28,6 @@ import {
IconPlayerPause,
IconPlayerPlay,
IconPointer,
IconRotateClockwise,
IconRuler2,
IconScanEye,
IconSettings,
Expand Down Expand Up @@ -387,7 +387,7 @@ export default function CompareViewerPage() {
};
const handlePaneHoverLeave = () => setHoverTip((t) => (t.visible ? { ...t, visible: false } : t));

// Focus (which pane cine/flip/rotate act on) is tracked inside compareViewer.ts itself
// Focus (which pane cine/flip act on) is tracked inside compareViewer.ts itself
// via its own mousedown/wheel listeners — this just forwards an explicit click there too,
// so clicking (without dragging) a pane also moves focus, matching the single viewer.
const handlePaneMouseDown = (viewportId: string) => () => handleRef.current?.setFocusedViewport(viewportId);
Expand Down Expand Up @@ -520,12 +520,12 @@ export default function CompareViewerPage() {
role="menuitem"
title="The focused pane — last one clicked or scrolled"
onClick={() => {
handleRef.current?.rotateFocused90();
handleRef.current?.flipFocusedVertical();
viewFlyout.close();
}}
>
<IconRotateClockwise size={18} />
<span>Rotate 90° clockwise</span>
<IconFlipVertical size={18} />
<span>Flip vertical</span>
</button>
</div>,
document.body
Expand Down
20 changes: 10 additions & 10 deletions PanTS-Demo/src/routes/VisualizationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import {
IconClick,
IconEye,
IconFlipHorizontal,
IconFlipVertical,
IconGrid3x3,
IconHome,
IconId,
IconKeyboard,
IconLasso,
IconLayoutSidebarRight,
IconListDetails, IconMicrophone, IconPlayerPause, IconPlayerPlay, IconPointer, IconReport,
IconRotateClockwise,
IconRuler2,
IconScanEye,
IconShare,
Expand Down Expand Up @@ -111,6 +111,7 @@ import {
ELLIPSE_TOOL,
enableVolume3D,
flipPaneHorizontal,
flipPaneVertical,
FREEHAND_ROI_TOOL,
getCrosshairMm,
getCurrentVolumeModality,
Expand All @@ -134,7 +135,6 @@ import {
resetMprOrientation,
releasePrimaryMouseTools,
ROI_TOOL,
rotatePane90Clockwise,
setActiveMaskEditTool,
setActiveMeasurementTool,
setFillOpacity,
Expand Down Expand Up @@ -1768,7 +1768,7 @@ function VisualizationPage({ liveRoom, soloChallenge, quizPractice }: Visualizat
const windowFlyout = useToolbarFlyout(); // CT window presets (stays open — a config panel)
const adjustFlyout = useToolbarFlyout(); // fill/border/brightness/contrast/zoom sliders + center/reset (stays open)
const measureFlyout = useToolbarFlyout(); // measurement tools + magnify loupe
const viewFlyout = useToolbarFlyout(); // hover-identify, reference lines, flip, rotate
const viewFlyout = useToolbarFlyout(); // hover-identify, reference lines, flip
const cineFlyout = useToolbarFlyout(); // play/pause + FPS (stays open — a live mini-panel, not a pick-and-dismiss menu)
const captureFlyout = useToolbarFlyout(); // snapshot, reading session, share link
const panelsFlyout = useToolbarFlyout(); // organs, organ stats, case metadata, measurements list
Expand Down Expand Up @@ -2711,10 +2711,10 @@ function VisualizationPage({ liveRoom, soloChallenge, quizPractice }: Visualizat
sessionRef.current?.log("view", `Flipped ${pane} horizontally`);
};

const handleRotate90Clockwise = () => {
const handleFlipVertical = () => {
const pane = focusedPane.getFocusedPane()
rotatePane90Clockwise(pane);
sessionRef.current?.log("view", `Rotated ${pane} 90° clockwise`);
flipPaneVertical(pane);
sessionRef.current?.log("view", `Flipped ${pane} vertically`);
};

// Reads cinePlaying directly rather than through setState's functional-updater form —
Expand Down Expand Up @@ -4696,7 +4696,7 @@ const aiAvailableOrgans = useMemo(() => {
)}
</div>

{/* View ▾ — hover-identify + reference lines (toggles) and flip/rotate
{/* View ▾ — hover-identify + reference lines (toggles) and flip
(one-shot actions on the focused pane). */}
<div className="vp-toolgroup" ref={viewFlyout.groupRef}>
<button
Expand Down Expand Up @@ -4761,12 +4761,12 @@ const aiAvailableOrgans = useMemo(() => {
role="menuitem"
title="The focused pane (last one scrolled or clicked)"
onClick={() => {
handleRotate90Clockwise();
handleFlipVertical();
viewFlyout.close();
}}
>
<IconRotateClockwise size={18} />
<span>Rotate 90° clockwise</span>
<IconFlipVertical size={18} />
<span>Flip vertical</span>
</button>
</div>,
document.body
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/cancerverseMasks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/compareHdCarry.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const handle = () => ({
setLinked: vi.fn(), setSyncCursor: vi.fn(), setSegVisible: vi.fn(), setSegOpacity: vi.fn(),
setOrganVisibility: vi.fn(), applyWindow: vi.fn(), applyZoom: vi.fn(), centerCursor: vi.fn(),
jumpToOrgan: vi.fn(), refit: vi.fn(), resetView: vi.fn(), setFocusedViewport: vi.fn(),
setReferenceLines: vi.fn(), flipFocused: vi.fn(), rotateFocused90: vi.fn(), startCine: vi.fn(() => true),
setReferenceLines: vi.fn(), flipFocused: vi.fn(), flipFocusedVertical: vi.fn(), startCine: vi.fn(() => true),
stopCine: vi.fn(), setActiveMeasurementTool: vi.fn(), clearMeasurements: vi.fn(),
getMeasurementSummaries: vi.fn(() => []), renameMeasurement: vi.fn(), removeMeasurement: vi.fn(),
jumpToMeasurement: vi.fn(), subscribeToMeasurementChanges: vi.fn(() => vi.fn()), destroy: vi.fn(),
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/compareViewer.smoke.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ beforeEach(() => {
setFocusedViewport: vi.fn(),
setReferenceLines: vi.fn(),
flipFocused: vi.fn(),
rotateFocused90: vi.fn(),
flipFocusedVertical: vi.fn(),
startCine: vi.fn(() => true),
stopCine: vi.fn(),
setActiveMeasurementTool: vi.fn(),
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/cvTumorGroupAfterLoad.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/hdCancellation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/organStats.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
17 changes: 15 additions & 2 deletions PanTS-Demo/src/test/viewer.smoke.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand All @@ -105,7 +105,7 @@ vi.mock("../helpers/NiiVueNifti", () => ({
}));

import { downloadBytes } from "../helpers/viewer/segmentationFile";
import { applyRemoteMeasurement, clearMeasurements, LENGTH_TOOL, renderVisualization, getSegmentationExport, saveSegmentation, saveSharedSegmentation, subscribeToSegmentationEdits } from "../helpers/CornerstoneNifti2";
import { applyRemoteMeasurement, clearMeasurements, LENGTH_TOOL, renderVisualization, flipPaneHorizontal, flipPaneVertical, getSegmentationExport, saveSegmentation, saveSharedSegmentation, subscribeToSegmentationEdits } from "../helpers/CornerstoneNifti2";
import VisualizationPage from "../routes/VisualizationPage";
import type { EducationResult, QuizPracticeController, SoloChallengeController } from "../education/types";
import type { LiveRoomController } from "../liveRooms/types";
Expand Down Expand Up @@ -356,6 +356,19 @@ describe("viewer smoke test", () => {
expect(screen.queryByRole("button", { name: "Save", exact: true })).not.toBeInTheDocument();
});

it("offers flip horizontal and flip vertical in the View menu, and no rotate", async () => {
render(<AuthProvider><MemoryRouter initialEntries={["/case/35"]}>
<Routes><Route path="/case/:caseId" element={<VisualizationPage />} /></Routes>
</MemoryRouter></AuthProvider>);
await waitFor(() => expect(renderVisualization).toHaveBeenCalled());
fireEvent.click(screen.getByRole("button", { name: "View options" }));
expect(screen.getByRole("menuitem", { name: "Flip horizontal" })).toBeInTheDocument();
expect(screen.queryByRole("menuitem", { name: /rotate/i })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("menuitem", { name: "Flip vertical" }));
expect(flipPaneVertical).toHaveBeenCalledTimes(1);
expect(flipPaneHorizontal).not.toHaveBeenCalled();
});

it("opens AI, live rooms, and shortcuts directly from the viewer toolbar", async () => {
render(<AuthProvider><MemoryRouter initialEntries={["/case/35"]}>
<Routes><Route path="/case/:caseId" element={<VisualizationPage />} /></Routes>
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/viewerBlockedStorage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/viewerCaseSources.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/viewerUnsavedEdits.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
2 changes: 1 addition & 1 deletion PanTS-Demo/src/test/volume3DPresetsAcrossLoads.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ vi.mock("../helpers/CornerstoneNifti2", async (importOriginal) => {
stopCine: vi.fn(),
setReferenceLinesEnabled: vi.fn(),
flipPaneHorizontal: vi.fn(),
rotatePane90Clockwise: vi.fn(),
flipPaneVertical: vi.fn(),
resetMprOrientation: vi.fn(),
};
});
Expand Down
Loading