Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Cornerstone Tools more extensible #186

Open
md-prog opened this issue Aug 24, 2022 · 6 comments
Open

Make Cornerstone Tools more extensible #186

md-prog opened this issue Aug 24, 2022 · 6 comments

Comments

@md-prog
Copy link
Contributor

md-prog commented Aug 24, 2022

Explained by use-case

I was trying to make a small customization of the EllipticalROI tool.
So I started with extending the existing EllipticalROITool class. (NOTE: I was using Typescript)

The problem was, I wasn't able to call renderAnnotation() method of the original tool class from the new class and therefore I had to copy the full code of the renderAnnotation() method to the new class. And this was followed by some hacks to import internal functions/constants that are not publicly exported from tools package.

Possible cause and solution?

I think it is because the renderAnnotation() method is defined in the form of renderAnnotation = () => {}.
And to be able to call this function from child classes, it'd better be defined in the form of renderAnnotation() {}

Necessity ?

This will help us to extend the existing tool without fully redefining the methods.
Particularly useful when we want to add small changes to the existing tools like additional annotations.

@kubanm3
Copy link
Contributor

kubanm3 commented Oct 20, 2022

It would also help with customizing text box. Which lines we want to show, have them translated to user language etc.

Right now I don't think there even is option to not have it shown.

@ahlaughland
Copy link

ahlaughland commented Jul 19, 2024

I need to use completeDrawClosedContour and completeClosedContourEdit but most of the API I need to build a custom tool is not available. I'm starting to think I'll have to fork cs3d to build tools.

@sedghi
Copy link
Member

sedghi commented Jul 26, 2024

maybe we can export them?

@ahlaughland
Copy link

maybe we can export them?

I'm going to try a fork for now. We have more tools we need to develop and I'm curious as to what's possible with the full power of cs3d.

@sedghi
Copy link
Member

sedghi commented Aug 20, 2024

@ahlaughland I'm in the process of wrapping up cornerstone3D. Can you maybe create a PR to our beta branch?

@ahlaughland
Copy link

ahlaughland commented Aug 21, 2024

For the Freehand draw tool I'm working on I was using v1.77 to build the initial working version. This is what I have working so far:
image

The cursor is the correct color for the segment but It's not clear how to draw a LABELMAP in freehand.

I can put this in a PR if you feel it worth the effort. Here is my code.

  
import { getEnabledElement, Types as csCoreTypes } from '@cornerstonejs/core';
import {
  config as segmentationConfig,
  segmentIndex as segmentIndexController,
  activeSegmentation,
} from '../../../stateManagement/segmentation';
import { drawCircle as drawCircleSvg } from '../../../drawingSvg';
import PlanarFreehandROITool from '../../annotation/PlanarFreehandROITool';
import type {
  AnnotationRenderContext,
  Annotation,
  EventTypes,
  ToolSpecificAnnotationTypes,
  SVGDrawingHelper,
  ToolProps,
  PublicToolProps,
} from '../../../types';
import { fillInsideCircle } from '../strategies/fillCircle';
import ToolModes from '../../../enums/ToolModes';
import triggerAnnotationRenderForViewportIds from '../../../utilities/triggerAnnotationRenderForViewportIds';
import registerDrawLoop from '../../annotation/planarFreehandROITool/drawLoop';
import registerEditLoopCommon from '../../annotation/planarFreehandROITool/editLoopCommon';
import registerClosedContourEditLoop from '../../annotation/planarFreehandROITool/closedContourEditLoop';
import registerOpenContourEditLoop from '../../annotation/planarFreehandROITool/openContourEditLoop';
import registerOpenContourEndEditLoop from '../../annotation/planarFreehandROITool/openContourEndEditLoop';
import registerRenderMethods from '../../annotation/planarFreehandROITool/renderMethods';
class FreeHandDrawTool extends PlanarFreehandROITool {
  _hoverData: {
    centerCanvas: csCoreTypes.Point2;
    segmentIndex: number;
    segmentationId: string;
    segmentationRepresentationUID: string;
    brushCursor: Annotation & {
      metadata: {  segmentColor: [number, number, number, number]; };
    };
    viewportIdsToRender: string[];
  };

