Skip to content

Commit

Permalink
Use and store plane points to localise annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmartel committed Sep 12, 2024
1 parent 1b65f4f commit 55625bf
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 54 deletions.
10 changes: 10 additions & 0 deletions src/app/viewController.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@ export class ViewController {
return this.#view.getOrigin(position);
}

/**
* Get a list of points that define the plane at position k.
*
* @param {number} k The slice index value.
* @returns {Point3D[]} A couple of 3D points.
*/
getPlanePoints(k) {
return this.#planeHelper.getPlanePoints(k);
}

/**
* Get the current scroll position value.
*
Expand Down
10 changes: 10 additions & 0 deletions src/dicom/dicomCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const DcmCodes = {
122438: 'Reference Points',
125007: 'Measurement Group',
125309: 'Short label',
128773: 'Reference Geometry'
};

/**
Expand Down Expand Up @@ -260,6 +261,15 @@ export function getImageRegionCode() {
return getDicomCode('111030', 'DCM');
}

/**
* Get a reference geometry DICOM code.
*
* @returns {DicomCode} The code.
*/
export function getReferenceGeometryCode() {
return getDicomCode('128773', 'DCM');
}

/**
* Get a path DICOM code.
*
Expand Down
62 changes: 32 additions & 30 deletions src/gui/drawLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,30 +640,33 @@ export class DrawLayer {
* @returns {string|undefined} The group id.
*/
#getAnnotationPosGroupId(annotation) {
const origin = annotation.getOrigin();
if (typeof origin === 'undefined') {
logger.warn('Unknown reference origin for annotation: ' +
const planePoints = annotation.planePoints;
if (typeof planePoints === 'undefined') {
logger.warn('Unknown reference plane points for annotation: ' +
annotation.referenceSopUID);
return;
}
const scrollIndex = this.#planeHelper.getScrollIndex();
return this.#getPositionId(origin, [scrollIndex]);
return this.#getPositionId(planePoints);
}

/**
* Get a string id from an input position.
* Get a string id from an input couple of plane points.
*
* @param {Point3D} position The input position.
* @param {number[]} [dims] Optional list of dimensions.
* @param {Point3D[]} planePoints A couple of points that defined a plane.
* @returns {string} The string id.
*/
#getPositionId(position, dims) {
const posValues = [
precisionRound(position.getX(), 2),
precisionRound(position.getY(), 2),
precisionRound(position.getZ(), 2),
#getPositionId(planePoints) {
const posValues0 = [
precisionRound(planePoints[0].getX(), 2),
precisionRound(planePoints[0].getY(), 2),
precisionRound(planePoints[0].getZ(), 2),
];
const posValues1 = [
precisionRound(planePoints[1].getX(), 2),
precisionRound(planePoints[1].getY(), 2),
precisionRound(planePoints[1].getZ(), 2),
];
return toStringId(posValues, dims);
return toStringId(posValues0) + '-' + toStringId(posValues1);
}

/**
Expand Down Expand Up @@ -1005,31 +1008,30 @@ export class DrawLayer {
* Set the current position.
*
* @param {Point} position The new position.
* @param {Index} _index The new index.
* @param {Index} index The new index.
* @returns {boolean} True if the position was updated.
*/
setCurrentPosition(position, _index) {
this.activateDrawLayer(
position, this.#planeHelper.getScrollIndex());
setCurrentPosition(position, index) {
if (typeof index === 'undefined') {
index = this.#planeHelper.worldToIndex(position);
}
const scrollIndex = this.#planeHelper.getScrollIndex();
const scrollIndexValue = index.get(scrollIndex);
const planePoints =
this.#planeHelper.getPlanePoints(scrollIndexValue);
const posGroupId = this.#getPositionId(planePoints);
this.activateDrawLayer(posGroupId);
// TODO: add check
return true;
}

/**
* Activate the current draw layer.
*
* @param {Point} position The current position.
* @param {number} scrollIndex The scroll index.
*/
activateDrawLayer(position, scrollIndex) {
// TODO: add layer info
// get and store the position group id
const dims = [scrollIndex];
// TODO: handle more dims
// for (let j = 3; j < position.length(); ++j) {
// dims.push(j);
// }
this.#currentPosGroupId = this.#getPositionId(position.get3D(), dims);
* @param {string} posGroupId The position group ID.
*/
activateDrawLayer(posGroupId) {
this.#currentPosGroupId = posGroupId;

// get all position groups
const posGroups = this.getKonvaLayer().getChildren(isPositionNode);
Expand Down
39 changes: 17 additions & 22 deletions src/image/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,14 @@ export class Annotation {
labelPosition;

/**
* Associated view controller.
* A couple of points that define the annotation plane.
*
* @type {Point3D[]|undefined}
*/
planePoints;

/**
* Associated view controller: needed for quantification and label.
*
* @type {ViewController|undefined}
*/
Expand All @@ -96,29 +103,17 @@ export class Annotation {
* @param {ViewController} viewController The associated view controller.
*/
setViewController(viewController) {
if (typeof this.#viewController === 'undefined') {
this.#viewController = viewController;
// set UID if empty
if (typeof this.referenceSopUID === 'undefined') {
this.referenceSopUID = viewController.getCurrentImageUid();
}
} else {
logger.warn('Cannot override previous view controller');
this.#viewController = viewController;
// set UID if empty
if (typeof this.referenceSopUID === 'undefined') {
this.referenceSopUID = viewController.getCurrentImageUid();
}
}

/**
* Get the image origin for a image UID.
*
* @returns {Point3D|undefined} The origin.
*/
getOrigin() {
let res;
if (typeof this.#viewController !== 'undefined') {
res =
this.#viewController.getOriginForImageUid(this.referenceSopUID);
// set plane points if empty
if (typeof this.planePoints === 'undefined') {
this.planePoints = viewController.getPlanePoints(
viewController.getCurrentScrollIndexValue()
);
}
return res;
}

/**
Expand Down
34 changes: 33 additions & 1 deletion src/image/annotationGroupFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getPathCode,
getMeasurementGroupCode,
getImageRegionCode,
getReferenceGeometryCode,
getSourceImageCode,
getTrackingIdentifierCode,
getShortLabelCode,
Expand All @@ -34,12 +35,13 @@ import {
getShapeFromScoord,
SpatialCoordinate
} from '../dicom/dicomSpatialCoordinate';
import {SpatialCoordinate3D} from '../dicom/dicomSpatialCoordinate3D';
import {guid} from '../math/stats';
import {logger} from '../utils/logger';
import {Annotation} from './annotation';
import {AnnotationGroup} from './annotationGroup';
import {Line} from '../math/line';
import {Point2D} from '../math/point';
import {Point2D, Point3D} from '../math/point';

// doc imports
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -165,6 +167,17 @@ export class AnnotationGroupFactory {
}
annotation.referencePoints = points;
}
// plane points
if (subItem.valueType === ValueTypes.scoord3d &&
subItem.relationshipType === RelationshipTypes.hasProperties &&
isEqualCode(
subItem.conceptNameCode, getReferenceGeometryCode()) &&
subItem.value.graphicType === GraphicTypes.multipoint) {
const data = subItem.value.graphicData;
const point0 = new Point3D(data[0], data[1], data[2]);
const point1 = new Point3D(data[3], data[4], data[5]);
annotation.planePoints = [point0, point1];
}
// quantification
if (subItem.valueType === ValueTypes.num &&
subItem.relationshipType === RelationshipTypes.contains) {
Expand Down Expand Up @@ -316,6 +329,25 @@ export class AnnotationGroupFactory {
itemContentSequence.push(referencePoints);
}

// plane points
if (typeof annotation.planePoints !== 'undefined') {
const planePoints = new DicomSRContent(ValueTypes.scoord3d);
planePoints.relationshipType = RelationshipTypes.hasProperties;
planePoints.conceptNameCode = getReferenceGeometryCode();
const pointsScoord = new SpatialCoordinate3D();
pointsScoord.graphicType = GraphicTypes.multipoint;
const graphicData = [];
for (const planePoint of annotation.planePoints) {
graphicData.push(planePoint.getX().toString());
graphicData.push(planePoint.getY().toString());
graphicData.push(planePoint.getZ().toString());
}
pointsScoord.graphicData = graphicData;

planePoints.value = pointsScoord;
itemContentSequence.push(planePoints);
}

// quantification
if (typeof annotation.quantification !== 'undefined') {
for (const key in annotation.quantification) {
Expand Down
42 changes: 41 additions & 1 deletion src/image/planeHelper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Vector3D} from '../math/vector';
import {Point3D} from '../math/point';
import {Point3D, Point2D} from '../math/point';
import {getTargetOrientation} from '../gui/layerGroup';
import {getOrientedArray3D, getDeOrientedArray3D} from './geometry';

Expand All @@ -16,6 +16,13 @@ import {Scalar2D, Scalar3D} from '../math/scalar';
*/
export class PlaneHelper {

/**
* The image geometry.
*
* @type {Geometry}
*/
#imageGeometry;

/**
* The associated spacing.
*
Expand Down Expand Up @@ -49,6 +56,7 @@ export class PlaneHelper {
* @param {Matrix33} viewOrientation The view orientation.
*/
constructor(imageGeometry, viewOrientation) {
this.#imageGeometry = imageGeometry;
this.#spacing = imageGeometry.getRealSpacing();
this.#imageOrientation = imageGeometry.getOrientation();
this.#viewOrientation = viewOrientation;
Expand Down Expand Up @@ -244,6 +252,38 @@ export class PlaneHelper {
return planePoint;
}

/**
* Get a world position from a 2D plane position.
*
* @param {Point2D} point2D The input point.
* @param {number} k The slice index.
* @returns {Point3D} The associated position.
*/
getPositionFromPlanePoint(point2D, k) {
const planePoint = new Point3D(point2D.getX(), point2D.getY(), k);
// de-orient
const point = this.getImageOrientedPoint3D(planePoint);
// ~indexToWorld to not loose precision
return this.#imageGeometry.pointToWorld(point);
}

/**
* Get a list of points that define the plane at position k.
*
* @param {number} k The slice index value.
* @returns {Point3D[]} A couple of 3D points.
*/
getPlanePoints(k) {
return [
this.getPositionFromPlanePoint(new Point2D(0, 0), k),
this.getPositionFromPlanePoint(new Point2D(1, 1), k)
];
}

worldToIndex(point) {
return this.#imageGeometry.worldToIndex(point);
}

/**
* Reorder values to follow target orientation.
*
Expand Down

0 comments on commit 55625bf

Please sign in to comment.