Skip to content

Commit 4242d7b

Browse files
committed
feat(validate_image): add validate image project & tutorial creation
1 parent 8f663c7 commit 4242d7b

File tree

6 files changed

+199
-0
lines changed

6 files changed

+199
-0
lines changed

mapswipe_workers/mapswipe_workers/definitions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class ProjectType(Enum):
137137
MEDIA_CLASSIFICATION = 5
138138
DIGITIZATION = 6
139139
STREET = 7
140+
VALIDATE_IMAGE = 10
140141

141142
@property
142143
def constructor(self):
@@ -149,6 +150,7 @@ def constructor(self):
149150
FootprintProject,
150151
MediaClassificationProject,
151152
StreetProject,
153+
ValidateImageProject,
152154
)
153155

154156
project_type_classes = {
@@ -159,6 +161,7 @@ def constructor(self):
159161
5: MediaClassificationProject,
160162
6: DigitizationProject,
161163
7: StreetProject,
164+
10: ValidateImageProject,
162165
}
163166
return project_type_classes[self.value]
164167

@@ -171,6 +174,7 @@ def tutorial(self):
171174
CompletenessTutorial,
172175
FootprintTutorial,
173176
StreetTutorial,
177+
ValidateImageTutorial,
174178
)
175179

176180
project_type_classes = {
@@ -179,5 +183,6 @@ def tutorial(self):
179183
3: ChangeDetectionTutorial,
180184
4: CompletenessTutorial,
181185
7: StreetTutorial,
186+
10: ValidateImageTutorial,
182187
}
183188
return project_type_classes[self.value]

mapswipe_workers/mapswipe_workers/firebase/firebase.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def save_project_to_firebase(self, project):
1414
# if a geometry exists in projects we want to delete it.
1515
# This geometry is not used in clients.
1616
project.pop("geometry", None)
17+
# FIXME: We might need to pop images
1718
# save project
1819
self.ref.update({f"v2/projects/{project['projectId']}": project})
1920
logger.info(
@@ -82,6 +83,7 @@ def save_tutorial_to_firebase(
8283
tutorialDict.pop("raw_tasks", None)
8384
tutorialDict.pop("examplesFile", None)
8485
tutorialDict.pop("tutorial_tasks", None)
86+
tutorialDict.pop("images", None)
8587

8688
if not tutorial.projectId or tutorial.projectId == "":
8789
raise CustomError(

mapswipe_workers/mapswipe_workers/project_types/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from .tile_map_service.classification.tutorial import ClassificationTutorial
1111
from .tile_map_service.completeness.project import CompletenessProject
1212
from .tile_map_service.completeness.tutorial import CompletenessTutorial
13+
from .validate_image.project import ValidateImageProject
14+
from .validate_image.tutorial import ValidateImageTutorial
1315

1416
__all__ = [
1517
"ClassificationProject",
@@ -21,6 +23,8 @@
2123
"MediaClassificationProject",
2224
"FootprintProject",
2325
"FootprintTutorial",
26+
"ValidateImageProject",
27+
"ValidateImageTutorial",
2428
"DigitizationProject",
2529
"StreetProject",
2630
"StreetTutorial",

mapswipe_workers/mapswipe_workers/project_types/validate_image/__init__.py

Whitespace-only changes.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import math
2+
from dataclasses import dataclass
3+
from typing import Dict, List
4+
5+
from mapswipe_workers.definitions import logger
6+
from mapswipe_workers.firebase.firebase import Firebase
7+
from mapswipe_workers.firebase_to_postgres.transfer_results import (
8+
results_to_file,
9+
save_results_to_postgres,
10+
truncate_temp_results,
11+
)
12+
from mapswipe_workers.generate_stats.project_stats import (
13+
get_statistics_for_integer_result_project,
14+
)
15+
from mapswipe_workers.project_types.project import BaseGroup, BaseProject
16+
17+
18+
@dataclass
19+
class ValidateImageGroup(BaseGroup):
20+
pass
21+
22+
23+
@dataclass
24+
class ValidateImageTask:
25+
# TODO(tnagorra): We need to check if fileName should be saved on project
26+
# NOTE: We do not need to add projectId and groupId so we are not extending BaseTask
27+
28+
# NOTE: taskId is the sourceIdentifier
29+
taskId: str
30+
31+
fileName: str
32+
url: str
33+
34+
# NOTE: This is not required but required by the base class
35+
geometry: str
36+
37+
38+
class ValidateImageProject(BaseProject):
39+
def __init__(self, project_draft):
40+
super().__init__(project_draft)
41+
self.groups: Dict[str, ValidateImageGroup] = {}
42+
self.tasks: Dict[str, List[ValidateImageTask]] = {} # dict keys are group ids
43+
44+
# NOTE: This is a standard structure defined on manager dashboard.
45+
# It's derived from other formats like COCO.
46+
# The transfromation is done in manager dashboard.
47+
self.images = project_draft["images"]
48+
49+
def save_tasks_to_firebase(self, projectId: str, tasks: dict):
50+
firebase = Firebase()
51+
firebase.save_tasks_to_firebase(projectId, tasks, useCompression=False)
52+
53+
@staticmethod
54+
def results_to_postgres(results: dict, project_id: str, filter_mode: bool):
55+
"""How to move the result data from firebase to postgres."""
56+
results_file, user_group_results_file = results_to_file(results, project_id)
57+
truncate_temp_results()
58+
save_results_to_postgres(results_file, project_id, filter_mode)
59+
return user_group_results_file
60+
61+
@staticmethod
62+
def get_per_project_statistics(project_id, project_info):
63+
"""How to aggregate the project results."""
64+
return get_statistics_for_integer_result_project(
65+
project_id, project_info, generate_hot_tm_geometries=False
66+
)
67+
68+
def validate_geometries(self):
69+
pass
70+
71+
def save_to_files(self, project):
72+
"""We do not have any geometry so we pass here"""
73+
pass
74+
75+
def create_groups(self):
76+
self.numberOfGroups = math.ceil(len(self.images) / self.groupSize)
77+
for group_index in range(self.numberOfGroups):
78+
self.groups[f"g{group_index + 100}"] = ValidateImageGroup(
79+
projectId=self.projectId,
80+
groupId=f"g{group_index + 100}",
81+
progress=0,
82+
finishedCount=0,
83+
requiredCount=0,
84+
numberOfTasks=self.groupSize,
85+
)
86+
logger.info(f"{self.projectId} - create_groups - created groups dictionary")
87+
88+
def create_tasks(self):
89+
if len(self.groups) == 0:
90+
raise ValueError("Groups needs to be created before tasks can be created.")
91+
for group_id, group in self.groups.items():
92+
self.tasks[group_id] = []
93+
for i in range(self.groupSize):
94+
# FIXME: We should try not to mutate values
95+
image_metadata = self.images.pop()
96+
task = ValidateImageTask(
97+
taskId=image_metadata["sourceIdentifier"],
98+
fileName=image_metadata["fileName"],
99+
url=image_metadata["url"],
100+
geometry="",
101+
)
102+
self.tasks[group_id].append(task)
103+
104+
# list now empty? if usual group size is not reached
105+
# the actual number of tasks for the group is updated
106+
if not self.images:
107+
group.numberOfTasks = i + 1
108+
break
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from dataclasses import dataclass
2+
3+
from mapswipe_workers.definitions import logger
4+
from mapswipe_workers.firebase.firebase import Firebase
5+
from mapswipe_workers.project_types.tutorial import BaseTutorial
6+
from mapswipe_workers.project_types.validate_image.project import (
7+
ValidateImageGroup,
8+
ValidateImageTask,
9+
)
10+
11+
12+
@dataclass
13+
class ValidateImageTutorialTask(ValidateImageTask):
14+
# TODO(tnagorra): Check if we need projectId and groupId in tutorial task
15+
projectId: str
16+
groupId: str
17+
referenceAnswer: int
18+
screen: int
19+
20+
21+
class ValidateImageTutorial(BaseTutorial):
22+
23+
def __init__(self, tutorial_draft):
24+
# this will create the basis attributes
25+
super().__init__(tutorial_draft)
26+
27+
self.groups = dict()
28+
self.tasks = dict()
29+
self.images = tutorial_draft["images"]
30+
31+
def create_tutorial_groups(self):
32+
"""Create group for the tutorial based on provided examples in images."""
33+
34+
# NOTE: The groupId must be a numeric 101. It's hardcoded in save_tutorial_to_firebase
35+
group = ValidateImageGroup(
36+
groupId=101,
37+
projectId=self.projectId,
38+
numberOfTasks=len(self.images),
39+
progress=0,
40+
finishedCount=0,
41+
requiredCount=0,
42+
)
43+
self.groups[101] = group
44+
45+
logger.info(
46+
f"{self.projectId} - create_tutorial_groups - created groups dictionary"
47+
)
48+
49+
def create_tutorial_tasks(self):
50+
"""Create the tasks dict based on provided examples in geojson file."""
51+
task_list = []
52+
for image_metadata in self.images:
53+
image_metadata = ValidateImageTutorialTask(
54+
projectId=self.projectId,
55+
groupId=101,
56+
taskId=image_metadata["sourceIdentifier"],
57+
fileName=image_metadata["fileName"],
58+
url=image_metadata["url"],
59+
geometry="",
60+
referenceAnswer=image_metadata["referenceAnswer"],
61+
screen=image_metadata["screen"],
62+
)
63+
task_list.append(image_metadata)
64+
65+
if task_list:
66+
self.tasks[101] = task_list
67+
else:
68+
logger.info(f"group in project {self.projectId} is not valid.")
69+
70+
logger.info(
71+
f"{self.projectId} - create_tutorial_tasks - created tasks dictionary"
72+
)
73+
74+
def save_tutorial(self):
75+
firebase = Firebase()
76+
firebase.save_tutorial_to_firebase(
77+
self, self.groups, self.tasks, useCompression=False
78+
)
79+
logger.info(self.tutorialDraftId)
80+
firebase.drop_tutorial_draft(self.tutorialDraftId)

0 commit comments

Comments
 (0)