vancauwe commited on
Commit
4ad78c2
·
unverified ·
2 Parent(s): b87ee04 7035c57

Merge pull request #46 from sdsc-ordes/feat/simple-symptoms

Browse files
README.md CHANGED
@@ -13,7 +13,7 @@ short_description: Digiwild
13
  ## Docker
14
 
15
  ``` bash
16
- docker build -t ordes/digiwild .
17
  ```
18
 
19
 
@@ -29,18 +29,17 @@ python3 main.py
29
  ### How to develop on docker
30
 
31
  ``` bash
32
- docker run -it -p 7860:7860 -v $(pwd):/home/user/digiwild/ ordes/digiwild
33
  ```
34
 
35
  ## TODO
36
 
37
  - [x] Change `wounded` to `wounded / sick`
38
  - [x] Info formatting
39
- - [ ] Use in memory object instead of files to avoid writting / reading problems.
40
  - [ ] Connection to a database? Maybe an open MongoDB
41
  - [x] GPS Compatibility
42
  - [x] New fields suggested: Number individuals, Species, Comments
43
- - [ ] Save new fields values into the JSON. Perform validation too.
44
  - [ ] Add info and placeholder information to the different components.
45
 
46
  ## Needs
@@ -50,5 +49,3 @@ docker run -it -p 7860:7860 -v $(pwd):/home/user/digiwild/ ordes/digiwild
50
  - GPS location
51
  - Comments
52
  - Symptomps selection (Dropdown)
53
-
54
- ## PR Updates
 
13
  ## Docker
14
 
15
  ``` bash
16
+ docker build -t ordes/digiwild .
17
  ```
18
 
19
 
 
29
  ### How to develop on docker
30
 
31
  ``` bash
32
+ docker run -it -p 7860:7860 -v $(pwd):/home/user/digiwild/ --entrypoint bash ordes/digiwild
33
  ```
34
 
35
  ## TODO
36
 
37
  - [x] Change `wounded` to `wounded / sick`
38
  - [x] Info formatting
39
+ - [x] Use in memory object instead of files to avoid writting / reading problems.
40
  - [ ] Connection to a database? Maybe an open MongoDB
41
  - [x] GPS Compatibility
42
  - [x] New fields suggested: Number individuals, Species, Comments
 
43
  - [ ] Add info and placeholder information to the different components.
44
 
45
  ## Needs
 
49
  - GPS location
50
  - Comments
51
  - Symptomps selection (Dropdown)
 
 
app/{credits.py → about.py} RENAMED
@@ -1,27 +1,46 @@
 
 
 
 
 
 
 
 
 
1
  credits_text = """
2
  # Credits
3
 
4
- ## Scientific Expertise
5
-
6
- - Isabelle Wethli (Institute for Fish and Wildlife Health, University of Bern)
7
- - Dr. Mirjam Pewsner (Institute for Fish and Wildlife Health, University of Bern)
8
- - Dr. Saskia Keller (Institute for Fish and Wildlife Health, University of Bern)
9
 
10
- ## Front End Development
11
 
12
- - Carlos Viviar Rios (Swiss Data Science Center)
13
- - Laure Vancauwenberghe (Swiss Data Science Center)
 
 
14
 
 
15
 
 
 
 
16
 
 
17
 
 
18
 
 
19
 
 
 
20
 
 
21
 
22
- ### Icons
 
 
23
 
24
- copyright: <a href="https://www.flaticon.com/free-icons/copyright" title="copyright icons">Copyright icons created by Freepik - Flaticon</a>
25
 
26
  flying-doves-group: <a href="https://www.flaticon.com/free-icons/animal" title="animal icons">Animal icons created by Freepik - Flaticon</a>
27
 
@@ -49,4 +68,31 @@ schedule: <a href="https://www.flaticon.com/free-icons/follow-up" title="follow
49
 
50
  Effective: <a href="https://www.flaticon.com/free-icons/cog" title="cog icons">Cog icons created by monkik - Flaticon</a>
51
 
52
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from dotenv import load_dotenv
4
+ import os
5
+ load_dotenv()
6
+ PATH = os.getcwd() + "/"
7
+ PATH_ASSETS = os.getenv('PATH_ASSETS')
8
+ PATH_ICONS = PATH + PATH_ASSETS + "icons/"
9
+
10
  credits_text = """
11
  # Credits
12
 
13
+ This work stemmed from a fruitful collaboration between SDSC and FIWI.
 
 
 
 
14
 
15
+ ## Scientific Expertise : FIWI from UniBE
16
 
