/** * SphereTool - Sphere drawing and calculator sphere operations * * Provides 3D sphere placement across multiple slice views. * Supports 4 sphere types: tumour, skin, nipple, ribcage. * Each type maps to a specific layer and channel for future MaskVolume integration. * * ## Interaction Flow * 1. Sphere mode activated (gui_states.sphere = true) * - Draw mode (Shift key) is disabled * - Crosshair toggle is disabled * 2. Left-click down: record sphere center (mouseX, mouseY, sliceIndex) * - Remove zoom/slice wheel event * - Bind sphere wheel event * - Draw preview circle on sphereCanvas * 3. While holding left-click, scroll wheel adjusts sphereRadius [1, 50] * 4. Left-click up: * - Fire getSphere / getCalculateSpherePositions callbacks * - (Plan B) Draw 3D sphere across x/y/z axes into sphereMaskVolume * - Remove sphere wheel event * - Restore zoom/slice wheel event * * ## Channel Mapping * Each sphere type defaults to layer1 with a specific channel: * - tumour → layer1, channel 1 * - ribcage → layer1, channel 3 * - skin → layer1, channel 4 * - nipple → layer1, channel 5 * * ## SphereMaskVolume * Sphere 3D data is stored in a dedicated MaskVolume (nrrd_states.sphereMaskVolume) * separate from the layer draw mask volumes, to avoid polluting layer1's draw data. * This volume is created in setAllSlices() and cleared in reset(). * * ## Future: Writing to Layer MaskVolume * Currently sphere data does NOT write to layer1's MaskVolume. * The channel mapping and interfaces are reserved for future integration. * When enabled, use SPHERE_CHANNEL_MAP to determine the target layer & channel, * then call volume.setVoxel() or similar to persist sphere data. * * Extracted from DrawToolCore.ts: * - drawSphere / drawSphereCore / clearSphereCanvas * - drawSphereOnEachViews / drawCalculatorSphereOnEachViews * - storeSphereImages / setSphereCanvasSize * - drawCalculatorSphere / configMouseSphereWheel * - getSpherePosition */ import { BaseTool } from "./BaseTool"; import type { ToolContext } from "./BaseTool"; import type { SphereHostDeps } from "./ToolHost"; /** The 4 supported sphere marker types. */ export type SphereType = 'tumour' | 'skin' | 'nipple' | 'ribcage'; /** * Default layer and channel for each sphere type. * * This mapping is used to: * 1. Determine the color for sphere preview rendering * 2. (Future) Write sphere data into the corresponding layer's MaskVolume channel * * @example * ```ts * const { layer, channel } = SPHERE_CHANNEL_MAP['tumour']; * // layer = 'layer1', channel = 1 * ``` */ export declare const SPHERE_CHANNEL_MAP: Record; /** * Label values for sphere types stored in sphereMaskVolume. * Regular sphere mode uses SPHERE_LABEL (1). * Calculator mode uses type-specific labels (1-4). */ export declare const SPHERE_LABELS: Record; export declare class SphereTool extends BaseTool { private callbacks; constructor(ctx: ToolContext, callbacks: SphereHostDeps); setCallbacks(callbacks: SphereHostDeps): void; /** * Get the channel number for a sphere type. * * @param type - Sphere type ('tumour', 'skin', 'ribcage', 'nipple') * @returns Channel number (1-8) mapped to this sphere type * * @example * ```ts * sphereTool.getChannelForSphereType('tumour'); // → 1 * sphereTool.getChannelForSphereType('skin'); // → 4 * ``` */ getChannelForSphereType(type: SphereType): number; /** * Get the default layer for a sphere type. * * @param type - Sphere type * @returns Layer ID (e.g. 'layer1') */ getLayerForSphereType(type: SphereType): string; /** * Get the preview color for a sphere type. * * If a volume is available for the target layer, uses the volume's * custom color map (respects per-layer color customization). * Otherwise falls back to SPHERE_COLORS (derived from CHANNEL_HEX_COLORS). * * @param type - Sphere type * @returns CSS color string (hex) */ getColorForSphereType(type: SphereType): string; setSphereCanvasSize(axis?: "x" | "y" | "z"): void; /** * Draw a filled circle on the given context. * * @param ctx - Canvas 2D context * @param x - Center X coordinate * @param y - Center Y coordinate * @param radius - Circle radius in pixels * @param color - Fill color (CSS string) */ drawSphereCore(ctx: CanvasRenderingContext2D, x: number, y: number, radius: number, color: string): void; clearSphereCanvas(): [HTMLCanvasElement, CanvasRenderingContext2D]; /** * Draw a sphere preview circle on the sphere canvas using the current * gui_states.fillColor. Called during sphere mode pointer-down. * * NOTE: Does NOT composite to master canvas — the start() render loop * draws the sphere canvas directly to drawingCtx for proper layering. */ drawSphere(mouseX: number, mouseY: number, radius: number): void; /** * Returns a wheel event handler that adjusts sphere radius while the user * holds left-click in sphere mode. * * Radius is clamped to [1, 50]. */ configMouseSphereWheel(): (e: WheelEvent) => void; /** * Store sphere slice image into SphereMaskVolume. * * Currently a no-op — sphere slice data is rendered as overlay only. * * TODO: Future — write sphere circle data into nrrd_states.sphereMaskVolume * at the specified slice index and axis. Use SPHERE_CHANNEL_MAP to determine * the target channel when integrating with layer MaskVolume. */ private storeSphereImages; /** * Draw a sphere cross-section at a given decay distance from center, * for a specific axis view. * * Called for each decay value [0..sphereRadius] for all 3 axes, * creating the 3D sphere effect. * * The sphere circle radius at each slice = sphereRadius - decay (linear decay). * * @param decay - Distance from sphere center slice * @param axis - Axis to render on ('x', 'y', 'z') */ drawSphereOnEachViews(decay: number, axis: "x" | "y" | "z"): void; private getSpherePosition; /** * Draw all 4 calculator sphere types on each view axis. * * Groups sphere positions by their slice index so that spheres on the * same slice are drawn together before storing. * * Uses SPHERE_COLORS for each sphere type instead of nrrd_states.*Color. */ drawCalculatorSphereOnEachViews(axis: "x" | "y" | "z"): void; /** * Draw all placed calculator spheres on the current slice. * * Called every frame to show existing sphere markers on the current view. * Uses SPHERE_COLORS for consistent type-based coloring. * * @param radius - Sphere radius to draw */ drawCalculatorSphere(radius: number): void; /** * Handle left-click in sphere mode — record origin, draw preview. * * Performs all data operations for sphere placement: * 1. Record sphere origin for current axis * 2. Convert origin to all three axes via setUpSphereOrigins * 3. Store origin for the active sphere type * 4. Enable crosshair at click position * 5. Draw calculator sphere preview * * Event binding (wheel, pointerup) stays in DrawToolCore as orchestration. */ onSphereClick(e: MouseEvent): void; /** * Handle pointer-up in sphere mode — write to volume, fire callbacks. * * Performs: * 1. Write all placed calculator spheres to sphereMaskVolume * 2. Refresh sphere canvas overlay from volume * 3. Fire onSphereChanged and onCalculatorPositionsChanged callbacks * * Event cleanup (wheel, pointerup listener removal) stays in DrawToolCore. */ onSpherePointerUp(): void; /** * Convert canvas (mm-space) coordinates to 3D voxel coordinates. * * @param canvasX - X position in canvas mm-space * @param canvasY - Y position in canvas mm-space * @param sliceIndex - Slice index along the viewing axis * @param axis - Viewing axis where the sphere was placed * @returns 3D voxel coordinates { x, y, z } */ canvasToVoxelCenter(canvasX: number, canvasY: number, sliceIndex: number, axis: "x" | "y" | "z"): { x: number; y: number; z: number; }; /** * Write a 3D solid sphere to sphereMaskVolume. * * Converts the sphere center from canvas mm-space to voxel coordinates, * then iterates over a bounding box and sets voxels within the sphere. * The sphere is an ellipsoid in voxel space to appear circular in * physical (mm) space on all axis views. * * @param label - Label value to write (1-4, default 1) */ write3DSphereToVolume(label?: number): void; /** * Write a calculator sphere type to sphereMaskVolume. * Uses the stored origin for the specified sphere type. * * @param type - Sphere type ('tumour', 'skin', 'nipple', 'ribcage') */ writeCalculatorSphereToVolume(type: SphereType): void; /** * Write all placed calculator spheres to the volume. * Clears the volume first, then writes each placed sphere type. */ writeAllCalculatorSpheresToVolume(): void; /** * Render the current slice of sphereMaskVolume to drawingSphereCanvas. * * Called after any operation that changes the view (slice, axis, zoom) * to keep the sphere overlay visible. Uses the same render pipeline as * layer masks (emptyCanvas → sphere canvas at display scale). */ refreshSphereCanvas(): void; }