Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e69bcc4
update requests dependancy
Jun 21, 2023
7b1e10a
fix get_item_by_id
Jul 18, 2023
0a94e2e
Merge pull request #625 from superannotateai/github_issue_623
nareksa Jul 18, 2023
cb9fe2b
fix 1372_issue
Jul 19, 2023
57a09c0
fix 2068_issue
Jul 20, 2023
fa66f95
remove consensus function in docs
Jul 20, 2023
1049fbb
fix 2057_issue
Jul 20, 2023
860cf01
add test pixel project blue mask
Jul 21, 2023
339d734
Merge pull request #626 from superannotateai/fix_issues
nareksa Jul 21, 2023
302dc81
Merge pull request #627 from superannotateai/friday
nareksa Jul 21, 2023
ae1125f
Update __init__.py
nareksa Jul 21, 2023
f411157
fix annotations mask upload
Jul 24, 2023
13f6ac6
Merge pull request #628 from superannotateai/friday
nareksa Jul 24, 2023
2c65c19
Update __init__.py
nareksa Jul 24, 2023
0388802
add ContentType header for mask file
Jul 24, 2023
254f9a8
Merge pull request #629 from superannotateai/friday
nareksa Jul 24, 2023
dd83913
Update __init__.py
nareksa Jul 24, 2023
90b5528
CustomEditor
VaghinakDev Jul 25, 2023
265aa1d
Merge pull request #630 from superannotateai/custom_editor
VaghinakDev Jul 28, 2023
8099907
Version update
VaghinakDev Jul 28, 2023
cf98291
Merge branch 'develop' into friday
VaghinakDev Jul 28, 2023
722627f
Merge pull request #631 from superannotateai/friday
VaghinakDev Jul 28, 2023
2cf349d
Code reformat
VaghinakDev Jul 28, 2023
d5112e6
add UnsupportedType in project_type enum
Aug 4, 2023
89d4aee
Merge pull request #632 from superannotateai/project_type_2092
VaghinakDev Aug 11, 2023
0c8c1d8
Merge pull request #634 from superannotateai/friday
nareksa Aug 14, 2023
c2567b8
Update __init__.py
nareksa Aug 15, 2023
d7d2233
CHANGELOG update
VaghinakDev Aug 20, 2023
af2fac7
Merge branch 'master' into develop
VaghinakDev Aug 20, 2023
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
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ History

All release highlights of this project will be documented in this file.

4.4.15 - August 20, 2023
_______________________

**Added**

- New project type support `CustomEditor`.

**Updated**

- ``SAClient.get_item_by_id()`` Fixed.
- ``SAClient.consensus()`` Deprecation.

4.4.13 - June 04, 2023
_______________________

Expand Down
7 changes: 0 additions & 7 deletions docs/source/api_reference/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,3 @@ ________________________
.. _ref_aggregate_annotations_as_df:
.. automethod:: superannotate.SAClient.validate_annotations
.. automethod:: superannotate.SAClient.aggregate_annotations_as_df

----------

Utility functions
--------------------------------

.. autofunction:: superannotate.SAClient.consensus
7 changes: 0 additions & 7 deletions docs/source/userguide/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@ It can be installed on Ubuntu with:

sudo apt-get install ffmpeg

To use the :py:obj:`consensus` function on Windows and Mac platforms, you might also need to install the shapely package
beforehand. The package works well only under the Anaconda distribution with:

.. code-block:: bash

conda install shapely

----------


Expand Down
61 changes: 0 additions & 61 deletions docs/source/userguide/utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,64 +126,3 @@ Example of created DataFrame:

Each row represents annotation information. One full annotation with multiple
attribute groups can be grouped under :code:`instanceId` field.


Working with DICOM files
------------------------

JPEG images with names :file:`<dicom_file_name>_<frame_num>.jpg` will be created
in :file:`<path_to_output_dir>`. Those JPEG images can be uploaded to
SuperAnnotate platform using the regular:

.. code-block:: python

sa.upload_images_from_folder_to_project(project, "<path_to_output_dir>")

Some DICOM files can have image frames that are compressed. To load them, `GDCM :
Grassroots DICOM library <http://gdcm.sourceforge.net/wiki/index.php/Main_Page>`_ needs to be installed:

.. code-block:: bash

# using conda
conda install -c conda-forge gdcm

# or on Ubuntu with versions above 19.04
sudo apt install python3-gdcm

Computing consensus scores for instances between several projects
-----------------------------------------------------------------


Consensus is a tool to compare the quallity of the annotations of the same image that is present in several projects.
To compute the consensus scores:

.. code-block:: python

res_df = sa.consensus([project_names], "<path_to_export_folder>", [image_list], "<annotation_type>")

Here pandas DataFrame with following columns is returned: creatorEmail, imageName, instanceId, className, area, attribute, projectName, score

.. image:: images/consensus_dataframe.png

Besides the pandas DataFrame there is an option to get the following plots by setting the show_plots flag to True:

* Box plot of consensus scores for each annotators
* Box plot of consensus scores for each project
* Scatter plots of consensus score vs instance area for each project

.. code-block:: python

sa.consensus([project_names], "<path_to_export_folder>", [image_list], "<annotation_type>", show_plots=True)

To the left of each box plot the original score points of that annotator is depicted, the box plots are colored by annotator.

.. image:: images/consensus_annotators_box.png

Analogically the box plots of consensus scores for each project are colored according to project name.

.. image:: images/consensus_projects_box.png

Scatter plot of consensus score vs instance area is separated by projects. Hovering on a point reveals its annotator and image name.
The points are colored according to class name. Each annotator is represented with separate symbol.

.. image:: images/consensus_scatter.png
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pandas~=1.3
ffmpeg-python~=0.2
pillow~=9.5
aiofiles==23.1.0
requests==2.30.0
requests==2.31.0
tqdm==4.64.0
fire==0.4.0
mixpanel==4.8.3
Expand Down
3 changes: 2 additions & 1 deletion src/superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import os
import sys

__version__ = "4.4.13"

__version__ = "4.4.14"

sys.path.append(os.path.split(os.path.realpath(__file__))[0])

Expand Down
17 changes: 7 additions & 10 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
PROJECT_STATUS = Literal["NotStarted", "InProgress", "Completed", "OnHold"]

PROJECT_TYPE = Literal[
"Vector", "Pixel", "Video", "Document", "Tiled", "Other", "PointCloud"
"Vector", "Pixel", "Video", "Document", "Tiled", "Other", "PointCloud", "CustomEditor"
]

CLASS_TYPE = Literal["object", "tag"]
Expand Down Expand Up @@ -156,8 +156,8 @@ def get_folder_by_id(self, project_id: int, folder_id: int):
response = self.controller.get_folder_by_id(
folder_id=folder_id, project_id=project_id
)

return FolderSerializer(response).serialize(
response.raise_for_status()
return FolderSerializer(response.data).serialize(
exclude={"completedCount", "is_root"}
)

Expand All @@ -173,12 +173,13 @@ def get_item_by_id(self, project_id: int, item_id: int):
:return: item metadata
:rtype: dict
"""

project_response = self.controller.get_project_by_id(project_id=project_id)
project_response.raise_for_status()
response = self.controller.get_item_by_id(
item_id=item_id, project_id=project_id
item_id=item_id, project=project_response.data
)

return ItemSerializer(response).serialize(exclude={"url", "meta"})
return ItemSerializer(response.data).serialize(exclude={"url", "meta"})

def get_team_metadata(self):
"""Returns team metadata
Expand Down Expand Up @@ -1513,10 +1514,6 @@ def download_export(
to_s3_bucket=None,
):
"""Download prepared export.

WARNING: Starting from version 1.9.0 :ref:`download_export <ref_download_export>` additionally
requires :py:obj:`project` as first argument.

:param project: project name
:type project: str
:param export: export name
Expand Down
30 changes: 30 additions & 0 deletions src/superannotate/lib/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,36 @@ def setup_logging(level=DEFAULT_LOGGING_LEVEL, file_path=LOG_FILE_LOCATION):

INVALID_JSON_MESSAGE = "Invalid json"

PROJECT_SETTINGS_VALID_ATTRIBUTES = [
"Brightness",
"Fill",
"Contrast",
"ShowLabels",
"ShowComments",
"Image",
"Lines",
"AnnotatorFinish",
"PointSize",
"FontSize",
"WorkflowEnable",
"ClassChange",
"ShowEntropy",
"UploadImages",
"DeleteImages",
"Download",
"RunPredictions",
"RunSegmentations",
"ImageQuality",
"ImageAutoAssignCount",
"FrameMode",
"FrameRate",
"JumpBackward",
"JumpForward",
"UploadFileType",
"Tokenization",
"ImageAutoAssignEnable",
]

__alL__ = (
FolderStatus,
ProjectStatus,
Expand Down
3 changes: 3 additions & 0 deletions src/superannotate/lib/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class ProjectType(BaseTitledEnum):
TILED = "Tiled", 5
OTHER = "Other", 6
POINT_CLOUD = "PointCloud", 7
CUSTOM_EDITOR = "CustomEditor", 8
UNSUPPORTED_TYPE_1 = "UnsupportedType", 9
UNSUPPORTED_TYPE_2 = "UnsupportedType", 10

@classproperty
def images(self):
Expand Down
2 changes: 2 additions & 0 deletions src/superannotate/lib/core/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ def __init__(
secret_key: str,
session_token: str,
bucket: str,
region: str,
):
self._session = boto3.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=session_token,
region_name=region,
)

self._resource = self._session.resource("s3")
Expand Down
12 changes: 12 additions & 0 deletions src/superannotate/lib/core/service_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ class Limit(BaseModel):
max_image_count: Optional[int]
remaining_image_count: int

class Config:
extra = Extra.ignore


class UserLimits(BaseModel):
user_limit: Optional[Limit]
project_limit: Limit
folder_limit: Limit

class Config:
extra = Extra.ignore


class UploadAnnotationAuthData(BaseModel):
access_key: str
Expand Down Expand Up @@ -80,12 +86,18 @@ class Resource(BaseModel):
failed_items: List[str] = Field([], alias="failedItems")
missing_resources: Resource = Field({}, alias="missingResources")

class Config:
extra = Extra.ignore


class UploadCustomFieldValues(BaseModel):
succeeded_items: Optional[List[Any]]
failed_items: Optional[List[str]]
error: Optional[Any]

class Config:
extra = Extra.ignore


class ServiceResponse(BaseModel):
status: Optional[int]
Expand Down
28 changes: 22 additions & 6 deletions src/superannotate/lib/core/usecases/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ def validate_project_type(self):
raise ValidationError("Unsupported project type.")

def _validate_json(self, json_data: dict) -> list:
if self._project.type >= constants.ProjectType.PIXEL.value:
return []
use_case = ValidateAnnotationUseCase(
reporter=self.reporter,
team_id=self._project.team_id,
Expand Down Expand Up @@ -571,7 +573,10 @@ def get_annotation_from_s3(bucket, path: str):

def prepare_annotation(self, annotation: dict, size) -> dict:
errors = None
if size < BIG_FILE_THRESHOLD:
if (
size < BIG_FILE_THRESHOLD
and self._project.type < constants.ProjectType.PIXEL.value
):
use_case = ValidateAnnotationUseCase(
reporter=self.reporter,
team_id=self._project.team_id,
Expand All @@ -591,21 +596,31 @@ def prepare_annotation(self, annotation: dict, size) -> dict:
)
return annotation

@staticmethod
def get_mask_path(path: str) -> str:
if path.endswith(constants.PIXEL_ANNOTATION_POSTFIX):
replacement = constants.PIXEL_ANNOTATION_POSTFIX
else:
replacement = ".json"
parts = path.rsplit(replacement, 1)
return constants.ANNOTATION_MASK_POSTFIX.join(parts)

async def get_annotation(
self, path: str
) -> (Optional[Tuple[io.StringIO]], Optional[io.BytesIO]):
mask = None
mask_path = path.replace(
constants.PIXEL_ANNOTATION_POSTFIX, constants.ANNOTATION_MASK_POSTFIX
)
mask_path = self.get_mask_path(path)
if self._client_s3_bucket:
content = self.get_annotation_from_s3(self._client_s3_bucket, path).read()
if self._project.type == constants.ProjectType.PIXEL.value:
mask = self.get_annotation_from_s3(self._client_s3_bucket, mask_path)
else:
async with aiofiles.open(path, encoding="utf-8") as file:
content = await file.read()
if self._project.type == constants.ProjectType.PIXEL.value:
if (
self._project.type == constants.ProjectType.PIXEL.value
and os.path.exists(mask_path)
):
async with aiofiles.open(mask_path, "rb") as mask:
mask = await mask.read()
if not isinstance(content, bytes):
Expand Down Expand Up @@ -691,12 +706,13 @@ def s3_bucket(self):
return self._s3_bucket

def _upload_mask(self, item_data: ItemToUpload):
if self._project.type == constants.ProjectType.PIXEL.value:
if self._project.type == constants.ProjectType.PIXEL.value and item_data.mask:
self.s3_bucket.put_object(
Key=self.annotation_upload_data.images[item_data.item.id][
"annotation_bluemap_path"
],
Body=item_data.mask,
ContentType="image/jpeg",
)

async def distribute_queues(self, items_to_upload: List[ItemToUpload]):
Expand Down
6 changes: 2 additions & 4 deletions src/superannotate/lib/core/usecases/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ def execute(self):
project_id=self._project_id,
team_id=self._team_id,
)
if not response.ok:
self._response.errors = AppException(response.error)
except AppException as e:
self._response.errors = e
else:
self._response.data = response.data

if not response.ok:
self._response.errors = AppException(response.error)

return self._response


Expand Down
2 changes: 2 additions & 0 deletions src/superannotate/lib/core/usecases/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,7 @@ def s3_repo(self):
self._auth_data.data["secretAccessKey"],
self._auth_data.data["sessionToken"],
self._auth_data.data["bucket"],
self._auth_data.data["region"],
)

def validate_project_type(self):
Expand Down Expand Up @@ -952,6 +953,7 @@ def s3_repository(self):
self.auth_data["secretAccessKey"],
self.auth_data["sessionToken"],
self.auth_data["bucket"],
self.auth_data["region"],
)
return self._s3_repo_instance

Expand Down
Loading