Skip to content
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.34",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.12",
"@fortawesome/free-regular-svg-icons": "^5.11.2",
"@fortawesome/free-solid-svg-icons": "^5.6.3",
"@fortawesome/react-fontawesome": "^0.1.3",
"@material-ui/core": "^4.6.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Annotator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default ({
showPointDistances,
pointDistancePrecision,
showTags = true,
enabledTools = ["select", "create-point", "create-box", "create-polygon"],
enabledTools = ["select", "create-point", "create-box", "create-polygon", "create-circle"],
regionTagList = [],
regionClsList = [],
imageTagList = [],
Expand Down
55 changes: 55 additions & 0 deletions src/Annotator/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const getRandomColor = () => {

const typesToSaveWithHistory = {
BEGIN_BOX_TRANSFORM: "Transform/Move Box",
BEGIN_CIRCLE_TRANSFORM: "Transform/Move Circle",
BEGIN_MOVE_POINT: "Move Point",
DELETE_REGION: "Delete Region"
}
Expand Down Expand Up @@ -183,6 +184,19 @@ export default (state: MainLayoutState, action: Action) => {
regionId: action.point.id
})
}
case "BEGIN_CIRCLE_TRANSFORM": {
const { circle, directions } = action
state = closeEditors(state)
if (directions === "MOVE_REGION") {
return setIn(state, ["mode"], { mode: "MOVE_REGION", regionId: circle.id })
} else {
return setIn(state, ["mode"], {
mode: "RESIZE_CIRCLE",
regionId: circle.id,
original: {x: circle.x, y: circle.y, xr: circle.xr, yr: circle.yr}
})
}
}
case "BEGIN_BOX_TRANSFORM": {
const { box, directions } = action
state = closeEditors(state)
Expand Down Expand Up @@ -309,6 +323,16 @@ export default (state: MainLayoutState, action: Action) => {
{ ...box, x: dx, w: dw, y: dy, h: dh }
)
}
case "RESIZE_CIRCLE": {
const { regionId } = state.mode
const [region, regionIndex] = getRegion(regionId)
if (!region) return setIn(state, ["mode"], null)
return setIn(
state,
["images", currentImageIndex, "regions", regionIndex],
{ ...region, xr: action.x, yr: action.y }
)
}
case "DRAW_POLYGON": {
const { regionId } = state.mode
const [region, regionIndex] = getRegion(regionId)
Expand Down Expand Up @@ -397,6 +421,28 @@ export default (state: MainLayoutState, action: Action) => {
})
break
}
case "create-circle": {
state = saveToHistory(state, "Create Circle")
newRegion = {
type: "circle",
x: x,
y: y,
xr: 0.00000000001,
yr: 0.00000000001,
highlighted: true,
editingLabels: false,
color: getRandomColor(),
id: getRandomId()
}
state = unselectRegions(state)
state = setIn(state, ["mode"], {
mode: "RESIZE_CIRCLE",
editLabelEditorAfter: true,
regionId: newRegion.id,
original: {x: x, y: y, xr: newRegion.xr, yr: newRegion.yr}
})
break
}
}
}

