import { _generateUID } from './utils.js'
const _coordinates = Symbol('coordinates')
const _frameOfReferenceUID = Symbol('frameOfReferenceUID')
const _fiducialUID = Symbol('fiducialUID')
/**
* 3D spatial coordinates.
*
* @class
* @abstract
* @memberof scoord3d
*/
class Scoord3D {
/**
* @param {Object} options - Options
* @param {string} options.frameOfReferenceUID - Frame of Reference UID
* @param {number[][]} options.coordinates - (x, y, z) coordinates for
* each point
* @param {string} options.fiducialUID - Fiducial UID
*/
constructor (options) {
if (!(typeof options.frameOfReferenceUID === 'string' ||
options.frameOfReferenceUID instanceof String)) {
throw new Error(
'Argument "frameOfReferenceUID" of Scoord3D must be a string.'
)
}
this[_frameOfReferenceUID] = options.frameOfReferenceUID
options.fiducialUID = options.fiducialUID || _generateUID()
if (!(typeof options.fiducialUID === 'string' ||
options.fiducialUID instanceof String)) {
throw new Error('Argument "fiducialUID" of Scoord3D must be a string.')
}
this[_fiducialUID] = options.fiducialUID
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Scoord3D must be an array.')
}
this[_coordinates] = options.coordinates
}
/**
* Graphic Data
*
* @type {number[][]}
*/
get graphicData () {
return this[_coordinates]
}
/**
* Graphic Type
*
* @type {string}
*/
get graphicType () {
throw new Error('Prototype property "graphicType" must be implemented')
}
/**
* Frame of Reference UID
*
* @type {string}
*/
get frameOfReferenceUID () {
return this[_frameOfReferenceUID]
}
/**
* Fiducial UID
*
* @type {string}
*/
get fiducialUID () {
return this[_fiducialUID]
}
}
/**
* POINT graphic denoted by a single (x, y, z) triplet.
*
* @class
* @extends scoord3d.Scoord3D
* @memberof scoord3d
*/
class Point extends Scoord3D {
/**
* @param {Object} options
* @param {string} options.frameOfReferenceUID - Unique identifier of the Frame of Reference
* @param {number[]} options.coordinates - X, Y and Z coordinate.
* @param {string} [options.fiducialUID] - Unique identifier of an imaging fiducial
*/
constructor (options) {
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Point must be an array.')
}
if (options.coordinates.length !== 3) {
throw new Error(
'Argument "coordinates" of Point must be an array of length 3.'
)
}
if (options.coordinates.some(c => c < 0)) {
console.warn('coordinates of Point are negative numbers')
}
super({
coordinates: options.coordinates,
frameOfReferenceUID: options.frameOfReferenceUID,
fiducialUID: options.fiducialUID
})
Object.freeze(this)
}
/** Graphic Data
*
* @type {number[]}
*/
get graphicData () {
return this[_coordinates]
}
get graphicType () {
return 'POINT'
}
}
/**
* MULTIPOINT graphic denoted by multiple, coplanar (x, y, z) coordinates that
* represent individual points.
*
* @class
* @extends scoord3d.Scoord3D
* @memberof scoord3d
*/
class Multipoint extends Scoord3D {
/**
* @param {Object} options
* @param {string} options.frameOfReferenceUID - Unique identifier of the Frame of Reference
* @param {number[][]} options.coordinates - (x, y, z) coordinates of each point.
* @param {string} [options.fiducialUID] - Unique identifier of an imaging fiducial
*/
constructor (options) {
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Multipoint must be an array.')
}
if (options.coordinates.find(c => c.length !== 3) !== undefined) {
throw new Error(
'Argument "coordinates" of Multipoint must be an array of ' +
'(x, y, z) triplets.'
)
}
if (options.coordinates.find(c => c.some(item => item < 0))) {
console.warn('coordinates of Multipoint contain negative numbers')
}
super({
coordinates: options.coordinates,
frameOfReferenceUID: options.frameOfReferenceUID,
fiducialUID: options.fiducialUID
})
Object.freeze(this)
}
get graphicType () {
return 'MULTIPOINT'
}
}
/**
* POLYLINE graphic denoted by multiple, ordered (x, y, z) coordinates that
* represent vertices of connected line segments.
*
* @class
* @extends scoord3d.Scoord3D
* @memberof scoord3d
*/
class Polyline extends Scoord3D {
/**
* @param {Object} options
* @param {string} options.frameOfReferenceUID - Unique identifier of the Frame of Reference
* @param {number[][]} options.coordinates - (x, y, z) coordinates of point on the line
* @param {string} [options.fiducialUID] - Unique identifier of an imaging fiducial
*/
constructor (options) {
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Polyline must be an array.')
}
if (options.coordinates.find(c => c.length !== 3) !== undefined) {
throw new Error(
'Argument "coordinates" of Polyline must be an array of ' +
'(x, y, z) triplets.'
)
}
if (options.coordinates.find(c => c.some(item => item < 0))) {
console.warn('coordinates of Polyline contain negative numbers')
}
super({
coordinates: options.coordinates,
frameOfReferenceUID: options.frameOfReferenceUID,
fiducialUID: options.fiducialUID
})
Object.freeze(this)
}
get graphicType () {
return 'POLYLINE'
}
}
/**
* POLYGON graphic denoted by multiple, ordered, coplaner (x, y, z) coordinates
* that represent vertices of connected line segments.
* The first and last coordinate should be identical.
*
* @class
* @extends scoord3d.Scoord3D
* @memberof scoord3d
*/
class Polygon extends Scoord3D {
/**
* @param {Object} options
* @param {string} options.frameOfReferenceUID - Unique identifier of the Frame of Reference
* @param {number[][]} options.coordinates - (x, y, z) coordinates of points on the perimeter of the polygon (first and last coordinate must be the same).
* @param {string} [options.fiducialUID] - Unique identifier of an imaging fiducial
*/
constructor (options) {
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Polygon must be an array.')
}
if (options.coordinates.find(c => c.length !== 3) !== undefined) {
throw new Error(
'Argument "coordinates" of Polygon must be an array of ' +
'(x, y, z) triplets.'
)
}
if (options.coordinates.find(c => c.some(item => item < 0))) {
console.warn('coordinates of Polygon contain negative numbers')
}
const n = options.coordinates.length
if ((options.coordinates[0][0] !== options.coordinates[n - 1][0]) ||
(options.coordinates[0][1] !== options.coordinates[n - 1][1]) ||
(options.coordinates[0][2] !== options.coordinates[n - 1][2])) {
throw new Error('First and last coordinate of Polygon must be the same.')
}
super({
coordinates: options.coordinates,
frameOfReferenceUID: options.frameOfReferenceUID,
fiducialUID: options.fiducialUID
})
Object.freeze(this)
}
get graphicType () {
return 'POLYGON'
}
}
/**
* ELLIPSOID graphic denoted by six (x, y, z) coordinates that represent
* endpoints of the three orthogonal geometric axes, where the first and second
* coordinates represent the endpoints of the first axis, the third and forth
* coordinates represent the endpoints of the second axis and the fifth and
* sixth coordinates represent the endpoints of the third axis.
*
* @class
* @extends scoord3d.Scoord3D
* @memberof scoord3d
*/
class Ellipsoid extends Scoord3D {
/**
* @param {Object} options
* @param {string} options.frameOfReferenceUID - Unique identifier of the Frame of Reference
* @param {number[][]} options.coordinates - (x, y, z) coordinates of the three axes endpoints
* @param {string} [options.fiducialUID] - Unique identifier of an imaging fiducial
*/
constructor (options) {
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Ellipsoid must be an array.')
}
if (options.coordinates.length !== 6) {
throw new Error(
'Argument "coordinates" of Ellipsoid must be an array of length 6.'
)
}
if (options.coordinates.find(c => c.length !== 3) !== undefined) {
throw new Error(
'Argument "coordinates" of Ellipsoid must be an array of ' +
'(x, y, z) triplets.'
)
}
if (options.coordinates.find(c => c.some(item => item < 0))) {
console.warn('coordinates of Ellipsoid contain negative numbers')
}
super({
coordinates: options.coordinates,
frameOfReferenceUID: options.frameOfReferenceUID,
fiducialUID: options.fiducialUID
})
Object.freeze(this)
}
get graphicType () {
return 'ELLIPSOID'
}
}
/**
* ELLIPSE graphic denoted by four, coplaner (x, y, z) coordinates that represent
* the endpoints of the major and minor axes, where the first and second
* coordinates represent the endpoints of the major axis and the third and
* forth coordinates represent the endpoints of the minor axis.
*
* @class
* @extends scoord3d.Scoord3D
* @memberof scoord3d
*/
class Ellipse extends Scoord3D {
/**
* @param {Object} options
* @param {string} options.frameOfReferenceUID - Unique identifier of the Frame of Reference
* @param {number[][]} options.coordinates - (x, y, z) coordinates of the major and minor axes endpoints
* @param {string} [options.fiducialUID] - Unique identifier of an imaging fiducial
*/
constructor (options) {
if (!Array.isArray(options.coordinates)) {
throw new Error('Argument "coordinates" of Ellipse must be an array.')
}
if (options.coordinates.length !== 4) {
throw new Error(
'Argument "coordinates" of Ellipse must be an array of length 4.'
)
}
if (options.coordinates.find(c => c.length !== 3) !== undefined) {
throw new Error(
'Argument "coordinates" of Ellipse must be an array of ' +
'(x, y, z) triplets.'
)
}
if (options.coordinates.find(c => c.some(item => item < 0))) {
console.warn('coordinates of Ellipse contain negative numbers')
}
const firstAxis = [
options.coordinates[0][0] - options.coordinates[1][0],
options.coordinates[0][1] - options.coordinates[1][1]
]
const secondAxis = [
options.coordinates[2][0] - options.coordinates[3][0],
options.coordinates[2][1] - options.coordinates[3][1]
]
const firstAxisNorm = Math.sqrt(
Math.pow(firstAxis[0], 2) + Math.pow(firstAxis[1], 2)
)
const secondAxisNorm = Math.sqrt(
Math.pow(secondAxis[0], 2) + Math.pow(secondAxis[1], 2)
)
const dotProduct = firstAxis[0] * secondAxis[0] + firstAxis[1] * secondAxis[1]
const angle = Math.acos(dotProduct / (firstAxisNorm * secondAxisNorm))
const degrees = angle * 180 / Math.PI
if (degrees !== 90) {
throw new Error('Two axis of Ellipse must have right angle')
}
let coordinates = options.coordinates
if (firstAxisNorm < secondAxisNorm) {
coordinates = [
coordinates[2],
coordinates[3],
coordinates[0],
coordinates[1]
]
}
super({
coordinates,
frameOfReferenceUID: options.frameOfReferenceUID,
fiducialUID: options.fiducialUID
})
Object.freeze(this)
}
get graphicType () {
return 'ELLIPSE'
}
}
export { Point, Multipoint, Polyline, Polygon, Ellipse, Ellipsoid }