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

Refactor: static annotations #5

Merged
merged 49 commits into from
Aug 26, 2024
Merged

Refactor: static annotations #5

merged 49 commits into from
Aug 26, 2024

Conversation

kbolashev
Copy link
Member

Goal of this refactor: go away from a nested class structure to a flatter structure,
Now instead of having project -> files -> annotations, everything is a list of annotations, which contain enough stuff to define the annotation, and additional per-framework stuff is provided in a context (right now it's only needed in YOLO here).

Tried to provide as much testing as possible, so hope it's easy enough to read.

Not implemented in this: DagsHub datasource converters, I'm putting that in the client instead.

@kbolashev kbolashev requested a review from guysmoilov June 27, 2024 15:12
@kbolashev kbolashev self-assigned this Jun 27, 2024
@kbolashev
Copy link
Member Author

kbolashev commented Jun 30, 2024

Noticed that there's no code for creating an LS task from IR annotations, fixing that.

Copy link
Member

@guysmoilov guysmoilov left a comment

Choose a reason for hiding this comment

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

Some comments


filename: Optional[str] = None

category: str
Copy link
Member

Choose a reason for hiding this comment

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

I feel like this should be on the individual annotation types - for example, maybe a bounding box might have multiple non-conflicting classes. Or the user might want to store a more general mapping of {category:confidence} or similar to that.

  1. Do you benefit from having this in the base class?
  2. Did you provide a way to have optional custom freestyle information in the annotations? How hard would that be (including for the backend)?

Copy link
Member Author

@kbolashev kbolashev Jun 30, 2024

Choose a reason for hiding this comment

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

Do you benefit from having this in the base class?

Yes, because I need it if I want to e.g. gather all the classes in the annotations.

I can turn this into

@abstractmethod
def get_categories(self) -> list[str]:
    ...

and it will work, but I want us to be aware of WHAT this will be used for.

If you want to store confidences like that, how do you expect to convert it to any other format?

Did you provide a way to have optional custom freestyle information in the annotations?

What for? If that optional information can't be used for conversion, then why have it?
I can add a meta: dict[str, Any] into any annotation, but then it's going to be a stringly-typed mess IF we want to utilize it at any point.

I understand your point that the classes here are non-extensible though.

How hard would that be (including for the backend)?

I didn't give a lot of thought for how this works on the backend, since this isn't interacting with anything for now. What do you mean by backend? I don't expect to be sending this IR to the backend/DE for storage.

Copy link
Member

Choose a reason for hiding this comment

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

I mean, in the end this gets converted to LS format and sent to the backend. We shouldn't put anything in there which will prevent LS from reading the task for the common image labeling formats - I'm guessing it will just ignore any additional fields

class IRPosePoint(BaseModel):
x: float
y: float
visible: Optional[bool] = None
Copy link
Member

Choose a reason for hiding this comment

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

What is this? Isn't it a display setting? Or does it mean something like "The user's hand is out of frame but I would estimate that it's in coordinate (-42,-314) outside the image bounds?"

Copy link
Member Author

Choose a reason for hiding this comment

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

CVAT calls it occluded I think, YOLO calls it visible.
I don't think it particularily refers to out-of-bounds points, but instead to occlusion. If a point is not visible, that means something is in front of the hand and you can't see it on the picture.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense. Occluded is more clear.

from .skeleton import parse_skeleton
from dagshub_annotation_converter.ir.image import IRAnnotationBase

CVATParserFunction = Callable[[ElementBase, ElementBase], IRAnnotationBase]
Copy link
Member

Choose a reason for hiding this comment

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

You probably want to rename this module to cvat/xml2 to disambiguate

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm out of the loop here, can you let me know what's the thing it's supposed to disambiguate with? They have multiple formats?

Copy link
Member

Choose a reason for hiding this comment

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

Yes they can output to multiple formats, and the one we support AFAIK is "CVAT for images" xml, I guess 1.1 not 2.0 actually
https://docs.cvat.ai/docs/manual/advanced/formats/

@kbolashev kbolashev requested a review from guysmoilov July 2, 2024 08:29
Copy link
Member

@guysmoilov guysmoilov left a comment

Choose a reason for hiding this comment

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

My work in progress review

Comment on lines 24 to 29
if label_extension is not None:
# Replace the extension
ext = get_extension(in_path)
if ext is None:
return None
new_parts[-1] = new_parts[-1].replace(ext, label_extension)
Copy link
Member

Choose a reason for hiding this comment

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

This isn't described in the function name or docstring - classic case of better no comment than a wrong one

Copy link
Member

Choose a reason for hiding this comment

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

Also, it searches for the extension in the whole path instead of just its final part, which is a bug if there's any dot in a dir name

Copy link
Member Author

Choose a reason for hiding this comment

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

get_extension only looks for the dot in the last part of the path (name = path.name at the beginning of that function)

# Conflicts:
#	dagshub_annotation_converter/__init__.py
#	dagshub_annotation_converter/image/exporters/yolo.py
#	dagshub_annotation_converter/image/importers/dagshub_datasource.py
#	dagshub_annotation_converter/image/util/path_util.py
#	dagshub_annotation_converter/schema/ir/annotation_ir.py
#	dagshub_annotation_converter/schema/label_studio/abc.py
#	dagshub_annotation_converter/schema/label_studio/keypointlabels.py
#	dagshub_annotation_converter/schema/label_studio/polygonlabels.py
#	dagshub_annotation_converter/schema/label_studio/rectanglelabels.py
#	dagshub_annotation_converter/schema/label_studio/task.py
@kbolashev kbolashev merged commit 4a742ff into main Aug 26, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants