NaturalIntelligence / imglab

To speedup and simplify image labeling/ annotation process with multiple supported formats.
https://solothought.com/imglab/
MIT License
977 stars 596 forks source link

Implemented copy paste SVG element #102

Closed alextychan closed 6 years ago

alextychan commented 6 years ago

Purpose / Goal

Implemented copy paste svg elements.

Notes: 1) My implementation works by copying then instantiating the attributes of all selected shapes and points. 2) The code may need to be refactored, but I'm not exactly sure how. There are similarities between the functions in "paste" and "drawOnCanvas".

In conjunction with issue #53

Type

Please mention the type of PR

Note : Please ensure that you've read contribution guidelines before raising this PR.

amitguptagwl commented 6 years ago

In my opinion, you can just add the copied elements to labelleddata object by generating new Ids for each shape. And then reload the workarea tag. In this way your code will reduce and you've not have to take care of attaching events.

alextychan commented 6 years ago

I understand that your suggestion is to use this: //use the new IDs : shapetype + shapeIndex, shapetype + shapeIndex + point + pointIndex

The thing I'm not sure about is how do I generate unique ID's that do not overlap. This is because one shape can be copied and pasted multiple times.

Do I need to keep a count of all the shapes and points in a separate global variable for this? e.g. shapeIndex = 1, pointIndex = 1

amitguptagwl commented 6 years ago

you need not to keep the count. Instead you can check the length of already added shapes and add one to it. ${image_index}_${shape_type}_${image1.shapes.length}

alextychan commented 6 years ago

From my perspective, there might be a bug if implemented that way. Here's a simple example.

eg. User creates polygon#1, then copies and paste 2 times. Now there are 3 polygons(image1.shapes.length = 3). Polygon#1, Polygon#2, Polygon#3

User removes polygon#2. Now we have 2 polygons(image1.shapes.length = 2): Polygon#1, Polygon#3

User copies and paste a new polygon. Now we have 3 polygons(image1.shapes.length = 3): Polygon#1, Polygon#3, Polygon#3

Now we have 2 polygons with the same ids. That's why I thought we might need something like a global count that only increments, so that the ID's will always be unique.

Could you provide an example of your idea for comparison? Thanks.

amitguptagwl commented 6 years ago

Hmm.. agree with your point.

One more issue, data is temporarily saved in browser. If we have a global counter starts from 1 (let's suppose), we restart the application, data will be loaded from browser, but the counter will reset to 1. And we can get duplicate id again.

what if calculate the highest id assigned to a shape for particular image? (we can remove any prefix from the id to make it simple)

alextychan commented 6 years ago

You also raised a good point. I didn't think about the browser cache.

Would you say that storing the global variable in labellingData is a good idea? My thought process is that each image shall hold a counter that can be used as a unique ID. So image 1, 2, 3, all have individual counters.

The benefit of doing that would be that each image has a separate counter and the data would automatically be stored in our save files. Besides that, we don't need to compute the highest id, as that might be costly if we have a large dataset.

The labellingData schema would look like this

labellingData: { 'imageName': { ... 'shapes', 'shapeindex' #polygon/shape counter } }

amitguptagwl commented 6 years ago

Well! we were planning to store application conifguration in browser cache ( image opacity, point size and color etc). But counter may not be suitable because it'll keep increasing otherwise. However I doubt it is ever gonna to reach to it's maximum value. But just a thought.

How many shapes we can have?

I've observed people labeling up to 350 images in a session. Assume there are 3 shape on each image and each shape has approx 120 feature points. We can have 350 x 3 x 120 = 126000 points. Now let's suppose, use copy paste each shape 2-3 times. We are still fine.

So ok you can go with that approach. But in this case, persist the app configuration in browser cache not the single counter.

I hope there is no other condition which may duplicate the Ids.

amitguptagwl commented 6 years ago

I missed your point to have counter for each image. Yeas you can do that as well

alextychan commented 6 years ago

It took me a while, but I managed to fix a bug with the positioning. The bug was as follows.

Add a new feature point after copy pasting. The result is that the point is drawn on the canvas wrongly but the coordinates were correct.

Problem: When adding a new point, in getPointToDraw, the containerOffset was incorrect. This is because of the plugin "svg-pan-zoom". The plugin adds a new tag to wrap our existing container. And the tag does not contain positional information. Below is the quote obtained from the documentation in SVG.js.

Note: Groups do not have a geometry of their own, it's inherited from their content. Therefore groups do not listen to x, y, width, and height attributes.

Solution: Just make sure the containerOffset referenced the correct parent.

alextychan commented 6 years ago

Here's a brief description of the current implementation.

"Copy" will grab the shape and point metadata and store it in copiedElements array. The reason I store metadata rather than the object itself is to allow copy-pasting between images, because some metadata requires context that changes, like shape.rbox(myCanvas).

"Paste" will add all the shapes and points into the image, then call self.update() which redraws the current shapes in the image.

Note: There is a small bug in the "getPoints" function regarding the polygon points. The issue was the same as the wrongly drawn points. The container we grabbed was the tag and not the wrapper. I just made sure that the container was pointing at the tag.

alextychan commented 6 years ago

As for the number of shapes possible, that shouldn't be a problem as the Javascript max int is pretty large.

The MAX_SAFE_INTEGER constant has a value of 9007199254740991 (9,007,199,254,740,991 or ~9 quadrillion).

That goes for the number of points as well.

alextychan commented 6 years ago

I've also noticed that after drawing a shape and adding a point. When you zoom in and add a point, the position of the point is incorrect. I suspect that has something to do with zoomScale. But that should be saved for another issue. This issue exists in the original implementation as well.