-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathassign_annotation.py
194 lines (164 loc) · 6.37 KB
/
assign_annotation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import os
from typing import Optional, Sequence
import django
from django.db.models import Q
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "parasite.settings")
django.setup()
from annotation.models import Annotation, Annotator
from sample.const import COMMON_ANNOTATION_SAMPLES, IMAGE_TYPE_CHOICES
from sample.models import SlideImage
def assign_annotations(
usernames: Optional[Sequence[str]] = None,
reference_annotator_username: Optional[str] = None,
assign_common: bool = True,
limit: int = COMMON_ANNOTATION_SAMPLES,
annotation_phase: str = "common",
):
"""
A function that assigns annotations to annotators based on certain conditions.
Parameters:
usernames: An optional sequence of strings representing usernames to assign annotations to.
reference_annotator_username: An optional string representing the reference annotator username.
assign_common: A boolean indicating whether to assign common annotations.
limit: An integer specifying the limit of common annotations to assign.
phase: A string representing the phase of the assignment.
Returns:
None
"""
# Get all annotators from username
annotators = (
Annotator.objects.filter(user__username__in=usernames)
if usernames
else Annotator.objects.all()
)
# If there are no annotators, return
if not annotators.exists():
print("No annotators found.")
return
if assign_common:
if reference_annotator_username is not None:
print(f"Using {reference_annotator_username} for common annotations.")
existing_annotations_for_reference_annotator = Annotation.objects.filter(
annotator__user__username=reference_annotator_username
).values("image")
else:
existing_annotations_for_reference_annotator = None
print(f"{existing_annotations_for_reference_annotator=}")
annotations = []
for image_type in IMAGE_TYPE_CHOICES:
slide_images_to_assign = (
SlideImage.objects.filter(
~Q(image=""),
image_type=image_type[0],
approved=True,
id__in=existing_annotations_for_reference_annotator,
)
if existing_annotations_for_reference_annotator is not None
else get_random_slide_images(image_type=image_type[0], limit=limit)
)
annotations.extend(
(
Annotation(
image=slide_image,
annotator=annotator,
annotation_phase=annotation_phase,
)
for annotator in annotators
for slide_image in slide_images_to_assign
)
)
print(len(annotations))
Annotation.objects.bulk_create(
annotations, ignore_conflicts=True, batch_size=512
)
else:
if reference_annotator_username is not None:
print(
f"`reference_annotator_username` not used for uncommon annotations. Skipping."
)
for image_type in IMAGE_TYPE_CHOICES:
filter_query = ~Q(image="") & ~Q(
id__in=Annotation.objects.all().values("image")
)
num_slide_images = SlideImage.objects.filter(
filter_query, approved=True, image_type=image_type[0]
).count()
if num_slide_images < limit * len(annotators):
msg = (
f"Not enough {image_type[1]} slide images: {num_slide_images} "
f"to equally assign {limit} annotations to {len(annotators)} annotators."
)
raise ValueError(msg)
for annotator in annotators:
# Slide Images to assign changes for each annotator ()
slide_images_to_assign = get_random_slide_images(
filter_query=filter_query,
image_type=image_type[0],
limit=limit,
)
anno = tuple(
Annotation(
image=slide_image,
annotator=annotator,
annotation_phase=annotation_phase,
)
for slide_image in slide_images_to_assign
)
Annotation.objects.bulk_create(
anno, ignore_conflicts=True, batch_size=512
)
def get_random_slide_images(
*, filter_query: Optional[Q] = None, limit: Optional[int] = None, **kwargs
):
"""
Retrieves random slide images based on optional filter query and limit parameters.
Args:
filter_query (Optional[Q]): Optional filter query to filter the slide images.
limit (Optional[int]): Optional limit on the number of slide images to retrieve.
**kwargs: Additional keyword arguments for filtering the slide images.
Returns:
QuerySet: QuerySet of SlideImage objects filtered based on the provided parameters.
"""
if filter_query is None:
filter_query = ~Q(image="")
return SlideImage.objects.filter(filter_query, approved=True, **kwargs).order_by(
"?"
)[:limit]
if __name__ == "__main__":
from argparse import ArgumentParser, BooleanOptionalAction
parser = ArgumentParser(description="Assign annotations to annotators.")
parser.add_argument(
"-a",
"--annotator",
nargs="+",
help="Usernames to be added to annotators",
default=None,
dest="usernames",
)
parser.add_argument(
"-r",
"--reference-annotator",
help="Reference annotator username",
default=None,
dest="reference_annotator_username",
)
parser.add_argument(
"-c",
"--assign-common",
action=BooleanOptionalAction,
help="Assign common annotations to annotators",
default=True,
)
parser.add_argument(
"-l",
"--limit",
help="Number of common annotations to assign",
type=int,
default=COMMON_ANNOTATION_SAMPLES,
)
parser.add_argument(
"-p", "--annotation-phase", help="Annotation phase", type=str, required=True
)
args = parser.parse_args()
assign_annotations(**vars(args))
print("Done!")