  // editData: {
  //   annotation: any;
  //   segmentIndex: number;
  //   //
  //   volumeId: string;
  //   referencedVolumeId: string;
  //   imageIdReferenceMap: Map<string, string>;
  //   //
  //   segmentsLocked: number[];
  //   segmentColor: [number, number, number, number];
  //   viewportIdsToRender: string[];
  //   handleIndex?: number;
  //   movingTextBox: boolean;
  //   newAnnotation?: boolean;
  //   hasMoved?: boolean;
  //   centerCanvas?: Array<number>;
  //   segmentationRepresentationUID?: string;
  // } | null;



  constructor(
    toolProps = {},
    defaultToolProps = {
      supportedInteractionTypes: ['Mouse', 'Touch'],
      configuration: {
        shadow: true,
        preventHandleOutsideImage: false,
        alwaysRenderOpenContourHandles: {
          enabled: false,
          radius: 2,
        },
        allowOpenContours: false,
        closeContourProximity: 10,
        checkCanvasEditFallbackProximity: 6,
        subPixelResolution: 4,
        interpolation: {
          interpolateOnAdd: false,
          interpolateOnEdit: false,
          knotsRatioPercentageOnAdd: 40,
          knotsRatioPercentageOnEdit: 40,
        },
      },
    }
  ) {
    super(toolProps, defaultToolProps);

    this.mouseMoveCallback = (evt: EventTypes.MouseMoveEventType) => {
      if (this.mode === ToolModes.Active) {
        this.updateCursor(evt);
      }
      return true;
    };

    const parentAddNewAnnotation = this.addNewAnnotation;
    this.addNewAnnotation = (
      evt: EventTypes.InteractionEventType
    ): ToolSpecificAnnotationTypes.PlanarFreehandROIAnnotation => {
      const toolGroupId = this.toolGroupId;
      const activeSegmentationRepresentation =
        activeSegmentation.getActiveSegmentationRepresentation(toolGroupId);

      if (!activeSegmentationRepresentation) {
        throw new Error(
          'No active segmentation detected, create one before using the freehanddraw tool'
        );
      }

      const { segmentationRepresentationUID, segmentationId } =
        activeSegmentationRepresentation;

      const segmentIndex =
        segmentIndexController.getActiveSegmentIndex(segmentationId);

      if (segmentIndex === 0) {
        throw new Error(
          'There is no segment selected, create or select one before using the freehanddraw tool'
        );
      }

      const segmentColorArray =
        segmentationConfig.color.getColorForSegmentIndex(
          toolGroupId,
          segmentationRepresentationUID,
          segmentIndex
        );
      const segmentColor = `rgb(${segmentColorArray.slice(0, 3).join(',')})`;
      const annotation = parentAddNewAnnotation(evt);
      (annotation.metadata as any).segmentColor = segmentColor;
      (annotation.metadata as any).segmentationId = segmentationId;
      (annotation.metadata as any).segmentIndex = segmentIndex;
      return annotation;
    };

    registerClosedContourEditLoop(this);
    registerDrawLoop(this);
    registerRenderMethods(this);

    const parentRenderAnnotation = this.renderAnnotationInstance;
    this.renderAnnotationInstance = (
      renderContext: AnnotationRenderContext
    ): boolean => {
      const { enabledElement, svgDrawingHelper } = renderContext;

      const isDrawing = this.isDrawing;
      const isEditingOpen = this.isEditingClosed;
      const isEditingClosed = this.isEditingClosed;
      let renderedCursorAnnotation = true;
      if (!(isDrawing || isEditingOpen || isEditingClosed)) {
        renderedCursorAnnotation = this.renderCursorAnnotation(
          enabledElement,
          svgDrawingHelper
        );
      }
      return (
        super.renderAnnotationInstance(renderContext) &&
        renderedCursorAnnotation
      );
    };
  }