Expand Down Expand Up @@ -439,6 +485,14 @@ export default (state: MainLayoutState, action: Action) => {
}
}
}
case "RESIZE_CIRCLE": {
if (state.mode.editLabelEditorAfter) {
return {
...modifyRegion(state.mode.regionId, { editingLabels: true }),
mode: null
}
}
}
case "MOVE_REGION":
case "MOVE_POLYGON_POINT": {
return { ...state, mode: null }
Expand Down Expand Up @@ -553,6 +607,7 @@ export default (state: MainLayoutState, action: Action) => {
}
case "MOVE_POLYGON_POINT":
case "RESIZE_BOX":
case "RESIZE_CIRCLE":
case "MOVE_REGION": {
return setIn(state, ["mode"], null)
}
Expand Down
8 changes: 7 additions & 1 deletion src/IconTools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
faHandPaper,
faSearch
} from "@fortawesome/free-solid-svg-icons"
import { faCircle } from "@fortawesome/free-regular-svg-icons"
import SmallToolButton, { SelectedTool } from "../SmallToolButton"
import { makeStyles } from "@material-ui/core/styles"
import { grey } from "@material-ui/core/colors"
Expand Down Expand Up @@ -41,7 +42,7 @@ export default ({
showTags,
selectedTool,
onClickTool,
enabledTools = ["select", "create-point", "create-box", "create-polygon"]
enabledTools = ["select", "create-point", "create-box", "create-polygon", "create-circle"]
}: Props) => {
const classes = useStyles()
return (
Expand Down Expand Up @@ -99,6 +100,11 @@ export default ({
name="Add Polygon"
icon={<FontAwesomeIcon size="xs" fixedWidth icon={faDrawPolygon} />}
/>
<SmallToolButton
id="create-circle"
name="Add Circle"
icon={<FontAwesomeIcon size="xs" fixedWidth icon={faCircle} />}
/>
{/* <SmallToolButton
id="create-pixel"
name="Add Pixel Region"
Expand Down
58 changes: 56 additions & 2 deletions src/ImageCanvas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type {
PixelRegion,
Point,
Polygon,
Box
Box,
Circle
} from "./region-tools.js"
import { getEnclosingBox } from "./region-tools.js"
import { makeStyles } from "@material-ui/core/styles"
Expand Down Expand Up @@ -53,6 +54,7 @@ type Props = {
onCloseRegionEdit: Region => any,
onDeleteRegion: Region => any,
onBeginBoxTransform: (Box, [number, number]) => any,
onBeginCircleTransform: (Circle, directions: string) => any,
onBeginMovePolygonPoint: (Polygon, index: number) => any,
onAddPolygonPoint: (Polygon, point: [number, number], index: number) => any,
onSelectRegion: Region => any,
Expand Down Expand Up @@ -84,6 +86,7 @@ export default ({
onChangeRegion,
onBeginRegionEdit,
onCloseRegionEdit,
onBeginCircleTransform,
onBeginBoxTransform,
onBeginMovePolygonPoint,
onAddPolygonPoint,
Expand Down Expand Up @@ -114,7 +117,7 @@ export default ({

const projectRegionBox = r => {
const { iw, ih } = layoutParams.current
const bbox = getEnclosingBox(r)
const bbox = getEnclosingBox(r, iw, ih)
const margin = r.type === "point" ? 15 : 2
const cbox = {
x: bbox.x * iw - margin,
Expand Down Expand Up @@ -282,6 +285,18 @@ export default ({
context.restore()
break
}
case "circle": {
context.save()
context.shadowColor = "black"
context.shadowBlur = 4
context.strokeStyle = region.color
context.beginPath();
context.arc(region.x * iw, region.y * ih,
Math.sqrt(Math.pow((region.xr-region.x)*iw,2) + Math.pow((region.yr-region.y)*ih,2)), 0, 2 * Math.PI);
context.stroke()
context.restore()
break
}
case "pixel": {
context.save()

Expand Down Expand Up @@ -566,6 +581,45 @@ export default ({
}}
/>
))}
{
r.type === "circle" &&
!dragWithPrimary &&
!zoomWithPrimary &&
!r.locked &&
r.highlighted &&
[
[r.x, r.y],
[(r.x*iw + Math.sqrt(Math.pow((r.xr-r.x)*iw,2) + Math.pow((r.yr-r.y)*ih,2)))/iw, r.y],
[r.x, (r.y*ih + Math.sqrt(Math.pow((r.xr-r.x)*iw,2) + Math.pow((r.yr-r.y)*ih,2)))/ih],
[(r.x*iw - Math.sqrt(Math.pow((r.xr-r.x)*iw,2) + Math.pow((r.yr-r.y)*ih,2)))/iw, r.y],
[r.x, (r.y*ih - Math.sqrt(Math.pow((r.xr-r.x)*iw,2) + Math.pow((r.yr-r.y)*ih,2)))/ih]
].map(([px, py], i) => {
const proj = mat
.clone()
.inverse()
.applyToPoint(px * iw, py * ih)
return(
<div
key={i}
className={classes.transformGrabber}
{...mouseEvents}
onMouseDown={e => {
if (e.button === 0 && i==0){
return onBeginCircleTransform(r, "MOVE_REGION")
}else if(e.button === 0 && i!=0){
return onBeginCircleTransform(r, "RESIZE_CIRCLE")
}
mouseEvents.onMouseDown(e)
}}
style={{
left: proj.x - 4,
top: proj.y - 4,
borderRadius: px === r.x && py === r.y ? 4 : undefined
}}
/>
)
})
}
{r.type === "polygon" &&
!dragWithPrimary &&
!zoomWithPrimary &&
Expand Down
28 changes: 27 additions & 1 deletion src/ImageCanvas/region-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,21 @@ export type Polygon = {|
open?: boolean,
points: Array<[number, number]>
|}
export type Circle = {|
...$Exact<BaseRegion>,
type: "circle",
// radius: number,
// x and y indicate the coordinates of the centre of the circle
x: number,
y: number,
// xr and yr indicate the x and y coordinates of the current mouse pointer while dragging.
xr: number,
yr: number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm lukewarm on this definition because technically in a circle xr=yr so it should really just have r. My suggestion is to have an optional r?: number, which is set in the case where xr=yr to be r=xr=yr because this is more consistent with expectation when dealing with circles.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seveibar, I started with a single r as you are suggesting but, since we are transforming the coordinates based on image height and width, single r won't be enough.

In the current code, xr and yr aren't actually representing the radius exactly but are representing the coordinates to last point while dragging by a mouse.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha

|}

export type Region = Point | PixelRegion | Box | Polygon

export const getEnclosingBox = (region: Region) => {
export const getEnclosingBox = (region: Region, iw: number, ih: number) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's unclear to me why iw and ih are needed (because they're not needed for other structures). It seems like the enclosing box should be equal to...

{ x: region.x - region.xr, y: region.y - region.yr, w: region.xr, h: region.yr } 

If possible, we should remove iw and ih from the parameters here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in my previous comment, xr and yr aren't actually representing the radius exactly but are representing the coordinates to last point while dragging by a mouse. So, I need ih and iw.
{ x: region.x - region.xr, y: region.y - region.yr, w: region.xr, h: region.yr }

This works if xr is x-radius and yr is y-radius.
I'm thinking to make xr as x-radius and yr as y-radius but then, I'd need ih and iw in the reducer. I'm new to react and redux. So, I'd need your advice here as to how I can set ih and iw in the state (store) of the reducer.

Reason I need ih and iw in the reducer:
Based on the center of the circle and the current point, I have to calculate x-radius and y-radius.

switch (region.type) {
case "polygon": {
const box = {
Expand Down Expand Up @@ -90,6 +101,18 @@ export const getEnclosingBox = (region: Region) => {
return box
}
}
case "circle": {
const radius = Math.sqrt(Math.pow((region.xr-region.x)*iw,2) + Math.pow((region.yr-region.y)*ih,2))
const box = {
x: (region.x*iw - radius)/iw,
y: (region.y*ih - radius)/ih,
w: 0,
h: 0
}
box.w = (region.x*iw + radius)/iw - box.x
box.h = (region.y*ih + radius)/ih - box.y
return box
}
}
throw new Error("unknown region")
}
Expand All @@ -102,6 +125,9 @@ export const moveRegion = (region: Region, x: number, y: number) => {
case "box": {
return { ...region, x: x - region.w / 2, y: y - region.h / 2 }
}
case "circle": {
return { ...region, x, y, xr: region.xr + x - region.x, yr: region.yr + y - region.y }
}
}
return region
}
5 changes: 5 additions & 0 deletions src/MainLayout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export default ({ state, dispatch }: Props) => {
onBeginRegionEdit={action("OPEN_REGION_EDITOR", "region")}
onCloseRegionEdit={action("CLOSE_REGION_EDITOR", "region")}
onDeleteRegion={action("DELETE_REGION", "region")}
onBeginCircleTransform={action(
"BEGIN_CIRCLE_TRANSFORM",
"circle",
"directions"
)}
onBeginBoxTransform={action(
"BEGIN_BOX_TRANSFORM",
"box",
Expand Down
7 changes: 7 additions & 0 deletions src/MainLayout/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export type Mode =
freedom: [number, number],
original: { x: number, y: number, w: number, h: number }
|}
| {|
mode: "RESIZE_CIRCLE",
editLabelEditorAfter?: boolean,
regionId: string,
original: {x: number, y: number, xr: number, yr: number}
|}
| {| mode: "MOVE_REGION" |}

export type MainLayoutState = {|
Expand Down Expand Up @@ -69,6 +75,7 @@ export type Action =
| {| type: "CLOSE_POLYGON", polygon: Polygon |}
| {| type: "SELECT_REGION", region: Region |}
| {| type: "BEGIN_MOVE_POINT", point: Point |}
| {| type: "BEGIN_CIRCLE_TRANSFORM", region: Circle, directions: string |}
| {| type: "BEGIN_BOX_TRANSFORM", box: Box, directions: [number, number] |}
| {| type: "BEGIN_MOVE_POLYGON_POINT", polygon: Polygon, pointIndex: number |}
| {|
Expand Down