UNPKG

@edsilv/ami.js

Version:

<p align="center"> <img src="https://user-images.githubusercontent.com/214063/46479857-4cd66e80-c7f0-11e8-9585-5748409c9490.png" width="60%"> </p>

507 lines (426 loc) 13.3 kB
import WidgetsCss from './widgets.css'; import {COLORS} from '../core/core.colors'; import CoreUtils from '../core/core.utils'; interface WidgetParameter { calibrationFactor: number; frameIndex: number; hideMesh: boolean; hideHandleMesh: boolean; ijk2LPS: THREE.Matrix4; lps2IJK: THREE.Matrix4; pixelSpacing: number; stack: {}; ultrasoundRegions: Array<{}>; worldPosition: THREE.Vector3; } interface USRegion { x0: number; x1: number; y0: number; y1: number; axisX: number; axisY: number; deltaX: number; deltaY: number; } /** * @module Abstract Widget */ // tslint:disable-next-line const widgetsBase = (three = (window as any).THREE) => { if (three === undefined || three.Object3D === undefined) { return null; } const Constructor = three.Object3D; return class extends Constructor { constructor(targetMesh: THREE.Mesh, controls: THREE.OrbitControls, params: WidgetParameter) { super(); this._widgetType = 'Base'; this._params = params; if (params.hideMesh === true) { this.visible = false; } const elementStyle = document.getElementById('ami-widgets'); if (elementStyle === null) { const styleEl = document.createElement('style'); styleEl.id = 'ami-widgets'; styleEl.innerHTML = WidgetsCss.code; document.head.appendChild(styleEl); } this._enabled = true; this._selected = false; this._hovered = true; this._active = true; this._colors = { default: COLORS.blue, active: COLORS.yellow, hover: COLORS.red, select: COLORS.green, text: COLORS.white, error: COLORS.lightRed, }; this._color = this._colors.default; this._dragged = false; // can not call it visible because it conflicts with THREE.Object3D this._displayed = true; this._targetMesh = targetMesh; this._controls = controls; this._camera = controls.object; this._container = controls.domElement; this._worldPosition = new three.Vector3(); // LPS position if (params.worldPosition) { this._worldPosition.copy(params.worldPosition); } else if (this._targetMesh !== null) { this._worldPosition.copy(this._targetMesh.position); } } public initOffsets() { const box = this._container.getBoundingClientRect(); const body = document.body; const docEl = document.documentElement; const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; const clientTop = docEl.clientTop || body.clientTop || 0; const clientLeft = docEl.clientLeft || body.clientLeft || 0; this._offsets = { top: Math.round(box.top + scrollTop - clientTop), left: Math.round(box.left + scrollLeft - clientLeft), }; } public getMouseOffsets(event: MouseEvent, container: HTMLDivElement) { return { x: ((event.clientX - this._offsets.left) / container.offsetWidth) * 2 - 1, y: -((event.clientY - this._offsets.top) / container.offsetHeight) * 2 + 1, screenX: event.clientX - this._offsets.left, screenY: event.clientY - this._offsets.top, }; } /** * Get area of polygon. * * @param {Array} points Ordered vertices' coordinates * * @returns {Number} */ public getArea(points: THREE.Vector3[]) { let area = 0; let j = points.length - 1; // the last vertex is the 'previous' one to the first for (let i = 0; i < points.length; i++) { area += (points[j].x + points[i].x) * (points[j].y - points[i].y); j = i; // j is the previous vertex to i } return Math.abs(area / 2); } /** * Get index of ultrasound region by data coordinates. * * @param {Array} regions US regions * @param {Vector3} point Data coordinates * * @returns {Number|null} */ public getRegionByXY(regions: USRegion[], point: THREE.Vector3) { let result = null; regions.some((region, ind) => { if ( point.x >= region.x0 && point.x <= region.x1 && point.y >= region.y0 && point.y <= region.y1 ) { result = ind; return true; } }); return result; } /** * Get point inside ultrasound region by data coordinates. * * @param {Object} region US region data * @param {Vector3} point Data coordinates * * @returns {Vector2|null} */ public getPointInRegion(region: USRegion, point: THREE.Vector3) { if (!region) { return null; } return new three.Vector2( (point.x - region.x0 - (region.axisX || 0)) * region.deltaX, (point.y - region.y0 - (region.axisY || 0)) * region.deltaY ); } /** * Get point's ultrasound coordinates by data coordinates. * * @param {Array} regions US regions * @param {Vector3} point Data coordinates * * @returns {Vector2|null} */ public getUsPoint(regions: USRegion[], point: THREE.Vector3) { return this.getPointInRegion(regions[this.getRegionByXY(regions, point)], point); } /** * Get distance between points inside ultrasound region. * * @param {Vector3} pointA Begin data coordinates * @param {Vector3} pointB End data coordinates * * @returns {Number|null} */ public getUsDistance(pointA: THREE.Vector3, pointB: THREE.Vector3) { const regions = this._params.ultrasoundRegions || []; if (regions.length < 1) { return null; } const regionA = this.getRegionByXY(regions, pointA); const regionB = this.getRegionByXY(regions, pointB); if ( regionA === null || regionB === null || regionA !== regionB || regions[regionA].unitsX !== 'cm' || regions[regionA].unitsY !== 'cm' ) { return null; } return this.getPointInRegion(regions[regionA], pointA).distanceTo( this.getPointInRegion(regions[regionA], pointB) ); } /** * Get distance between points * * @param {Vector3} pointA Begin world coordinates * @param {Vector3} pointB End world coordinates * @param {number} cf Calibration factor * * @returns {Object} */ public getDistanceData(pointA: THREE.Vector3, pointB: THREE.Vector3, calibrationFactor: number) { let distance = null; let units = null; if (calibrationFactor) { distance = pointA.distanceTo(pointB) * calibrationFactor; } else if (this._params.ultrasoundRegions && this._params.lps2IJK) { const usDistance = this.getUsDistance( CoreUtils.worldToData(this._params.lps2IJK, pointA), CoreUtils.worldToData(this._params.lps2IJK, pointB) ); if (usDistance !== null) { distance = usDistance * 10; units = 'mm'; } else { distance = pointA.distanceTo(pointB); units = this._params.pixelSpacing ? 'mm' : 'units'; } } else { distance = pointA.distanceTo(pointB); } return { distance, units, }; } public getLineData(pointA: THREE.Vector3, pointB: THREE.Vector3) { const line = pointB.clone().sub(pointA); const center = pointB .clone() .add(pointA) .multiplyScalar(0.5); const length = line.length(); const angle = line.angleTo(new three.Vector3(1, 0, 0)); return { line, length, transformX: center.x - length / 2, transformY: center.y - this._container.offsetHeight, transformAngle: pointA.y < pointB.y ? angle : -angle, center, }; } public getRectData(pointA: THREE.Vector3, pointB: THREE.Vector3) { const line = pointB.clone().sub(pointA); const vertical = line.clone().projectOnVector(new three.Vector3(0, 1, 0)); const min = pointA.clone().min(pointB); // coordinates of the top left corner return { width: line .clone() .projectOnVector(new three.Vector3(1, 0, 0)) .length(), height: vertical.length(), transformX: min.x, transformY: min.y - this._container.offsetHeight, paddingVector: vertical.clone().normalize(), }; } /** * @param {HTMLElement} label * @param {Vector3} point label's center coordinates (default) * @param {Boolean} corner if true, then point is the label's top left corner coordinates */ public adjustLabelTransform(label: HTMLDivElement, point: THREE.Vector3, corner: boolean) { let x = Math.round(point.x - (corner ? 0 : label.offsetWidth / 2)); let y = Math.round(point.y - (corner ? 0 : label.offsetHeight / 2)) - this._container.offsetHeight; if (x < 0) { x = x > -label.offsetWidth ? 0 : x + label.offsetWidth; } else if (x > this._container.offsetWidth - label.offsetWidth) { x = x < this._container.offsetWidth ? this._container.offsetWidth - label.offsetWidth : x - label.offsetWidth; } if (y < -this._container.offsetHeight) { y = y > -this._container.offsetHeight - label.offsetHeight ? -this._container.offsetHeight : y + label.offsetHeight; } else if (y > -label.offsetHeight) { y = y < 0 ? -label.offsetHeight : y - label.offsetHeight; } return new three.Vector2(x, y); } public worldToScreen(worldCoordinate: THREE.Vector3) { const screenCoordinates = worldCoordinate.clone(); screenCoordinates.project(this._camera); screenCoordinates.x = Math.round( ((screenCoordinates.x + 1) * this._container.offsetWidth) / 2 ); screenCoordinates.y = Math.round( ((-screenCoordinates.y + 1) * this._container.offsetHeight) / 2 ); screenCoordinates.z = 0; return screenCoordinates; } public update() { // to be overloaded window.console.log('update() should be overloaded!'); } public updateColor() { if (this._active) { this._color = this._colors.active; } else if (this._hovered) { this._color = this._colors.hover; } else if (this._selected) { this._color = this._colors.select; } else { this._color = this._colors.default; } } // tslint:disable-next-line public setDefaultColor(color: any) { this._colors.default = color; if (this._handles) { this._handles.forEach(elem => (elem._colors.default = color)); } this.update(); } public show() { this.showDOM(); this.showMesh(); this.update(); this._displayed = true; } public hide() { this.hideDOM(); this.hideMesh(); this._displayed = false; } public hideDOM() { // to be overloaded window.console.log('hideDOM() should be overloaded!'); } public showDOM() { // to be overloaded window.console.log('showDOM() should be overloaded!'); } public hideMesh() { this.visible = false; } public showMesh() { if (this._params.hideMesh === true) { return; } this.visible = true; } public free() { this._camera = null; this._container = null; this._controls = null; this._params = null; this._targetMesh = null; } get widgetType() { return this._widgetType; } get targetMesh() { return this._targetMesh; } set targetMesh(targetMesh: THREE.Mesh) { this._targetMesh = targetMesh; this.update(); } get worldPosition() { return this._worldPosition; } set worldPosition(worldPosition: THREE.Vector3) { this._worldPosition.copy(worldPosition); this.update(); } get enabled() { return this._enabled; } set enabled(enabled: boolean) { this._enabled = enabled; this.update(); } get selected() { return this._selected; } set selected(selected: boolean) { this._selected = selected; this.update(); } get hovered() { return this._hovered; } set hovered(hovered: boolean) { this._hovered = hovered; this.update(); } get dragged() { return this._dragged; } set dragged(dragged: boolean) { this._dragged = dragged; this.update(); } get displayed() { return this._displayed; } set displayed(displayed: boolean) { this._displayed = displayed; this.update(); } get active() { return this._active; } set active(active: boolean) { this._active = active; this.update(); } get color() { return this._color; } // tslint:disable-next-line set color(color: any) { this._color = color; this.update(); } }; }; export { widgetsBase }; export default widgetsBase();