  updateCursor(evt: EventTypes.MouseMoveEventType): void {
    const { element, currentPoints } = evt.detail;

    const centerCanvas = currentPoints.canvas;
    const { renderingEngine, viewport } = getEnabledElement(element);
    const { viewPlaneNormal, viewUp } = viewport.getCamera();

    const toolGroupId = this.toolGroupId;
    const activeSegmentationRepresentation =
      activeSegmentation.getActiveSegmentationRepresentation(toolGroupId);

    if (!activeSegmentationRepresentation) {
      console.warn(
        'No active segmentation detected, create one before using the freehanddraw tool'
      );
      return;
    }

    const { segmentationRepresentationUID, segmentationId } =
      activeSegmentationRepresentation;

    const segmentIndex =
      segmentIndexController.getActiveSegmentIndex(segmentationId);

    if (segmentIndex === 0) {
      console.warn(
        'There is no segment selected, create or select one before using the freehanddraw tool'
      );
      return;
    }

    const segmentColor = segmentationConfig.color.getColorForSegmentIndex(
      toolGroupId,
      segmentationRepresentationUID,
      segmentIndex
    );
    const viewportIdsToRender = [viewport.id];

    const brushCursor = {
      metadata: {
        viewPlaneNormal: [...viewPlaneNormal] as csCoreTypes.Point3,
        viewUp: [...viewUp] as csCoreTypes.Point3,
        FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
        referencedImageId: '',
        toolName: this.getToolName(),
        segmentColor,
      },
      data: {},
    };

    this._hoverData = {
      brushCursor,
      centerCanvas,
      segmentIndex,
      segmentationId,
      segmentationRepresentationUID,
      viewportIdsToRender,
    };

    this._calculateCursor(element, centerCanvas);

    triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
  }

  _calculateCursor(
    element: HTMLDivElement,
    centerCanvas: csCoreTypes.Point2
  ): void {
    const enabledElement = getEnabledElement(element);
    const { viewport } = enabledElement;
    const { canvasToWorld } = viewport;

    const radius = 1;
    const bottomCanvas = [
      centerCanvas[0],
      centerCanvas[1] + radius,
    ] as csCoreTypes.Point2;

    const topCanvas = [
      centerCanvas[0],
      centerCanvas[1] - radius,
    ] as csCoreTypes.Point2;

    const leftCanvas = [
      centerCanvas[0] - radius,
      centerCanvas[1],
    ] as csCoreTypes.Point2;

    const rightCanvas = [
      centerCanvas[0] + radius,
      centerCanvas[1],
    ] as csCoreTypes.Point2;

    const { data } = this._hoverData.brushCursor;

    if (data.handles === undefined) {
      data.handles = {};
    }

    data.handles.points = [
      canvasToWorld(bottomCanvas),
      canvasToWorld(topCanvas),
      canvasToWorld(leftCanvas),
      canvasToWorld(rightCanvas),
    ];

    data.invalidated = false;
  }

  completeDrawClosedContour(element: HTMLElement): void {
    this.completeDrawClosedContour(element);
  }

  completeClosedContourEdit(element: HTMLElement): void {
    this.completeClosedContourEdit(element);
  }

  _getRenderingOptions(element: HTMLElement): void {
    this._getRenderingOptions(element);
  }

  renderCursorAnnotation = (
    enabledElement: csCoreTypes.IEnabledElement,
    svgDrawingHelper: SVGDrawingHelper
  ): boolean => {
    const { viewport } = enabledElement;

    if (!viewport.getRenderingEngine()) {
      return false;
    }

    if (!this._hoverData) {
      return false;
    }

    const brushCursor = this._hoverData.brushCursor;

    if (brushCursor.data.invalidated === true) {
      const { centerCanvas } = this._hoverData;
      const { element } = viewport;
      this._calculateCursor(element, centerCanvas);
    }

    const toolMetadata = brushCursor.metadata;
    const data = brushCursor.data;

    const { points } = data.handles;
    const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));

    const bottom = canvasCoordinates[0];
    const top = canvasCoordinates[1];
    const center = [
      Math.floor((bottom[0] + top[0]) / 2),
      Math.floor((bottom[1] + top[1]) / 2),
    ] as csCoreTypes.Point2;

    const circleUID = '0';
    const radius = 1;
    const color = `rgb(${toolMetadata.segmentColor.slice(0, 3)})`;

    drawCircleSvg(svgDrawingHelper, null, circleUID, center, radius, {
      color,
    });

    return true;
  };

}
FreeHandDrawTool.toolName = 'FreeHandDraw';
export default FreeHandDrawTool;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants