Skip to content

Commit d70dba5

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

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

0 commit comments

Comments
 (0)