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

Add support for ellipse and circle annotations #811

Merged
merged 2 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

## Unreleased

### Features
- Add support for ellipse and circle annotations ([811](../../pull/811))

### Improvements
- Improve parsing OME TIFF channel names ([806](../../pull/806))
- Improve handling when a file vanishes ([807](../../pull/807))
- Add TileSourceXYZRangeError ([809](../../pull/809))

## Version 1.12.0

Expand Down
2 changes: 1 addition & 1 deletion girder/girder_large_image/web_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"dependencies": {
"copy-webpack-plugin": "^4.5.2",
"d3": "^3.5.16",
"geojs": "^1.0.1",
"geojs": "^1.8.0",
"hammerjs": "^2.0.8",
"js-yaml": "^3.14.0",
"sinon": "^2.1.0",
Expand Down
85 changes: 46 additions & 39 deletions girder_annotation/docs/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,28 @@ it is not allowed. If element IDs are specified, they must be unique.
"fontSize": 3.4, # Number. Optional
"color": "#0000FF" # String. See note about colors. Optional
},
"lineColor": "#000000", # String. See note about colors. Optional
"lineWidth": 1, # Number >= 0. Optional
"group": "group name", # String. Optional
"user": {}, # User properties -- this can contain anything,
# but should be kept small. Optional.
<shape specific properties>
}

Arrow
~~~~~

Not currently rendered.
All Vector Shapes
~~~~~~~~~~~~~~~~~

These properties exist for all vector shapes (all but heatmaps, grid data, and image and pixelmap overlays).

::

{
"type": "arrow", # Exact string. Required
<id, label, group, user, lineColor, lineWidth> # Optional general shape properties
"points": [ # Arrows ALWAYS have two points
[5,6,0], # Coordinate. Arrow head. Required
[-17,6,0] # Coordinate. Aroow tail. Required
]
"lineColor": "#000000", # String. See note about colors. Optional
"lineWidth": 1, # Number >= 0. Optional
}

Circle
~~~~~~

Not currently rendered.

::

{
Expand All @@ -90,8 +83,6 @@ Not currently rendered.
Ellipse
~~~~~~~

Not currently rendered.

The width and height of an ellipse are the major and minor axes.

::
Expand Down Expand Up @@ -155,30 +146,6 @@ Rectangle
"fillColor": "rgba(0, 255, 0, 1)" # String. See note about colors. Optional
}

Rectangle Grid
~~~~~~~~~~~~~~

Not currently rendered.

A Rectangle Grid is a rectangle which contains regular subdivisions,
such as that used to show a regular scale grid overlay on an image.

::

{
"type": "rectanglegrid", # Exact string. Required
<id, label, group, user, lineColor, lineWidth> # Optional general shape properties
"center": [10.3, -40.0, 0], # Coordinate. Required
"width": 5.3, # Number >= 0. Required
"height": 17.3, # Number >= 0. Required
"rotation": 0, # Number. Counterclockwise radians around normal. Required
"normal": [0, 0, 1.0], # Three numbers specifying normal. Default is positive Z.
# Optional
"widthSubdivisions": 3, # Integer > 0. Required
"heightSubdivisions": 4, # Integer > 0. Required
"fillColor": "rgba(0, 255, 0, 1)" # String. See note about colors. Optional
}

Heatmap
~~~~~~~

Expand Down Expand Up @@ -357,6 +324,46 @@ integers which correspond to indices in a ``categories`` array.
]
}

Arrow
~~~~~

Not currently rendered.

::

{
"type": "arrow", # Exact string. Required
<id, label, group, user, lineColor, lineWidth> # Optional general shape properties
"points": [ # Arrows ALWAYS have two points
[5,6,0], # Coordinate. Arrow head. Required
[-17,6,0] # Coordinate. Aroow tail. Required
]
}

Rectangle Grid
~~~~~~~~~~~~~~

Not currently rendered.

A Rectangle Grid is a rectangle which contains regular subdivisions,
such as that used to show a regular scale grid overlay on an image.

::

{
"type": "rectanglegrid", # Exact string. Required
<id, label, group, user, lineColor, lineWidth> # Optional general shape properties
"center": [10.3, -40.0, 0], # Coordinate. Required
"width": 5.3, # Number >= 0. Required
"height": 17.3, # Number >= 0. Required
"rotation": 0, # Number. Counterclockwise radians around normal. Required
"normal": [0, 0, 1.0], # Three numbers specifying normal. Default is positive Z.
# Optional
"widthSubdivisions": 3, # Integer > 0. Required
"heightSubdivisions": 4, # Integer > 0. Required
"fillColor": "rgba(0, 255, 0, 1)" # String. See note about colors. Optional
}

Component Values
----------------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
fillColor: 'rgba(0,0,0,0)',
lineColor: 'rgb(0,0,0)',
lineWidth: 2
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
fillColor: 'rgba(0,0,0,0)',
lineColor: 'rgb(0,0,0)',
lineWidth: 2,
rotation: 0,
normal: [0, 0, 1]
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import rectangle from './rectangle';
import ellipse from './ellipse';
import circle from './circle';
import polyline from './polyline';
import point from './point';

