feat: df for extra info and pydantic validator
Browse files- app/assets/config/config_checkbox_behavior.json +1 -1
- app/assets/config/config_dropdown_circumstances.json +29 -22
- app/assets/config/config_fields.json +0 -20
- app/behavior/behavior_checkbox.py +1 -0
- app/behavior/class_behavior.py +13 -14
- app/circumstances/circumstances_dropdowns.py +6 -5
- app/circumstances/class_circumstance.py +39 -18
- app/classes.py +29 -13
- app/dead.py +3 -3
- app/follow_up/class_follow_up.py +16 -23
- app/gallery.py +91 -127
- app/main_multianimal.py +9 -7
- app/physical/class_physical.py +12 -12
- app/physical/physical_checkbox.py +3 -2
- app/validation_submission/add_json.py +1 -1
- app/validation_submission/create_json.py +1 -1
- app/validation_submission/get_json.py +1 -1
- app/validation_submission/processing.py +110 -0
- app/validation_submission/submission.py +6 -0
- app/validation_submission/validation.py +61 -128
- app/wounded.py +2 -2
app/assets/config/config_checkbox_behavior.json
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
{
|
2 |
-
"Abnormal
|
3 |
{
|
4 |
"Description": "Problems breathing, breathing sounds"
|
5 |
},
|
|
|
1 |
{
|
2 |
+
"Abnormal breathing":
|
3 |
{
|
4 |
"Description": "Problems breathing, breathing sounds"
|
5 |
},
|
app/assets/config/config_dropdown_circumstances.json
CHANGED
@@ -3,12 +3,10 @@
|
|
3 |
{
|
4 |
"road vehicle":
|
5 |
{
|
6 |
-
|
7 |
"Options": {
|
8 |
-
"road_type" : ["highway", "main road", "secondary road", "local road/path/trail", "parking lot", "other", "unknown"]
|
9 |
},
|
10 |
"Open": "Infrastructure number"
|
11 |
-
|
12 |
},
|
13 |
"train":
|
14 |
{
|
@@ -16,7 +14,7 @@
|
|
16 |
},
|
17 |
"aircraft": {},
|
18 |
"boat": {},
|
19 |
-
"other": {},
|
20 |
"unknown": {}
|
21 |
},
|
22 |
"Destruction / Deliberatly removed" :
|
@@ -24,27 +22,33 @@
|
|
24 |
"hunting":
|
25 |
{
|
26 |
"Options": {
|
27 |
-
"method" : ["shooting", "bow", "falconry",
|
|
|
28 |
}
|
29 |
},
|
30 |
"trap":
|
31 |
{
|
32 |
"Options": {
|
33 |
-
"method": ["killing trap", "pole trap",
|
|
|
|
|
34 |
}
|
35 |
},
|
36 |
"poisoning": {},
|
37 |
"removal or direct capture":
|
38 |
{
|
39 |
-
"Options": {"method": ["gassing", "raptor captured at nest",
|
|
|
|
|
40 |
}
|
41 |
},
|
42 |
"fishing":
|
43 |
{
|
44 |
-
"Options": {"method" : ["drowned/tangled", "beached with capture indications",
|
|
|
45 |
}
|
46 |
},
|
47 |
-
"other": {},
|
48 |
"unknown": {}
|
49 |
},
|
50 |
"Indirect destruction":
|
@@ -53,46 +57,48 @@
|
|
53 |
{
|
54 |
|
55 |
"Options": {
|
56 |
-
"infrastructure" : ["electric line", "pole/pylon", "other", "unknown"]
|
57 |
},
|
58 |
"Extra":
|
59 |
{
|
60 |
-
"Cause": ["collision", "electrocution"
|
61 |
}
|
62 |
},
|
63 |
"windfarm": {},
|
64 |
"other collision":
|
65 |
{
|
66 |
"Options": {
|
67 |
-
"object": ["window", "building", "lighthouse", "cable", "wire fence/barbed wire", "other crash", "unknown"]
|
68 |
}
|
69 |
},
|
70 |
"fall":
|
71 |
{
|
72 |
"Options": {
|
73 |
-
"location": ["chimney", "empty pole", "hole/well", "other", "unknown"]
|
74 |
}
|
75 |
},
|
76 |
"development work":
|
77 |
{
|
78 |
"Options": {
|
79 |
-
"work_type" : ["transport infrastructure", "building", "other", "unknown"]
|
80 |
}
|
81 |
},
|
82 |
"pollution / contamination":
|
83 |
{
|
84 |
"Options": {
|
85 |
-
"pollution_type" : ["oil pollution", "chemical pollution", "heavy metals",
|
|
|
86 |
},
|
87 |
"agricultural net protection": {},
|
88 |
"vegetal / forest work":
|
89 |
{
|
90 |
"Options": {
|
91 |
-
"work_type" : ["clearing/mowing/plowing", "tree felling/pruning",
|
|
|
92 |
}
|
93 |
}
|
94 |
},
|
95 |
-
"other": {},
|
96 |
"unknown": {}
|
97 |
},
|
98 |
"Natural cause":
|
@@ -100,19 +106,20 @@
|
|
100 |
"predation":
|
101 |
{
|
102 |
"Options": {
|
103 |
-
"predator" : ["cat", "dog", "rooster/hen", "other domestic animal", "wild birds",
|
|
|
104 |
}
|
105 |
},
|
106 |
"weather":
|
107 |
{
|
108 |
"Options": {
|
109 |
-
"condition" : ["cold wave", "drought", "hail", "lightening", "storm", "other", "unknown"]
|
110 |
}
|
111 |
},
|
112 |
"natural disaster":
|
113 |
{
|
114 |
"Options": {
|
115 |
-
"disaster" : ["fire", "avalanche", "rock fall", "mudslide", "volcanic eruption/ashes", "other", "unknown"]
|
116 |
}
|
117 |
},
|
118 |
"nest fall": {},
|
@@ -121,10 +128,10 @@
|
|
121 |
"accidental drowing":
|
122 |
{
|
123 |
"Options": {
|
124 |
-
"drowning_location" : ["drinking trough", "pool", "storm pool", "irrigation pool", "natural pool", "flood", "other
|
125 |
}
|
126 |
},
|
127 |
-
"other": {},
|
128 |
"unknown": {}
|
129 |
},
|
130 |
"Unknown": {}
|
|
|
3 |
{
|
4 |
"road vehicle":
|
5 |
{
|
|
|
6 |
"Options": {
|
7 |
+
"road_type" : ["highway", "main road", "secondary road", "local road/path/trail", "parking lot", "other road", "unknown road"]
|
8 |
},
|
9 |
"Open": "Infrastructure number"
|
|
|
10 |
},
|
11 |
"train":
|
12 |
{
|
|
|
14 |
},
|
15 |
"aircraft": {},
|
16 |
"boat": {},
|
17 |
+
"other transport collision": {},
|
18 |
"unknown": {}
|
19 |
},
|
20 |
"Destruction / Deliberatly removed" :
|
|
|
22 |
"hunting":
|
23 |
{
|
24 |
"Options": {
|
25 |
+
"method" : ["shooting", "bow", "falconry",
|
26 |
+
"hounds hunting", "digging up", "other hunting", "unknow hunting"]
|
27 |
}
|
28 |
},
|
29 |
"trap":
|
30 |
{
|
31 |
"Options": {
|
32 |
+
"method": ["killing trap", "pole trap",
|
33 |
+
"trap cage", "corvids nasse", "net", "cage trap",
|
34 |
+
"fall-trap", "glue trap", "insect trap", "other trap", "unknown trap"]
|
35 |
}
|
36 |
},
|
37 |
"poisoning": {},
|
38 |
"removal or direct capture":
|
39 |
{
|
40 |
+
"Options": {"method": ["gassing", "raptor captured at nest",
|
41 |
+
"brood destruction", "traffic/trade", "capture accident",
|
42 |
+
"scientific sample", "other removal", "unknown removal"]
|
43 |
}
|
44 |
},
|
45 |
"fishing":
|
46 |
{
|
47 |
+
"Options": {"method" : ["drowned/tangled", "beached with capture indications",
|
48 |
+
"other fishing", "unknown fishing"]
|
49 |
}
|
50 |
},
|
51 |
+
"other destruction": {},
|
52 |
"unknown": {}
|
53 |
},
|
54 |
"Indirect destruction":
|
|
|
57 |
{
|
58 |
|
59 |
"Options": {
|
60 |
+
"infrastructure" : ["electric line", "pole/pylon", "other structure", "unknown structure"]
|
61 |
},
|
62 |
"Extra":
|
63 |
{
|
64 |
+
"Cause": ["collision", "electrocution"]
|
65 |
}
|
66 |
},
|
67 |
"windfarm": {},
|
68 |
"other collision":
|
69 |
{
|
70 |
"Options": {
|
71 |
+
"object": ["window", "building", "lighthouse", "cable", "wire fence/barbed wire", "other crash", "unknown crash"]
|
72 |
}
|
73 |
},
|
74 |
"fall":
|
75 |
{
|
76 |
"Options": {
|
77 |
+
"location": ["chimney", "empty pole", "hole/well", "other fall", "unknown fall"]
|
78 |
}
|
79 |
},
|
80 |
"development work":
|
81 |
{
|
82 |
"Options": {
|
83 |
+
"work_type" : ["transport infrastructure", "building", "other work", "unknown work"]
|
84 |
}
|
85 |
},
|
86 |
"pollution / contamination":
|
87 |
{
|
88 |
"Options": {
|
89 |
+
"pollution_type" : ["oil pollution", "chemical pollution", "heavy metals",
|
90 |
+
"light", "noise", "plastic ingestion", "other pollution", "unknown pollution"]
|
91 |
},
|
92 |
"agricultural net protection": {},
|
93 |
"vegetal / forest work":
|
94 |
{
|
95 |
"Options": {
|
96 |
+
"work_type" : ["clearing/mowing/plowing", "tree felling/pruning",
|
97 |
+
"other forest work", "unknown forest work"]
|
98 |
}
|
99 |
}
|
100 |
},
|
101 |
+
"other indirect destruction": {},
|
102 |
"unknown": {}
|
103 |
},
|
104 |
"Natural cause":
|
|
|
106 |
"predation":
|
107 |
{
|
108 |
"Options": {
|
109 |
+
"predator" : ["cat", "dog", "rooster/hen", "other domestic animal", "wild birds",
|
110 |
+
"wild mammal", "other predator", "unknown predator"]
|
111 |
}
|
112 |
},
|
113 |
"weather":
|
114 |
{
|
115 |
"Options": {
|
116 |
+
"condition" : ["cold wave", "drought", "hail", "lightening", "storm", "other weather", "unknown weather"]
|
117 |
}
|
118 |
},
|
119 |
"natural disaster":
|
120 |
{
|
121 |
"Options": {
|
122 |
+
"disaster" : ["fire", "avalanche", "rock fall", "mudslide", "volcanic eruption/ashes", "other natural disaster", "unknown natural disaster"]
|
123 |
}
|
124 |
},
|
125 |
"nest fall": {},
|
|
|
128 |
"accidental drowing":
|
129 |
{
|
130 |
"Options": {
|
131 |
+
"drowning_location" : ["drinking trough", "pool", "storm pool", "irrigation pool", "natural pool", "flood", "other location", "unknown location"]
|
132 |
}
|
133 |
},
|
134 |
+
"other natural cause": {},
|
135 |
"unknown": {}
|
136 |
},
|
137 |
"Unknown": {}
|
app/assets/config/config_fields.json
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"fields": [
|
3 |
-
"latitude",
|
4 |
-
"longitude",
|
5 |
-
"wounded",
|
6 |
-
"dead",
|
7 |
-
"circumstance",
|
8 |
-
"circumstance_dropdown_level1",
|
9 |
-
"circumstance_dropdown_level2",
|
10 |
-
"circumstance_openfield_level2",
|
11 |
-
"circumstance_dropdown_extra_level2",
|
12 |
-
"behavior",
|
13 |
-
"physical_changes_beak",
|
14 |
-
"physical_changes_body",
|
15 |
-
"physical_changes_head",
|
16 |
-
"physical_changes_feathers",
|
17 |
-
"physical_changes_legs",
|
18 |
-
"image"
|
19 |
-
]
|
20 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/behavior/behavior_checkbox.py
CHANGED
@@ -5,6 +5,7 @@ from utils.utils_visible import set_visible
|
|
5 |
from validation_submission.add_json import add_data_tmp
|
6 |
|
7 |
def on_select_behavior(behavior_checkbox):
|
|
|
8 |
add_data_tmp("wounded_dead", "behaviors_type", behavior_checkbox)
|
9 |
|
10 |
def retrieve_behavior_options_description():
|
|
|
5 |
from validation_submission.add_json import add_data_tmp
|
6 |
|
7 |
def on_select_behavior(behavior_checkbox):
|
8 |
+
behavior_checkbox = [behavior.lower() for behavior in behavior_checkbox]
|
9 |
add_data_tmp("wounded_dead", "behaviors_type", behavior_checkbox)
|
10 |
|
11 |
def retrieve_behavior_options_description():
|
app/behavior/class_behavior.py
CHANGED
@@ -1,51 +1,50 @@
|
|
1 |
from pydantic import BaseModel, Field
|
2 |
-
from typing import Literal, List, Union
|
3 |
-
|
4 |
|
5 |
class Behavior(BaseModel):
|
6 |
type: str
|
7 |
-
description: str
|
8 |
|
9 |
# --- Specific Behavior classes ---
|
10 |
class AbnormalBreathing(Behavior):
|
11 |
type: Literal['abnormal breathing']
|
12 |
-
description: Literal["Problems breathing, breathing sounds"]
|
13 |
|
14 |
class CrashFalling(Behavior):
|
15 |
type: Literal['crash, falling from the sky']
|
16 |
-
description: Literal["Suddenly falling from the sky"]
|
17 |
|
18 |
class Diarrhea(Behavior):
|
19 |
type: Literal['diarrhea']
|
20 |
-
description: Literal["Observed diarrhea"]
|
21 |
|
22 |
class Lameness(Behavior):
|
23 |
type: Literal['lameness']
|
24 |
-
description: Literal["Apparent limping or not able to walk properly"]
|
25 |
|
26 |
class Neurological(Behavior):
|
27 |
type: Literal['neurological']
|
28 |
-
description: Literal["Circling, incoordination, tremors, convulsions, fast eye movements"]
|
29 |
|
30 |
class OtherAbnormalBehavior(Behavior):
|
31 |
type: Literal['other abnormal behavior']
|
32 |
-
description: Literal["Other than weakness, other than neurologic"]
|
33 |
|
34 |
class UnableToFly(Behavior):
|
35 |
type: Literal['unable to fly']
|
36 |
-
description: Literal["Animal alert and tries to fly but can not take off"]
|
37 |
|
38 |
class Vomiting(Behavior):
|
39 |
type: Literal['vomiting']
|
40 |
-
description: Literal["Throwing up undigested food, regurgitating"]
|
41 |
|
42 |
class Weakness(Behavior):
|
43 |
type: Literal['weakness']
|
44 |
-
description: Literal["Non responsive, does not fly away when approached, lethargy"]
|
45 |
|
46 |
class NoChanges(Behavior):
|
47 |
type: Literal['no changes']
|
48 |
-
description: Literal["Animal is acting normally"]
|
49 |
|
50 |
# Union of all possible behaviors
|
51 |
BehaviorType = Union[
|
@@ -64,4 +63,4 @@ BehaviorType = Union[
|
|
64 |
# Main class that logs multiple behaviors
|
65 |
class Behaviors(BaseModel):
|
66 |
behaviors_radio: str # e.g., "Yes"
|
67 |
-
behaviors_type: List[BehaviorType] =
|
|
|
1 |
from pydantic import BaseModel, Field
|
2 |
+
from typing import Literal, List, Union, Optional
|
|
|
3 |
|
4 |
class Behavior(BaseModel):
|
5 |
type: str
|
6 |
+
description: Optional[str] = None # Making the description field optional
|
7 |
|
8 |
# --- Specific Behavior classes ---
|
9 |
class AbnormalBreathing(Behavior):
|
10 |
type: Literal['abnormal breathing']
|
11 |
+
description: Optional[Literal["Problems breathing, breathing sounds"]] = None
|
12 |
|
13 |
class CrashFalling(Behavior):
|
14 |
type: Literal['crash, falling from the sky']
|
15 |
+
description: Optional[Literal["Suddenly falling from the sky"]] = None
|
16 |
|
17 |
class Diarrhea(Behavior):
|
18 |
type: Literal['diarrhea']
|
19 |
+
description: Optional[Literal["Observed diarrhea"]] = None
|
20 |
|
21 |
class Lameness(Behavior):
|
22 |
type: Literal['lameness']
|
23 |
+
description: Optional[Literal["Apparent limping or not able to walk properly"]] = None
|
24 |
|
25 |
class Neurological(Behavior):
|
26 |
type: Literal['neurological']
|
27 |
+
description: Optional[Literal["Circling, incoordination, tremors, convulsions, fast eye movements"]] = None
|
28 |
|
29 |
class OtherAbnormalBehavior(Behavior):
|
30 |
type: Literal['other abnormal behavior']
|
31 |
+
description: Optional[Literal["Other than weakness, other than neurologic"]] = None
|
32 |
|
33 |
class UnableToFly(Behavior):
|
34 |
type: Literal['unable to fly']
|
35 |
+
description: Optional[Literal["Animal alert and tries to fly but can not take off"]] = None
|
36 |
|
37 |
class Vomiting(Behavior):
|
38 |
type: Literal['vomiting']
|
39 |
+
description: Optional[Literal["Throwing up undigested food, regurgitating"]] = None
|
40 |
|
41 |
class Weakness(Behavior):
|
42 |
type: Literal['weakness']
|
43 |
+
description: Optional[Literal["Non responsive, does not fly away when approached, lethargy"]] = None
|
44 |
|
45 |
class NoChanges(Behavior):
|
46 |
type: Literal['no changes']
|
47 |
+
description: Optional[Literal["Animal is acting normally"]] = None
|
48 |
|
49 |
# Union of all possible behaviors
|
50 |
BehaviorType = Union[
|
|
|
63 |
# Main class that logs multiple behaviors
|
64 |
class Behaviors(BaseModel):
|
65 |
behaviors_radio: str # e.g., "Yes"
|
66 |
+
behaviors_type: Optional[List[BehaviorType]] = None
|
app/circumstances/circumstances_dropdowns.py
CHANGED
@@ -73,7 +73,7 @@ def get_options(value):
|
|
73 |
def on_select(evt: gr.SelectData): # SelectData is a subclass of EventData
|
74 |
options_label, options_dropdown, open_field, extras, extras_label = get_options(evt.value)
|
75 |
add_data_tmp("wounded_dead",
|
76 |
-
"
|
77 |
{"type": (evt.value).lower(),
|
78 |
"option_dropdown_label" : options_label.lower() if options_label is not None else 'NA',
|
79 |
"open_field_label" : open_field.lower() if open_field is not None else 'NA',
|
@@ -98,14 +98,15 @@ def on_select(evt: gr.SelectData): # SelectData is a subclass of EventData
|
|
98 |
def on_select_dropdown_level2(evt: gr.SelectData):
|
99 |
add_data_tmp("wounded_dead",
|
100 |
"circumstance_option_dropdown",
|
101 |
-
evt.value)
|
102 |
|
103 |
-
def
|
|
|
104 |
add_data_tmp("wounded_dead",
|
105 |
"circumstance_open_field",
|
106 |
-
evt.value)
|
107 |
|
108 |
def on_select_dropdown_extra_level2(evt: gr.SelectData):
|
109 |
add_data_tmp("wounded_dead",
|
110 |
"circumstance_extra",
|
111 |
-
evt.value)
|
|
|
73 |
def on_select(evt: gr.SelectData): # SelectData is a subclass of EventData
|
74 |
options_label, options_dropdown, open_field, extras, extras_label = get_options(evt.value)
|
75 |
add_data_tmp("wounded_dead",
|
76 |
+
"circumstance_type",
|
77 |
{"type": (evt.value).lower(),
|
78 |
"option_dropdown_label" : options_label.lower() if options_label is not None else 'NA',
|
79 |
"open_field_label" : open_field.lower() if open_field is not None else 'NA',
|
|
|
98 |
def on_select_dropdown_level2(evt: gr.SelectData):
|
99 |
add_data_tmp("wounded_dead",
|
100 |
"circumstance_option_dropdown",
|
101 |
+
evt.value.lower())
|
102 |
|
103 |
+
def on_change_openfield_level2(evt: gr.EventData):
|
104 |
+
print("Saving open field")
|
105 |
add_data_tmp("wounded_dead",
|
106 |
"circumstance_open_field",
|
107 |
+
evt.value.lower())
|
108 |
|
109 |
def on_select_dropdown_extra_level2(evt: gr.SelectData):
|
110 |
add_data_tmp("wounded_dead",
|
111 |
"circumstance_extra",
|
112 |
+
evt.value.lower())
|
app/circumstances/class_circumstance.py
CHANGED
@@ -9,7 +9,9 @@ class CircumstanceTypeBase(BaseModel):
|
|
9 |
class RoadVehicleCollision(CircumstanceTypeBase):
|
10 |
type: Literal['road_vehicle']
|
11 |
infrastructure_number: Optional[str] = None
|
12 |
-
road_type: Literal['highway', 'main road',
|
|
|
|
|
13 |
|
14 |
class TrainCollision(CircumstanceTypeBase):
|
15 |
type: Literal['train']
|
@@ -30,22 +32,28 @@ class UnknownTransportCollision(CircumstanceTypeBase):
|
|
30 |
# Destruction / Deliberately removed
|
31 |
class HuntingDestruction(CircumstanceTypeBase):
|
32 |
type: Literal['hunting']
|
33 |
-
method: Literal['shooting', 'bow', 'falconry',
|
|
|
|
|
34 |
|
35 |
class TrapDestruction(CircumstanceTypeBase):
|
36 |
type: Literal['trap']
|
37 |
-
method: Literal['killing trap', 'pole trap', 'trap cage', 'corvids nasse',
|
|
|
|
|
38 |
|
39 |
class PoisoningDestruction(CircumstanceTypeBase):
|
40 |
type: Literal['poisoning']
|
41 |
|
42 |
class RemovalDestruction(CircumstanceTypeBase):
|
43 |
type: Literal['removal or direct capture']
|
44 |
-
method: Literal['gassing', 'raptor captured at nest', 'brood destruction',
|
|
|
|
|
45 |
|
46 |
class FishingDestruction(CircumstanceTypeBase):
|
47 |
type: Literal['fishing']
|
48 |
-
method: Literal['drowned/tangled', 'beached with capture indications',
|
49 |
|
50 |
class OtherDestruction(CircumstanceTypeBase):
|
51 |
type: Literal['other destruction']
|
@@ -56,34 +64,38 @@ class UnknownDestruction(CircumstanceTypeBase):
|
|
56 |
# Indirect destruction
|
57 |
class PylonElectricGridDestruction(CircumstanceTypeBase):
|
58 |
type: Literal['pylone and electric grid']
|
59 |
-
infrastructure: Literal['electric line', 'pole/pylon',
|
60 |
-
cause: Literal['collision', 'electrocution'
|
61 |
|
62 |
class WindfarmDestruction(CircumstanceTypeBase):
|
63 |
type: Literal['windfarm']
|
64 |
|
65 |
class OtherCollisionDestruction(CircumstanceTypeBase):
|
66 |
type: Literal['other collision']
|
67 |
-
object: Literal['window', 'building', 'lighthouse',
|
|
|
68 |
|
69 |
class FallDestruction(CircumstanceTypeBase):
|
70 |
type: Literal['fall']
|
71 |
-
location: Literal['chimney', 'empty pole', 'hole/well', 'other', 'unknown']
|
72 |
|
73 |
class DevelopmentWorkDestruction(CircumstanceTypeBase):
|
74 |
type: Literal['development work']
|
75 |
-
work_type: Literal['transport infrastructure', 'building', 'other', 'unknown']
|
76 |
|
77 |
class PollutionContaminationDestruction(CircumstanceTypeBase):
|
78 |
type: Literal['pollution / contamination']
|
79 |
-
pollution_type: Literal['oil pollution', 'chemical pollution', 'heavy metals',
|
|
|
|
|
80 |
|
81 |
class AgriculturalNetProtectionDestruction(CircumstanceTypeBase):
|
82 |
type: Literal['agricultural net protection']
|
83 |
|
84 |
class VegetalForestWorkDestruction(CircumstanceTypeBase):
|
85 |
type: Literal['vegetal / forest work']
|
86 |
-
work_type: Literal['clearing/mowing/plowing', 'tree felling/pruning',
|
|
|
87 |
|
88 |
class OtherIndirectDestruction(CircumstanceTypeBase):
|
89 |
type: Literal['other indirect desctruction']
|
@@ -94,15 +106,21 @@ class UnknownIndirectDestruction(CircumstanceTypeBase):
|
|
94 |
# Natural cause
|
95 |
class Predation(CircumstanceTypeBase):
|
96 |
type: Literal['predation']
|
97 |
-
predator: Literal['cat', 'dog', 'rooster/hen',
|
|
|
|
|
98 |
|
99 |
class Weather(CircumstanceTypeBase):
|
100 |
type: Literal['weather']
|
101 |
-
condition: Literal['cold wave', 'drought', 'hail',
|
|
|
|
|
102 |
|
103 |
class NaturalDisaster(CircumstanceTypeBase):
|
104 |
type: Literal['natural disaster']
|
105 |
-
disaster: Literal['fire', 'avalanche', 'rock fall',
|
|
|
|
|
106 |
|
107 |
class NestFall(CircumstanceTypeBase):
|
108 |
type: Literal['nest fall']
|
@@ -115,7 +133,10 @@ class DiseaseParasite(CircumstanceTypeBase):
|
|
115 |
|
116 |
class AccidentalDrowning(CircumstanceTypeBase):
|
117 |
type: Literal['accidental drowning']
|
118 |
-
drowning_location: Literal['drinking trough', 'pool',
|
|
|
|
|
|
|
119 |
|
120 |
class OtherNaturalCause(CircumstanceTypeBase):
|
121 |
type: Literal['other natural cause']
|
@@ -166,9 +187,9 @@ CircumstanceType = Union[
|
|
166 |
|
167 |
# Main Circumstance class
|
168 |
class Circumstances(BaseModel):
|
169 |
-
circumstance: str # e.g., "COLLISION"
|
170 |
circumstance_radio: str # e.g., "Yes"
|
171 |
-
|
|
|
172 |
|
173 |
|
174 |
# Example usage
|
|
|
9 |
class RoadVehicleCollision(CircumstanceTypeBase):
|
10 |
type: Literal['road_vehicle']
|
11 |
infrastructure_number: Optional[str] = None
|
12 |
+
road_type: Literal['highway', 'main road',
|
13 |
+
'secondary road', 'local road/path/trail',
|
14 |
+
'parking lot']
|
15 |
|
16 |
class TrainCollision(CircumstanceTypeBase):
|
17 |
type: Literal['train']
|
|
|
32 |
# Destruction / Deliberately removed
|
33 |
class HuntingDestruction(CircumstanceTypeBase):
|
34 |
type: Literal['hunting']
|
35 |
+
method: Literal['shooting', 'bow', 'falconry',
|
36 |
+
'hounds hunting', 'digging up',
|
37 |
+
"other hunting", "unknow hunting"]
|
38 |
|
39 |
class TrapDestruction(CircumstanceTypeBase):
|
40 |
type: Literal['trap']
|
41 |
+
method: Literal['killing trap', 'pole trap', 'trap cage', 'corvids nasse',
|
42 |
+
'net', 'cage trap', 'fall-trap', 'glue trap', 'insect trap',
|
43 |
+
"other trap", "unknown trap"]
|
44 |
|
45 |
class PoisoningDestruction(CircumstanceTypeBase):
|
46 |
type: Literal['poisoning']
|
47 |
|
48 |
class RemovalDestruction(CircumstanceTypeBase):
|
49 |
type: Literal['removal or direct capture']
|
50 |
+
method: Literal['gassing', 'raptor captured at nest', 'brood destruction',
|
51 |
+
'traffic/trade', 'capture accident', 'scientific sample',
|
52 |
+
"other removal", "unknown removal"]
|
53 |
|
54 |
class FishingDestruction(CircumstanceTypeBase):
|
55 |
type: Literal['fishing']
|
56 |
+
method: Literal['drowned/tangled', 'beached with capture indications', "other fishing", "unknown fishing"]
|
57 |
|
58 |
class OtherDestruction(CircumstanceTypeBase):
|
59 |
type: Literal['other destruction']
|
|
|
64 |
# Indirect destruction
|
65 |
class PylonElectricGridDestruction(CircumstanceTypeBase):
|
66 |
type: Literal['pylone and electric grid']
|
67 |
+
infrastructure: Literal['electric line', 'pole/pylon', "other structure", "unknown structure"]
|
68 |
+
cause: Literal['collision', 'electrocution']
|
69 |
|
70 |
class WindfarmDestruction(CircumstanceTypeBase):
|
71 |
type: Literal['windfarm']
|
72 |
|
73 |
class OtherCollisionDestruction(CircumstanceTypeBase):
|
74 |
type: Literal['other collision']
|
75 |
+
object: Literal['window', 'building', 'lighthouse',
|
76 |
+
'cable', 'wire fence/barbed wire', 'other crash', 'unknown crash']
|
77 |
|
78 |
class FallDestruction(CircumstanceTypeBase):
|
79 |
type: Literal['fall']
|
80 |
+
location: Literal['chimney', 'empty pole', 'hole/well', 'other fall', 'unknown fall']
|
81 |
|
82 |
class DevelopmentWorkDestruction(CircumstanceTypeBase):
|
83 |
type: Literal['development work']
|
84 |
+
work_type: Literal['transport infrastructure', 'building', 'other work', 'unknown work']
|
85 |
|
86 |
class PollutionContaminationDestruction(CircumstanceTypeBase):
|
87 |
type: Literal['pollution / contamination']
|
88 |
+
pollution_type: Literal['oil pollution', 'chemical pollution', 'heavy metals',
|
89 |
+
'light', 'noise',
|
90 |
+
'plastic ingestion', 'other pollution', 'unknown pollution']
|
91 |
|
92 |
class AgriculturalNetProtectionDestruction(CircumstanceTypeBase):
|
93 |
type: Literal['agricultural net protection']
|
94 |
|
95 |
class VegetalForestWorkDestruction(CircumstanceTypeBase):
|
96 |
type: Literal['vegetal / forest work']
|
97 |
+
work_type: Literal['clearing/mowing/plowing', 'tree felling/pruning',
|
98 |
+
'other forest work', 'unknown forest work']
|
99 |
|
100 |
class OtherIndirectDestruction(CircumstanceTypeBase):
|
101 |
type: Literal['other indirect desctruction']
|
|
|
106 |
# Natural cause
|
107 |
class Predation(CircumstanceTypeBase):
|
108 |
type: Literal['predation']
|
109 |
+
predator: Literal['cat', 'dog', 'rooster/hen',
|
110 |
+
'other domestic animal', 'wild birds',
|
111 |
+
'wild mammal', 'other predator', 'unknown predator']
|
112 |
|
113 |
class Weather(CircumstanceTypeBase):
|
114 |
type: Literal['weather']
|
115 |
+
condition: Literal['cold wave', 'drought', 'hail',
|
116 |
+
'lightening', 'storm',
|
117 |
+
'other weather', 'unknown weather']
|
118 |
|
119 |
class NaturalDisaster(CircumstanceTypeBase):
|
120 |
type: Literal['natural disaster']
|
121 |
+
disaster: Literal['fire', 'avalanche', 'rock fall',
|
122 |
+
'mudslide', 'volcanic eruption/ashes',
|
123 |
+
'other natural disaster', 'unknown natural disaster']
|
124 |
|
125 |
class NestFall(CircumstanceTypeBase):
|
126 |
type: Literal['nest fall']
|
|
|
133 |
|
134 |
class AccidentalDrowning(CircumstanceTypeBase):
|
135 |
type: Literal['accidental drowning']
|
136 |
+
drowning_location: Literal['drinking trough', 'pool',
|
137 |
+
'storm pool', 'irrigation pool',
|
138 |
+
'natural pool', 'flood',
|
139 |
+
'other location', 'unknown location']
|
140 |
|
141 |
class OtherNaturalCause(CircumstanceTypeBase):
|
142 |
type: Literal['other natural cause']
|
|
|
187 |
|
188 |
# Main Circumstance class
|
189 |
class Circumstances(BaseModel):
|
|
|
190 |
circumstance_radio: str # e.g., "Yes"
|
191 |
+
circumstance: Optional[str] = None # e.g., "COLLISION"
|
192 |
+
circumstance_type: Optional[CircumstanceType] = Field(None, discriminator='type')
|
193 |
|
194 |
|
195 |
# Example usage
|
app/classes.py
CHANGED
@@ -1,5 +1,10 @@
|
|
1 |
from pydantic import BaseModel, Field
|
2 |
-
from typing import
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
from behavior.class_behavior import Behaviors
|
5 |
from circumstances.class_circumstance import Circumstances
|
@@ -9,22 +14,33 @@ from geolocalisation.class_geolocalisation import Geolocalisation
|
|
9 |
|
10 |
class Wounded(BaseModel):
|
11 |
circumstances: Circumstances
|
12 |
-
behaviors:
|
13 |
-
physical_anomalies:
|
14 |
-
follow_up_events:
|
15 |
|
16 |
class Dead(BaseModel):
|
17 |
-
circumstances:
|
18 |
-
physical_anomalies:
|
19 |
-
follow_up_events:
|
20 |
-
|
21 |
-
class Image(BaseModel):
|
22 |
-
image: List[float]
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
class Report(BaseModel):
|
25 |
-
|
|
|
26 |
geolocalisation: Geolocalisation
|
27 |
-
wounded_state:
|
28 |
wounded: Optional[Wounded] = None
|
29 |
-
dead_state:
|
30 |
dead: Optional[Dead] = None
|
|
|
1 |
from pydantic import BaseModel, Field
|
2 |
+
from typing import Optional
|
3 |
+
import numpy as np
|
4 |
+
from PIL import Image
|
5 |
+
import io
|
6 |
+
import base64
|
7 |
+
import uuid
|
8 |
|
9 |
from behavior.class_behavior import Behaviors
|
10 |
from circumstances.class_circumstance import Circumstances
|
|
|
14 |
|
15 |
class Wounded(BaseModel):
|
16 |
circumstances: Circumstances
|
17 |
+
behaviors: Behaviors
|
18 |
+
physical_anomalies: PhysicalAnomalies
|
19 |
+
follow_up_events: FollowUpEvents
|
20 |
|
21 |
class Dead(BaseModel):
|
22 |
+
circumstances: Circumstances
|
23 |
+
physical_anomalies: PhysicalAnomalies
|
24 |
+
follow_up_events: FollowUpEvents
|
|
|
|
|
|
|
25 |
|
26 |
+
class ImageBase64(BaseModel):
|
27 |
+
image: str
|
28 |
+
@classmethod
|
29 |
+
def to_base64(cls, pixel_data: list):
|
30 |
+
img_array = np.array(pixel_data, dtype=np.uint8)
|
31 |
+
img = Image.fromarray(img_array)
|
32 |
+
# Save the image to a bytes buffer
|
33 |
+
buffer = io.BytesIO()
|
34 |
+
img.save(buffer, format="PNG")
|
35 |
+
buffer.seek(0)
|
36 |
+
base64_str = base64.b64encode(buffer.read()).decode('utf-8')
|
37 |
+
return cls(image=base64_str)
|
38 |
+
|
39 |
class Report(BaseModel):
|
40 |
+
identifier: str
|
41 |
+
image: ImageBase64
|
42 |
geolocalisation: Geolocalisation
|
43 |
+
wounded_state: str
|
44 |
wounded: Optional[Wounded] = None
|
45 |
+
dead_state: str
|
46 |
dead: Optional[Dead] = None
|
app/dead.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
import gradio as gr
|
2 |
from circumstances.circumstances import create_circumstances
|
3 |
from follow_up.followup_events import create_followup_dropdowns, create_followup_open
|
4 |
-
from validation_submission.add_json import add_data_to_individual
|
5 |
|
6 |
def show_section_dead(visible):
|
7 |
if visible==True:
|
8 |
-
add_data_to_individual("
|
9 |
-
add_data_to_individual("
|
10 |
|
11 |
with gr.Column(visible=visible, elem_id="dead") as section_dead:
|
12 |
gr.Markdown("# Dead Animal")
|
|
|
1 |
import gradio as gr
|
2 |
from circumstances.circumstances import create_circumstances
|
3 |
from follow_up.followup_events import create_followup_dropdowns, create_followup_open
|
4 |
+
from validation_submission.add_json import add_data_to_individual
|
5 |
|
6 |
def show_section_dead(visible):
|
7 |
if visible==True:
|
8 |
+
add_data_to_individual("wounded_state", "No")
|
9 |
+
add_data_to_individual("dead_state", "Yes")
|
10 |
|
11 |
with gr.Column(visible=visible, elem_id="dead") as section_dead:
|
12 |
gr.Markdown("# Dead Animal")
|
app/follow_up/class_follow_up.py
CHANGED
@@ -3,45 +3,40 @@ from typing import Literal, Union, Optional, List
|
|
3 |
|
4 |
# --- Event follow-up classes ---
|
5 |
|
6 |
-
# Animal collected event
|
7 |
class AnimalCollectedEvent(BaseModel):
|
8 |
type: Literal['animal collected']
|
9 |
-
collected: Literal['
|
10 |
|
11 |
-
# Recipient event
|
12 |
class RecipientEvent(BaseModel):
|
13 |
type: Literal['recipient']
|
14 |
-
recipient: Literal['
|
15 |
-
|
16 |
-
|
|
|
17 |
class RadiographyEvent(BaseModel):
|
18 |
type: Literal['radiography']
|
19 |
-
radiography: Literal['
|
20 |
|
21 |
-
# Given answer event
|
22 |
class GivenAnswerEvent(BaseModel):
|
23 |
type: Literal['given answer']
|
24 |
answer: Literal[
|
25 |
-
'
|
26 |
-
'
|
27 |
-
'
|
28 |
-
'
|
29 |
-
'
|
30 |
-
'
|
31 |
-
'
|
32 |
]
|
33 |
|
34 |
-
# Name of recipient/museum (open text field)
|
35 |
class NameOfRecipientEvent(BaseModel):
|
36 |
type: Literal['recipient name']
|
37 |
-
name: str
|
38 |
|
39 |
-
# Collection reference (open text field)
|
40 |
class CollectionReferenceEvent(BaseModel):
|
41 |
type: Literal['collection reference']
|
42 |
-
reference: str
|
43 |
|
44 |
-
# Union of all possible follow-up event types
|
45 |
FollowUpEventType = Union[
|
46 |
AnimalCollectedEvent,
|
47 |
RecipientEvent,
|
@@ -51,7 +46,5 @@ FollowUpEventType = Union[
|
|
51 |
CollectionReferenceEvent
|
52 |
]
|
53 |
|
54 |
-
# Main class that logs multiple follow-up events
|
55 |
class FollowUpEvents(BaseModel):
|
56 |
-
follow_up_events: List[FollowUpEventType]
|
57 |
-
|
|
|
3 |
|
4 |
# --- Event follow-up classes ---
|
5 |
|
|
|
6 |
class AnimalCollectedEvent(BaseModel):
|
7 |
type: Literal['animal collected']
|
8 |
+
collected: Literal['yes', 'no']
|
9 |
|
|
|
10 |
class RecipientEvent(BaseModel):
|
11 |
type: Literal['recipient']
|
12 |
+
recipient: Literal['veterinary', 'care center',
|
13 |
+
'local museum', 'national museum',
|
14 |
+
'other']
|
15 |
+
|
16 |
class RadiographyEvent(BaseModel):
|
17 |
type: Literal['radiography']
|
18 |
+
radiography: Literal['yes', 'no', 'unknown']
|
19 |
|
|
|
20 |
class GivenAnswerEvent(BaseModel):
|
21 |
type: Literal['given answer']
|
22 |
answer: Literal[
|
23 |
+
'nothing',
|
24 |
+
'complaint against x',
|
25 |
+
'complaint',
|
26 |
+
'police call',
|
27 |
+
'discussion with the speaker',
|
28 |
+
'press release',
|
29 |
+
'unknown'
|
30 |
]
|
31 |
|
|
|
32 |
class NameOfRecipientEvent(BaseModel):
|
33 |
type: Literal['recipient name']
|
34 |
+
name: str
|
35 |
|
|
|
36 |
class CollectionReferenceEvent(BaseModel):
|
37 |
type: Literal['collection reference']
|
38 |
+
reference: str
|
39 |
|
|
|
40 |
FollowUpEventType = Union[
|
41 |
AnimalCollectedEvent,
|
42 |
RecipientEvent,
|
|
|
46 |
CollectionReferenceEvent
|
47 |
]
|
48 |
|
|
|
49 |
class FollowUpEvents(BaseModel):
|
50 |
+
follow_up_events: Optional[List[FollowUpEventType]] = None
|
|
app/gallery.py
CHANGED
@@ -1,84 +1,18 @@
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
3 |
-
import numpy as np
|
4 |
-
from dotenv import load_dotenv
|
5 |
-
import os
|
6 |
|
7 |
-
from
|
8 |
-
from validation_submission.get_json import
|
9 |
-
from validation_submission.submission import save_to_all_individuals
|
10 |
-
from validation_submission.validation import validate_individual
|
11 |
|
|
|
12 |
|
13 |
-
load_dotenv()
|
14 |
-
PATH = os.getcwd() + "/"
|
15 |
-
PATH_ASSETS = os.getenv('PATH_ASSETS')
|
16 |
-
PATH_CONFIG = PATH + PATH_ASSETS + "config/"
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
fields_config = load_config(PATH_CONFIG + "config_fields.json")
|
25 |
-
fields = fields_config["fields"]
|
26 |
-
return fields
|
27 |
-
|
28 |
-
def match_data_to_fields(fields, one_individual):
|
29 |
-
new_row = {}
|
30 |
-
for key in fields:
|
31 |
-
if key in one_individual:
|
32 |
-
if key=="image":
|
33 |
-
new_row[key] = one_individual[key]
|
34 |
-
elif type(one_individual[key])==list:
|
35 |
-
new_row[key] = ' , '.join(one_individual[key])
|
36 |
-
else:
|
37 |
-
new_row[key] = one_individual[key]
|
38 |
-
else:
|
39 |
-
new_row[key] = "NA"
|
40 |
-
return new_row
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
def process_animals(all_animals):
|
45 |
-
processed_animals = []
|
46 |
-
for _, animal in all_animals.items():
|
47 |
-
image = np.array(animal["image"])
|
48 |
-
caption = []
|
49 |
-
for key, val in animal.items():
|
50 |
-
if key!="image":
|
51 |
-
if key=="latitude":
|
52 |
-
caption.extend([
|
53 |
-
" | Latitude: " + str(animal["latitude"])])
|
54 |
-
elif key=="longitude":
|
55 |
-
caption.extend([
|
56 |
-
" | Longitude: " + str(animal["longitude"])])
|
57 |
-
elif key=="wounded" and val=="True":
|
58 |
-
caption.extend([" | Wounded: " + animal["wounded"]])
|
59 |
-
elif key=="dead" and val=="True":
|
60 |
-
caption.extend([" | Dead: " + animal["dead"]])
|
61 |
-
# elif key=="circumstance":
|
62 |
-
# caption.extend([" | Circumstances: " ,
|
63 |
-
# animal["circumstance"],
|
64 |
-
# animal["circumstance_dropdown_level1"],
|
65 |
-
# animal["circumstance_dropdown_level2"],
|
66 |
-
# animal["circumstance_openfield_level2"],
|
67 |
-
# animal["circumstance_dropdown_extra_level2"]])
|
68 |
-
# elif key=="behavior":
|
69 |
-
# caption.extend([" | Behavior: ", animal[key]])
|
70 |
-
# elif "physical_changes" in key:
|
71 |
-
# if not(" | Physical Changes: " in caption) :
|
72 |
-
# caption.extend([" | Physical Changes: ",
|
73 |
-
# "Beak: " + animal["physical_changes_beak"],
|
74 |
-
# "Body: " + animal["physical_changes_body"],
|
75 |
-
# "Head: " + animal["physical_changes_head"],
|
76 |
-
# "Feathers: " + animal["physical_changes_feathers"],
|
77 |
-
# "Legs: " + animal["physical_changes_legs"]])
|
78 |
-
caption_str = " ".join(caption)
|
79 |
-
animal = (image, caption_str)
|
80 |
-
processed_animals.append(animal)
|
81 |
-
return processed_animals
|
82 |
|
83 |
def set_gallery_size(len_animals):
|
84 |
if len_animals < 10:
|
@@ -89,62 +23,92 @@ def set_gallery_size(len_animals):
|
|
89 |
num_rows = len_animals/(num_cols)
|
90 |
return num_cols, num_rows
|
91 |
|
92 |
-
def save_individual_to_gallery(gallery):
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
gallery = gr.Gallery(
|
101 |
label="Gallery of Records", elem_id="gallery",
|
102 |
columns=[num_cols], rows=[num_rows],
|
103 |
-
value=
|
104 |
object_fit="contain", height="auto", interactive=False)
|
105 |
return gallery
|
106 |
-
|
107 |
-
# def save_individual_to_df(df):
|
108 |
-
# fields = get_fields()
|
109 |
-
# one_individual = get_json_one_individual()
|
110 |
-
# headers = get_headers()
|
111 |
-
# new_row = match_data_to_fields(fields, one_individual)
|
112 |
-
# new_row = format_row(new_row, headers)
|
113 |
-
# new_row_df = pd.DataFrame([new_row], columns=headers)
|
114 |
-
# df_new = pd.concat([df, new_row_df], ignore_index=True)
|
115 |
-
# df_gr = gr.DataFrame(visible=True,
|
116 |
-
# value=df_new,
|
117 |
-
# headers=headers)
|
118 |
-
# return df_gr
|
119 |
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
|
126 |
-
# def format_row(new_row, headers):
|
127 |
-
# formatted_row = {}
|
128 |
-
# #formatted_row["image"] = new_row["image"]
|
129 |
-
# for header in headers:
|
130 |
-
# if header=="location":
|
131 |
-
# formatted_row[header] = " | ".join(["Latitude: " + str(new_row["latitude"]),
|
132 |
-
# "Longitude: " + str(new_row["longitude"])])
|
133 |
-
# elif header=="state":
|
134 |
-
# formatted_row[header] = " | ".join(["Wounded: " + new_row["wounded"],
|
135 |
-
# "Dead: " + new_row["dead"]])
|
136 |
-
# elif header=="circumstance":
|
137 |
-
# formatted_row[header] = " | ".join([new_row["circumstance"],
|
138 |
-
# new_row["circumstance_dropdown_level1"],
|
139 |
-
# new_row["circumstance_dropdown_level2"],
|
140 |
-
# new_row["circumstance_openfield_level2"],
|
141 |
-
# new_row["circumstance_dropdown_extra_level2"]])
|
142 |
-
# elif header=="behavior":
|
143 |
-
# formatted_row[header] = new_row[header]
|
144 |
-
# elif header=="physical_changes":
|
145 |
-
# formatted_row[header] = " | ".join([new_row["physical_changes_beak"],
|
146 |
-
# new_row["physical_changes_body"],
|
147 |
-
# new_row["physical_changes_head"],
|
148 |
-
# new_row["physical_changes_feathers"],
|
149 |
-
# new_row["physical_changes_legs"]])
|
150 |
-
# return list(formatted_row.values())
|
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
|
|
|
|
|
|
3 |
|
4 |
+
from validation_submission.submission import validate_save_individual
|
5 |
+
from validation_submission.get_json import get_json_all_individuals
|
|
|
|
|
6 |
|
7 |
+
HEADERS = ["Identifier", "Location", "Wounded", "Dead"]
|
8 |
|
|
|
|
|
|
|
|
|
9 |
|
10 |
+
from PIL import Image
|
11 |
+
from io import BytesIO
|
12 |
+
import base64
|
13 |
+
def convert_image(image_base64_str):
|
14 |
+
im = Image.open(BytesIO(base64.b64decode(image_base64_str)))
|
15 |
+
return im
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
def set_gallery_size(len_animals):
|
18 |
if len_animals < 10:
|
|
|
23 |
num_rows = len_animals/(num_cols)
|
24 |
return num_cols, num_rows
|
25 |
|
26 |
+
def save_individual_to_gallery(gallery, df):
|
27 |
+
validate_save_individual()
|
28 |
+
all_animals = get_json_all_individuals()
|
29 |
+
gallery_animals = process_animals_for_gallery(all_animals)
|
30 |
+
gallery = make_gallery(gallery_animals)
|
31 |
+
df_animals = process_animals_for_df(all_animals)
|
32 |
+
df = make_df(df_animals)
|
33 |
+
return gallery, df
|
34 |
+
|
35 |
+
def process_animals_for_gallery(all_animals):
|
36 |
+
gallery_animals = []
|
37 |
+
for _, animal in all_animals.items():
|
38 |
+
image = convert_image(animal["image"]["image"])
|
39 |
+
caption = animal["identifier"]
|
40 |
+
animal = (image, caption)
|
41 |
+
gallery_animals.append(animal)
|
42 |
+
return gallery_animals
|
43 |
+
|
44 |
+
def make_gallery(gallery_animals):
|
45 |
+
num_cols, num_rows = set_gallery_size(len(gallery_animals))
|
46 |
gallery = gr.Gallery(
|
47 |
label="Gallery of Records", elem_id="gallery",
|
48 |
columns=[num_cols], rows=[num_rows],
|
49 |
+
value=gallery_animals,
|
50 |
object_fit="contain", height="auto", interactive=False)
|
51 |
return gallery
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
+
def keep_only_values(dict_to_filter):
|
54 |
+
info_text = ""
|
55 |
+
values_to_ignore = ["Yes", "No", "NA"]
|
56 |
+
if dict_to_filter:
|
57 |
+
for key, val in dict_to_filter.items():
|
58 |
+
if type(val) is dict:
|
59 |
+
subset_text = keep_only_values(val)
|
60 |
+
info_text += f"{subset_text}"
|
61 |
+
elif type(val) is list:
|
62 |
+
for item in val:
|
63 |
+
if type(item) is dict:
|
64 |
+
subset_text = keep_only_values(item)
|
65 |
+
info_text += f"{subset_text}"
|
66 |
+
elif (val is not None) and (type(val) is not bool) and (val not in values_to_ignore):
|
67 |
+
info_text += f" {key} : {val} |"
|
68 |
+
else:
|
69 |
+
print("Ignoring value: ", val)
|
70 |
+
print("Associated key: ", key)
|
71 |
+
else:
|
72 |
+
info_text = "NaN"
|
73 |
+
return info_text
|
74 |
+
|
75 |
+
|
76 |
+
def process_animals_for_df(all_animals):
|
77 |
+
df_animals = {}
|
78 |
+
identifiers =[]
|
79 |
+
geo =[]
|
80 |
+
wounded =[]
|
81 |
+
dead =[]
|
82 |
+
for _, animal in all_animals.items():
|
83 |
+
identifier_value = animal["identifier"]
|
84 |
+
identifiers.append(identifier_value)
|
85 |
+
geo_dict = animal["geolocalisation"]
|
86 |
+
geo_values = keep_only_values(geo_dict)
|
87 |
+
geo.append(geo_values)
|
88 |
+
wounded_dict = animal["wounded"]
|
89 |
+
wounded_values = keep_only_values(wounded_dict)
|
90 |
+
wounded.append(wounded_values)
|
91 |
+
dead_dict = animal["dead"]
|
92 |
+
dead_values = keep_only_values(dead_dict)
|
93 |
+
dead.append(dead_values)
|
94 |
+
df_animals["Identifier"] = identifiers
|
95 |
+
df_animals["Location"] = geo
|
96 |
+
df_animals["Wounded"] = wounded
|
97 |
+
df_animals["Dead"] = dead
|
98 |
+
return df_animals
|
99 |
+
|
100 |
+
|
101 |
+
def make_df(df_animals):
|
102 |
+
df = pd.DataFrame.from_dict(df_animals)
|
103 |
+
styled_df = df.style.set_properties(**{
|
104 |
+
'max-width': '100px', # Adjust width as needed
|
105 |
+
'white-space': 'normal', # Allows text to wrap to the next line
|
106 |
+
'word-wrap': 'break-word' # Breaks long words to fit within the width
|
107 |
+
})
|
108 |
+
df_gradio = gr.DataFrame(visible=True,
|
109 |
+
value=df,
|
110 |
+
headers=HEADERS, interactive=False)
|
111 |
+
# df = gr.DataFrame(visible=True,
|
112 |
+
# headers=HEADERS)
|
113 |
+
return df_gradio
|
114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/main_multianimal.py
CHANGED
@@ -29,8 +29,7 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
29 |
with gr.Row():
|
30 |
show_modal = gr.Button("Add an Animal", scale=3)
|
31 |
submit_button = gr.Button("Submit All Animals", scale=1)
|
32 |
-
|
33 |
-
# visible=False)
|
34 |
gallery = gr.Gallery(
|
35 |
label="Gallery of Records", elem_id="gallery",
|
36 |
columns=[1], rows=[1],
|
@@ -126,6 +125,7 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
126 |
fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded, \
|
127 |
fe_name_recipient_wounded, fe_collection_ref_wounded \
|
128 |
])
|
|
|
129 |
# ---------------------------------------------------------
|
130 |
# Wounded Button Logic
|
131 |
partial_show_section_wounded = partial(show_section_wounded, True)
|
@@ -149,6 +149,9 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
149 |
fe_collection_dropdown_dead, fe_recepient_dropdown_dead, fe_radio_dropdown_dead, fe_answer_dropdown_dead, \
|
150 |
fe_name_recipient_dead, fe_collection_ref_dead \
|
151 |
])
|
|
|
|
|
|
|
152 |
# ---------------------------------------------------------
|
153 |
# Dropdowns Dead
|
154 |
button_collision_dead.click(dropdown_collision,
|
@@ -159,7 +162,7 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
159 |
|
160 |
dropdown_dead.select(on_select, None, [dropdown_level2_dead, openfield_level2_dead, dropdown_extra_level2_dead])
|
161 |
dropdown_level2_dead.select(on_select_dropdown_level2)
|
162 |
-
openfield_level2_dead.
|
163 |
dropdown_extra_level2_dead.select(on_select_dropdown_extra_level2)
|
164 |
# ---------------------------------------------------------
|
165 |
# Radio Cause Wounded
|
@@ -178,7 +181,7 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
178 |
|
179 |
dropdown_wounded.select(on_select, None, [dropdown_level2_wounded, openfield_level2_wounded, dropdown_extra_level2_wounded])
|
180 |
dropdown_level2_wounded.select(on_select_dropdown_level2)
|
181 |
-
openfield_level2_wounded.select(
|
182 |
dropdown_extra_level2_wounded.select(on_select_dropdown_extra_level2)
|
183 |
# ---------------------------------------------------------
|
184 |
# Radio Behavior Wounded
|
@@ -255,8 +258,8 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
255 |
# inputs=[df],
|
256 |
# outputs=[df])
|
257 |
button_df.click(save_individual_to_gallery,
|
258 |
-
inputs=[gallery],
|
259 |
-
outputs=[gallery]
|
260 |
)
|
261 |
button_df.click(lambda: Modal(visible=False), None, modal)
|
262 |
|
@@ -266,7 +269,6 @@ with gr.Blocks(theme=theme, css=css) as demo:
|
|
266 |
show_modal.click(create_json_one_individual)
|
267 |
show_modal.click(create_tmp)
|
268 |
#submit_button.click(save_and_rest_df, inputs=[df], outputs=[df])
|
269 |
-
submit_button.click(save_individual_to_gallery)
|
270 |
|
271 |
|
272 |
|
|
|
29 |
with gr.Row():
|
30 |
show_modal = gr.Button("Add an Animal", scale=3)
|
31 |
submit_button = gr.Button("Submit All Animals", scale=1)
|
32 |
+
df = gr.Dataframe(headers=["Identifier", "Location", "Wounded", "Dead"], visible=False, interactive=False)
|
|
|
33 |
gallery = gr.Gallery(
|
34 |
label="Gallery of Records", elem_id="gallery",
|
35 |
columns=[1], rows=[1],
|
|
|
125 |
fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded, \
|
126 |
fe_name_recipient_wounded, fe_collection_ref_wounded \
|
127 |
])
|
128 |
+
|
129 |
# ---------------------------------------------------------
|
130 |
# Wounded Button Logic
|
131 |
partial_show_section_wounded = partial(show_section_wounded, True)
|
|
|
149 |
fe_collection_dropdown_dead, fe_recepient_dropdown_dead, fe_radio_dropdown_dead, fe_answer_dropdown_dead, \
|
150 |
fe_name_recipient_dead, fe_collection_ref_dead \
|
151 |
])
|
152 |
+
def save_wounded_state():
|
153 |
+
add_data_tmp("wounded_dead", "wounded", "Yes")
|
154 |
+
butt_wounded.click(save_wounded_state())
|
155 |
# ---------------------------------------------------------
|
156 |
# Dropdowns Dead
|
157 |
button_collision_dead.click(dropdown_collision,
|
|
|
162 |
|
163 |
dropdown_dead.select(on_select, None, [dropdown_level2_dead, openfield_level2_dead, dropdown_extra_level2_dead])
|
164 |
dropdown_level2_dead.select(on_select_dropdown_level2)
|
165 |
+
openfield_level2_dead.input(on_change_openfield_level2)
|
166 |
dropdown_extra_level2_dead.select(on_select_dropdown_extra_level2)
|
167 |
# ---------------------------------------------------------
|
168 |
# Radio Cause Wounded
|
|
|
181 |
|
182 |
dropdown_wounded.select(on_select, None, [dropdown_level2_wounded, openfield_level2_wounded, dropdown_extra_level2_wounded])
|
183 |
dropdown_level2_wounded.select(on_select_dropdown_level2)
|
184 |
+
openfield_level2_wounded.select(on_change_openfield_level2)
|
185 |
dropdown_extra_level2_wounded.select(on_select_dropdown_extra_level2)
|
186 |
# ---------------------------------------------------------
|
187 |
# Radio Behavior Wounded
|
|
|
258 |
# inputs=[df],
|
259 |
# outputs=[df])
|
260 |
button_df.click(save_individual_to_gallery,
|
261 |
+
inputs=[gallery, df],
|
262 |
+
outputs=[gallery, df]
|
263 |
)
|
264 |
button_df.click(lambda: Modal(visible=False), None, modal)
|
265 |
|
|
|
269 |
show_modal.click(create_json_one_individual)
|
270 |
show_modal.click(create_tmp)
|
271 |
#submit_button.click(save_and_rest_df, inputs=[df], outputs=[df])
|
|
|
272 |
|
273 |
|
274 |
|
app/physical/class_physical.py
CHANGED
@@ -16,52 +16,52 @@ CommonAnomalies = Literal[
|
|
16 |
# --- Beak-related Anomalies ---
|
17 |
class BeakAnomaly(BaseModel):
|
18 |
type: Literal['beak']
|
19 |
-
anomaly_type: Literal[
|
20 |
'adhesion',
|
21 |
'deformation',
|
22 |
CommonAnomalies
|
23 |
-
]
|
24 |
|
25 |
# --- Body-related Anomalies ---
|
26 |
class BodyAnomaly(BaseModel):
|
27 |
type: Literal['body']
|
28 |
-
anomaly_type: Literal[
|
29 |
'emaciation',
|
30 |
'fluffed up',
|
31 |
'stained feathers',
|
32 |
CommonAnomalies
|
33 |
-
]
|
34 |
|
35 |
# --- Legs-related Anomalies ---
|
36 |
class LegAnomaly(BaseModel):
|
37 |
type: Literal['legs']
|
38 |
-
anomaly_type: Literal[
|
39 |
'missing limb',
|
40 |
'deformation',
|
41 |
CommonAnomalies
|
42 |
-
]
|
43 |
|
44 |
# --- Feathers/Wings/Tail-related Anomalies ---
|
45 |
class FeathersWingsTailAnomaly(BaseModel):
|
46 |
type: Literal['feathers/wings/tail']
|
47 |
-
anomaly_type: Literal[
|
48 |
'fluffed up',
|
49 |
'feather abnormalities',
|
50 |
'stained feathers',
|
51 |
'abnormal wing posture',
|
52 |
'missing limb',
|
53 |
CommonAnomalies
|
54 |
-
]
|
55 |
|
56 |
# --- Head-related Anomalies (including eyes) ---
|
57 |
class HeadAnomaly(BaseModel):
|
58 |
-
type: Literal['head']
|
59 |
-
anomaly_type: Literal[
|
60 |
'ear changes',
|
61 |
'eye changes',
|
62 |
'tilted head',
|
63 |
CommonAnomalies
|
64 |
-
]
|
65 |
|
66 |
|
67 |
# Union of all possible anomaly types for specific body parts
|
@@ -76,4 +76,4 @@ AnomalyType = Union[
|
|
76 |
# Main PhysicalAnomaly class that logs anomalies across different body parts
|
77 |
class PhysicalAnomalies(BaseModel):
|
78 |
physical_radio: str
|
79 |
-
physical_anomalies_type: List[AnomalyType] =
|
|
|
16 |
# --- Beak-related Anomalies ---
|
17 |
class BeakAnomaly(BaseModel):
|
18 |
type: Literal['beak']
|
19 |
+
anomaly_type: List[Literal[
|
20 |
'adhesion',
|
21 |
'deformation',
|
22 |
CommonAnomalies
|
23 |
+
]]
|
24 |
|
25 |
# --- Body-related Anomalies ---
|
26 |
class BodyAnomaly(BaseModel):
|
27 |
type: Literal['body']
|
28 |
+
anomaly_type: List[Literal[
|
29 |
'emaciation',
|
30 |
'fluffed up',
|
31 |
'stained feathers',
|
32 |
CommonAnomalies
|
33 |
+
]]
|
34 |
|
35 |
# --- Legs-related Anomalies ---
|
36 |
class LegAnomaly(BaseModel):
|
37 |
type: Literal['legs']
|
38 |
+
anomaly_type: List[Literal[
|
39 |
'missing limb',
|
40 |
'deformation',
|
41 |
CommonAnomalies
|
42 |
+
]]
|
43 |
|
44 |
# --- Feathers/Wings/Tail-related Anomalies ---
|
45 |
class FeathersWingsTailAnomaly(BaseModel):
|
46 |
type: Literal['feathers/wings/tail']
|
47 |
+
anomaly_type: List[Literal[
|
48 |
'fluffed up',
|
49 |
'feather abnormalities',
|
50 |
'stained feathers',
|
51 |
'abnormal wing posture',
|
52 |
'missing limb',
|
53 |
CommonAnomalies
|
54 |
+
]]
|
55 |
|
56 |
# --- Head-related Anomalies (including eyes) ---
|
57 |
class HeadAnomaly(BaseModel):
|
58 |
+
type: Literal['head incl. eyes']
|
59 |
+
anomaly_type: List[Literal[
|
60 |
'ear changes',
|
61 |
'eye changes',
|
62 |
'tilted head',
|
63 |
CommonAnomalies
|
64 |
+
]]
|
65 |
|
66 |
|
67 |
# Union of all possible anomaly types for specific body parts
|
|
|
76 |
# Main PhysicalAnomaly class that logs anomalies across different body parts
|
77 |
class PhysicalAnomalies(BaseModel):
|
78 |
physical_radio: str
|
79 |
+
physical_anomalies_type: Optional[List[AnomalyType]] = None
|
app/physical/physical_checkbox.py
CHANGED
@@ -72,8 +72,9 @@ def process_body_parts(section, matched_box):
|
|
72 |
#---------------------------------------------------------
|
73 |
|
74 |
def on_select_body_part(body_part_checkbox, body_part):
|
75 |
-
add_data_tmp("wounded_dead", "physical_type_"+body_part, body_part)
|
76 |
-
|
|
|
77 |
|
78 |
#---------------------------------------------------------
|
79 |
|
|
|
72 |
#---------------------------------------------------------
|
73 |
|
74 |
def on_select_body_part(body_part_checkbox, body_part):
|
75 |
+
add_data_tmp("wounded_dead", "physical_type_"+body_part.lower(), body_part.lower())
|
76 |
+
body_part_checkbox = [body_part_check.lower() for body_part_check in body_part_checkbox]
|
77 |
+
add_data_tmp("wounded_dead", "physical_anomaly_"+body_part.lower(), body_part_checkbox)
|
78 |
|
79 |
#---------------------------------------------------------
|
80 |
|
app/validation_submission/add_json.py
CHANGED
@@ -8,7 +8,7 @@ def add_data_to_individual(key, value):
|
|
8 |
create_json_one_individual(one_individual)
|
9 |
|
10 |
def add_data_tmp(tmp_name, key, value):
|
11 |
-
with open(f"app/assets/
|
12 |
tmp = json.load(openfile)
|
13 |
tmp[key] = value
|
14 |
create_tmp(tmp_name, tmp)
|
|
|
8 |
create_json_one_individual(one_individual)
|
9 |
|
10 |
def add_data_tmp(tmp_name, key, value):
|
11 |
+
with open(f"app/assets/tmp_json/tmp_{tmp_name}.json", 'r') as openfile:
|
12 |
tmp = json.load(openfile)
|
13 |
tmp[key] = value
|
14 |
create_tmp(tmp_name, tmp)
|
app/validation_submission/create_json.py
CHANGED
@@ -12,5 +12,5 @@ def create_json_all_individuals(all_individuals={}):
|
|
12 |
|
13 |
def create_tmp(tmp_name="wounded_dead", tmp={}):
|
14 |
tmp = json.dumps(tmp)
|
15 |
-
with open(f"app/assets/
|
16 |
outfile.write(tmp)
|
|
|
12 |
|
13 |
def create_tmp(tmp_name="wounded_dead", tmp={}):
|
14 |
tmp = json.dumps(tmp)
|
15 |
+
with open(f"app/assets/tmp_json/tmp_{tmp_name}.json", "w") as outfile:
|
16 |
outfile.write(tmp)
|
app/validation_submission/get_json.py
CHANGED
@@ -11,6 +11,6 @@ def get_json_all_individuals():
|
|
11 |
return all_individuals
|
12 |
|
13 |
def get_json_tmp(tmp_name):
|
14 |
-
with open(f"app/assets/
|
15 |
tmp_json = json.load(openfile)
|
16 |
return tmp_json
|
|
|
11 |
return all_individuals
|
12 |
|
13 |
def get_json_tmp(tmp_name):
|
14 |
+
with open(f"app/assets/tmp_json/tmp_{tmp_name}.json", "r") as openfile:
|
15 |
tmp_json = json.load(openfile)
|
16 |
return tmp_json
|
app/validation_submission/processing.py
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#### PROCESS FUNCTIONS
|
2 |
+
|
3 |
+
def process_circumstance(data):
|
4 |
+
fields_to_check = ["option_dropdown", "open_field", "extra"]
|
5 |
+
if data["circumstance_radio"] == "Yes":
|
6 |
+
for field in fields_to_check:
|
7 |
+
if data["circumstance_type"][field+"_label"] == "NA":
|
8 |
+
data["circumstance_type"].pop(field+"_label")
|
9 |
+
else :
|
10 |
+
val = data[f"circumstance_{field}"]
|
11 |
+
key = data["circumstance_type"][field+"_label"]
|
12 |
+
data["circumstance_type"][key] = val
|
13 |
+
data["circumstance_type"].pop(field+"_label")
|
14 |
+
# {"circumstance_radio": true,
|
15 |
+
# "circumstance": "destruction / deliberatly removed",
|
16 |
+
# "cirumstance_type": {"type": "removal or direct capture",
|
17 |
+
# "option_dropdown_label": "method",
|
18 |
+
# "open_field_label": "NA",
|
19 |
+
# "extra_label": "NA"},
|
20 |
+
# "circumstance_option_dropdown": "Traffic/Trade"}
|
21 |
+
return data
|
22 |
+
|
23 |
+
def process_behaviors(data):
|
24 |
+
# INPUT :
|
25 |
+
#"behaviors_radio": true,
|
26 |
+
# "behaviors_type": ["Crash, Falling From The Sky", "Neurological"]
|
27 |
+
#OUTPUT:
|
28 |
+
# "behaviors_radio": "Yes",
|
29 |
+
# "behaviors_type": [
|
30 |
+
# {
|
31 |
+
# "type": "abnormal breathing",
|
32 |
+
# "description": "Problems breathing, breathing sounds"
|
33 |
+
# }
|
34 |
+
behaviors =[]
|
35 |
+
if data["behaviors_radio"] == "Yes":
|
36 |
+
for type in data["behaviors_type"]:
|
37 |
+
new_behavior = {}
|
38 |
+
new_behavior["type"] = type
|
39 |
+
behaviors.append(new_behavior)
|
40 |
+
data["behaviors_type"] = behaviors
|
41 |
+
return data
|
42 |
+
|
43 |
+
def process_physical(data):
|
44 |
+
# INPUT
|
45 |
+
# "physical_type_feathers": "feathers",
|
46 |
+
# "physical_anomaly_type_feathers": ["Blood", "Swelling"]}
|
47 |
+
|
48 |
+
# OUTPUT
|
49 |
+
# "physical_radio": "Yes",
|
50 |
+
# "physical_anomalies_type": [
|
51 |
+
# {
|
52 |
+
# "type": "beak",
|
53 |
+
# "anomaly_type": "deformation"
|
54 |
+
# },
|
55 |
+
# {
|
56 |
+
# "type": "body",
|
57 |
+
# "anomaly_type": "fluffed up"
|
58 |
+
# },
|
59 |
+
body_parts= ["beak", "body", "legs", "feathers/wings/tail", "head incl. eyes"]
|
60 |
+
anomalies=[]
|
61 |
+
reformatted = {}
|
62 |
+
reformatted["physical_radio"] = data["physical_radio"]
|
63 |
+
if data["physical_radio"] == "Yes":
|
64 |
+
for body_part in body_parts:
|
65 |
+
anomaly = {}
|
66 |
+
for key, val in data.items():
|
67 |
+
if "type_"+ body_part in key:
|
68 |
+
anomaly["type"] = body_part
|
69 |
+
elif "anomaly_"+ body_part in key:
|
70 |
+
anomaly["anomaly_type"] = val
|
71 |
+
if anomaly:
|
72 |
+
anomalies.append(anomaly)
|
73 |
+
reformatted["physical_anomalies_type"] = anomalies
|
74 |
+
return reformatted
|
75 |
+
|
76 |
+
def process_followup(data):
|
77 |
+
# "follow_up_events": [
|
78 |
+
# {
|
79 |
+
# "type": "animal collected",
|
80 |
+
# "option": "Yes"
|
81 |
+
# },
|
82 |
+
# {
|
83 |
+
# "type": "recipient",
|
84 |
+
# "option": "Veterinary",
|
85 |
+
# "name_recipient": "Dr. Jane Smith"
|
86 |
+
# },
|
87 |
+
# {
|
88 |
+
# "type": "radiography",
|
89 |
+
# "option": "Unknown"
|
90 |
+
# },
|
91 |
+
# {
|
92 |
+
# "type": "given answer",
|
93 |
+
# "option": "Discussion with the speaker"
|
94 |
+
# },
|
95 |
+
# {
|
96 |
+
# "type": "collection reference",
|
97 |
+
# "reference": "Specimen ID: 12345, Collected on 2023-09-15"
|
98 |
+
# }
|
99 |
+
# ]
|
100 |
+
followup_events = []
|
101 |
+
for key, val in data.items():
|
102 |
+
followup_event={}
|
103 |
+
type = key.split("followup")[-1]
|
104 |
+
option = type.split(" ")[-1]
|
105 |
+
followup_event["type"] = type.strip()
|
106 |
+
followup_event[option.strip()] = val
|
107 |
+
followup_events.append(followup_event)
|
108 |
+
reformatted ={}
|
109 |
+
reformatted["follow_up_events"] = followup_events
|
110 |
+
return reformatted
|
app/validation_submission/submission.py
CHANGED
@@ -1,5 +1,11 @@
|
|
1 |
import json
|
2 |
from validation_submission.get_json import get_json_all_individuals
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
def save_to_all_individuals(one_individual):
|
5 |
all_individuals = get_json_all_individuals()
|
|
|
1 |
import json
|
2 |
from validation_submission.get_json import get_json_all_individuals
|
3 |
+
from validation_submission.validation import validate_individual
|
4 |
+
|
5 |
+
def validate_save_individual():
|
6 |
+
individual = validate_individual()
|
7 |
+
save_to_all_individuals(individual.model_dump())
|
8 |
+
return individual
|
9 |
|
10 |
def save_to_all_individuals(one_individual):
|
11 |
all_individuals = get_json_all_individuals()
|
app/validation_submission/validation.py
CHANGED
@@ -1,9 +1,12 @@
|
|
|
|
|
|
1 |
from validation_submission.get_json import get_json_tmp, get_json_one_individual
|
2 |
-
from classes import Report
|
3 |
from circumstances.class_circumstance import Circumstances
|
4 |
from behavior.class_behavior import Behaviors
|
5 |
from physical.class_physical import PhysicalAnomalies
|
6 |
from follow_up.class_follow_up import FollowUpEvents
|
|
|
|
|
7 |
|
8 |
def get_fields(data_dict, keyword):
|
9 |
extract = {}
|
@@ -13,154 +16,84 @@ def get_fields(data_dict, keyword):
|
|
13 |
return extract
|
14 |
|
15 |
def validate_individual():
|
16 |
-
data =
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
def process_circumstance(data):
|
27 |
-
fields_to_check = ["option_dropdown", "open_field", "extra"]
|
28 |
-
for field in fields_to_check:
|
29 |
-
if data["circumstance_type"][field+"_label"] == "NA":
|
30 |
-
data["circumstance_type"].pop([field+"_label"])
|
31 |
-
else :
|
32 |
-
key = data[f"circumstance_{field}"]
|
33 |
-
val = data["circumstance_type"][field+"_label"]
|
34 |
-
data["circumstance_type"][key] = val
|
35 |
-
data["circumstance_type"].pop([field+"_label"])
|
36 |
-
# {"circumstance_radio": true,
|
37 |
-
# "circumstance": "destruction / deliberatly removed",
|
38 |
-
# "cirumstance_type": {"type": "removal or direct capture",
|
39 |
-
# "option_dropdown_label": "method",
|
40 |
-
# "open_field_label": "NA",
|
41 |
-
# "extra_label": "NA"},
|
42 |
-
# "circumstance_option_dropdown": "Traffic/Trade"}
|
43 |
-
return data
|
44 |
-
|
45 |
-
def process_behaviors(data):
|
46 |
-
# INPUT :
|
47 |
-
#"behaviors_radio": true,
|
48 |
-
# "behaviors_type": ["Crash, Falling From The Sky", "Neurological"]
|
49 |
-
#OUTPUT:
|
50 |
-
# "behaviors_radio": "Yes",
|
51 |
-
# "behaviors_type": [
|
52 |
-
# {
|
53 |
-
# "type": "abnormal breathing",
|
54 |
-
# "description": "Problems breathing, breathing sounds"
|
55 |
-
# }
|
56 |
-
behaviors =[]
|
57 |
-
for type in data["behaviors_type"]:
|
58 |
-
new_behavior = {}
|
59 |
-
new_behavior["type"] = type
|
60 |
-
behaviors.append(new_behavior)
|
61 |
-
data["behaviors_type"] = behaviors
|
62 |
-
return data
|
63 |
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
-
# OUTPUT
|
70 |
-
# "physical_radio": "Yes",
|
71 |
-
# "physical_anomalies_type": [
|
72 |
-
# {
|
73 |
-
# "type": "beak",
|
74 |
-
# "anomaly_type": "deformation"
|
75 |
-
# },
|
76 |
-
# {
|
77 |
-
# "type": "body",
|
78 |
-
# "anomaly_type": "fluffed up"
|
79 |
-
# },
|
80 |
-
body_parts= ["beak", "body", "legs", "feathers", "head"]
|
81 |
-
anomalies=[]
|
82 |
-
reformatted = {}
|
83 |
-
reformatted["physical_radio"] = data["physical_radio"]
|
84 |
-
for body_part in body_parts:
|
85 |
-
anomaly = {}
|
86 |
-
for key, val in data.items():
|
87 |
-
if "type_"+ body_part in key:
|
88 |
-
anomaly["type"] = val
|
89 |
-
elif "anomaly_type_"+ body_part in key:
|
90 |
-
anomaly["anomaly_type"] = val
|
91 |
-
anomalies.append(anomaly)
|
92 |
-
reformatted["physical_anomalies_type"] = anomalies
|
93 |
-
return reformatted
|
94 |
|
95 |
-
def process_followup(data):
|
96 |
-
# "follow_up_events": [
|
97 |
-
# {
|
98 |
-
# "type": "animal collected",
|
99 |
-
# "option": "Yes"
|
100 |
-
# },
|
101 |
-
# {
|
102 |
-
# "type": "recipient",
|
103 |
-
# "option": "Veterinary",
|
104 |
-
# "name_recipient": "Dr. Jane Smith"
|
105 |
-
# },
|
106 |
-
# {
|
107 |
-
# "type": "radiography",
|
108 |
-
# "option": "Unknown"
|
109 |
-
# },
|
110 |
-
# {
|
111 |
-
# "type": "given answer",
|
112 |
-
# "option": "Discussion with the speaker"
|
113 |
-
# },
|
114 |
-
# {
|
115 |
-
# "type": "collection reference",
|
116 |
-
# "reference": "Specimen ID: 12345, Collected on 2023-09-15"
|
117 |
-
# }
|
118 |
-
# ]
|
119 |
-
followup_events = []
|
120 |
-
for key, val in data.items():
|
121 |
-
followup_event={}
|
122 |
-
type = key.split("followup")[-1]
|
123 |
-
option = type.split(" ")[-1]
|
124 |
-
followup_event["type"] = type
|
125 |
-
followup_event[option] = val
|
126 |
-
followup_events.append(followup_event)
|
127 |
-
return followup_events
|
128 |
|
129 |
#### VALIDATION FUNCTIONS
|
130 |
def validate_circumstance(data):
|
131 |
circumstance_raw = get_fields(data, "circumstance")
|
132 |
circumstance_formatted = process_circumstance(circumstance_raw)
|
133 |
-
if not Circumstances(
|
134 |
print("Validation failed for the circumstance.")
|
135 |
else:
|
136 |
return Circumstances(**circumstance_formatted)
|
137 |
|
138 |
def validate_behavior(data):
|
139 |
-
behaviors_raw = get_fields(data, "
|
140 |
behaviors_formatted = process_behaviors(behaviors_raw)
|
141 |
-
|
142 |
-
|
143 |
-
else:
|
144 |
return Behaviors(**behaviors_formatted)
|
|
|
|
|
|
|
145 |
|
146 |
def validate_physical(data):
|
147 |
physical_raw = get_fields(data, "physical")
|
148 |
physical_formatted = process_physical(physical_raw)
|
149 |
-
|
150 |
-
|
151 |
-
else:
|
152 |
return PhysicalAnomalies(**physical_formatted)
|
153 |
-
|
|
|
|
|
154 |
def validate_follow_up(data):
|
155 |
followup_raw = get_fields(data, "followup")
|
156 |
followup_formatted = process_followup(followup_raw)
|
157 |
-
|
158 |
-
|
159 |
-
else:
|
160 |
return FollowUpEvents(**followup_formatted)
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
pass
|
|
|
1 |
+
import uuid
|
2 |
+
|
3 |
from validation_submission.get_json import get_json_tmp, get_json_one_individual
|
|
|
4 |
from circumstances.class_circumstance import Circumstances
|
5 |
from behavior.class_behavior import Behaviors
|
6 |
from physical.class_physical import PhysicalAnomalies
|
7 |
from follow_up.class_follow_up import FollowUpEvents
|
8 |
+
from classes import Report, Wounded, Dead, ImageBase64
|
9 |
+
from validation_submission.processing import process_circumstance, process_behaviors, process_physical, process_followup
|
10 |
|
11 |
def get_fields(data_dict, keyword):
|
12 |
extract = {}
|
|
|
16 |
return extract
|
17 |
|
18 |
def validate_individual():
|
19 |
+
data = get_json_one_individual()
|
20 |
+
data["identifier"] = str(uuid.uuid4())
|
21 |
+
if "wounded_state" not in data or "dead_state" not in data:
|
22 |
+
data["wounded_state"] = "No"
|
23 |
+
data["dead_state"] = "No"
|
24 |
+
if (data["wounded_state"] == "Yes") or (data["dead_state"] == "Yes"):
|
25 |
+
data_wounded_dead = get_json_tmp("wounded_dead")
|
26 |
+
circumstance = validate_circumstance(data_wounded_dead)
|
27 |
+
physical = validate_physical(data_wounded_dead)
|
28 |
+
followup = validate_follow_up(data_wounded_dead)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
+
if data["wounded_state"]=="Yes":
|
31 |
+
behavior = validate_behavior(data_wounded_dead)
|
32 |
+
individual = Report(identifier = data["identifier"],
|
33 |
+
image = ImageBase64.to_base64(data["image"]),
|
34 |
+
geolocalisation = data["geolocalisation"],
|
35 |
+
wounded_state = data["wounded_state"],
|
36 |
+
wounded = Wounded(circumstances = circumstance,
|
37 |
+
behaviors = behavior,
|
38 |
+
physical_anomalies = physical,
|
39 |
+
follow_up_events = followup),
|
40 |
+
dead_state = data["dead_state"])
|
41 |
+
elif data["dead_state"]=="Yes":
|
42 |
+
individual = Report(identifier = data["identifier"],
|
43 |
+
image = ImageBase64.to_base64(data["image"]),
|
44 |
+
geolocalisation = data["geolocalisation"],
|
45 |
+
wounded_state = data["wounded_state"],
|
46 |
+
dead_state = data["dead_state"],
|
47 |
+
dead = Dead(circumstances = circumstance,
|
48 |
+
physical_anomalies = physical,
|
49 |
+
follow_up_events = followup)
|
50 |
+
)
|
51 |
+
else:
|
52 |
+
data["image"] = ImageBase64.to_base64(data["image"])
|
53 |
+
if not Report(**data).validate():
|
54 |
+
print("Validation failed for creating the individual report.")
|
55 |
+
else:
|
56 |
+
individual = Report(**data)
|
57 |
+
return individual
|
58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
#### VALIDATION FUNCTIONS
|
62 |
def validate_circumstance(data):
|
63 |
circumstance_raw = get_fields(data, "circumstance")
|
64 |
circumstance_formatted = process_circumstance(circumstance_raw)
|
65 |
+
if not Circumstances.model_validate(circumstance_formatted):
|
66 |
print("Validation failed for the circumstance.")
|
67 |
else:
|
68 |
return Circumstances(**circumstance_formatted)
|
69 |
|
70 |
def validate_behavior(data):
|
71 |
+
behaviors_raw = get_fields(data, "behaviors")
|
72 |
behaviors_formatted = process_behaviors(behaviors_raw)
|
73 |
+
try:
|
74 |
+
Behaviors.model_validate(behaviors_formatted)
|
|
|
75 |
return Behaviors(**behaviors_formatted)
|
76 |
+
except:
|
77 |
+
print("Validation failed for the behaviors.")
|
78 |
+
|
79 |
|
80 |
def validate_physical(data):
|
81 |
physical_raw = get_fields(data, "physical")
|
82 |
physical_formatted = process_physical(physical_raw)
|
83 |
+
try:
|
84 |
+
PhysicalAnomalies.model_validate(physical_formatted)
|
|
|
85 |
return PhysicalAnomalies(**physical_formatted)
|
86 |
+
except:
|
87 |
+
print("Validation failed for the physical anomalies.")
|
88 |
+
|
89 |
def validate_follow_up(data):
|
90 |
followup_raw = get_fields(data, "followup")
|
91 |
followup_formatted = process_followup(followup_raw)
|
92 |
+
try:
|
93 |
+
FollowUpEvents.model_validate(followup_formatted)
|
|
|
94 |
return FollowUpEvents(**followup_formatted)
|
95 |
+
except:
|
96 |
+
print("Validation failed for the follow-up events.")
|
97 |
+
|
98 |
+
|
99 |
+
|
|
app/wounded.py
CHANGED
@@ -8,8 +8,8 @@ from validation_submission.add_json import add_data_to_individual
|
|
8 |
|
9 |
def show_section_wounded(visible):
|
10 |
if visible==True:
|
11 |
-
add_data_to_individual("
|
12 |
-
add_data_to_individual("
|
13 |
|
14 |
with gr.Column(visible=visible, elem_id="wounded") as wounded_section:
|
15 |
gr.Markdown("# Wounded Animal")
|
|
|
8 |
|
9 |
def show_section_wounded(visible):
|
10 |
if visible==True:
|
11 |
+
add_data_to_individual("wounded_state", "Yes")
|
12 |
+
add_data_to_individual("dead_state", "No")
|
13 |
|
14 |
with gr.Column(visible=visible, elem_id="wounded") as wounded_section:
|
15 |
gr.Markdown("# Wounded Animal")
|