17
+ From the [Institute for Fish and Wildlife Health, University of Bern](https://www.fiwi.vetsuisse.unibe.ch)
18
+ - **Isabelle Wethli**
19
+ - **Dr. Mirjam Pewsner**
20
+ - **Dr. Saskia Keller**
21
 
22
+ ## Front End Development: SDSC
23
 
24
+ From the [Swiss Data Science Center](https://www.datascience.ch)
25
+ - **Carlos Viviar Rios**
26
+ - **Laure Vancauwenberghe**
27
 
28
+ ## How to Contact Us?
29
 
30
+ Please reach out to FIWI via [their contacts](https://www.fiwi.vetsuisse.unibe.ch/about_us/team/index_eng.html).
31
 
32
+ ## Special Thanks
33
 
34
+ - **Vogelwarte** for their advice, especially Samuel Wechsler.
35
+ - **Biolovision SA**, providers of **ornitho.ch**, for their collaboration: circumstances are matched to their current data collection schema on ornitho.ch
36
 
37
+ """
38
 
39
+ icons_text = """
40
+ ### Icons' Attributions
41
+ (scroll to see all)
42
 
43
+ Biolovision for the circumstances icons.
44
 
45
  flying-doves-group: <a href="https://www.flaticon.com/free-icons/animal" title="animal icons">Animal icons created by Freepik - Flaticon</a>
46
 
 
68
 
69
  Effective: <a href="https://www.flaticon.com/free-icons/cog" title="cog icons">Cog icons created by monkik - Flaticon</a>
70
 
71
+ correct: <a href="https://www.flaticon.com/fr/icones-gratuites/termine" title="terminé icônes">Terminé icônes créées par kliwir art - Flaticon</a>
72
+
73
+ supprimer: <a href="https://www.flaticon.com/fr/icones-gratuites/faux" title="faux icônes">Faux icônes créées par hqrloveq - Flaticon</a>
74
+
75
+ balai-magique: <a href="https://www.flaticon.com/fr/icones-gratuites/la-magie" title="la magie icônes">La magie icônes créées par Freepik - Flaticon</a>
76
+
77
+ contact-information: <a href="https://www.flaticon.com/free-icons/contact-information" title="contact information icons">Contact information icons created by Freepik - Flaticon</a>
78
+
79
+ help: <a href="https://www.flaticon.com/free-icons/help" title="help icons">Help icons created by Freepik - Flaticon</a>
80
+
81
+ question: <a href="https://www.flaticon.com/free-icons/question" title="question icons">Question icons created by Freepik - Flaticon</a>
82
+ """
83
+
84
+ with gr.Blocks(theme='shivi/calm_seafoam') as about:
85
+ with gr.Row(scale = 1):
86
+ gr.Image(PATH_ICONS+"sdsc.png",
87
+ height=200,
88
+ interactive=False,
89
+ show_fullscreen_button = False, show_share_button=False,
90
+ show_download_button=False, show_label=False)
91
+ gr.Image(PATH_ICONS+"fiwi.png",
92
+ height=200,
93
+ interactive=False,
94
+ show_fullscreen_button = False, show_share_button=False,
95
+ show_download_button=False, show_label=False)
96
+
97
+ gr.Markdown(credits_text, show_label=False)
98
+ gr.Markdown(icons_text, show_label=False, height=100)
app/assets/config/config_checkbox_behavior_simple.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "General weakness":
3
+ {
4
+ "Description": "Abnormal breathing (dyspnoea), sudden crash, apathy, lethargy, unable to fly but responsive."
5
+ },
6
+ "Vomiting":
7
+ {
8
+ "Description": "Throwing up undigested food, regurgitating"
9
+ },
10
+ "Atypical behavior":
11
+ {
12
+ "Description": "Circling, incoordination, tremors, convulsions"
13
+ },
14
+ "No changes":
15
+ {
16
+ "Description": "Animal is acting normally"
17
+ }
18
+ }
app/assets/config/config_checkbox_physical_simple.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Common":
3
+ {
4
+ "Abnormal position": {
5
+ "Description": "Hanging wing, tilted head, lameness"},
6
+ "Injury": {
7
+ "Description": "Visible blood, fracture, missing limb, swelling without visible injury, foreign body"}
8
+ },
9
+ "Beak":
10
+ {
11
+ "Adhesion/Discharge": {
12
+ "Description": "Material (food, saliva) around/ in the beak, discharge from ears/ eyes/ beak/ nose"
13
+ }
14
+ },
15
+ "Body":
16
+ {},
17
+ "Feathers/Wings/Tail":
18
+ {
19
+ "Feathers and skin change": {
20
+ "Description": "Blackened/ burnt skin or feathers, deformed feathers, ear changes (swollen, crusts, plugged, discharge), discolored/ missing/ broken/ stained feathers, fluffed up plumage, warts/ crusts/ tumor-like growth, parasites"}
21
+ },
22
+ "Head incl. eyes":
23
+ {
24
+ "Eye changes": {
25
+ "Description": "Swollen, reddened, closed, discharge, crusts"}
26
+ },
27
+ "Legs":
28
+ {}
29
+
30
+ }
app/assets/icons/balai-magique.png ADDED
app/assets/icons/contact-information.png ADDED
app/assets/icons/correct.png ADDED
app/assets/icons/fiwi.png ADDED
app/assets/icons/help.png ADDED
app/assets/icons/question.png ADDED
app/assets/icons/sdsc.png ADDED
app/assets/icons/supprimer.png ADDED
app/behavior/behavior_checkbox.py CHANGED
@@ -4,28 +4,41 @@ from utils.utils_checkbox import create_checkbox
4
  from utils.utils_visible import set_visible
5
  from validation_submission.utils_individual import add_data_to_individual
6
 
7
- def on_select_behavior(behavior_checkbox, individual):
 
8
  behavior_checkbox = [behavior.lower() for behavior in behavior_checkbox]
9
  individual = add_data_to_individual("behaviors_type", behavior_checkbox, individual)
10
  return individual
11
 
12
- def retrieve_behavior_options_description():
13
- dropdown_config = get_custom_config_dropdowns("config_checkbox_behavior.json")
 
 
 
 
 
 
 
14
  options = list(dropdown_config.keys())
15
  options = [option.title() for option in options]
16
- descriptions =[]
17
- for _,subdict in dropdown_config.items():
18
  descriptions.append(subdict["Description"])
19
  return options, descriptions
20
 
21
- def create_behavior_checkbox(section: str, visible):
22
- options, descriptions = retrieve_behavior_options_description()
 
23
  label_checkbox = "Behavior changes observed"
24
- checkbox, text = create_checkbox("", section, label_checkbox, visible, options, descriptions)
 
 
25
  return checkbox, text
26
 
27
- def show_behavior(choice, section: str, individual):
 
 
28
  visible = set_visible(choice)
29
- checkbox, text = create_behavior_checkbox(section, visible)
30
  individual = add_data_to_individual("behaviors_radio", choice, individual)
31
- return checkbox, text, individual
 
4
  from utils.utils_visible import set_visible
5
  from validation_submission.utils_individual import add_data_to_individual
6
 
7
+
8
+ def on_select_behavior(behavior_checkbox, individual):
9
  behavior_checkbox = [behavior.lower() for behavior in behavior_checkbox]
10
  individual = add_data_to_individual("behaviors_type", behavior_checkbox, individual)
11
  return individual
12
 
13
+
14
+ def retrieve_behavior_options_description(mode: str):
15
+ # print(f"Retrieve Behavior Option Description: {mode}")
16
+ if mode == "simple":
17
+ dropdown_config = get_custom_config_dropdowns(
18
+ "config_checkbox_behavior_simple.json"
19
+ )
20
+ elif mode == "advanced":
21
+ dropdown_config = get_custom_config_dropdowns("config_checkbox_behavior.json")
22
  options = list(dropdown_config.keys())
23
  options = [option.title() for option in options]
24
+ descriptions = []
25
+ for _, subdict in dropdown_config.items():
26
  descriptions.append(subdict["Description"])
27
  return options, descriptions
28
 
29
+
30
+ def create_behavior_checkbox(section: str, mode: str, visible):
31
+ options, descriptions = retrieve_behavior_options_description(mode)
32
  label_checkbox = "Behavior changes observed"
33
+ checkbox, text = create_checkbox(
34
+ "", section, label_checkbox, visible, options, descriptions
35
+ )
36
  return checkbox, text
37
 
38
+
39
+ def show_behavior(choice, section: str, mode: str, individual):
40
+ # print(f"Show Behavior: {mode}")
41
  visible = set_visible(choice)
42
+ checkbox, text = create_behavior_checkbox(section, mode, visible)
43
  individual = add_data_to_individual("behaviors_radio", choice, individual)
44
+ return checkbox, text, individual
app/behavior/class_behavior_simple.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Literal, List, Union, Optional
3
+
4
+
5
+ class BehaviorSimple(BaseModel):
6
+ type: str
7
+ description: Optional[str] = None # Making the description field optional
8
+
9
+
10
+ # --- Specific BehaviorSimple classes ---
11
+ class GeneralWeakness(BehaviorSimple):
12
+ type: Literal["general weakness"]
13
+ description: Optional[
14
+ Literal[
15
+ "Abnormal breathing (dyspnoea), sudden crash, apathy, lethargy, unable to fly but responsive"
16
+ ]
17
+ ] = None
18
+
19
+
20
+ class Vomiting(BehaviorSimple):
21
+ type: Literal["vomiting"]
22
+ description: Optional[Literal["Throwing up undigested food, regurgitating"]] = None
23
+
24
+
25
+ class AtypicalBehavior(BehaviorSimple):
26
+ type: Literal["atypical behavior"]
27
+ description: Optional[
28
+ Literal["Circling, incoordination, tremors, convulsions"]
29
+ ] = None
30
+
31
+
32
+ class NoChanges(BehaviorSimple):
33
+ type: Literal["no changes"]
34
+ description: Optional[Literal["Animal is acting normally"]] = None
35
+
36
+
37
+ # Union of all possible behaviors
38
+ BehaviorSimpleType = Union[GeneralWeakness, Vomiting, AtypicalBehavior, NoChanges]
39
+
40
+
41
+ # Main class that logs multiple behaviors
42
+ class BehaviorsSimple(BaseModel):
43
+ behaviors_radio: str # e.g., "Yes"
44
+ behaviors_type: Optional[List[BehaviorSimpleType]] = None
app/circumstances/circumstances.py CHANGED
@@ -15,7 +15,7 @@ CAUSE_COL_WIDTH = "50px"
15
 
16
  def show_circumstances(choice, individual):
17
  visible = set_visible(choice)
18
- individual = add_data_to_individual("wounded_dead",
19
  "circumstance_radio",
20
  choice, individual)
21
  button_collision, button_deliberate_destruction, button_indirect_destruction, button_natural_cause, dropdown, dropdown_level2, openfield_level2, dropdown_extra_level2 = create_circumstances(visible)
 
15
 
16
  def show_circumstances(choice, individual):
17
  visible = set_visible(choice)
18
+ individual = add_data_to_individual(
19
  "circumstance_radio",
20
  choice, individual)
21
  button_collision, button_deliberate_destruction, button_indirect_destruction, button_natural_cause, dropdown, dropdown_level2, openfield_level2, dropdown_extra_level2 = create_circumstances(visible)
app/circumstances/circumstances_dropdowns.py CHANGED
@@ -2,114 +2,141 @@ import gradio as gr
2
  from utils.utils_config import get_custom_config_dropdowns
3
  from validation_submission.utils_individual import add_data_to_individual
4
 
5
- #--------------------------------------------------------- LEVEL 1 DROPDOWNS
 
6
  def retrieve_config_options(label, dropdown_config):
7
  options = list(dropdown_config[label].keys())
8
  options = [option.title() for option in options]
9
  return options
10
 
11
- def reinitialise_level2():
 
12
  dropdown_level2 = gr.Dropdown(choices=[], visible=False)
13
  openfield_level2 = gr.Textbox(visible=False)
14
  dropdown_extra_level2 = gr.Dropdown(choices=[], visible=False)
15
  return dropdown_level2, openfield_level2, dropdown_extra_level2
16
 
17
- def create_dropdown_level1(label, individual):
 
18
  dropdown_config = get_custom_config_dropdowns("config_dropdown_circumstances.json")
19
  options = retrieve_config_options(label, dropdown_config)
20
  dropdown = gr.Dropdown(choices=options, label=label, interactive=True, visible=True)
21
  dropdown_level2, openfield_level2, dropdown_extra_level2 = reinitialise_level2()
22
- return dropdown, dropdown_level2, openfield_level2, dropdown_extra_level2, individual
23
-
 
 
 
 
 
 
 
24
  def dropdown_collision(individual):
25
  label = "Collision with a means of transport"
26
- individual = add_data_to_individual("wounded_dead", "circumstance", label.lower(), individual)
27
  return create_dropdown_level1(label, individual)
28
 
 
29
  def dropdown_deliberate_destruction(individual):
30
  label = "Destruction / Deliberatly removed"
31
- individual = add_data_to_individual("wounded_dead", "circumstance", label.lower(), individual)
32
- return create_dropdown_level1(label, individual)
 
33
 
34
- def dropdown_indirect_destruction(individual):
35
  label = "Indirect destruction"
36
- individual = add_data_to_individual("wounded_dead", "circumstance", label.lower(), individual)
37
- return create_dropdown_level1(label, individual)
 
38
 
39
- def dropdown_natural_cause(individual):
40
  label = "Natural cause"
41
- individual = add_data_to_individual("wounded_dead", "circumstance", label.lower(), individual)
42
- return create_dropdown_level1(label, individual)
43
 
44
 
45
- #--------------------------------------------------------- LEVEL 2 DROPDOWNS
46
  def get_options(value):
47
- value = value.lower()
48
- options_label = None
49
- options_dropdown= None
50
- open_field = None
51
- extras = None
52
- extras_label = None
53
- dropdown_config = get_custom_config_dropdowns("config_dropdown_circumstances.json")
54
- for _, sub_dict in dropdown_config.items():
55
- nested_dict = sub_dict.get(value)
56
- if nested_dict is not None:
57
- if "Options" in nested_dict.keys():
58
- options_dict = nested_dict["Options"]
59
- options_label = list(options_dict.keys())[0]
60
- options_dropdown = list(options_dict.values())[0]
61
- options_dropdown = [option.title() for option in options_dropdown]
62
- if "Open" in nested_dict.keys():
63
- open_field = nested_dict["Open"]
64
- open_field = open_field.title()
65
- if "Extra" in nested_dict.keys():
66
- for key, val in nested_dict["Extra"].items():
67
- extras_label = key
68
- extras = val
69
- extras = [extra.title() for extra in extras]
70
- return options_label, options_dropdown, open_field, extras, extras_label
71
-
72
-
73
  def on_select(evt: gr.SelectData, individual): # SelectData is a subclass of EventData
74
- options_label, options_dropdown, open_field, extras, extras_label = get_options(evt.value)
75
- individual = add_data_to_individual("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',
80
- "extra_label": extras_label.lower() if extras_label is not None else 'NA'
81
- }, individual)
 
 
 
 
 
 
 
82
  if options_dropdown is not None:
83
- dropdown_level2 = gr.Dropdown(choices=options_dropdown, label=evt.value, interactive=True, visible=True)
84
- else:
 
 
85
  dropdown_level2 = gr.Dropdown(choices=[], visible=False)
86
 
87
  if open_field is not None:
88
  openfield_level2 = gr.Textbox(label=open_field, interactive=True, visible=True)
89
- else:
90
  openfield_level2 = gr.Textbox(visible=False)
91
 
92
- if extras is not None:
93
- dropdown_extra_level2 = gr.Dropdown(choices=extras, label=extras_label, interactive=True, visible=True)
94
- else:
 
 
95
  dropdown_extra_level2 = gr.Dropdown(choices=[], visible=False)
96
  return dropdown_level2, openfield_level2, dropdown_extra_level2, individual
97
 
98
- def on_select_dropdown_level2(evt: gr.SelectData, individual):
99
- individual = add_data_to_individual("wounded_dead",
100
- "circumstance_option_dropdown",
101
- evt.value.lower(), individual)
 
102
  return individual
103
 
104
- def on_select_dropdown_extra_level2(evt: gr.SelectData, individual):
105
- individual = add_data_to_individual("wounded_dead",
106
- "circumstance_extra",
107
- evt.value.lower(), individual)
 
 
 
 
 
 
 
 
 
108
  return individual
109
-
110
- def on_change_openfield_level2(openfield_level2_dead, individual):
111
- print("Saving open field")
112
- individual = add_data_to_individual("wounded_dead",
113
- "circumstance_open_field",
114
- str(openfield_level2_dead).lower(), individual)
115
- return individual
 
2
  from utils.utils_config import get_custom_config_dropdowns
3
  from validation_submission.utils_individual import add_data_to_individual
4
 
5
+
6
+ # --------------------------------------------------------- LEVEL 1 DROPDOWNS
7
  def retrieve_config_options(label, dropdown_config):
8
  options = list(dropdown_config[label].keys())
9
  options = [option.title() for option in options]
10
  return options
11
 
12
+
13
+ def reinitialise_level2():
14
  dropdown_level2 = gr.Dropdown(choices=[], visible=False)
15
  openfield_level2 = gr.Textbox(visible=False)
16
  dropdown_extra_level2 = gr.Dropdown(choices=[], visible=False)
17
  return dropdown_level2, openfield_level2, dropdown_extra_level2
18
 
19
+
20
+ def create_dropdown_level1(label, individual):
21
  dropdown_config = get_custom_config_dropdowns("config_dropdown_circumstances.json")
22
  options = retrieve_config_options(label, dropdown_config)
23
  dropdown = gr.Dropdown(choices=options, label=label, interactive=True, visible=True)
24
  dropdown_level2, openfield_level2, dropdown_extra_level2 = reinitialise_level2()
25
+ return (
26
+ dropdown,
27
+ dropdown_level2,
28
+ openfield_level2,
29
+ dropdown_extra_level2,
30
+ individual,
31
+ )
32
+
33
+
34
  def dropdown_collision(individual):
35
  label = "Collision with a means of transport"
36
+ individual = add_data_to_individual("circumstance", label.lower(), individual)
37
  return create_dropdown_level1(label, individual)
38
 
39
+
40
  def dropdown_deliberate_destruction(individual):
41
  label = "Destruction / Deliberatly removed"
42
+ individual = add_data_to_individual("circumstance", label.lower(), individual)
43
+ return create_dropdown_level1(label, individual)
44
+
45
 
46
+ def dropdown_indirect_destruction(individual):
47
  label = "Indirect destruction"
48
+ individual = add_data_to_individual("circumstance", label.lower(), individual)
49
+ return create_dropdown_level1(label, individual)
50
+
51
 
52
+ def dropdown_natural_cause(individual):
53
  label = "Natural cause"
54
+ individual = add_data_to_individual("circumstance", label.lower(), individual)
55
+ return create_dropdown_level1(label, individual)
56
 
57
 
58
+ # --------------------------------------------------------- LEVEL 2 DROPDOWNS
59
  def get_options(value):
60
+ value = value.lower()
61
+ options_label = None
62
+ options_dropdown = None
63
+ open_field = None
64
+ extras = None
65
+ extras_label = None
66
+ dropdown_config = get_custom_config_dropdowns("config_dropdown_circumstances.json")
67
+ for _, sub_dict in dropdown_config.items():
68
+ nested_dict = sub_dict.get(value)
69
+ if nested_dict is not None:
70
+ if "Options" in nested_dict.keys():
71
+ options_dict = nested_dict["Options"]
72
+ options_label = list(options_dict.keys())[0]
73
+ options_dropdown = list(options_dict.values())[0]
74
+ options_dropdown = [option.title() for option in options_dropdown]
75
+ if "Open" in nested_dict.keys():
76
+ open_field = nested_dict["Open"]
77
+ open_field = open_field.title()
78
+ if "Extra" in nested_dict.keys():
79
+ for key, val in nested_dict["Extra"].items():
80
+ extras_label = key
81
+ extras = val
82
+ extras = [extra.title() for extra in extras]
83
+ return options_label, options_dropdown, open_field, extras, extras_label
84
+
85
+
86
  def on_select(evt: gr.SelectData, individual): # SelectData is a subclass of EventData
87
+ options_label, options_dropdown, open_field, extras, extras_label = get_options(
88
+ evt.value
89
+ )
90
+ individual = add_data_to_individual(
91
+ "circumstance_type",
92
+ {
93
+ "type": (evt.value).lower(),
94
+ "option_dropdown_label": options_label.lower()
95
+ if options_label is not None
96
+ else "NA",
97
+ "open_field_label": open_field.lower() if open_field is not None else "NA",
98
+ "extra_label": extras_label.lower() if extras_label is not None else "NA",
99
+ },
100
+ individual,
101
+ )
102
  if options_dropdown is not None:
103
+ dropdown_level2 = gr.Dropdown(
104
+ choices=options_dropdown, label=evt.value, interactive=True, visible=True
105
+ )
106
+ else:
107
  dropdown_level2 = gr.Dropdown(choices=[], visible=False)
108
 
109
  if open_field is not None:
110
  openfield_level2 = gr.Textbox(label=open_field, interactive=True, visible=True)
111
+ else:
112
  openfield_level2 = gr.Textbox(visible=False)
113
 
114
+ if extras is not None:
115
+ dropdown_extra_level2 = gr.Dropdown(
116
+ choices=extras, label=extras_label, interactive=True, visible=True
117
+ )
118
+ else:
119
  dropdown_extra_level2 = gr.Dropdown(choices=[], visible=False)
120
  return dropdown_level2, openfield_level2, dropdown_extra_level2, individual
121
 
122
+
123
+ def on_select_dropdown_level2(evt: gr.SelectData, individual):
124
+ individual = add_data_to_individual(
125
+ "circumstance_option_dropdown", evt.value.lower(), individual
126
+ )
127
  return individual
128
 
129
+
130
+ def on_select_dropdown_extra_level2(evt: gr.SelectData, individual):
131
+ individual = add_data_to_individual(
132
+ "circumstance_extra", evt.value.lower(), individual
133
+ )
134
+ return individual
135
+
136
+
137
+ def on_change_openfield_level2(openfield_level2_dead, individual):
138
+ # print("Saving open field")
139
+ individual = add_data_to_individual(
140
+ "circumstance_open_field", str(openfield_level2_dead).lower(), individual
141
+ )
142
  return individual
 
 
 
 
 
 
 
app/classes.py CHANGED
@@ -1,5 +1,5 @@
1
  from pydantic import BaseModel, Field
2
- from typing import Optional
3
  import numpy as np
4
  from PIL import Image
5
  import io
@@ -7,24 +7,30 @@ import base64
7
  import uuid
8
 
9
  from behavior.class_behavior import Behaviors
 
10
  from circumstances.class_circumstance import Circumstances
11
  from physical.class_physical import PhysicalAnomalies
 
12
  from follow_up.class_follow_up import FollowUpEvents
13
  from geolocalisation.class_geolocalisation import Geolocalisation
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)
@@ -33,15 +39,16 @@ class ImageBase64(BaseModel):
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
  image_md5: str
43
  geolocalisation: Geolocalisation
44
  wounded_state: str
45
- wounded: Optional[Wounded] = None
46
  dead_state: str
47
- dead: Optional[Dead] = None
 
1
  from pydantic import BaseModel, Field
2
+ from typing import Optional, Union
3
  import numpy as np
4
  from PIL import Image
5
  import io
 
7
  import uuid
8
 
9
  from behavior.class_behavior import Behaviors
10
+ from behavior.class_behavior_simple import BehaviorsSimple
11
  from circumstances.class_circumstance import Circumstances
12
  from physical.class_physical import PhysicalAnomalies
13
+ from physical.class_physical_simple import PhysicalAnomaliesSimple
14
  from follow_up.class_follow_up import FollowUpEvents
15
  from geolocalisation.class_geolocalisation import Geolocalisation
16
 
17
+
18
  class Wounded(BaseModel):
19
  circumstances: Circumstances
20
+ behaviors: Union[Behaviors, BehaviorsSimple]
21
+ physical_anomalies: Union[PhysicalAnomalies, PhysicalAnomaliesSimple]
22
  follow_up_events: FollowUpEvents
23
 
24
+
25
  class Dead(BaseModel):
26
  circumstances: Circumstances
27
+ physical_anomalies: Union[PhysicalAnomalies, PhysicalAnomaliesSimple]
28
  follow_up_events: FollowUpEvents
29
 
30
+
31
  class ImageBase64(BaseModel):
32
+ image: str
33
+
34
  @classmethod
35
  def to_base64(cls, pixel_data: list):
36
  img_array = np.array(pixel_data, dtype=np.uint8)
 
39
  buffer = io.BytesIO()
40
  img.save(buffer, format="PNG")
41
  buffer.seek(0)
42
+ base64_str = base64.b64encode(buffer.read()).decode("utf-8")
43
  return cls(image=base64_str)
44
+
45
+
46
  class Report(BaseModel):
47
  identifier: str
48
  image: ImageBase64
49
  image_md5: str
50
  geolocalisation: Geolocalisation
51
  wounded_state: str
52
+ wounded: Optional[Wounded] = None
53
  dead_state: str
54
+ dead: Optional[Dead] = None
app/contacts.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from dotenv import load_dotenv
4
+ import os
5
+ load_dotenv()
6
+ PATH = os.getcwd() + "/"
7
+ PATH_ASSETS = os.getenv('PATH_ASSETS')
8
+ PATH_ICONS = PATH + PATH_ASSETS + "icons/"
9
+
10
+ contact_text = """
11
+ # Contacts
12
+
13
+ You have an animal alive or dead in front of you and you do not know what to do?
14
+ Please call the contact below corresponding to your canton. (Cantons are in alphabetical order.)
15
+
16
+ ## AG: Aargau
17
+ 117 | Departement Bau, Verkehr und Umwelt, Abteilung Wald, Sektion Jagd und Fischerei
18
+
19
+ ## AI: Appenzell Innerrhoden
20
+ 071 788 92 87 | Bau- und Umweltdepartement, Amt für Umwelt, Fachstelle Jagd und Fischerei
21
+
22
+ ## AR: Appenzell Ausserrhoden
23
+ 079 698 19 16 |Departement Bau und Volkswirtschaft, Amt für Raum und Wald, Abteilung Natur und Wildtiere
24
+
25
+ ## BE: Bern
26
+ 0800 940 100 | Wirtschafts-, Energie- und Umweltdirektion
27
+
28
+ ## BL: Basel-Landschaft
29
+ 061 922 03 66, 061 552 56 59 | Volkswirtschafts- und Gesundheitsdirektion, Amt für Wald und Wild beider Basel
30
+
31
+ ## BS: Basel-Stadt
32
+ 061 922 03 66, 061 552 56 59 | Volkswirtschafts- und Gesundheitsdirektion, Amt für Wald und Wild beider Basel
33
+
34
+ ## FR: Fribourg
35
+ 026 305 23 31 | Energie, Landwirtschaft und Umwelt, Amt für Wald und Natur, Sektion Fauna, Jagd und Fischerei
36
+
37
+ ## GE: Geneva
38
+ 022 388 55 00 | Office cantonal de l'agriculture et de la nature, Centrale d'engagement et des transmissions (CET)
39
+
40
+ ## GL: Glarus
41
+ Verwaltung Bau und Umwelt, Umwelt, Wald und Energie, Jagd und Fischerei
42
+
43
+ ## GR: Graubünden
44
+ 055 645 66 66 | Amt für Jagd und Fischerei Jagdbezirke - Über uns
45
+
46
+ ## JU: Jura
47
+ 032 420 48 00, 032 420 65 65 | Departement de l'environnement, Office de l'environnement, Chasse et protection de la faune sauvage
48
+
49
+ ## LU: Luzern
50
+ 041 248 81 17, 117 | Bau-, Umwelt- und Wirtschaftsdepartement, Landwirtschaft und Wald, Jagd, Wildhut und Jagdaufsicht
51
+
52
+ ## NE: Neuchatel
53
+ 032 889 67 80 | Service de la faune, des fôrets et de la nature, faune
54
+
55
+ ## NW: Nidwalden
56
+ 041 618 44 66 | Justiz- und Sicherheitsdirektion, Amt für Justiz, Abteilung Jagd und Fischerei
57
+
58
+ ## OW: Obwalden
59
+ 041 666 64 76 | Bau- und Raumentwicklungsdepartement, Amt für Wald und Landschaft, Wildtiere und Jagd
60
+
61
+ ## SG: St. Gallen
62
+ 117 | Volkwirtschaftsdepartement, Amt für Natur, Jagd und Fischerei
63
+
64
+ ## SH: Schaffhausen
65
+ 052 632 74 66 | Departement des Innern, Jagd und Fischerei
66
+
67
+ ## SO: Solothurn
68
+ 117| Volkswirtschaftdepartement, Amt für Wald, Jagd und Fischerei
69
+
70
+ ## SZ: Schwyz
71
+ 041 819 29 29 | Umweltdepartement, Amt für Wald und Natur, Jagd und Wildtiere
72
+
73
+ ## TG: Thurgau
74
+ 058 345 61 50 | Jagd- und Fischereiverwaltung
75
+
76
+ ## TI: Ticino
77
+ 091 814 28 71 | Divisione dell'ambiente, Ufficio della caccia e della pesca
78
+
79
+ ## UR: Uri
80
+ 041 875 2316 | Sicherheitsdirektion, Amt für Forst und Jagd
81
+
82
+ ## VD: Vaud
83
+ 021 557 88 55 | Environnement, Biodiversité et paysage, Police Faune-nature
84
+
85
+ ## VS: Valais
86
+ 027 606 70 00, 117 | Umwelt, Energie und Landwirtschaft, Dienststelle für Jagd, Fischerei und Wildtiere
87
+
88
+ ## ZG: Zug
89
+ 041 595 41 41 | Natur, Umwelt und Tiere, Arten, Lebensräume, Wildhut und Fischereiaufsicht
90
+
91
+ """
92
+
93
+
94
+ with gr.Blocks(theme='shivi/calm_seafoam') as contacts:
95
+ with gr.Row(scale = 1):
96
+ gr.Image(PATH_ICONS+"help.png", height=300,
97
+ interactive=False,
98
+ show_fullscreen_button = False, show_share_button=False,
99
+ show_download_button=False, show_label=False)
100
+ gr.Image(PATH_ICONS+"contact-information.png", height=300,
101
+ interactive=False,
102
+ show_fullscreen_button = False, show_share_button=False,
103
+ show_download_button=False, show_label=False)
104
+ gr.Image(PATH_ICONS+"question.png", height=300,
105
+ interactive=False,
106
+ show_fullscreen_button = False, show_share_button=False,
107
+ show_download_button=False, show_label=False)
108
+
109
+ gr.Markdown(contact_text, show_label=False)
110
+
app/{dead.py → dead_wounded/dead.py} RENAMED
@@ -12,7 +12,7 @@ PATH = os.getcwd() + "/"
12
  PATH_ASSETS = os.getenv('PATH_ASSETS')
13
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
14
 
15
- def show_section_dead(visible, individual):
16
  if visible==True:
17
  individual = add_data_to_individual("wounded_state", "No", individual)
18
  individual = add_data_to_individual("dead_state", "Yes", individual)
@@ -33,7 +33,7 @@ def show_section_dead(visible, individual):
33
  with gr.Row():
34
  physical_boxes = create_bird_anatomy(False, "dead")
35
  with gr.Column():
36
- checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts("dead", "None")
37
 
38
  gr.Button("Follow-Up Events",
39
  icon=PATH_ICONS + "schedule.png",
 
12
  PATH_ASSETS = os.getenv('PATH_ASSETS')
13
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
14
 
15
+ def show_section_dead(visible, mode, individual):
16
  if visible==True:
17
  individual = add_data_to_individual("wounded_state", "No", individual)
18
  individual = add_data_to_individual("dead_state", "Yes", individual)
 
33
  with gr.Row():
34
  physical_boxes = create_bird_anatomy(False, "dead")
35
  with gr.Column():
36
+ checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts("dead", mode, "None")
37
 
38
  gr.Button("Follow-Up Events",
39
  icon=PATH_ICONS + "schedule.png",
app/{wounded.py → dead_wounded/wounded.py} RENAMED
@@ -13,7 +13,7 @@ PATH = os.getcwd() + "/"
13
  PATH_ASSETS = os.getenv('PATH_ASSETS')
14
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
15
 
16
- def show_section_wounded(visible, individual):
17
  if visible==True:
18
  individual = add_data_to_individual("wounded_state", "Yes", individual)
19
  individual = add_data_to_individual("dead_state", "No", individual)
@@ -32,7 +32,7 @@ def show_section_wounded(visible, individual):
32
  variant= "primary")
33
  radio_behaviour = gr.Radio(["Yes", "No"], value=None, show_label=False, interactive=True)
34
  with gr.Row():
35
- behavior_checkbox, behavior_text = create_behavior_checkbox("wounded", False)
36
 
37
  gr.Button("Are there physical changes on the animal?",
38
  icon=PATH_ICONS + "cardiogram.png",
@@ -41,7 +41,7 @@ def show_section_wounded(visible, individual):
41
  with gr.Row():
42
  physical_boxes = create_bird_anatomy(False, "wounded")
43
  with gr.Column():
44
- checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts("wounded", "None")
45
 
46
 
47
  gr.Button("Follow-Up Events",
 
13
  PATH_ASSETS = os.getenv('PATH_ASSETS')
14
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
15
 
16
+ def show_section_wounded(visible, mode, individual):
17
  if visible==True:
18
  individual = add_data_to_individual("wounded_state", "Yes", individual)
19
  individual = add_data_to_individual("dead_state", "No", individual)
 
32
  variant= "primary")
33
  radio_behaviour = gr.Radio(["Yes", "No"], value=None, show_label=False, interactive=True)
34
  with gr.Row():
35
+ behavior_checkbox, behavior_text = create_behavior_checkbox("wounded", mode, False)
36
 
37
  gr.Button("Are there physical changes on the animal?",
38
  icon=PATH_ICONS + "cardiogram.png",
 
41
  with gr.Row():
42
  physical_boxes = create_bird_anatomy(False, "wounded")
43
  with gr.Column():
44
+ checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts("wounded", mode, "None")
45
 
46
 
47
  gr.Button("Follow-Up Events",
app/gradio_test/test_selectdata_event.py DELETED
@@ -1,16 +0,0 @@
1
- import gradio as gr
2
-
3
- with gr.Blocks() as demo:
4
- table = gr.Dataframe([[1, 2, 3], [4, 5, 6]])
5
- #gallery = gr.Gallery([("cat.jpg", "Cat"), ("dog.jpg", "Dog")])
6
- textbox = gr.Textbox("Hello World!")
7
- statement = gr.Textbox()
8
-
9
- def on_select(evt: gr.SelectData):
10
- return gr.Textbox(f"You selected {evt.value} at {evt.index} from {evt.target}")
11
-
12
- table.select(on_select, inputs=[table], outputs=[statement])
13
- #gallery.select(on_select, gallery, statement)
14
- textbox.select(on_select, inputs=[textbox], outputs=[statement])
15
-
16
- demo.launch(server_name="0.0.0.0", server_port=3131)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/gradio_test/tests_delete_gallery.py DELETED
@@ -1,25 +0,0 @@
1
- import gradio as gr
2
-
3
- # Example initial image list
4
- images = [
5
- "https://via.placeholder.com/150/0000FF",
6
- "https://via.placeholder.com/150/FF0000",
7
- "https://via.placeholder.com/150/00FF00"
8
- ]
9
-
10
- # Function to remove a selected image from the gallery
11
- def delete_image(selected_image, image_list):
12
- if selected_image in image_list:
13
- image_list.remove(selected_image) # Remove the selected image
14
- return image_list # Return the updated image list
15
-
16
- # Gradio app
17
- with gr.Blocks() as demo:
18
- gallery = gr.Gallery(value=images, label="Gallery") # Gallery of images
19
- selected_image = gr.Dropdown(choices=images, label="Select Image to Delete") # Dropdown for selection
20
- delete_button = gr.Button("Delete Selected Image") # Button to delete
21
-
22
- # When button is clicked, delete the selected image and update gallery
23
- delete_button.click(fn=delete_image, inputs=[selected_image, gallery], outputs=gallery)
24
-
25
- demo.launch(server_name="0.0.0.0", server_port=3232)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/main.py CHANGED
@@ -5,8 +5,12 @@ disable_caching()
5
 
6
  from mode_advanced import advanced
7
  from mode_simple import simple
 
 
8
 
9
- demo = gr.TabbedInterface([simple, advanced], ["Simple Mode" , "Advanced Reporting"],
 
 
10
  theme='shivi/calm_seafoam')
11
 
12
  if __name__ == "__main__":
 
5
 
6
  from mode_advanced import advanced
7
  from mode_simple import simple
8
+ from contacts import contacts
9
+ from about import about
10
 
11
+
12
+ demo = gr.TabbedInterface([simple, advanced, contacts, about],
13
+ ["Simple Mode" , "Advanced Reporting", "Cantonal Contacts", "About"],
14
  theme='shivi/calm_seafoam')
15
 
16
  if __name__ == "__main__":
app/mode_advanced.py CHANGED
@@ -1,24 +1,23 @@
1
  import gradio as gr
2
  from gradio_modal import Modal
 
3
 
4
- from validation_submission.utils_individual import add_data_to_individual
5
- from validation_submission.submission import validate_save_individual
6
- from validation_submission.validation import reset_error_box
7
  from geolocalisation.maps import get_location
8
- from functools import partial
9
- from dead import show_section_dead
10
- from wounded import show_section_wounded
11
  from circumstances.circumstances import show_circumstances
12
  from circumstances.circumstances_dropdowns import *
13
  from physical.physical_select_animal import show_physical, find_bounding_box
14
  from physical.physical_checkbox import on_select_body_part, hide_physical
15
  from behavior.behavior_checkbox import show_behavior, on_select_behavior
16
  from follow_up.followup_events import save_fe
17
- from styling.style import *
18
- from credits import credits_text
19
-
20
- from geolocalisation.js_geolocation import js_geocode, display_location
21
  from validation_submission.utils_individual import generate_random_md5
 
 
 
 
 
22
 
23
  from dotenv import load_dotenv
24
  import os
@@ -28,6 +27,7 @@ PATH_ASSETS = os.getenv('PATH_ASSETS')
28
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
29
 
30
  with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
 
31
  individual = gr.State({})
32
  individual.value = add_data_to_individual("image_md5", generate_random_md5(), individual.value)
33
 
@@ -149,7 +149,7 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
149
  checkbox_beak_dead, text_beak_dead, checkbox_body_dead, text_body_dead, checkbox_feathers_dead, text_feathers_dead, checkbox_head_dead, text_head_dead, checkbox_legs_dead, text_legs_dead, \
150
  fe_collection_dropdown_dead, fe_recepient_dropdown_dead, fe_radio_dropdown_dead, fe_answer_dropdown_dead, \
151
  fe_name_recipient_dead, fe_collection_ref_dead \
152
- = show_section_dead(False, individual)
153
 
154
  section_wounded, individual, radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded, \
155
  button_collision_wounded, button_deliberate_destruction_wounded, button_indirect_destruction_wounded, button_natural_cause_wounded, \
@@ -159,7 +159,7 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
159
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded, \
160
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded, \
161
  fe_name_recipient_wounded, fe_collection_ref_wounded \
162
- = show_section_wounded(False, individual)
163
 
164
  # ---------------------------------------------------------
165
  # ---------------------------------------------------------
@@ -168,7 +168,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
168
  partial_show_section_dead = partial(show_section_dead, True)
169
  partial_hide_section_wounded = partial(show_section_wounded, False)
170
  butt_dead.click(partial_show_section_dead,
171
- inputs=[individual],
 
172
  outputs=[section_dead,
173
  individual,
174
  radio_circumstance_dead, radio_physical_dead,
@@ -181,7 +182,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
181
  ])
182
 
183
  butt_dead.click(partial_hide_section_wounded,
184
- inputs=[individual],
 
185
  outputs=[section_wounded,
186
  individual,
187
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
@@ -200,7 +202,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
200
  partial_hide_section_dead = partial(show_section_dead, False)
201
 
202
  butt_wounded.click(partial_show_section_wounded,
203
- inputs=[individual],
 
204
  outputs=[section_wounded,
205
  individual,
206
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
@@ -214,7 +217,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
214
  ])
215
 
216
  butt_wounded.click(partial_hide_section_dead,
217
- inputs=[individual],
 
218
  outputs=[section_dead,
219
  individual,
220
  radio_circumstance_dead, radio_physical_dead,
@@ -254,7 +258,9 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
254
  # ---------------------------------------------------------
255
  # Radio Physical Dead
256
  radio_physical_dead.change(fn=show_physical,
257
- inputs=[radio_physical_dead, gr.Text("dead", visible=False), individual],
 
 
258
  outputs=[physical_boxes_dead, individual])
259
 
260
  # Checkbox Physical Dead
@@ -299,7 +305,10 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
299
  # ---------------------------------------------------------
300
  # Radio Behavior Wounded
301
  radio_behavior_wounded.change(fn=show_behavior,
302
- inputs=[radio_behavior_wounded, gr.Text("wounded / sick", visible=False), individual],
 
 
 
303
  outputs=[behavior_checkbox, behavior_text, individual])
304
  behavior_checkbox.select(on_select_behavior,
305
  inputs=[behavior_checkbox, individual],
@@ -307,7 +316,9 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
307
  # ---------------------------------------------------------
308
  # Radio Physical Wounded
309
  radio_physical_wounded.change(fn=show_physical,
310
- inputs=[radio_physical_wounded, gr.Text("wounded / sick", visible=False), individual],
 
 
311
  outputs=[physical_boxes_wounded, individual])
312
 
313
  # Checkbox Physical Wounded
@@ -343,10 +354,6 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
343
  fe_name_recipient_dead.input(save_fe, inputs=[fe_name_recipient_dead, gr.Textbox("recipient name", visible=False), individual],outputs=[individual])
344
  fe_collection_ref_dead.input(save_fe, inputs=[fe_collection_ref_dead, gr.Textbox("collection reference", visible=False), individual], outputs=[individual])
345
 
346
- # ---------------------------------------------------------
347
- # Error Box
348
- error_box = gr.Text(value=None, visible=False)
349
-
350
  # ---------------------------------------------------------
351
  # Spacer
352
  with gr.Row(elem_id="centered-row"):
@@ -360,6 +367,14 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
360
  justify-content: center;
361
  }
362
  """
 
 
 
 
 
 
 
 
363
 
364
 
365
  # ---------------------------------------------------------
@@ -389,26 +404,35 @@ with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
389
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded,
390
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded,
391
  fe_name_recipient_wounded, fe_collection_ref_wounded,
392
- error_box
393
  ])
394
- show_creds = gr.Button("CREDITS", icon=PATH_ICONS+"copyright.png", scale=0.5)
395
-
 
 
 
 
 
 
 
 
396
  # ---------------------------------------------------------
397
- # Button Click Logic
398
  button_clear.click()
399
  button_clear.click(hide_physical,
400
- outputs=[checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded])
 
 
 
 
 
401
  button_clear.click(hide_physical,
402
- outputs=[checkbox_beak_dead, text_beak_dead, checkbox_body_dead, text_body_dead, checkbox_feathers_dead, text_feathers_dead, checkbox_head_dead, text_head_dead, checkbox_legs_dead, text_legs_dead])
403
- button_clear.click(reset_error_box, inputs=[error_box], outputs=[error_box])
404
-
405
-
406
- # ---------------------------------------------------------
407
- # VALIDATE ANIMAL
408
- button_df.click(validate_save_individual, inputs=[individual, error_box],
409
- outputs=[error_box])
410
- # ---------------------------------------------------------
411
- #CREDITS
412
- with Modal(visible=False) as modal_creds:
413
- gr.Markdown(credits_text)
414
- show_creds.click(lambda: Modal(visible=True), None, modal_creds)
 
1
  import gradio as gr
2
  from gradio_modal import Modal
3
+ from functools import partial
4
 
5
+ from geolocalisation.js_geolocation import js_geocode, display_location
 
 
6
  from geolocalisation.maps import get_location
7
+ from dead_wounded.dead import show_section_dead
8
+ from dead_wounded.wounded import show_section_wounded
 
9
  from circumstances.circumstances import show_circumstances
10
  from circumstances.circumstances_dropdowns import *
11
  from physical.physical_select_animal import show_physical, find_bounding_box
12
  from physical.physical_checkbox import on_select_body_part, hide_physical
13
  from behavior.behavior_checkbox import show_behavior, on_select_behavior
14
  from follow_up.followup_events import save_fe
 
 
 
 
15
  from validation_submission.utils_individual import generate_random_md5
16
+ from validation_submission.utils_individual import add_data_to_individual
17
+ from validation_submission.submission import validate_save_individual
18
+ from validation_submission.validation import reset_error_box
19
+ from validation_submission.utils_individual import reset_individual
20
+ from styling.style import *
21
 
22
  from dotenv import load_dotenv
23
  import os
 
27
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
28
 
29
  with gr.Blocks(theme='shivi/calm_seafoam') as advanced:
30
+ mode = "advanced"
31
  individual = gr.State({})
32
  individual.value = add_data_to_individual("image_md5", generate_random_md5(), individual.value)
33
 
 
149
  checkbox_beak_dead, text_beak_dead, checkbox_body_dead, text_body_dead, checkbox_feathers_dead, text_feathers_dead, checkbox_head_dead, text_head_dead, checkbox_legs_dead, text_legs_dead, \
150
  fe_collection_dropdown_dead, fe_recepient_dropdown_dead, fe_radio_dropdown_dead, fe_answer_dropdown_dead, \
151
  fe_name_recipient_dead, fe_collection_ref_dead \
152
+ = show_section_dead(False, mode, individual)
153
 
154
  section_wounded, individual, radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded, \
155
  button_collision_wounded, button_deliberate_destruction_wounded, button_indirect_destruction_wounded, button_natural_cause_wounded, \
 
159
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded, \
160
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded, \
161
  fe_name_recipient_wounded, fe_collection_ref_wounded \
162
+ = show_section_wounded(False, mode, individual)
163
 
164
  # ---------------------------------------------------------
165
  # ---------------------------------------------------------
 
168
  partial_show_section_dead = partial(show_section_dead, True)
169
  partial_hide_section_wounded = partial(show_section_wounded, False)
170
  butt_dead.click(partial_show_section_dead,
171
+ inputs=[gr.Text(mode, visible=False),
172
+ individual],
173
  outputs=[section_dead,
174
  individual,
175
  radio_circumstance_dead, radio_physical_dead,
 
182
  ])
183
 
184
  butt_dead.click(partial_hide_section_wounded,
185
+ inputs=[gr.Text(mode, visible=False),
186
+ individual],
187
  outputs=[section_wounded,
188
  individual,
189
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
 
202
  partial_hide_section_dead = partial(show_section_dead, False)
203
 
204
  butt_wounded.click(partial_show_section_wounded,
205
+ inputs=[gr.Text(mode, visible=False),
206
+ individual],
207
  outputs=[section_wounded,
208
  individual,
209
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
 
217
  ])
218
 
219
  butt_wounded.click(partial_hide_section_dead,
220
+ inputs=[gr.Text(mode, visible=False),
221
+ individual],
222
  outputs=[section_dead,
223
  individual,
224
  radio_circumstance_dead, radio_physical_dead,
 
258
  # ---------------------------------------------------------
259
  # Radio Physical Dead
260
  radio_physical_dead.change(fn=show_physical,
261
+ inputs=[radio_physical_dead,
262
+ gr.Text("dead", visible=False),
263
+ individual],
264
  outputs=[physical_boxes_dead, individual])
265
 
266
  # Checkbox Physical Dead
 
305
  # ---------------------------------------------------------
306
  # Radio Behavior Wounded
307
  radio_behavior_wounded.change(fn=show_behavior,
308
+ inputs=[radio_behavior_wounded,
309
+ gr.Text("wounded / sick", visible=False),
310
+ gr.Text(mode, visible=False),
311
+ individual],
312
  outputs=[behavior_checkbox, behavior_text, individual])
313
  behavior_checkbox.select(on_select_behavior,
314
  inputs=[behavior_checkbox, individual],
 
316
  # ---------------------------------------------------------
317
  # Radio Physical Wounded
318
  radio_physical_wounded.change(fn=show_physical,
319
+ inputs=[radio_physical_wounded,
320
+ gr.Text("wounded / sick", visible=False),
321
+ individual],
322
  outputs=[physical_boxes_wounded, individual])
323
 
324
  # Checkbox Physical Wounded
 
354
  fe_name_recipient_dead.input(save_fe, inputs=[fe_name_recipient_dead, gr.Textbox("recipient name", visible=False), individual],outputs=[individual])
355
  fe_collection_ref_dead.input(save_fe, inputs=[fe_collection_ref_dead, gr.Textbox("collection reference", visible=False), individual], outputs=[individual])
356
 
 
 
 
 
357
  # ---------------------------------------------------------
358
  # Spacer
359
  with gr.Row(elem_id="centered-row"):
 
367
  justify-content: center;
368
  }
369
  """
370
+
371
+ # ---------------------------------------------------------
372
+ # Error Box
373
+ with gr.Row():
374
+ error_icon = gr.Image(PATH_ICONS+"chicken.png",
375
+ height=80, width=80, visible=False,
376
+ scale=1)
377
+ error_box = gr.Text(value=None, visible=False, scale=3)
378
 
379
 
380
  # ---------------------------------------------------------
 
404
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded,
405
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded,
406
  fe_name_recipient_wounded, fe_collection_ref_wounded,
407
+ error_icon, error_box
408
  ])
409
+
410
+ # ---------------------------------------------------------
411
+ # VALIDATE ANIMAL
412
+ button_df.click(validate_save_individual,
413
+ inputs=[individual,
414
+ error_icon,
415
+ error_box,
416
+ gr.Text(mode, visible=False)],
417
+ outputs=[error_icon, error_box])
418
+
419
  # ---------------------------------------------------------
420
+ # CLEAR BUTTON
421
  button_clear.click()
422
  button_clear.click(hide_physical,
423
+ inputs =[gr.Text(mode, visible=False)],
424
+ outputs=[checkbox_beak_wounded, text_beak_wounded,
425
+ checkbox_body_wounded, text_body_wounded,
426
+ checkbox_feathers_wounded, text_feathers_wounded,
427
+ checkbox_head_wounded, text_head_wounded,
428
+ checkbox_legs_wounded, text_legs_wounded])
429
  button_clear.click(hide_physical,
430
+ inputs =[gr.Text(mode, visible=False)],
431
+ outputs=[checkbox_beak_dead, text_beak_dead,
432
+ checkbox_body_dead, text_body_dead,
433
+ checkbox_feathers_dead, text_feathers_dead,
434
+ checkbox_head_dead, text_head_dead,
435
+ checkbox_legs_dead, text_legs_dead])
436
+ button_clear.click(reset_error_box, inputs=[error_icon, error_box], outputs=[error_icon, error_box])
437
+ button_clear.click(reset_individual, inputs=[individual], outputs=[individual])
438
+
 
 
 
 
app/mode_simple.py CHANGED
@@ -1,13 +1,10 @@
1
  import gradio as gr
2
  from gradio_modal import Modal
3
-
4
- from validation_submission.utils_individual import add_data_to_individual
5
- from validation_submission.submission import validate_save_individual
6
- from validation_submission.validation import reset_error_box
7
- from geolocalisation.maps import get_location
8
  from functools import partial
9
- from dead import show_section_dead
10
- from wounded import show_section_wounded
 
 
11
  from circumstances.circumstances import show_circumstances
12
  from circumstances.circumstances_dropdowns import *
13
  from physical.physical_select_animal import show_physical, find_bounding_box
@@ -15,9 +12,10 @@ from physical.physical_checkbox import on_select_body_part, hide_physical
15
  from behavior.behavior_checkbox import show_behavior, on_select_behavior
16
  from follow_up.followup_events import save_fe
17
  from styling.style import *
18
- from credits import credits_text
19
-
20
- from geolocalisation.js_geolocation import js_geocode, display_location
 
21
  from validation_submission.utils_individual import generate_random_md5
22
 
23
  from dotenv import load_dotenv
@@ -28,6 +26,7 @@ PATH_ASSETS = os.getenv('PATH_ASSETS')
28
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
29
 
30
  with gr.Blocks(theme='shivi/calm_seafoam') as simple:
 
31
  individual = gr.State({})
32
  individual.value = add_data_to_individual("image_md5", generate_random_md5(), individual.value)
33
 
@@ -128,7 +127,7 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
128
  checkbox_beak_dead, text_beak_dead, checkbox_body_dead, text_body_dead, checkbox_feathers_dead, text_feathers_dead, checkbox_head_dead, text_head_dead, checkbox_legs_dead, text_legs_dead, \
129
  fe_collection_dropdown_dead, fe_recepient_dropdown_dead, fe_radio_dropdown_dead, fe_answer_dropdown_dead, \
130
  fe_name_recipient_dead, fe_collection_ref_dead \
131
- = show_section_dead(False, individual)
132
 
133
  section_wounded, individual, radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded, \
134
  button_collision_wounded, button_deliberate_destruction_wounded, button_indirect_destruction_wounded, button_natural_cause_wounded, \
@@ -138,7 +137,7 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
138
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded, \
139
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded, \
140
  fe_name_recipient_wounded, fe_collection_ref_wounded \
141
- = show_section_wounded(False, individual)
142
 
143
  # ---------------------------------------------------------
144
  # ---------------------------------------------------------
@@ -147,7 +146,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
147
  partial_show_section_dead = partial(show_section_dead, True)
148
  partial_hide_section_wounded = partial(show_section_wounded, False)
149
  butt_dead.click(partial_show_section_dead,
150
- inputs=[individual],
 
151
  outputs=[section_dead,
152
  individual,
153
  radio_circumstance_dead, radio_physical_dead,
@@ -160,7 +160,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
160
  ])
161
 
162
  butt_dead.click(partial_hide_section_wounded,
163
- inputs=[individual],
 
164
  outputs=[section_wounded,
165
  individual,
166
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
@@ -179,7 +180,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
179
  partial_hide_section_dead = partial(show_section_dead, False)
180
 
181
  butt_wounded.click(partial_show_section_wounded,
182
- inputs=[individual],
 
183
  outputs=[section_wounded,
184
  individual,
185
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
@@ -193,7 +195,8 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
193
  ])
194
 
195
  butt_wounded.click(partial_hide_section_dead,
196
- inputs=[individual],
 
197
  outputs=[section_dead,
198
  individual,
199
  radio_circumstance_dead, radio_physical_dead,
@@ -233,12 +236,16 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
233
  # ---------------------------------------------------------
234
  # Radio Physical Dead
235
  radio_physical_dead.change(fn=show_physical,
236
- inputs=[radio_physical_dead, gr.Text("dead", visible=False), individual],
 
 
237
  outputs=[physical_boxes_dead, individual])
238
 
239
  # Checkbox Physical Dead
240
  physical_boxes_dead.select(find_bounding_box,
241
- inputs=[physical_boxes_dead, gr.Textbox(value="dead", visible=False)],
 
 
242
  outputs=[checkbox_beak_dead, text_beak_dead,
243
  checkbox_body_dead, text_body_dead,
244
  checkbox_feathers_dead, text_feathers_dead,
@@ -278,7 +285,10 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
278
  # ---------------------------------------------------------
279
  # Radio Behavior Wounded
280
  radio_behavior_wounded.change(fn=show_behavior,
281
- inputs=[radio_behavior_wounded, gr.Text("wounded / sick", visible=False), individual],
 
 
 
282
  outputs=[behavior_checkbox, behavior_text, individual])
283
  behavior_checkbox.select(on_select_behavior,
284
  inputs=[behavior_checkbox, individual],
@@ -286,12 +296,16 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
286
  # ---------------------------------------------------------
287
  # Radio Physical Wounded
288
  radio_physical_wounded.change(fn=show_physical,
289
- inputs=[radio_physical_wounded, gr.Text("wounded / sick", visible=False), individual],
 
 
290
  outputs=[physical_boxes_wounded, individual])
291
 
292
  # Checkbox Physical Wounded
293
  physical_boxes_wounded.select(find_bounding_box,
294
- inputs=[physical_boxes_wounded, gr.Textbox(value="wounded / sick", visible=False)],
 
 
295
  outputs=[checkbox_beak_wounded, text_beak_wounded,
296
  checkbox_body_wounded, text_body_wounded,
297
  checkbox_feathers_wounded, text_feathers_wounded,
@@ -322,10 +336,6 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
322
  fe_name_recipient_dead.input(save_fe, inputs=[fe_name_recipient_dead, gr.Textbox("recipient name", visible=False), individual],outputs=[individual])
323
  fe_collection_ref_dead.input(save_fe, inputs=[fe_collection_ref_dead, gr.Textbox("collection reference", visible=False), individual], outputs=[individual])
324
 
325
- # ---------------------------------------------------------
326
- # Error Box
327
- error_box = gr.Text(value=None, visible=False)
328
-
329
  # ---------------------------------------------------------
330
  # Spacer
331
  with gr.Row(elem_id="centered-row"):
@@ -340,14 +350,21 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
340
  }
341
  """
342
 
 
 
 
 
 
 
343
  # ---------------------------------------------------------
344
  # Allow clearing of all previous output
345
  with gr.Row():
346
  button_df = gr.Button("SUBMIT OBSERVATION", icon=PATH_ICONS+"effective.png",
347
  scale = 3)
348
- button_clear = gr.ClearButton(value="CLEAR",
349
- scale = 1,
350
- components=[
 
351
  camera,
352
  #dead reset
353
  radio_circumstance_dead, radio_physical_dead,
@@ -366,26 +383,36 @@ with gr.Blocks(theme='shivi/calm_seafoam') as simple:
366
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded,
367
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded,
368
  fe_name_recipient_wounded, fe_collection_ref_wounded,
369
- error_box
370
  ])
371
- show_creds = gr.Button("CREDITS", icon=PATH_ICONS+"copyright.png", scale=0.5)
372
-
373
  # ---------------------------------------------------------
374
- # Button Click Logic
 
 
 
 
 
 
 
 
 
375
  button_clear.click()
376
  button_clear.click(hide_physical,
377
- outputs=[checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded])
 
 
 
 
 
378
  button_clear.click(hide_physical,
379
- outputs=[checkbox_beak_dead, text_beak_dead, checkbox_body_dead, text_body_dead, checkbox_feathers_dead, text_feathers_dead, checkbox_head_dead, text_head_dead, checkbox_legs_dead, text_legs_dead])
380
- button_clear.click(reset_error_box, inputs=[error_box], outputs=[error_box])
381
-
 
 
 
 
 
382
 
383
- # ---------------------------------------------------------
384
- # VALIDATE ANIMAL
385
- button_df.click(validate_save_individual, inputs=[individual, error_box],
386
- outputs=[error_box])
387
- # ---------------------------------------------------------
388
- #CREDITS
389
- with Modal(visible=False) as modal_creds:
390
- gr.Markdown(credits_text)
391
- show_creds.click(lambda: Modal(visible=True), None, modal_creds)
 
1
  import gradio as gr
2
  from gradio_modal import Modal
 
 
 
 
 
3
  from functools import partial
4
+
5
+ from geolocalisation.js_geolocation import js_geocode, display_location
6
+ from dead_wounded.dead import show_section_dead
7
+ from dead_wounded.wounded import show_section_wounded
8
  from circumstances.circumstances import show_circumstances
9
  from circumstances.circumstances_dropdowns import *
10
  from physical.physical_select_animal import show_physical, find_bounding_box
 
12
  from behavior.behavior_checkbox import show_behavior, on_select_behavior
13
  from follow_up.followup_events import save_fe
14
  from styling.style import *
15
+ from validation_submission.utils_individual import reset_individual
16
+ from validation_submission.utils_individual import add_data_to_individual
17
+ from validation_submission.submission import validate_save_individual
18
+ from validation_submission.validation import reset_error_box
19
  from validation_submission.utils_individual import generate_random_md5
20
 
21
  from dotenv import load_dotenv
 
26
  PATH_ICONS = PATH + PATH_ASSETS + "icons/"
27
 
28
  with gr.Blocks(theme='shivi/calm_seafoam') as simple:
29
+ mode = "simple"
30
  individual = gr.State({})
31
  individual.value = add_data_to_individual("image_md5", generate_random_md5(), individual.value)
32
 
 
127
  checkbox_beak_dead, text_beak_dead, checkbox_body_dead, text_body_dead, checkbox_feathers_dead, text_feathers_dead, checkbox_head_dead, text_head_dead, checkbox_legs_dead, text_legs_dead, \
128
  fe_collection_dropdown_dead, fe_recepient_dropdown_dead, fe_radio_dropdown_dead, fe_answer_dropdown_dead, \
129
  fe_name_recipient_dead, fe_collection_ref_dead \
130
+ = show_section_dead(False, mode, individual)
131
 
132
  section_wounded, individual, radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded, \
133
  button_collision_wounded, button_deliberate_destruction_wounded, button_indirect_destruction_wounded, button_natural_cause_wounded, \
 
137
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded, \
138
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded, \
139
  fe_name_recipient_wounded, fe_collection_ref_wounded \
140
+ = show_section_wounded(False, mode, individual)
141
 
142
  # ---------------------------------------------------------
143
  # ---------------------------------------------------------
 
146
  partial_show_section_dead = partial(show_section_dead, True)
147
  partial_hide_section_wounded = partial(show_section_wounded, False)
148
  butt_dead.click(partial_show_section_dead,
149
+ inputs=[gr.Text(mode, visible=False),
150
+ individual],
151
  outputs=[section_dead,
152
  individual,
153
  radio_circumstance_dead, radio_physical_dead,
 
160
  ])
161
 
162
  butt_dead.click(partial_hide_section_wounded,
163
+ inputs=[gr.Text(mode, visible=False),
164
+ individual],
165
  outputs=[section_wounded,
166
  individual,
167
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
 
180
  partial_hide_section_dead = partial(show_section_dead, False)
181
 
182
  butt_wounded.click(partial_show_section_wounded,
183
+ inputs=[gr.Text(mode, visible=False),
184
+ individual],
185
  outputs=[section_wounded,
186
  individual,
187
  radio_circumstance_wounded, radio_behavior_wounded, radio_physical_wounded,
 
195
  ])
196
 
197
  butt_wounded.click(partial_hide_section_dead,
198
+ inputs=[gr.Text(mode, visible=False),
199
+ individual],
200
  outputs=[section_dead,
201
  individual,
202
  radio_circumstance_dead, radio_physical_dead,
 
236
  # ---------------------------------------------------------
237
  # Radio Physical Dead
238
  radio_physical_dead.change(fn=show_physical,
239
+ inputs=[radio_physical_dead,
240
+ gr.Text("dead", visible=False),
241
+ individual],
242
  outputs=[physical_boxes_dead, individual])
243
 
244
  # Checkbox Physical Dead
245
  physical_boxes_dead.select(find_bounding_box,
246
+ inputs=[physical_boxes_dead,
247
+ gr.Textbox(value="dead", visible=False),
248
+ gr.Text(mode, visible=False)],
249
  outputs=[checkbox_beak_dead, text_beak_dead,
250
  checkbox_body_dead, text_body_dead,
251
  checkbox_feathers_dead, text_feathers_dead,
 
285
  # ---------------------------------------------------------
286
  # Radio Behavior Wounded
287
  radio_behavior_wounded.change(fn=show_behavior,
288
+ inputs=[radio_behavior_wounded,
289
+ gr.Text("wounded / sick", visible=False),
290
+ gr.Text(mode, visible=False),
291
+ individual],
292
  outputs=[behavior_checkbox, behavior_text, individual])
293
  behavior_checkbox.select(on_select_behavior,
294
  inputs=[behavior_checkbox, individual],
 
296
  # ---------------------------------------------------------
297
  # Radio Physical Wounded
298
  radio_physical_wounded.change(fn=show_physical,
299
+ inputs=[radio_physical_wounded,
300
+ gr.Text("wounded / sick", visible=False),
301
+ individual],
302
  outputs=[physical_boxes_wounded, individual])
303
 
304
  # Checkbox Physical Wounded
305
  physical_boxes_wounded.select(find_bounding_box,
306
+ inputs=[physical_boxes_wounded,
307
+ gr.Textbox(value="wounded / sick", visible=False),
308
+ gr.Text(mode, visible=False)],
309
  outputs=[checkbox_beak_wounded, text_beak_wounded,
310
  checkbox_body_wounded, text_body_wounded,
311
  checkbox_feathers_wounded, text_feathers_wounded,
 
336
  fe_name_recipient_dead.input(save_fe, inputs=[fe_name_recipient_dead, gr.Textbox("recipient name", visible=False), individual],outputs=[individual])
337
  fe_collection_ref_dead.input(save_fe, inputs=[fe_collection_ref_dead, gr.Textbox("collection reference", visible=False), individual], outputs=[individual])
338
 
 
 
 
 
339
  # ---------------------------------------------------------
340
  # Spacer
341
  with gr.Row(elem_id="centered-row"):
 
350
  }
351
  """
352
 
353
+ # ---------------------------------------------------------
354
+ # Error Box
355
+ with gr.Row():
356
+ error_icon = gr.Image(PATH_ICONS+"chicken.png", height=80, width=80, visible=False, scale=1)
357
+ error_box = gr.Text(value=None, visible=False, scale=4)
358
+
359
  # ---------------------------------------------------------
360
  # Allow clearing of all previous output
361
  with gr.Row():
362
  button_df = gr.Button("SUBMIT OBSERVATION", icon=PATH_ICONS+"effective.png",
363
  scale = 3)
364
+ button_clear = gr.ClearButton(value="CLEAR / Create NEW Observation",
365
+ scale = 2,
366
+ icon = PATH_ICONS+"balai-magique.png",
367
+ components=[
368
  camera,
369
  #dead reset
370
  radio_circumstance_dead, radio_physical_dead,
 
383
  checkbox_beak_wounded, text_beak_wounded, checkbox_body_wounded, text_body_wounded, checkbox_feathers_wounded, text_feathers_wounded, checkbox_head_wounded, text_head_wounded, checkbox_legs_wounded, text_legs_wounded,
384
  fe_collection_dropdown_wounded, fe_recepient_dropdown_wounded, fe_radio_dropdown_wounded, fe_answer_dropdown_wounded,
385
  fe_name_recipient_wounded, fe_collection_ref_wounded,
386
+ error_icon, error_box
387
  ])
388
+
 
389
  # ---------------------------------------------------------
390
+ # VALIDATE & SUBMIT ANIMAL
391
+ button_df.click(validate_save_individual,
392
+ inputs=[individual,
393
+ error_icon,
394
+ error_box,
395
+ gr.Text(mode, visible=False)],
396
+ outputs=[error_icon, error_box])
397
+
398
+ # ---------------------------------------------------------
399
+ # CLEAR BUTTON
400
  button_clear.click()
401
  button_clear.click(hide_physical,
402
+ inputs=[gr.Text(mode, visible=False)],
403
+ outputs=[checkbox_beak_wounded, text_beak_wounded,
404
+ checkbox_body_wounded, text_body_wounded,
405
+ checkbox_feathers_wounded, text_feathers_wounded,
406
+ checkbox_head_wounded, text_head_wounded,
407
+ checkbox_legs_wounded, text_legs_wounded])
408
  button_clear.click(hide_physical,
409
+ inputs=[gr.Text(mode, visible=False)],
410
+ outputs=[checkbox_beak_dead, text_beak_dead,
411
+ checkbox_body_dead, text_body_dead,
412
+ checkbox_feathers_dead, text_feathers_dead,
413
+ checkbox_head_dead, text_head_dead,
414
+ checkbox_legs_dead, text_legs_dead])
415
+ button_clear.click(reset_error_box, inputs=[error_icon, error_box], outputs=[error_icon, error_box])
416
+ button_clear.click(reset_individual, inputs=[individual], outputs=[individual])
417
 
418
+
 
 
 
 
 
 
 
 
app/physical/class_physical_simple.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Literal, List, Union, Optional
3
+
4
+ # Define common anomalies as a Literal type
5
+ CommonAnomaliesSimple = Literal[
6
+ 'injury',
7
+ 'abnormal position'
8
+ ]
9
+
10
+ # --- Beak-related Anomalies ---
11
+ class BeakAnomaly(BaseModel):
12
+ type: Literal['beak']
13
+ anomaly_type: List[Literal[
14
+ 'adhesion/discharge',
15
+ CommonAnomaliesSimple
16
+ ]]
17
+
18
+ # --- Body-related Anomalies ---
19
+ class BodyAnomaly(BaseModel):
20
+ type: Literal['body']
21
+ anomaly_type: List[Literal[
22
+ CommonAnomaliesSimple
23
+ ]]
24
+
25
+ # --- Feathers/Wings/Tail-related Anomalies ---
26
+ class FeathersWingsTailAnomaly(BaseModel):
27
+ type: Literal['feathers/wings/tail']
28
+ anomaly_type: List[Literal[
29
+ 'feather and skin change',
30
+ CommonAnomaliesSimple
31
+ ]]
32
+
33
+ # --- Head-related Anomalies (including eyes) ---
34
+ class HeadAnomaly(BaseModel):
35
+ type: Literal['head incl. eyes']
36
+ anomaly_type: List[Literal[
37
+ 'eye changes',
38
+ CommonAnomaliesSimple
39
+ ]]
40
+
41
+ # --- Legs-related Anomalies ---
42
+ class LegAnomaly(BaseModel):
43
+ type: Literal['legs']
44
+ anomaly_type: List[Literal[
45
+ CommonAnomaliesSimple
46
+ ]]
47
+
48
+ # Union of all possible anomaly types for specific body parts
49
+ AnomalyTypeSimple = Union[
50
+ BeakAnomaly,
51
+ BodyAnomaly,
52
+ LegAnomaly,
53
+ FeathersWingsTailAnomaly,
54
+ HeadAnomaly
55
+ ]
56
+
57
+ # Main PhysicalAnomaly class that logs anomalies across different body parts
58
+ class PhysicalAnomaliesSimple(BaseModel):
59
+ physical_radio: str
60
+ physical_anomalies_type: Optional[List[AnomalyTypeSimple]] = None
app/physical/physical_checkbox.py CHANGED
@@ -1,85 +1,163 @@
1
  import gradio as gr
2
  from utils.utils_config import get_custom_config_dropdowns
3
  from utils.utils_checkbox import create_checkbox
4
- from validation_submission.utils_individual import add_data_to_individual
5
- #---------------------------------------------------------
 
 
6
  def get_body_parts():
7
  dropdown_config = get_custom_config_dropdowns("config_checkbox_physical.json")
8
  return list(dropdown_config.keys())
9
 
 
10
  def retrieve_config_options(label, dropdown_config):
11
  options = list(dropdown_config[label].keys())
12
  options = [option.title() for option in options]
13
  return options
14
 
15
- def get_options_description(value):
 
 
 
 
 
 
 
16
  dropdown_config = get_custom_config_dropdowns("config_checkbox_physical.json")
17
- # get options
18
- options_common = retrieve_config_options("Common", dropdown_config)
19
- options_for_value = retrieve_config_options(value, dropdown_config)
20
- options_common.extend(options_for_value)
21
- options = options_common
22
- # get descriptions
23
- descriptions = []
24
- for key, sub_dict in dropdown_config.items():
25
- if key==value or key=="Common":
26
- for _, option_dict in sub_dict.items():
27
- for description_tag, description in option_dict.items():
28
- if "Description"==description_tag:
29
- descriptions.append(description)
30
- return options, descriptions
31
-
32
- #---------------------------------------------------------
33
- def create_checkbox_beak(section, label_checkbox, visible):
34
- body_part="Beak"
35
- options, descriptions = get_options_description(body_part)
36
- return create_checkbox(body_part, section, label_checkbox, visible, options, descriptions)
37
-
38
- def create_checkbox_body(section, label_checkbox, visible):
39
- body_part="Body"
40
- options, descriptions = get_options_description(body_part)
41
- return create_checkbox(body_part, section, label_checkbox, visible, options, descriptions)
42
-
43
- def create_checkbox_feathers(section, label_checkbox, visible):
44
- body_part="Feathers/Wings/Tail"
45
- options, descriptions = get_options_description(body_part)
46
- return create_checkbox(body_part, section, label_checkbox, visible, options, descriptions)
47
-
48
- def create_checkbox_head(section, label_checkbox, visible):
49
- body_part="Head incl. eyes"
50
- options, descriptions = get_options_description(body_part)
51
- return create_checkbox(body_part, section, label_checkbox, visible, options, descriptions)
52
-
53
- def create_checkbox_legs(section, label_checkbox, visible):
54
- body_part="Legs"
55
- options, descriptions = get_options_description(body_part)
56
- return create_checkbox(body_part, section, label_checkbox, visible, options, descriptions)
57
-
58
- #---------------------------------------------------------
59
- def process_body_parts(section, matched_box):
60
- #take all except "Common"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  body_parts = get_body_parts()
62
  body_parts = body_parts[1:]
63
  label_checkbox = "Physical changes to "
64
- visibles = [True if matched_box==body_part else False for body_part in body_parts ]
65
- checkbox_beak, text_beak = create_checkbox_beak(section, label_checkbox, visibles[0])
66
- checkbox_body, text_body = create_checkbox_body(section, label_checkbox, visibles[1])
67
- checkbox_feathers, text_feathers = create_checkbox_feathers(section, label_checkbox, visibles[2])
68
- checkbox_head, text_head = create_checkbox_head(section, label_checkbox, visibles[3])
69
- checkbox_legs, text_legs = create_checkbox_legs(section, label_checkbox, visibles[4])
70
- return checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs
71
-
72
- #---------------------------------------------------------
73
-
74
- def on_select_body_part(body_part_checkbox, body_part, individual):
75
- individual = add_data_to_individual("wounded_dead", "physical_type_"+body_part.lower(), body_part.lower(), individual)
76
- body_part_checkbox = [body_part_check.lower() for body_part_check in body_part_checkbox]
77
- individual = add_data_to_individual("wounded_dead", "physical_anomaly_"+body_part.lower(), body_part_checkbox, individual)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  return individual
79
 
80
- #---------------------------------------------------------
81
 
82
- def hide_physical():
83
- checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts("wounded", "None")
84
- return checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs
85
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from utils.utils_config import get_custom_config_dropdowns
3
  from utils.utils_checkbox import create_checkbox
4
+ from validation_submission.utils_individual import add_data_to_individual
5
+
6
+
7
+ # ---------------------------------------------------------
8
  def get_body_parts():
9
  dropdown_config = get_custom_config_dropdowns("config_checkbox_physical.json")
10
  return list(dropdown_config.keys())
11
 
12
+
13
  def retrieve_config_options(label, dropdown_config):
14
  options = list(dropdown_config[label].keys())
15
  options = [option.title() for option in options]
16
  return options
17
 
18
+
19
+ def get_options_description(value, mode):
20
+ # print(f"Get options description: {mode}")
21
+ if mode == "simple":
22
+ dropdown_config = get_custom_config_dropdowns(
23
+ "config_checkbox_physical_simple.json"
24
+ )
25
+ elif mode == "advanced":
26
  dropdown_config = get_custom_config_dropdowns("config_checkbox_physical.json")
27
+ # get options
28
+ options_common = retrieve_config_options("Common", dropdown_config)
29
+ options_for_value = retrieve_config_options(value, dropdown_config)
30
+ options_common.extend(options_for_value)
31
+ options = options_common
32
+ # get descriptions
33
+ descriptions = []
34
+ for key, sub_dict in dropdown_config.items():
35
+ if key == value or key == "Common":
36
+ for _, option_dict in sub_dict.items():
37
+ for description_tag, description in option_dict.items():
38
+ if "Description" == description_tag:
39
+ descriptions.append(description)
40
+ return options, descriptions
41
+
42
+
43
+ # ---------------------------------------------------------
44
+ def create_checkbox_beak(section, mode, label_checkbox, visible):
45
+ body_part = "Beak"
46
+ options, descriptions = get_options_description(body_part, mode)
47
+ return create_checkbox(
48
+ body_part, section, label_checkbox, visible, options, descriptions
49
+ )
50
+
51
+
52
+ def create_checkbox_body(section, mode, label_checkbox, visible):
53
+ body_part = "Body"
54
+ options, descriptions = get_options_description(body_part, mode)
55
+ return create_checkbox(
56
+ body_part, section, label_checkbox, visible, options, descriptions
57
+ )
58
+
59
+
60
+ def create_checkbox_feathers(section, mode, label_checkbox, visible):
61
+ body_part = "Feathers/Wings/Tail"
62
+ options, descriptions = get_options_description(body_part, mode)
63
+ return create_checkbox(
64
+ body_part, section, label_checkbox, visible, options, descriptions
65
+ )
66
+
67
+
68
+ def create_checkbox_head(section, mode, label_checkbox, visible):
69
+ body_part = "Head incl. eyes"
70
+ options, descriptions = get_options_description(body_part, mode)
71
+ return create_checkbox(
72
+ body_part, section, label_checkbox, visible, options, descriptions
73
+ )
74
+
75
+
76
+ def create_checkbox_legs(section, mode, label_checkbox, visible):
77
+ body_part = "Legs"
78
+ options, descriptions = get_options_description(body_part, mode)
79
+ return create_checkbox(
80
+ body_part, section, label_checkbox, visible, options, descriptions
81
+ )
82
+
83
+
84
+ # ---------------------------------------------------------
85
+ def process_body_parts(section, mode, matched_box):
86
+ # take all except "Common"
87
  body_parts = get_body_parts()
88
  body_parts = body_parts[1:]
89
  label_checkbox = "Physical changes to "
90
+ visibles = [True if matched_box == body_part else False for body_part in body_parts]
91
+ checkbox_beak, text_beak = create_checkbox_beak(
92
+ section, mode, label_checkbox, visibles[0]
93
+ )
94
+ checkbox_body, text_body = create_checkbox_body(
95
+ section, mode, label_checkbox, visibles[1]
96
+ )
97
+ checkbox_feathers, text_feathers = create_checkbox_feathers(
98
+ section, mode, label_checkbox, visibles[2]
99
+ )
100
+ checkbox_head, text_head = create_checkbox_head(
101
+ section, mode, label_checkbox, visibles[3]
102
+ )
103
+ checkbox_legs, text_legs = create_checkbox_legs(
104
+ section, mode, label_checkbox, visibles[4]
105
+ )
106
+ return (
107
+ checkbox_beak,
108
+ text_beak,
109
+ checkbox_body,
110
+ text_body,
111
+ checkbox_feathers,
112
+ text_feathers,
113
+ checkbox_head,
114
+ text_head,
115
+ checkbox_legs,
116
+ text_legs,
117
+ )
118
+
119
+
120
+ # ---------------------------------------------------------
121
+
122
+
123
+ def on_select_body_part(body_part_checkbox, body_part, individual):
124
+ individual = add_data_to_individual(
125
+ "physical_type_" + body_part.lower(), body_part.lower(), individual
126
+ )
127
+ body_part_checkbox = [
128
+ body_part_check.lower() for body_part_check in body_part_checkbox
129
+ ]
130
+ individual = add_data_to_individual(
131
+ "physical_anomaly_" + body_part.lower(), body_part_checkbox, individual
132
+ )
133
  return individual
134
 
 
135
 
136
+ # ---------------------------------------------------------
137
+
138
+
139
+ def hide_physical(mode):
140
+ (
141
+ checkbox_beak,
142
+ text_beak,
143
+ checkbox_body,
144
+ text_body,
145
+ checkbox_feathers,
146
+ text_feathers,
147
+ checkbox_head,
148
+ text_head,
149
+ checkbox_legs,
150
+ text_legs,
151
+ ) = process_body_parts("wounded", mode, "None")
152
+ return (
153
+ checkbox_beak,
154
+ text_beak,
155
+ checkbox_body,
156
+ text_body,
157
+ checkbox_feathers,
158
+ text_feathers,
159
+ checkbox_head,
160
+ text_head,
161
+ checkbox_legs,
162
+ text_legs,
163
+ )
app/physical/physical_select_animal.py CHANGED
@@ -8,20 +8,22 @@ from physical.physical_checkbox import *
8
  from physical.physical_boxes_define import gdf
9
  from utils.utils_visible import set_visible
10
 
 
 
11
  load_dotenv()
12
  PATH_ASSETS = os.getenv('PATH_ASSETS')
13
 
14
  # Function to find the matching bounding box for a given point and return the image with boxes
15
- def find_bounding_box(evt: gr.SelectData, img, section: str):
16
  x, y = evt.index[0], evt.index[1]
17
  point = Point(x, y)
18
  match = gdf[gdf.contains(point)]
19
  if not match.empty:
20
  matched_box = match.iloc[0]['name']
21
- checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts(section, matched_box)
22
  else:
23
  matched_box = None
24
- checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts(section, matched_box)
25
  return checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs
26
 
27
 
@@ -39,7 +41,7 @@ def create_bird_anatomy(visible, section: str):
39
  def show_physical(choice, section: str, individual):
40
  visible = set_visible(choice)
41
  physical_boxes = create_bird_anatomy(visible, section)
42
- individual = add_data_tmp("wounded_dead", "physical_radio", choice, individual)
43
  return physical_boxes, individual
44
 
45
 
 
8
  from physical.physical_boxes_define import gdf
9
  from utils.utils_visible import set_visible
10
 
11
+ from validation_submission.utils_individual import add_data_to_individual
12
+
13
  load_dotenv()
14
  PATH_ASSETS = os.getenv('PATH_ASSETS')
15
 
16
  # Function to find the matching bounding box for a given point and return the image with boxes
17
+ def find_bounding_box(evt: gr.SelectData, img, section: str, mode: str):
18
  x, y = evt.index[0], evt.index[1]
19
  point = Point(x, y)
20
  match = gdf[gdf.contains(point)]
21
  if not match.empty:
22
  matched_box = match.iloc[0]['name']
23
+ checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts(section, mode, matched_box)
24
  else:
25
  matched_box = None
26
+ checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs = process_body_parts(section, mode, matched_box)
27
  return checkbox_beak, text_beak, checkbox_body, text_body, checkbox_feathers, text_feathers, checkbox_head, text_head, checkbox_legs, text_legs
28
 
29
 
 
41
  def show_physical(choice, section: str, individual):
42
  visible = set_visible(choice)
43
  physical_boxes = create_bird_anatomy(visible, section)
44
+ individual = add_data_to_individual("physical_radio", choice, individual)
45
  return physical_boxes, individual
46
 
47
 
app/styling/theme.py CHANGED
@@ -1,7 +1,13 @@
1
- import gradio as gr
 
 
 
 
 
 
 
2
 
3
- css= ""
4
- css_old = """
5
  .gradio-container {background: url(https://openclipart.org/image/2000px/279687)}
6
 
7
  /* Main background */
@@ -84,22 +90,3 @@ body {
84
  #error {background-color: #e82323}
85
  #valid {background-color: #07e63f}
86
  """
87
-
88
- # OLD THEME:
89
-
90
- # css = """
91
- # .gradio-container {background: url(https://openclipart.org/image/2000px/279687)}
92
- # #image {background-color: #73b9ae}
93
- # #dead {background-color: #333333}
94
- # #wounded {background-color: #5e0724}
95
- # #bird-boxes {background-color: #f2f3f3}
96
- # #buttons-conditions {background-color: #b3b3b3}
97
- # #dropdown-conditions {background-color: #b3b3b3}
98
- # #followup {background-color: #38241c}
99
- # #submit {background-color: #abb2bf}
100
- # #error {background-color: #e82323}
101
- # #valid {background-color: #07e63f}
102
- # """
103
-
104
- # theme = gr.themes.Soft(primary_hue="teal", secondary_hue="teal", neutral_hue="emerald",
105
- # font=[gr.themes.GoogleFont("Inconsolata"), "Arial", "sans-serif"])
 
1
+ css_for_error_box= """
2
+ #error {background-color: #e82323 !important;
3
+ color: white !important;
4
+ font-weight: bold;}
5
+ #valid {background-color: #07e63f !important;
6
+ color: white !important;
7
+ font-weight: bold;}
8
+ """
9
 
10
+ css_alternative = """
 
11
  .gradio-container {background: url(https://openclipart.org/image/2000px/279687)}
12
 
13
  /* Main background */
 
90
  #error {background-color: #e82323}
91
  #valid {background-color: #07e63f}
92
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/validation_submission/processing.py CHANGED
@@ -1,45 +1,60 @@
1
  #### PROCESS FUNCTIONS
2
 
3
  def process_circumstance(data):
4
-
5
  fields_to_check = ["option_dropdown", "open_field", "extra"]
6
- if data["circumstance_radio"] == "Yes":
 
 
 
 
 
 
7
  for field in fields_to_check:
8
- if data["circumstance_type"][field+"_label"] == "NA":
9
- data["circumstance_type"].pop(field+"_label")
10
- else :
11
  val = data[f"circumstance_{field}"]
12
  key = data["circumstance_type"][field+"_label"]
13
- data["circumstance_type"][key] = val
14
- data["circumstance_type"].pop(field+"_label")
15
- return data
 
 
 
16
 
17
  def process_behaviors(data):
18
  behaviors =[]
19
- if data["behaviors_radio"] == "Yes":
 
 
20
  for type in data["behaviors_type"]:
21
  new_behavior = {}
22
  new_behavior["type"] = type
23
  behaviors.append(new_behavior)
24
- data["behaviors_type"] = behaviors
25
- return data
 
 
 
26
 
27
  def process_physical(data):
28
  body_parts= ["beak", "body", "legs", "feathers/wings/tail", "head incl. eyes"]
 
29
  anomalies=[]
30
  reformatted = {}
31
- reformatted["physical_radio"] = data["physical_radio"]
32
- if data["physical_radio"] == "Yes":
33
- for body_part in body_parts:
34
  anomaly = {}
35
  for key, val in data.items():
36
  if "type_"+ body_part in key:
37
- anomaly["type"] = body_part
38
  elif "anomaly_"+ body_part in key:
39
  anomaly["anomaly_type"] = val
40
  if anomaly:
41
  anomalies.append(anomaly)
42
  reformatted["physical_anomalies_type"] = anomalies
 
 
 
43
  return reformatted
44
 
45
  def process_followup(data):
 
1
  #### PROCESS FUNCTIONS
2
 
3
  def process_circumstance(data):
 
4
  fields_to_check = ["option_dropdown", "open_field", "extra"]
5
+ reformatted ={}
6
+ if ("circumstance_radio" in data.keys()) and ("circumstance" in data.keys()) and ("circumstance_type" in data.keys()) and (data["circumstance_radio"] == "Yes"):
7
+ reformatted["circumstance_radio"] = data["circumstance_radio"]
8
+ reformatted["circumstance"] = data["circumstance"]
9
+ reformatted["circumstance_type"] = {}
10
+ if "type" in data["circumstance_type"]:
11
+ reformatted["circumstance_type"]["type"] = data["circumstance_type"]["type"]
12
  for field in fields_to_check:
13
+ if not data["circumstance_type"][field+"_label"] == "NA":
 
 
14
  val = data[f"circumstance_{field}"]
15
  key = data["circumstance_type"][field+"_label"]
16
+ reformatted["circumstance_type"][key] = val
17
+ else:
18
+ reformatted["circumstance_radio"] = None
19
+ reformatted["circumstance"] = None
20
+ reformatted["circumstance_type"] = {}
21
+ return reformatted
22
 
23
  def process_behaviors(data):
24
  behaviors =[]
25
+ reformatted = {}
26
+ if ("behaviors_radio" in data.keys()) and ("behaviors_type" in data.keys()) and (data["behaviors_radio"] == "Yes"):
27
+ reformatted["behaviors_radio"] = data["behaviors_radio"]
28
  for type in data["behaviors_type"]:
29
  new_behavior = {}
30
  new_behavior["type"] = type
31
  behaviors.append(new_behavior)
32
+ reformatted["behaviors_type"] = behaviors
33
+ else:
34
+ reformatted["behaviors_radio"] = None
35
+ reformatted["behaviors_type"] = []
36
+ return reformatted
37
 
38
  def process_physical(data):
39
  body_parts= ["beak", "body", "legs", "feathers/wings/tail", "head incl. eyes"]
40
+ body_parts_search = ["beak", "body", "legs", "feathers", "head"]
41
  anomalies=[]
42
  reformatted = {}
43
+ if ("physical_radio" in data.keys()) and (data["physical_radio"] == "Yes") and any("type_" in key for key in data.keys()) and any("anomaly_" in key for key in data.keys()):
44
+ reformatted["physical_radio"] = data["physical_radio"]
45
+ for b, body_part in enumerate(body_parts_search):
46
  anomaly = {}
47
  for key, val in data.items():
48
  if "type_"+ body_part in key:
49
+ anomaly["type"] = body_parts[b]
50
  elif "anomaly_"+ body_part in key:
51
  anomaly["anomaly_type"] = val
52
  if anomaly:
53
  anomalies.append(anomaly)
54
  reformatted["physical_anomalies_type"] = anomalies
55
+ else:
56
+ reformatted["physical_radio"] = None
57
+ reformatted["physical_anomalies_type"] = []
58
  return reformatted
59
 
60
  def process_followup(data):
app/validation_submission/submission.py CHANGED
@@ -1,27 +1,31 @@
1
- import json
2
  from validation_submission.validation import validate_individual
3
-
4
  from huggingface_hub import HfApi
5
  import os
6
 
7
- def validate_save_individual(data, error_box):
8
- individual, error_box = validate_individual(data, error_box)
 
9
  if individual:
 
10
  push_to_dataset_hf(individual.model_dump())
11
- return error_box
12
-
 
13
  def push_to_dataset_hf(individual):
14
  token = os.environ.get("HF_TOKEN", None)
15
  api = HfApi(token=token)
16
  import tempfile
 
17
  f = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
18
  json.dump(individual, f)
19
  f.flush()
20
  f.close()
21
- path_in_repo= f"data/{individual['image_md5']}.json"
22
  api.upload_file(
23
  path_or_fileobj=f.name,
24
  path_in_repo=path_in_repo,
25
  repo_id="SDSC/digiwild-dataset",
26
  repo_type="dataset",
27
- )
 
1
+ import json
2
  from validation_submission.validation import validate_individual
3
+ import gradio as gr
4
  from huggingface_hub import HfApi
5
  import os
6
 
7
+
8
+ def validate_save_individual(data, error_icon, error_box, mode):
9
+ individual, error_icon, error_box = validate_individual(data, error_icon, error_box, mode)
10
  if individual:
11
+ print("pushing to hugging face")
12
  push_to_dataset_hf(individual.model_dump())
13
+ return error_icon, error_box
14
+
15
+
16
  def push_to_dataset_hf(individual):
17
  token = os.environ.get("HF_TOKEN", None)
18
  api = HfApi(token=token)
19
  import tempfile
20
+
21
  f = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
22
  json.dump(individual, f)
23
  f.flush()
24
  f.close()
25
+ path_in_repo = f"data/{individual['image_md5']}.json"
26
  api.upload_file(
27
  path_or_fileobj=f.name,
28
  path_in_repo=path_in_repo,
29
  repo_id="SDSC/digiwild-dataset",
30
  repo_type="dataset",
31
+ )
app/validation_submission/utils_individual.py CHANGED
@@ -1,6 +1,7 @@
1
  import random
2
  import string
3
  import hashlib
 
4
 
5
  def generate_random_md5():
6
  # Generate a random string
@@ -12,4 +13,8 @@ def generate_random_md5():
12
  def add_data_to_individual(key, value, individual):
13
  individual[key] = value
14
  return individual
 
 
 
 
15
 
 
1
  import random
2
  import string
3
  import hashlib
4
+ import gradio as gr
5
 
6
  def generate_random_md5():
7
  # Generate a random string
 
13
  def add_data_to_individual(key, value, individual):
14
  individual[key] = value
15
  return individual
16
+
17
+ def reset_individual(individual):
18
+ individual = gr.State({})
19
+ return individual
20
 
app/validation_submission/validation.py CHANGED
@@ -5,25 +5,41 @@ import gradio as gr
5
  # from validation_submission.get_json import get_json_tmp, get_json_one_individual
6
  from circumstances.class_circumstance import Circumstances
7
  from behavior.class_behavior import Behaviors
 
8
  from physical.class_physical import PhysicalAnomalies
 
9
  from follow_up.class_follow_up import FollowUpEvents
10
  from classes import Report, Wounded, Dead, ImageBase64
11
- from validation_submission.processing import process_circumstance, process_behaviors, process_physical, process_followup
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  def get_fields(data_dict, keyword):
14
- extract = {}
15
  for key, val in data_dict.items():
16
  if keyword in key:
17
  extract[key] = val
18
  return extract
19
 
20
- def validate_individual(data, error_box):
21
- error_box = reset_error_box(error_box)
22
- #data = get_json_one_individual() # TODO: This should change
 
23
  data["identifier"] = str(uuid.uuid4())
24
  if "image" in data.keys():
25
  img = ImageBase64.to_base64(data["image"])
26
- else:
27
  img = None
28
  if "geolocalisation" in data.keys():
29
  geolocalisation = data["geolocalisation"]
@@ -31,71 +47,117 @@ def validate_individual(data, error_box):
31
  geolocalisation = None
32
 
33
  error_behavior = None
34
- error_circumstance = None
35
- error_followup = None
36
- error_physical = None
37
  error_individual = None
38
  if "wounded_state" not in data or "dead_state" not in data:
39
  data["wounded_state"] = "No"
40
  data["dead_state"] = "No"
41
  if (data["wounded_state"] == "Yes") or (data["dead_state"] == "Yes"):
42
- data_wounded_dead = data #get_json_tmp("wounded_dead")
43
  circumstance, error_circumstance = validate_circumstance(data_wounded_dead)
44
- physical, error_physical = validate_physical(data_wounded_dead)
45
  followup, error_followup = validate_follow_up(data_wounded_dead)
46
 
47
- if data["wounded_state"]=="Yes":
48
- print(physical)
49
- behavior, error_behavior = validate_behavior(data_wounded_dead)
50
- try :
51
- individual = Report(identifier = data["identifier"],
52
- image = img,
53
- image_md5 = data["image_md5"],
54
- geolocalisation = geolocalisation,
55
- wounded_state = data["wounded_state"],
56
- wounded = Wounded(circumstances = circumstance,
57
- behaviors = behavior,
58
- physical_anomalies = physical,
59
- follow_up_events = followup),
60
- dead_state = data["dead_state"])
 
 
 
61
  except ValidationError as e:
62
- print(e)
 
63
  error_individual = e
64
 
65
- elif data["dead_state"]=="Yes":
66
- try:
67
- individual = Report(identifier = data["identifier"],
68
- image = img,
69
- image_md5 = data["image_md5"],
70
- geolocalisation = geolocalisation,
71
- wounded_state = data["wounded_state"],
72
- dead_state = data["dead_state"],
73
- dead = Dead(circumstances = circumstance,
74
- physical_anomalies = physical,
75
- follow_up_events = followup)
76
- )
 
 
 
77
  except ValidationError as e:
78
- print(e)
 
79
  error_individual = e
80
  else:
81
- try:
82
- individual = Report(identifier = data["identifier"],
83
- image = img,
84
- image_md5 = data["image_md5"],
85
- geolocalisation = geolocalisation,
86
- wounded_state = data["wounded_state"],
87
- dead_state = data["dead_state"])
 
 
88
  except ValidationError as e:
89
- print(e)
90
  error_individual = e
91
- if error_behavior or error_circumstance or error_followup or error_physical or error_individual:
92
- error_box = show_error(error_box, error_behavior, error_circumstance, error_followup, error_physical, error_individual)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  individual = None
94
- else:
95
- error_box= gr.Text(label="ALL VALID.", value="Record Registered. You can return to the Display.", visible=True, elem_id="valid")
96
- return individual, error_box
 
 
 
 
 
 
 
 
 
 
97
 
98
- def show_error(error_box, error_behavior, error_circumstance, error_followup, error_physical, error_individual):
 
 
 
 
 
 
 
 
99
  error_text = ""
100
  if error_circumstance:
101
  error_text += f"Error in circumstance: {error_circumstance}\n"
@@ -108,69 +170,79 @@ def show_error(error_box, error_behavior, error_circumstance, error_followup, er
108
  if error_individual:
109
  error_text += f"Error in individual: {error_individual}\n"
110
  error_text += "PLEASE CORRECT THESE ERRORS BEFORE SUBMITTING."
111
- error_box= gr.Text(label="ERROR DETECTED !", value=error_text, visible=True, elem_id="error")
 
 
112
  return error_box
113
 
114
- def reset_error_box(error_box):
 
 
115
  error_box = gr.Text(value=None, visible=False)
116
- return error_box
 
117
 
118
  #### VALIDATION FUNCTIONS
119
- def validate_circumstance(data):
120
  circumstance_raw = get_fields(data, "circumstance")
121
  circumstance_formatted = process_circumstance(circumstance_raw)
122
- try:
123
  Circumstances.model_validate(circumstance_formatted)
124
  circumstances = Circumstances(**circumstance_formatted)
125
  error = None
126
  except ValidationError as e:
127
  error = e
128
- print(e)
129
  circumstances = None
130
  return circumstances, error
131
-
132
 
133
- def validate_behavior(data):
 
134
  behaviors_raw = get_fields(data, "behaviors")
135
  behaviors_formatted = process_behaviors(behaviors_raw)
136
  try:
137
- Behaviors.model_validate(behaviors_formatted)
138
- behavior = Behaviors(**behaviors_formatted)
 
 
 
 
139
  error = None
140
  except ValidationError as e:
141
- print(e)
142
- print("Validation failed for the behaviors.")
143
  behavior = None
144
  error = e
145
  return behavior, error
146
-
147
 
148
- def validate_physical(data):
 
149
  physical_raw = get_fields(data, "physical")
150
  physical_formatted = process_physical(physical_raw)
151
- try:
152
- PhysicalAnomalies.model_validate(physical_formatted)
153
- physical = PhysicalAnomalies(**physical_formatted)
 
 
 
 
 
154
  error = None
155
  except ValidationError as e:
156
- print(e)
157
- print("Validation failed for the physical anomalies.")
158
  physical = None
159
  error = e
160
  return physical, error
161
-
 
162
  def validate_follow_up(data):
163
  followup_raw = get_fields(data, "followup")
164
  followup_formatted = process_followup(followup_raw)
165
- try:
166
  FollowUpEvents.model_validate(followup_formatted)
167
  followup = FollowUpEvents(**followup_formatted)
168
  error = None
169
  except ValidationError as e:
170
- print(e)
171
- print("Validation failed for the follow-up events.")
172
  followup = None
173
  return followup, error
174
-
175
-
176
-
 
5
  # from validation_submission.get_json import get_json_tmp, get_json_one_individual
6
  from circumstances.class_circumstance import Circumstances
7
  from behavior.class_behavior import Behaviors
8
+ from behavior.class_behavior_simple import BehaviorsSimple
9
  from physical.class_physical import PhysicalAnomalies
10
+ from physical.class_physical_simple import PhysicalAnomaliesSimple
11
  from follow_up.class_follow_up import FollowUpEvents
12
  from classes import Report, Wounded, Dead, ImageBase64
13
+ from validation_submission.processing import (
14
+ process_circumstance,
15
+ process_behaviors,
16
+ process_physical,
17
+ process_followup,
18
+ )
19
+
20
+ from dotenv import load_dotenv
21
+ import os
22
+ load_dotenv()
23
+ PATH = os.getcwd() + "/"
24
+ PATH_ASSETS = os.getenv('PATH_ASSETS')
25
+ PATH_ICONS = PATH + PATH_ASSETS + "icons/"
26
+
27
 
28
  def get_fields(data_dict, keyword):
29
+ extract = {}
30
  for key, val in data_dict.items():
31
  if keyword in key:
32
  extract[key] = val
33
  return extract
34
 
35
+
36
+ def validate_individual(data, error_icon, error_box, mode: str):
37
+ error_icon, error_box = reset_error_box(error_icon, error_box)
38
+ # data = get_json_one_individual() # TODO: This should change
39
  data["identifier"] = str(uuid.uuid4())
40
  if "image" in data.keys():
41
  img = ImageBase64.to_base64(data["image"])
42
+ else:
43
  img = None
44
  if "geolocalisation" in data.keys():
45
  geolocalisation = data["geolocalisation"]
 
47
  geolocalisation = None
48
 
49
  error_behavior = None
50
+ error_circumstance = None
51
+ error_followup = None
52
+ error_physical = None
53
  error_individual = None
54
  if "wounded_state" not in data or "dead_state" not in data:
55
  data["wounded_state"] = "No"
56
  data["dead_state"] = "No"
57
  if (data["wounded_state"] == "Yes") or (data["dead_state"] == "Yes"):
58
+ data_wounded_dead = data
59
  circumstance, error_circumstance = validate_circumstance(data_wounded_dead)
60
+ physical, error_physical = validate_physical(data_wounded_dead, mode)
61
  followup, error_followup = validate_follow_up(data_wounded_dead)
62
 
63
+ if data["wounded_state"] == "Yes":
64
+ behavior, error_behavior = validate_behavior(data_wounded_dead, mode)
65
+ try:
66
+ individual = Report(
67
+ identifier=data["identifier"],
68
+ image=img,
69
+ image_md5=data["image_md5"],
70
+ geolocalisation=geolocalisation,
71
+ wounded_state=data["wounded_state"],
72
+ wounded=Wounded(
73
+ circumstances=circumstance,
74
+ behaviors=behavior,
75
+ physical_anomalies=physical,
76
+ follow_up_events=followup,
77
+ ),
78
+ dead_state=data["dead_state"],
79
+ )
80
  except ValidationError as e:
81
+ print("Error in wounded_state:")
82
+ print(e.json())
83
  error_individual = e
84
 
85
+ elif data["dead_state"] == "Yes":
86
+ try:
87
+ individual = Report(
88
+ identifier=data["identifier"],
89
+ image=img,
90
+ image_md5=data["image_md5"],
91
+ geolocalisation=geolocalisation,
92
+ wounded_state=data["wounded_state"],
93
+ dead_state=data["dead_state"],
94
+ dead=Dead(
95
+ circumstances=circumstance,
96
+ physical_anomalies=physical,
97
+ follow_up_events=followup,
98
+ ),
99
+ )
100
  except ValidationError as e:
101
+ print("Error in dead_state:")
102
+ print(e.json())
103
  error_individual = e
104
  else:
105
+ try:
106
+ individual = Report(
107
+ identifier=data["identifier"],
108
+ image=img,
109
+ image_md5=data["image_md5"],
110
+ geolocalisation=geolocalisation,
111
+ wounded_state=data["wounded_state"],
112
+ dead_state=data["dead_state"],
113
+ )
114
  except ValidationError as e:
115
+ print(f"""Error in individual else: {e}""")
116
  error_individual = e
117
+ if (
118
+ error_behavior
119
+ or error_circumstance
120
+ or error_followup
121
+ or error_physical
122
+ or error_individual
123
+ ):
124
+ error_icon = gr.Image(PATH_ICONS+"supprimer.png", height=80, width=80,
125
+ interactive=False,
126
+ show_fullscreen_button = False, show_share_button=False,
127
+ show_download_button=False, show_label=False,
128
+ visible=True)
129
+ error_box = show_error(
130
+ error_box,
131
+ error_behavior,
132
+ error_circumstance,
133
+ error_followup,
134
+ error_physical,
135
+ error_individual,
136
+ )
137
  individual = None
138
+ else:
139
+ error_icon = gr.Image(PATH_ICONS+"correct.png", height=80, width=80,
140
+ interactive=False,
141
+ show_fullscreen_button = False, show_share_button=False,
142
+ show_download_button=False, show_label=False,
143
+ visible=True)
144
+ error_box = gr.Text(
145
+ label="ALL VALID.",
146
+ value="Record Registered. Remember to press the CLEAR button before submitting a new record.",
147
+ visible=True,
148
+ elem_id="valid",
149
+ )
150
+ return individual, error_icon, error_box
151
 
152
+
153
+ def show_error(
154
+ error_box,
155
+ error_behavior,
156
+ error_circumstance,
157
+ error_followup,
158
+ error_physical,
159
+ error_individual,
160
+ ):
161
  error_text = ""
162
  if error_circumstance:
163
  error_text += f"Error in circumstance: {error_circumstance}\n"
 
170
  if error_individual:
171
  error_text += f"Error in individual: {error_individual}\n"
172
  error_text += "PLEASE CORRECT THESE ERRORS BEFORE SUBMITTING."
173
+ error_box = gr.Text(
174
+ label="ERROR DETECTED !", value=error_text, visible=True, elem_id="error"
175
+ )
176
  return error_box
177
 
178
+
179
+ def reset_error_box(error_icon, error_box):
180
+ error_icon = gr.Image(PATH_ICONS+"supprimer.png", height=80, width=80, visible=False)
181
  error_box = gr.Text(value=None, visible=False)
182
+ return error_icon, error_box
183
+
184
 
185
  #### VALIDATION FUNCTIONS
186
+ def validate_circumstance(data):
187
  circumstance_raw = get_fields(data, "circumstance")
188
  circumstance_formatted = process_circumstance(circumstance_raw)
189
+ try:
190
  Circumstances.model_validate(circumstance_formatted)
191
  circumstances = Circumstances(**circumstance_formatted)
192
  error = None
193
  except ValidationError as e:
194
  error = e
195
+ print(f"""Error in Validate_circumstance: {e}""")
196
  circumstances = None
197
  return circumstances, error
 
198
 
199
+
200
+ def validate_behavior(data, mode):
201
  behaviors_raw = get_fields(data, "behaviors")
202
  behaviors_formatted = process_behaviors(behaviors_raw)
203
  try:
204
+ if mode == "simple":
205
+ BehaviorsSimple.model_validate(behaviors_formatted)
206
+ behavior = BehaviorsSimple(**behaviors_formatted)
207
+ elif mode == "advanced":
208
+ Behaviors.model_validate(behaviors_formatted)
209
+ behavior = Behaviors(**behaviors_formatted)
210
  error = None
211
  except ValidationError as e:
212
+ print(f"""Error in behaviors validation: {e}""")
 
213
  behavior = None
214
  error = e
215
  return behavior, error
 
216
 
217
+
218
+ def validate_physical(data, mode):
219
  physical_raw = get_fields(data, "physical")
220
  physical_formatted = process_physical(physical_raw)
221
+ # print(physical_formatted)
222
+ try:
223
+ if mode == "simple":
224
+ PhysicalAnomaliesSimple.model_validate(physical_formatted)
225
+ physical = PhysicalAnomaliesSimple(**physical_formatted)
226
+ elif mode == "advanced":
227
+ PhysicalAnomalies.model_validate(physical_formatted)
228
+ physical = PhysicalAnomalies(**physical_formatted)
229
  error = None
230
  except ValidationError as e:
231
+ print(f"""Error in physical_anomalies validation: {e}""")
 
232
  physical = None
233
  error = e
234
  return physical, error
235
+
236
+
237
  def validate_follow_up(data):
238
  followup_raw = get_fields(data, "followup")
239
  followup_formatted = process_followup(followup_raw)
240
+ try:
241
  FollowUpEvents.model_validate(followup_formatted)
242
  followup = FollowUpEvents(**followup_formatted)
243
  error = None
244
  except ValidationError as e:
245
+ print(f"""Error in follow-up events validation: {e}""")
246
+
247
  followup = None
248
  return followup, error