export {
rectangle,
ellipse,
circle,
polyline,
point
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import rectangle from './rectangle';

/**
* Convert a geojs circle annotation to the large_image
* annotation schema.
*/
function circle(annotation) {
const element = rectangle(annotation);
element.type = 'circle';
element.radius = Math.max(element.width, element.height) / 2;
delete element.width;
delete element.height;
delete element.rotation;
delete element.normal;
return element;
}

export default circle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import rectangle from './rectangle';

/**
* Convert a geojs ellipse annotation to the large_image
* annotation schema.
*/
function ellipse(annotation) {
const element = rectangle(annotation);
element.type = 'ellipse';
return element;
}

export default ellipse;
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import point from './point';
import rectangle from './rectangle';
import ellipse from './ellipse';
import circle from './circle';
import polygon from './polygon';
import line from './line';

export {
point,
rectangle,
ellipse,
circle,
polygon,
line
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export default function circle(json) {
const center = json.center;
const x = center[0];
const y = center[1];
const radius = json.radius;

const left = x - radius;
const right = x + radius;
const top = y - radius;
const bottom = y + radius;

return {
type: 'Polygon',
coordinates: [[
[left, top],
[right, top],
[right, bottom],
[left, bottom],
[left, top]
]],
annotationType: 'circle'
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import _ from 'underscore';

import rotate from '../rotate';

export default function ellipse(json) {
const center = json.center;
const x = center[0];
const y = center[1];
const height = json.height;
const width = json.width;
const rotation = json.rotation || 0;

const left = x - width / 2;
const right = x + width / 2;
const top = y - height / 2;
const bottom = y + height / 2;

return {
type: 'Polygon',
coordinates: [_.map([
[left, top],
[right, top],
[right, bottom],
[left, bottom],
[left, top]
], rotate(rotation, center))],
annotationType: 'ellipse'
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import rectangle from './rectangle';
import ellipse from './ellipse';
import circle from './circle';
import polyline from './polyline';
import point from './point';

export {
rectangle,
ellipse,
circle,
polyline,
point
};
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ export default AccessControlledModel.extend({
strokeColor: {r: 153 / 255, g: 153 / 255, b: 153 / 255},
strokeWidth: 2
},
ellipse: {
fillColor: {r: 176 / 255, g: 222 / 255, b: 92 / 255},
strokeColor: {r: 153 / 255, g: 153 / 255, b: 153 / 255},
strokeWidth: 2
},
circle: {
fillColor: {r: 176 / 255, g: 222 / 255, b: 92 / 255},
strokeColor: {r: 153 / 255, g: 153 / 255, b: 153 / 255},
strokeWidth: 2
},
polyline: {
strokeColor: {r: 1, g: 120 / 255, b: 0},
strokeOpacity: 0.5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ var GeojsImageViewerWidgetExtension = function (viewer) {
_postRender: function () {
// the feature layer is for annotations that are loaded
this.featureLayer = this.viewer.createLayer('feature', {
features: ['point', 'line', 'polygon']
features: ['point', 'line', 'polygon', 'marker']
});
this.setGlobalAnnotationOpacity(this._globalAnnotationOpacity);
this.setGlobalAnnotationFillOpacity(this._globalAnnotationFillOpacity);
// the annotation layer is for annotations that are actively drawn
this.annotationLayer = this.viewer.createLayer('annotation', {
annotations: ['point', 'line', 'rectangle', 'polygon'],
annotations: ['point', 'line', 'rectangle', 'ellipse', 'circle', 'polygon'],
showLabels: false
});
var geo = window.geo; // this makes the style checker happy
Expand Down Expand Up @@ -220,7 +220,8 @@ var GeojsImageViewerWidgetExtension = function (viewer) {
/**
* Render an annotation model on the image. Currently, this is limited
* to annotation types that can be (1) directly converted into geojson
* primitives, OR (2) be represented as heatmaps.
* primitives, (2) be represented as heatmaps, or (3) shown as image
* overlays.
*
* Internally, this generates a new feature layer for the annotation
* that is referenced by the annotation id. All "elements" contained
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ describe('Annotations', function () {
expect(obj.coordinates).toEqual([1, 2]);
});

it('ellipse', function () {
var obj = largeImageAnnotation.annotations.geometry.ellipse({
type: 'ellipse',
id: 'a',
center: [10, 20, 0],
width: 5,
height: 10,
rotation: 0
});

expect(obj.type).toBe('Polygon');
expect(obj.coordinates.length).toBe(1);
expect(obj.annotationType).toBe('ellipse');
expectClose(
obj.coordinates[0], [
[7.5, 15], [12.5, 15], [12.5, 25], [7.5, 25], [7.5, 15]
]
);
});

it('circle', function () {
var obj = largeImageAnnotation.annotations.geometry.circle({
type: 'circle',
id: 'a',
center: [10, 20, 0],
radius: 5
});

expect(obj.type).toBe('Polygon');
expect(obj.coordinates.length).toBe(1);
expect(obj.annotationType).toBe('circle');
expectClose(
obj.coordinates[0], [
[5, 15], [15, 15], [15, 25], [5, 25], [5, 15]
]
);
});

describe('heatmapColorTable', function () {
var values = [0.508, 0.806, 0.311, 0.402, 0.535, 0.661, 0.866, 0.31, 0.241, 0.63, 0.555, 0.067, 0.668, 0.164, 0.512, 0.647, 0.501, 0.637, 0.498, 0.658, 0.332, 0.431, 0.053, 0.531];
var tests = [{
Expand Down
Loading