Skip to content

Commit

Permalink
fix(CobbAngleTool, RectangleRoi): Fix the interaction of the tools wi…
Browse files Browse the repository at this point in the history
…th the TID.300 saving changes.

Correct notification on tool completion, with values restored correctly when loaded from TID.300 is
required for store/save functionality.  This is implemented in dcmjs, but the changes are required
here.  The cobb angle needed to be udpated after restoring, the Rectangle ROI needed the perimeter,
and changes needed to publish a completed measurement event.
  • Loading branch information
wayfarer3130 committed Oct 25, 2021
1 parent fb6019f commit 8f13fa9
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 18 deletions.
48 changes: 32 additions & 16 deletions src/tools/annotation/CobbAngleTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,14 @@ export default class CobbAngleTool extends BaseAnnotationTool {
return false;
}

return (
const seg1Near =
lineSegDistance(element, data.handles.start, data.handles.end, coords) <
25 ||
25;
const seg2Near =
lineSegDistance(element, data.handles.start2, data.handles.end2, coords) <
25
);
25;

return seg1Near || seg2Near;
}

updateCachedStats(image, element, data) {
Expand Down Expand Up @@ -182,6 +184,9 @@ export default class CobbAngleTool extends BaseAnnotationTool {
const lineWidth = toolStyle.getToolWidth();
const lineDash = getModule('globalConfiguration').configuration.lineDash;
const font = textStyle.getFont();
const { element } = evt.detail;
const image = external.cornerstone.getEnabledElement(element).image;
const { rowPixelSpacing, colPixelSpacing } = getPixelSpacing(image);

for (let i = 0; i < toolData.data.length; i++) {
const data = toolData.data[i];
Expand All @@ -190,6 +195,10 @@ export default class CobbAngleTool extends BaseAnnotationTool {
continue;
}

if (!data.value) {
data.value = this.textBoxText(data, rowPixelSpacing, colPixelSpacing);
}

draw(context, context => {
setShadow(context, this.configuration);

Expand Down Expand Up @@ -291,6 +300,15 @@ export default class CobbAngleTool extends BaseAnnotationTool {

return;
}
const eventType = EVENTS.MEASUREMENT_COMPLETED;
const eventData = {
toolName: this.name,
toolType: this.name, // Deprecation notice: toolType will be replaced by toolName
element,
measurementData,
};

triggerEvent(element, eventType, eventData);
};

// Search for incomplete measurements
Expand Down Expand Up @@ -373,22 +391,20 @@ export default class CobbAngleTool extends BaseAnnotationTool {
}
}

const { rAngle } = data;

data.value = '';
data.value = this.textBoxText(data, rowPixelSpacing, colPixelSpacing);
}

if (!Number.isNaN(rAngle)) {
data.value = textBoxText(rAngle, rowPixelSpacing, colPixelSpacing);
textBoxText({ rAngle }, rowPixelSpacing, colPixelSpacing) {
if (rAngle === undefined) {
return '';
}
if (Number.isNaN(rAngle)) {
return '';
}

function textBoxText(rAngle, rowPixelSpacing, colPixelSpacing) {
const suffix = !rowPixelSpacing || !colPixelSpacing ? ' (isotropic)' : '';
const str = '00B0'; // Degrees symbol
const suffix = !rowPixelSpacing || !colPixelSpacing ? ' (isotropic)' : '';

return (
rAngle.toString() + String.fromCharCode(parseInt(str, 16)) + suffix
);
}
return `${rAngle}\u00B0${suffix}`;
}

activeCallback(element) {
Expand Down
183 changes: 183 additions & 0 deletions src/tools/annotation/CobbAngleTool.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import Tool from './CobbAngleTool.js';
import { getToolState } from '../../stateManagement/toolState.js';

jest.mock('./../../stateManagement/toolState.js', () => ({
getToolState: jest.fn(),
}));

jest.mock('./../../importInternal.js', () => ({
default: jest.fn(),
}));

jest.mock('./../../externalModules.js', () => ({
cornerstone: {
metaData: {
get: jest.fn(),
},
},
}));

const goodMouseEventData = {
currentPoints: {
image: {
x: 0,
y: 0,
},
},
};

const image = {
rowPixelSpacing: 0.8984375,
columnPixelSpacing: 0.8984375,
};

describe('CobbAngleTool.js', () => {
describe('default values', () => {
it('has a default name of "CobbAngle"', () => {
const defaultName = 'CobbAngle';
const instantiatedTool = new Tool();

expect(instantiatedTool.name).toEqual(defaultName);
});

it('can be created with a custom tool name', () => {
const customToolName = { name: 'customToolName' };
const instantiatedTool = new Tool(customToolName);

expect(instantiatedTool.name).toEqual(customToolName.name);
});
});

describe('createNewMeasurement', () => {
it('returns aa new measurement object', () => {
const instantiatedTool = new Tool('CobbAngle');

const toolMeasurement = instantiatedTool.createNewMeasurement(
goodMouseEventData
);

expect(typeof toolMeasurement).toBe(typeof {});
});

it("returns a measurement with a start, end, start2 and end2 handles at the eventData's x and y", () => {
const instantiatedTool = new Tool('toolName');

const toolMeasurement = instantiatedTool.createNewMeasurement(
goodMouseEventData
);
const startHandle = {
x: toolMeasurement.handles.start.x,
y: toolMeasurement.handles.start.y,
};
const endHandle = {
x: toolMeasurement.handles.end.x,
y: toolMeasurement.handles.end.y,
};
const start2Handle = {
x: toolMeasurement.handles.start2.x,
y: toolMeasurement.handles.start2.y,
};
const end2Handle = {
x: toolMeasurement.handles.end2.x,
y: toolMeasurement.handles.end2.y,
};

expect(startHandle.x).toBe(goodMouseEventData.currentPoints.image.x);
expect(startHandle.y).toBe(goodMouseEventData.currentPoints.image.y);
expect(start2Handle.x).toBe(goodMouseEventData.currentPoints.image.x);
expect(start2Handle.y).toBe(goodMouseEventData.currentPoints.image.y);
expect(endHandle.x).toBe(goodMouseEventData.currentPoints.image.x);
expect(endHandle.y).toBe(goodMouseEventData.currentPoints.image.y);
expect(end2Handle.x).toBe(goodMouseEventData.currentPoints.image.x + 1);
expect(end2Handle.y).toBe(goodMouseEventData.currentPoints.image.y);
});

it('returns a measurement with a textBox handle', () => {
const instantiatedTool = new Tool('toolName');

const toolMeasurement = instantiatedTool.createNewMeasurement(
goodMouseEventData
);

expect(typeof toolMeasurement.handles.textBox).toBe(typeof {});
});
});

describe('pointNearTool', () => {
let element, coords;

beforeEach(() => {
element = jest.fn();
coords = jest.fn();
});

it('returns false when measurement data is not visible', () => {
const instantiatedTool = new Tool('AngleTool');
const notVisibleMeasurementData = {
visible: false,
};

const isPointNearTool = instantiatedTool.pointNearTool(
element,
notVisibleMeasurementData,
coords
);

expect(isPointNearTool).toBe(false);
});
});

describe('updateCachedStats', () => {
let element;

beforeEach(() => {
element = jest.fn();
});

it('should calculate and update annotation value', () => {
const instantiatedTool = new Tool('AngleTool');

const data = {
handles: {
start: {
x: 166,
y: 90,
},
end: {
x: 120,
y: 113,
},
start2: {
x: 120,
y: 113,
},
end2: {
x: 145,
y: 143,
},
},
};

instantiatedTool.updateCachedStats(image, element, data);
expect(data.rAngle).toBe(76.76);
expect(data.invalidated).toBe(false);
});
});

describe('renderToolData', () => {
it('returns undefined when no toolData exists for the tool', () => {
const instantiatedTool = new Tool('AngleTool');
const mockEvent = {
detail: {
enabledElement: undefined,
},
};

getToolState.mockReturnValueOnce(undefined);

const renderResult = instantiatedTool.renderToolData(mockEvent);

expect(renderResult).toBe(undefined);
});
});
});
2 changes: 1 addition & 1 deletion src/tools/annotation/ProbeTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default class ProbeTool extends BaseAnnotationTool {

if (this.configuration.drawHandles) {
// Draw the handles
let handleOptions = { handleRadius, color };
const handleOptions = { handleRadius, color };

if (renderDashed) {
handleOptions.lineDash = lineDash;
Expand Down
5 changes: 5 additions & 0 deletions src/tools/annotation/RectangleRoiTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,13 @@ function _calculateStats(image, element, handles, modality, pixelSpacing) {
(pixelSpacing.colPixelSpacing || 1) *
(roiCoordinates.height * (pixelSpacing.rowPixelSpacing || 1));

const perimeter =
roiCoordinates.width * 2 * (pixelSpacing.colPixelSpacing || 1) +
roiCoordinates.height * 2 * (pixelSpacing.rowPixelSpacing || 1);

return {
area: area || 0,
perimeter,
count: roiMeanStdDev.count || 0,
mean: roiMeanStdDev.mean || 0,
variance: roiMeanStdDev.variance || 0,
Expand Down
1 change: 1 addition & 0 deletions src/tools/annotation/RectangleRoiTool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ describe('RectangleRoiTool.js', () => {

instantiatedTool.updateCachedStats(image, element, data);
expect(data.cachedStats.area.toFixed(2)).toEqual('7.26');
expect(data.cachedStats.perimeter.toFixed(2)).toEqual('10.78');
expect(data.cachedStats.mean.toFixed(2)).toEqual('57.56');
expect(data.cachedStats.stdDev.toFixed(2)).toEqual('47.46');

Expand Down
1 change: 1 addition & 0 deletions src/util/convertToVector3.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import external from '../externalModules.js';

jest.mock('../externalModules.js', () => {
const cornerstoneMath = require('cornerstone-math');

return {
cornerstoneMath: {
Vector3: cornerstoneMath.Vector3,
Expand Down
17 changes: 16 additions & 1 deletion src/util/findAndMoveHelpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { state } from '../store/index.js';
import getHandleNearImagePoint from '../manipulators/getHandleNearImagePoint.js';
import { moveHandle, moveAllHandles } from './../manipulators/index.js';
import EVENTS from '../events';
import triggerEvent from './triggerEvent';

// TODO this should just be in manipulators? They are just manipulator wrappers anyway.

Expand All @@ -26,14 +28,27 @@ const moveHandleNearImagePoint = function(
) {
toolData.active = true;
state.isToolLocked = true;
const doneHandler = success => {
const { element } = evt.detail;
const toolName = toolData.toolType || toolData.toolName;
const modifiedEventData = {
toolName,
toolType: toolName, // Deprecation notice: toolType will be replaced by toolName
element,
measurementData: { ...toolData, active: false },
};

triggerEvent(element, EVENTS.MEASUREMENT_COMPLETED, modifiedEventData);
};

moveHandle(
evt.detail,
tool.name,
toolData,
handle,
tool.options,
interactionType
interactionType,
doneHandler
);

evt.stopImmediatePropagation();
Expand Down

0 comments on commit 8f13fa9

Please sign in to comment.