vancauwe commited on
Commit
f8bf7d4
·
1 Parent(s): f5800be

feat: sync github with huggingface

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .github/workflows/push_to_hf.yml +20 -0
  2. .gitignore +3 -0
  3. README.md +39 -2
  4. app.py +1 -0
  5. basic_map/app.py +21 -0
  6. basic_map/app1.py +42 -0
  7. basic_map/requirements.txt +4 -0
  8. call_models/alps_map.py +171 -0
  9. call_models/click_map.py +18 -0
  10. call_models/d_entry.py +108 -0
  11. call_models/entry_and_hotdog.py +304 -0
  12. call_models/fix_tabrender.py +69 -0
  13. call_models/hotdogs.py +24 -0
  14. call_models/images/references/640x427-atlantic-white-sided-dolphin.jpg +0 -0
  15. call_models/images/references/640x427-long-finned-pilot-whale.webp +0 -0
  16. call_models/images/references/640x427-southern-right-whale.jpg +0 -0
  17. call_models/images/references/Humpback.webp +0 -0
  18. call_models/images/references/Whale_Short-Finned_Pilot-markedDW.png +0 -0
  19. call_models/images/references/beluga.webp +0 -0
  20. call_models/images/references/blue-whale.webp +0 -0
  21. call_models/images/references/bottlenose_dolphin.webp +0 -0
  22. call_models/images/references/brydes.webp +0 -0
  23. call_models/images/references/common_dolphin.webp +0 -0
  24. call_models/images/references/cuviers_beaked_whale.webp +0 -0
  25. call_models/images/references/false-killer-whale.webp +0 -0
  26. call_models/images/references/fin-whale.webp +0 -0
  27. call_models/images/references/gray-whale.webp +0 -0
  28. call_models/images/references/killer_whale.webp +0 -0
  29. call_models/images/references/melon.webp +0 -0
  30. call_models/images/references/minke-whale.webp +0 -0
  31. call_models/images/references/pantropical-spotted-dolphin.webp +0 -0
  32. call_models/images/references/pygmy-killer-whale.webp +0 -0
  33. call_models/images/references/rough-toothed-dolphin.webp +0 -0
  34. call_models/images/references/sei.webp +0 -0
  35. call_models/images/references/spinner.webp +0 -0
  36. call_models/imgs/cakes.jpg +0 -0
  37. call_models/input_handling.py +184 -0
  38. call_models/obs_map.py +163 -0
  39. call_models/requirements.txt +17 -0
  40. call_models/st_logs.py +128 -0
  41. call_models/test_upload.py +49 -0
  42. call_models/whale_gallery.py +89 -0
  43. call_models/whale_viewer.py +145 -0
  44. git +0 -0
  45. images/references/640x427-atlantic-white-sided-dolphin.jpg +0 -0
  46. images/references/640x427-long-finned-pilot-whale.webp +0 -0
  47. images/references/640x427-southern-right-whale.jpg +0 -0
  48. images/references/Humpback.webp +0 -0
  49. images/references/Whale_Short-Finned_Pilot-markedDW.png +0 -0
  50. images/references/beluga.webp +0 -0
.github/workflows/push_to_hf.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ # to run this workflow manually from the Actions tab
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ sync-to-hub:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+ with:
14
+ fetch-depth: 0
15
+ lfs: true
16
+ - name: Push to hub
17
+ env:
18
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
19
+ HF_USERNAME: ${{secrets.HF_USERNAME}}
20
+ run: git push --force https://$HF_USERNAME:[email protected]/spaces/Saving-Willy/saving-willy-space main
.gitignore CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  # Byte-compiled / optimized / DLL files
2
  __pycache__/
3
  *.py[cod]
 
1
+ # OS Related
2
+ .DS_Store
3
+
4
  # Byte-compiled / optimized / DLL files
5
  __pycache__/
6
  *.py[cod]
README.md CHANGED
@@ -1,2 +1,39 @@
1
- # saving-willy
2
- Research Data Infrastructure for cetacean identification
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Saving Willy
3
+ emoji: 👀
4
+ colorFrom: yellow
5
+ colorTo: red
6
+ sdk: streamlit
7
+ sdk_version: 1.39.0
8
+ app_file: call_models/entry_and_hotdog.py
9
+ pinned: false
10
+ license: apache-2.0
11
+ short_description: 'SDSC Hackathon - Project 10. '
12
+ ---
13
+
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+
16
+ app.py is the one and only app
17
+
18
+
19
+ ## Install
20
+
21
+ ```
22
+ git clone [email protected]:spaces/Saving-Willy/saving-willy-space
23
+
24
+ pip install -r requirements.txt
25
+ ```
26
+
27
+ ```
28
+ streamlit run app.py
29
+ ```
30
+
31
+
32
+ ## Test data
33
+
34
+ https://www.kaggle.com/competitions/happy-whale-and-dolphin/data
35
+
36
+
37
+
38
+
39
+ Have a lot of fun!
app.py ADDED
@@ -0,0 +1 @@
 
 
1
+ call_models/entry_and_hotdog.py
basic_map/app.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import folium
4
+
5
+ from streamlit_folium import st_folium
6
+ from streamlit_folium import folium_static
7
+
8
+
9
+ visp_loc = 46.295833, 7.883333
10
+ #m = folium.Map(location=visp_loc, zoom_start=9)
11
+
12
+
13
+ st.markdown("# :whale: :whale: Cetaceans :red[& friends] :balloon:")
14
+
15
+ m = folium.Map(location=visp_loc, zoom_start=9,
16
+ tiles='https://tile.opentopomap.org/{z}/{x}/{y}.png',
17
+ attr='<a href="https://opentopomap.org/">Open Topo Map</a>')
18
+
19
+ folium_static(m)
20
+
21
+
basic_map/app1.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # lets try using map stuff without folium, maybe stlite doesnt support that.
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+
6
+ # Load data
7
+ f = 'mountains_clr.csv'
8
+ df = pd.read_csv(f).dropna()
9
+
10
+ print(df)
11
+
12
+ st.markdown("# :whale: :whale: Cetaceans :red[& friends] :balloon:")
13
+
14
+ st.markdown("## :mountain: Mountains")
15
+ st.markdown(f"library version: **{st.__version__}**")
16
+ # not sure where my versions are getting pegged from, but we have a 1y spread :(
17
+ # https://github.com/streamlit/streamlit/blob/1.24.1/lib/streamlit/elements/map.py
18
+ # rather hard to find the docs for old versions, no selector unlike many libraries.
19
+
20
+ visp_loc = 46.295833, 7.883333
21
+ tile_xyz = 'https://tile.opentopomap.org/{z}/{x}/{y}.png'
22
+ tile_attr = '<a href="https://opentopomap.org/">Open Topo Map</a>'
23
+ st.map(df, latitude='lat', longitude='lon', color='color', size='size', zoom=7)
24
+ #, tiles=tile_xyz, attr=tile_attr)
25
+
26
+ #st.map(df)
27
+
28
+ #st.map(df, latitude="col1", longitude="col2", size="col3", color="col4")
29
+
30
+ import numpy as np
31
+
32
+ df2 = pd.DataFrame(
33
+ {
34
+ "col1": np.random.randn(1000) / 50 + 37.76,
35
+ "col2": np.random.randn(1000) / 50 + -122.4,
36
+ "col3": np.random.randn(1000) * 100,
37
+ "col4": np.random.rand(1000, 4).tolist(),
38
+ }
39
+ )
40
+ #st.map(df, latitude="col1", longitude="col2", size="col3", color="col4")
41
+
42
+
basic_map/requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ streamlit
2
+ folium
3
+ streamlit-folium
4
+
call_models/alps_map.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import folium
4
+ from streamlit_folium import st_folium
5
+
6
+ _map_data = {
7
+ 'name': {
8
+ 0: 'matterhorn',
9
+ 1: 'zinalrothorn',
10
+ 2: 'alphubel',
11
+ 3: 'allalinhorn',
12
+ 4: 'weissmies',
13
+ 5: 'lagginhorn',
14
+ 6: 'lenzspitze',
15
+ 10: 'strahlhorn',
16
+ 11: 'parrotspitze'},
17
+ 'lat': {
18
+ 0: 45.9764263,
19
+ 1: 46.0648271,
20
+ 2: 46.0628767,
21
+ 3: 46.0460858,
22
+ 4: 46.127633,
23
+ 5: 46.1570635,
24
+ 6: 46.1045505,
25
+ 10: 46.0131498,
26
+ 11: 45.9197881},
27
+ 'lon': {
28
+ 0: 7.6586024,
29
+ 1: 7.6901238,
30
+ 2: 7.8638549,
31
+ 3: 7.8945842,
32
+ 4: 8.0120569,
33
+ 5: 8.0031044,
34
+ 6: 7.8686568,
35
+ 10: 7.9021703,
36
+ 11: 7.8710552},
37
+ 'height': {
38
+ 0: 4181.0,
39
+ 1: 3944.0,
40
+ 2: 4174.0,
41
+ 3: 3940.0,
42
+ 4: 3983.0,
43
+ 5: 3916.0,
44
+ 6: 4255.0,
45
+ 10: 4072.0,
46
+ 11: 4419.0},
47
+ 'color': {
48
+ 0: '#aa0000',
49
+ 1: '#aa0000',
50
+ 2: '#aa0000',
51
+ 3: '#aa0000',
52
+ 4: '#aa0000',
53
+ 5: '#aa0000',
54
+ 6: '#aa0000',
55
+ 10: '#00aa00',
56
+ 11: '#aa0000'},
57
+ 'size': {0: 30, 1: 30, 2: 30, 3: 30, 4: 30, 5: 30, 6: 30, 10: 500, 11: 30}
58
+ }
59
+
60
+ tile_sets = [
61
+ 'Open Street Map',
62
+ #'Stamen Terrain',
63
+ #'Stamen Toner',
64
+ 'Esri Ocean',
65
+ 'Esri Images',
66
+ 'Stamen Watercolor',
67
+ 'CartoDB Positron',
68
+ #'CartoDB Dark_Matter'
69
+ ]
70
+
71
+ def create_map(tile_name, location, zoom_start: int = 7):
72
+ # https://xyzservices.readthedocs.io/en/stable/gallery.html
73
+ # get teh attribtuions from here once we pick the 2-3-4 options
74
+ # make esri ocean the default
75
+ m = folium.Map(location=location, zoom_start=zoom_start,
76
+ tiles='Esri.OceanBasemap', attr="Esri")
77
+ #m = folium.Map(location=location, zoom_start=zoom_start)
78
+
79
+ attr = ""
80
+ if tile_name == 'Open Street Map':
81
+ folium.TileLayer('openstreetmap').add_to(m)
82
+ pass
83
+
84
+ #Esri.OceanBasemap
85
+ elif tile_name == 'Esri Ocean':
86
+ pass # made this one default ()
87
+ #attr = "Esri"
88
+ #folium.TileLayer('Esri.OceanBasemap', attr=attr).add_to(m)
89
+
90
+ elif tile_name == 'Esri Images':
91
+ attr = "Esri &mdash; Source: Esri, i-cubed, USDA"
92
+ #folium.TileLayer('stamenterrain', attr=attr).add_to(m)
93
+ folium.TileLayer('Esri.WorldImagery', attr=attr).add_to(m)
94
+ elif tile_name == 'Stamen Toner':
95
+ attr = "Stamen"
96
+ folium.TileLayer('stamentoner', attr=attr).add_to(m)
97
+ elif tile_name == 'Stamen Watercolor':
98
+ attr = "Stamen"
99
+ folium.TileLayer('Stadia.StamenWatercolor', attr=attr).add_to(m)
100
+ elif tile_name == 'CartoDB Positron':
101
+ folium.TileLayer('cartodb positron').add_to(m)
102
+ elif tile_name == 'CartoDB Dark_Matter':
103
+ folium.TileLayer('cartodb dark_matter').add_to(m)
104
+
105
+ #folium.LayerControl().add_to(m)
106
+ return m
107
+
108
+
109
+ def present_alps_map():
110
+ '''show a map of the alps with peaks (from the event's teamnames) marked
111
+
112
+ there are two rendering modes:
113
+ a) basic - this uses a streamlit map, which doesn't offer much flexibility on
114
+ the tiles, but if you supply a dataframe then you just tell it the columns to
115
+ use for lat, lon, color, size of points
116
+
117
+ b) advanced - this uses folium, which allows for more control over the tiles,
118
+ but sadly it seems much less flexible for the point markers.
119
+
120
+ '''
121
+
122
+ st.markdown("# :whale: :whale: Cetaceans :red[& friends] :balloon:")
123
+ show_points = st.toggle("Show Points", False)
124
+ basic_map = st.toggle("Use Basic Map", False)
125
+
126
+ visp_loc = 46.295833, 7.883333 # position of town nearby to the peaks
127
+ # (maybe zermatt or Taesch better? all the mountains seem on valais gauche)
128
+ _df = pd.DataFrame(_map_data)
129
+ if basic_map:
130
+ # render using streamlit map element
131
+ st.map(_df, latitude='lat', longitude='lon', color='color', size='size', zoom=7)
132
+ else:
133
+ # setup a dropdown to pick tiles, and render with folium
134
+ selected_tile = st.selectbox("Choose a tile set", tile_sets)
135
+ #st.info(f"Selected tile: {selected_tile}")
136
+ # don't get why the default selection doesn't get renderd.
137
+ # generate a layer
138
+ map_ = create_map(selected_tile, visp_loc)
139
+ # and render it
140
+ #tile_xyz = 'https://tile.opentopomap.org/{z}/{x}/{y}.png'
141
+ #tile_attr = '<a href="https://opentopomap.org/">Open Topo Map</a>'
142
+
143
+ if show_points:
144
+ folium.Marker(
145
+ location=visp_loc,
146
+ popup="Visp",
147
+ tooltip="Visp",
148
+ icon=folium.Icon(color='blue', icon='info-sign')
149
+ ).add_to(map_)
150
+
151
+ for i, row in _df.iterrows():
152
+ c = 'red'
153
+ if row['name'] == 'strahlhorn':
154
+ c = 'green'
155
+ kw = {"prefix": "fa", "color": c, "icon": "mountain-sun"}
156
+ folium.Marker(
157
+ location=[row['lat'], row['lon']],
158
+ popup=f"{row['name']} ({row['height']} m)",
159
+ tooltip=row['name'],
160
+ icon=folium.Icon(**kw)
161
+ ).add_to(map_)
162
+ #st.info(f"Added marker for {row['name']} {row['lat']} {row['lon']}")
163
+
164
+
165
+ #folium_static(map_)
166
+ st_data = st_folium(map_, width=725)
167
+
168
+ # maybe solution for click => new marker
169
+ # https://discuss.streamlit.io/t/add-marker-after-clicking-on-map/69472
170
+ return st_data
171
+
call_models/click_map.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import folium
2
+ import streamlit as st
3
+
4
+ from streamlit_folium import st_folium
5
+
6
+ # center on Liberty Bell, add marker
7
+ m = folium.Map(location=[39.949610, -75.150282], zoom_start=16)
8
+ folium.Marker(
9
+ [39.949610, -75.150282], popup="Liberty Bell", tooltip="Liberty Bell"
10
+ ).add_to(m)
11
+
12
+ # call to render Folium map in Streamlit
13
+ st_data = st_folium(m, width=725)
14
+
15
+ if st_data['last_clicked'] is not None:
16
+ print(st_data)
17
+ st.info(st_data['last_clicked'])
18
+
call_models/d_entry.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+ import datetime
4
+ import re
5
+ #import os
6
+ import json
7
+
8
+ import hashlib
9
+
10
+
11
+ allowed_image_types = ['webp']
12
+ #allowed_image_types = ['jpg', 'jpeg', 'png', 'webp']
13
+
14
+
15
+ # Function to validate email address
16
+ def is_valid_email(email):
17
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
18
+ return re.match(pattern, email) is not None
19
+
20
+ # Function to extract date and time from image metadata
21
+ def get_image_datetime(image_file):
22
+ try:
23
+ from PIL import ExifTags
24
+ image = Image.open(image_file)
25
+ exif_data = image._getexif()
26
+ if exif_data is not None:
27
+ for tag, value in exif_data.items():
28
+ if ExifTags.TAGS.get(tag) == 'DateTimeOriginal':
29
+ return value
30
+ except Exception as e:
31
+ st.warning("Could not extract date from image metadata.")
32
+ return None
33
+
34
+ # Streamlit app
35
+ st.sidebar.title("Input Form")
36
+
37
+ # 1. Image Selector
38
+ uploaded_filename = st.sidebar.file_uploader("Upload an image", type=allowed_image_types)
39
+ image_datetime = None # For storing date-time from image
40
+
41
+ if uploaded_filename is not None:
42
+ # Display the uploaded image
43
+ image = Image.open(uploaded_filename)
44
+ st.sidebar.image(image, caption='Uploaded Image.', use_column_width=True)
45
+
46
+ # Extract and display image date-time
47
+ image_datetime = get_image_datetime(uploaded_filename)
48
+ print(f"[D] image date extracted as {image_datetime}")
49
+
50
+ metadata = {
51
+ "latitude": 23.5,
52
+ "longitude": 44,
53
+ "author_email": "[email protected]",
54
+ "date": None,
55
+ "time": None,
56
+ }
57
+
58
+ # 2. Latitude Entry Box
59
+ latitude = st.sidebar.text_input("Latitude", metadata.get('latitude', ""))
60
+ # 3. Longitude Entry Box
61
+ longitude = st.sidebar.text_input("Longitude", metadata.get('longitude', ""))
62
+ # 4. Author Box with Email Address Validator
63
+ author_email = st.sidebar.text_input("Author Email", metadata.get('author_email', ""))
64
+
65
+ if author_email and not is_valid_email(author_email):
66
+ st.sidebar.error("Please enter a valid email address.")
67
+
68
+
69
+
70
+
71
+ # 5. date/time
72
+ ## first from image metadata
73
+ if image_datetime is not None:
74
+ time_value = datetime.datetime.strptime(image_datetime, '%Y:%m:%d %H:%M:%S').time()
75
+ date_value = datetime.datetime.strptime(image_datetime, '%Y:%m:%d %H:%M:%S').date()
76
+ else:
77
+ time_value = datetime.datetime.now().time() # Default to current time
78
+ date_value = datetime.datetime.now().date()
79
+
80
+ ## if not, give user the option to enter manually
81
+ date_option = st.sidebar.date_input("Date", value=date_value)
82
+ time_option = st.sidebar.time_input("Time", time_value)
83
+
84
+
85
+
86
+ # Display submitted data
87
+ if st.sidebar.button("Upload"):
88
+ # create a dictionary with the submitted data
89
+ submitted_data = {
90
+ "latitude": latitude,
91
+ "longitude": longitude,
92
+ "author_email": author_email,
93
+ "date": str(date_option),
94
+ "time": str(time_option),
95
+ "predicted_class": None,
96
+ "image_filename": uploaded_filename.name if uploaded_filename else None,
97
+ "image_md5": hashlib.md5(uploaded_filename.read()).hexdigest() if uploaded_filename else None,
98
+
99
+ }
100
+
101
+ st.write("Submitted Data:")
102
+ st.write(f"Latitude: {submitted_data['latitude']}")
103
+ st.write(f"Longitude: {submitted_data['longitude']}")
104
+ st.write(f"Author Email: {submitted_data['author_email']}")
105
+ st.write(f"Date: {submitted_data['date']}")
106
+ st.write(f"Time: {submitted_data['time']}")
107
+
108
+ st.write(f"full dict of data: {json.dumps(submitted_data)}")
call_models/entry_and_hotdog.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import os
3
+ import json
4
+ import logging
5
+ import tempfile
6
+ import pandas as pd
7
+ import streamlit as st
8
+ import folium
9
+ from streamlit_folium import st_folium
10
+ from huggingface_hub import HfApi
11
+ #from datasets import load_dataset
12
+ #from fix_tabrender import js_show_zeroheight_iframe
13
+
14
+ import whale_viewer as sw_wv
15
+ import input_handling as sw_inp
16
+ import alps_map as sw_am
17
+ import whale_gallery as sw_wg
18
+ import obs_map as sw_map
19
+ import st_logs as sw_logs
20
+
21
+
22
+
23
+ from transformers import pipeline
24
+ from transformers import AutoModelForImageClassification
25
+
26
+ # setup for the ML model on huggingface (our wrapper)
27
+ os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
28
+ # and the dataset of observations (hf dataset in our space)
29
+ dataset_id = "Saving-Willy/Happywhale-kaggle"
30
+ data_files = "data/train-00000-of-00001.parquet"
31
+
32
+ USE_BASIC_MAP = False
33
+ DEV_SIDEBAR_LIB = True
34
+
35
+ # get a global var for logger accessor in this module
36
+ LOG_LEVEL = logging.DEBUG
37
+ g_logger = logging.getLogger(__name__)
38
+ g_logger.setLevel(LOG_LEVEL)
39
+
40
+ st.set_page_config(layout="wide")
41
+ #sw_logs.setup_logging(level=LOG_LEVEL, buffer_len=40)
42
+
43
+
44
+
45
+ # initialise various session state variables
46
+ if "handler" not in st.session_state:
47
+ st.session_state['handler'] = sw_logs.setup_logging()
48
+
49
+ if "full_data" not in st.session_state:
50
+ st.session_state.full_data = {}
51
+
52
+ if "classify_whale_done" not in st.session_state:
53
+ st.session_state.classify_whale_done = False
54
+
55
+ if "whale_prediction1" not in st.session_state:
56
+ st.session_state.whale_prediction1 = None
57
+
58
+ if "image" not in st.session_state:
59
+ st.session_state.image = None
60
+
61
+ if "tab_log" not in st.session_state:
62
+ st.session_state.tab_log = None
63
+
64
+
65
+ def metadata2md():
66
+ markdown_str = "\n"
67
+ for key, value in st.session_state.full_data.items():
68
+ markdown_str += f"- **{key}**: {value}\n"
69
+ return markdown_str
70
+
71
+
72
+ def push_observation(tab_log=None):
73
+ # we get the data from session state: 1 is the dict 2 is the image.
74
+ # first, lets do an info display (popup)
75
+ metadata_str = json.dumps(st.session_state.full_data)
76
+
77
+ st.toast(f"Uploading observation: {metadata_str}", icon="🦭")
78
+ tab_log = st.session_state.tab_log
79
+ if tab_log is not None:
80
+ tab_log.info(f"Uploading observation: {metadata_str}")
81
+
82
+ # get huggingface api
83
+ api = HfApi()
84
+
85
+ f = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
86
+ f.write(metadata_str)
87
+ f.close()
88
+ st.info(f"temp file: {f.name} with metadata written...")
89
+
90
+ path_in_repo= f"metadata/{st.session_state.full_data['author_email']}/{st.session_state.full_data['image_md5']}.json"
91
+ msg = f"fname: {f.name} | path: {path_in_repo}"
92
+ print(msg)
93
+ st.warning(msg)
94
+ rv = api.upload_file(
95
+ path_or_fileobj=f.name,
96
+ path_in_repo=path_in_repo,
97
+ repo_id="Saving-Willy/Happywhale-kaggle",
98
+ repo_type="dataset",
99
+ )
100
+ print(rv)
101
+ msg = f"data attempted tx to repo happy walrus: {rv}"
102
+ g_logger.info(msg)
103
+ st.info(msg)
104
+
105
+
106
+ if __name__ == "__main__":
107
+
108
+ g_logger.info("App started.")
109
+
110
+ #g_logger.debug("debug message")
111
+ #g_logger.info("info message")
112
+ #g_logger.warning("warning message")
113
+
114
+ # Streamlit app
115
+ #tab_gallery, tab_inference, tab_hotdogs, tab_map, tab_data, tab_log = st.tabs(["Cetecean classifier", "Hotdog classifier", "Map", "Data", "Log", "Beautiful cetaceans"])
116
+ tab_inference, tab_hotdogs, tab_map, tab_data, tab_log, tab_gallery = st.tabs(["Cetecean classifier", "Hotdog classifier", "Map", "Data", "Log", "Beautiful cetaceans"])
117
+ st.session_state.tab_log = tab_log
118
+
119
+
120
+ # create a sidebar, and parse all the input (returned as `observation` object)
121
+ observation = sw_inp.setup_input(viewcontainer=st.sidebar)
122
+
123
+
124
+ if 0:## WIP
125
+ # goal of this code is to allow the user to override the ML prediction, before transmitting an observation
126
+ predicted_class = st.sidebar.selectbox("Predicted Class", sw_wv.WHALE_CLASSES)
127
+ override_prediction = st.sidebar.checkbox("Override Prediction")
128
+
129
+ if override_prediction:
130
+ overridden_class = st.sidebar.selectbox("Override Class", sw_wv.WHALE_CLASSES)
131
+ st.session_state.full_data['class_overriden'] = overridden_class
132
+ else:
133
+ st.session_state.full_data['class_overriden'] = None
134
+
135
+
136
+ with tab_map:
137
+ # visual structure: a couple of toggles at the top, then the map inlcuding a
138
+ # dropdown for tileset selection.
139
+ tab_map_ui_cols = st.columns(2)
140
+ with tab_map_ui_cols[0]:
141
+ show_db_points = st.toggle("Show Points from DB", True)
142
+ with tab_map_ui_cols[1]:
143
+ dbg_show_extra = st.toggle("Show Extra points (test)", False)
144
+
145
+ if show_db_points:
146
+ # show a nicer map, observations marked, tileset selectable.
147
+ st_data = sw_map.present_obs_map(
148
+ dataset_id=dataset_id, data_files=data_files,
149
+ dbg_show_extra=dbg_show_extra)
150
+
151
+ else:
152
+ # development map.
153
+ st_data = sw_am.present_alps_map()
154
+
155
+
156
+ with tab_log:
157
+ handler = st.session_state['handler']
158
+ if handler is not None:
159
+ records = sw_logs.parse_log_buffer(handler.buffer)
160
+ st.dataframe(records[::-1], use_container_width=True,)
161
+ st.info(f"Length of records: {len(records)}")
162
+ else:
163
+ st.error("⚠️ No log handler found!")
164
+
165
+
166
+
167
+ with tab_data:
168
+ # the goal of this tab is to allow selection of the new obsvation's location by map click/adjust.
169
+ st.markdown("Coming later hope! :construction:")
170
+
171
+ st.write("Click on the map to capture a location.")
172
+ #m = folium.Map(location=visp_loc, zoom_start=7)
173
+ mm = folium.Map(location=[39.949610, -75.150282], zoom_start=16)
174
+ folium.Marker( [39.949610, -75.150282], popup="Liberty Bell", tooltip="Liberty Bell"
175
+ ).add_to(mm)
176
+
177
+ st_data2 = st_folium(mm, width=725)
178
+ st.write("below the map...")
179
+ if st_data2['last_clicked'] is not None:
180
+ print(st_data2)
181
+ st.info(st_data2['last_clicked'])
182
+
183
+
184
+ with tab_gallery:
185
+ # here we make a container to allow filtering css properties
186
+ # specific to the gallery (otherwise we get side effects)
187
+ tg_cont = st.container(key="swgallery")
188
+ with tg_cont:
189
+ sw_wg.render_whale_gallery(n_cols=4)
190
+
191
+
192
+ # Display submitted data
193
+ if st.sidebar.button("Validate"):
194
+ # create a dictionary with the submitted data
195
+ submitted_data = observation.to_dict()
196
+ #print(submitted_data)
197
+
198
+ #full_data.update(**submitted_data)
199
+ for k, v in submitted_data.items():
200
+ st.session_state.full_data[k] = v
201
+
202
+ #st.write(f"full dict of data: {json.dumps(submitted_data)}")
203
+ #tab_inference.info(f"{st.session_state.full_data}")
204
+ tab_log.info(f"{st.session_state.full_data}")
205
+
206
+ df = pd.DataFrame(submitted_data, index=[0])
207
+ with tab_data:
208
+ st.table(df)
209
+
210
+
211
+
212
+
213
+ # inside the inference tab, on button press we call the model (on huggingface hub)
214
+ # which will be run locally.
215
+ # - the model predicts the top 3 most likely species from the input image
216
+ # - these species are shown
217
+ # - the user can override the species prediction using the dropdown
218
+ # - an observation is uploaded if the user chooses.
219
+
220
+ if tab_inference.button("Identify with cetacean classifier"):
221
+ #pipe = pipeline("image-classification", model="Saving-Willy/cetacean-classifier", trust_remote_code=True)
222
+ cetacean_classifier = AutoModelForImageClassification.from_pretrained("Saving-Willy/cetacean-classifier",
223
+ revision='0f9c15e2db4d64e7f622ade518854b488d8d35e6', trust_remote_code=True)
224
+
225
+ if st.session_state.image is None:
226
+ # TODO: cleaner design to disable the button until data input done?
227
+ st.info("Please upload an image first.")
228
+ else:
229
+ # run classifier model on `image`, and persistently store the output
230
+ out = cetacean_classifier(st.session_state.image) # get top 3 matches
231
+ st.session_state.whale_prediction1 = out['predictions'][0]
232
+ st.session_state.classify_whale_done = True
233
+ msg = f"[D]2 classify_whale_done: {st.session_state.classify_whale_done}, whale_prediction1: {st.session_state.whale_prediction1}"
234
+ st.info(msg)
235
+ g_logger.info(msg)
236
+
237
+ # dropdown for selecting/overriding the species prediction
238
+ #st.info(f"[D] classify_whale_done: {st.session_state.classify_whale_done}, whale_prediction1: {st.session_state.whale_prediction1}")
239
+ if not st.session_state.classify_whale_done:
240
+ selected_class = tab_inference.sidebar.selectbox("Species", sw_wv.WHALE_CLASSES, index=None, placeholder="Species not yet identified...", disabled=True)
241
+ else:
242
+ pred1 = st.session_state.whale_prediction1
243
+ # get index of pred1 from WHALE_CLASSES, none if not present
244
+ print(f"[D] pred1: {pred1}")
245
+ ix = sw_wv.WHALE_CLASSES.index(pred1) if pred1 in sw_wv.WHALE_CLASSES else None
246
+ selected_class = tab_inference.selectbox("Species", sw_wv.WHALE_CLASSES, index=ix)
247
+
248
+ st.session_state.full_data['predicted_class'] = selected_class
249
+ if selected_class != st.session_state.whale_prediction1:
250
+ st.session_state.full_data['class_overriden'] = selected_class
251
+
252
+ btn = st.button("Upload observation to THE INTERNET!", on_click=push_observation)
253
+ # TODO: the metadata only fills properly if `validate` was clicked.
254
+ tab_inference.markdown(metadata2md())
255
+
256
+ msg = f"[D] full data after inference: {st.session_state.full_data}"
257
+ g_logger.debug(msg)
258
+ print(msg)
259
+ # TODO: add a link to more info on the model, next to the button.
260
+
261
+ whale_classes = out['predictions'][:]
262
+ # render images for the top 3 (that is what the model api returns)
263
+ with tab_inference:
264
+ st.markdown("## Species detected")
265
+ for i in range(len(whale_classes)):
266
+ sw_wv.display_whale(whale_classes, i)
267
+
268
+
269
+
270
+
271
+ # inside the hotdog tab, on button press we call a 2nd model (totally unrelated at present, just for demo
272
+ # purposes, an hotdog image classifier) which will be run locally.
273
+ # - this model predicts if the image is a hotdog or not, and returns probabilities
274
+ # - the input image is the same as for the ceteacean classifier - defined in the sidebar
275
+
276
+ if tab_hotdogs.button("Get Hotdog Prediction"):
277
+
278
+ pipeline = pipeline(task="image-classification", model="julien-c/hotdog-not-hotdog")
279
+ tab_hotdogs.title("Hot Dog? Or Not?")
280
+
281
+ if st.session_state.image is None:
282
+ st.info("Please upload an image first.")
283
+ st.info(str(observation.to_dict()))
284
+
285
+ else:
286
+ col1, col2 = tab_hotdogs.columns(2)
287
+
288
+ # display the image (use cached version, no need to reread)
289
+ col1.image(st.session_state.image, use_column_width=True)
290
+ # and then run inference on the image
291
+ predictions = pipeline(st.session_state.image)
292
+
293
+ col2.header("Probabilities")
294
+ first = True
295
+ for p in predictions:
296
+ col2.subheader(f"{ p['label'] }: { round(p['score'] * 100, 1)}%")
297
+ if first:
298
+ st.session_state.full_data['predicted_class'] = p['label']
299
+ st.session_state.full_data['predicted_score'] = round(p['score'] * 100, 1)
300
+ first = False
301
+
302
+ tab_hotdogs.write(f"Session Data: {json.dumps(st.session_state.full_data)}")
303
+
304
+
call_models/fix_tabrender.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ # code for fixing the issue with streamlit tabs rendering height 0 when not active
4
+ # https://github.com/streamlit/streamlit/issues/7376
5
+ #
6
+ # see also https://github.com/randyzwitch/streamlit-folium/issues/128, got
7
+ # closed becasue it is apparently a st.tabs problem
8
+
9
+
10
+ import uuid, html
11
+ # workaround for streamlit making tabs height 0 when not active, breaks map
12
+ def inject_iframe_js_code(source: str) -> None:
13
+ div_id = uuid.uuid4()
14
+
15
+ st.markdown(
16
+ f"""
17
+ <div style="height: 0; width: 0; overflow: hidden;" id="{div_id}">
18
+ <iframe src="javascript: \
19
+ var script = document.createElement('script'); \
20
+ script.type = 'text/javascript'; \
21
+ script.text = {html.escape(repr(source))}; \
22
+ var div = window.parent.document.getElementById('{div_id}'); \
23
+ div.appendChild(script); \
24
+ setTimeout(function() {{ }}, 0); \
25
+ "></iframe>
26
+ </div>
27
+ """,
28
+ unsafe_allow_html=True,
29
+ )
30
+
31
+ def js_show_zeroheight_iframe(component_iframe_title: str, height: str = "auto"):
32
+ source = f"""
33
+ (function() {{
34
+ var attempts = 0;
35
+ const maxAttempts = 20; // Max attempts to find the iframe
36
+ const intervalMs = 250; // Interval between attempts in milliseconds
37
+
38
+ function setIframeHeight() {{
39
+ const intervalId = setInterval(function() {{
40
+ var iframes = document.querySelectorAll('iframe[title="{component_iframe_title}"]');
41
+ if (iframes.length > 0 || attempts > maxAttempts) {{
42
+ if (iframes.length > 0) {{
43
+ iframes.forEach(iframe => {{
44
+ if (iframe || iframe.height === "0" || iframe.style.height === "0px") {{
45
+ iframe.style.height = "{height}";
46
+ iframe.setAttribute("height", "{height}");
47
+ console.log('Height of iframe with title "{component_iframe_title}" set to {height}.');
48
+ }}
49
+ }});
50
+ }} else {{
51
+ console.log('Iframes with title "{component_iframe_title}" not found after ' + maxAttempts + ' attempts.');
52
+ }}
53
+ clearInterval(intervalId); // Stop checking
54
+ }}
55
+ attempts++;
56
+ }}, intervalMs);
57
+ }}
58
+
59
+
60
+ function trackInteraction(event) {{
61
+ console.log('User interaction detected:', event.type);
62
+ setIframeHeight();
63
+ }}
64
+
65
+ setIframeHeight();
66
+ document.addEventListener('click', trackInteraction);
67
+ }})();
68
+ """
69
+ inject_iframe_js_code(source)
call_models/hotdogs.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from transformers import pipeline
3
+ from PIL import Image
4
+ import time
5
+
6
+
7
+ pipeline = pipeline(task="image-classification", model="julien-c/hotdog-not-hotdog")
8
+
9
+
10
+
11
+ st.title("Hot Dog? Or Not?")
12
+
13
+ file_name = st.file_uploader("Upload a hot dog candidate image")
14
+
15
+ if file_name is not None:
16
+ col1, col2 = st.columns(2)
17
+
18
+ image = Image.open(file_name)
19
+ col1.image(image, use_column_width=True)
20
+ predictions = pipeline(image)
21
+
22
+ col2.header("Probabilities")
23
+ for p in predictions:
24
+ col2.subheader(f"{ p['label'] }: { round(p['score'] * 100, 1)}%")
call_models/images/references/640x427-atlantic-white-sided-dolphin.jpg ADDED
call_models/images/references/640x427-long-finned-pilot-whale.webp ADDED
call_models/images/references/640x427-southern-right-whale.jpg ADDED
call_models/images/references/Humpback.webp ADDED
call_models/images/references/Whale_Short-Finned_Pilot-markedDW.png ADDED
call_models/images/references/beluga.webp ADDED
call_models/images/references/blue-whale.webp ADDED
call_models/images/references/bottlenose_dolphin.webp ADDED
call_models/images/references/brydes.webp ADDED
call_models/images/references/common_dolphin.webp ADDED
call_models/images/references/cuviers_beaked_whale.webp ADDED
call_models/images/references/false-killer-whale.webp ADDED
call_models/images/references/fin-whale.webp ADDED
call_models/images/references/gray-whale.webp ADDED
call_models/images/references/killer_whale.webp ADDED
call_models/images/references/melon.webp ADDED
call_models/images/references/minke-whale.webp ADDED
call_models/images/references/pantropical-spotted-dolphin.webp ADDED
call_models/images/references/pygmy-killer-whale.webp ADDED
call_models/images/references/rough-toothed-dolphin.webp ADDED
call_models/images/references/sei.webp ADDED
call_models/images/references/spinner.webp ADDED
call_models/imgs/cakes.jpg ADDED
call_models/input_handling.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ from PIL import ExifTags
3
+ import re
4
+ import datetime
5
+ import hashlib
6
+ import logging
7
+
8
+ import streamlit as st
9
+
10
+ m_logger = logging.getLogger(__name__)
11
+ # we can set the log level locally for funcs in this module
12
+ #g_m_logger.setLevel(logging.DEBUG)
13
+ m_logger.setLevel(logging.INFO)
14
+
15
+ '''
16
+ A module to setup the input handling for the whale observation guidance tool
17
+
18
+ both the UI elements (setup_input_UI) and the validation functions.
19
+ '''
20
+ #allowed_image_types = ['webp']
21
+ allowed_image_types = ['jpg', 'jpeg', 'png', 'webp']
22
+
23
+
24
+ # autogenerated class to hold the input data
25
+ class InputObservation:
26
+ def __init__(self, image=None, latitude=None, longitude=None, author_email=None, date=None, time=None, date_option=None, time_option=None, uploaded_filename=None):
27
+ self.image = image
28
+ self.latitude = latitude
29
+ self.longitude = longitude
30
+ self.author_email = author_email
31
+ self.date = date
32
+ self.time = time
33
+ self.date_option = date_option
34
+ self.time_option = time_option
35
+ self.uploaded_filename = uploaded_filename
36
+
37
+ def __str__(self):
38
+ return f"Observation: {self.image}, {self.latitude}, {self.longitude}, {self.author_email}, {self.date}, {self.time}, {self.date_option}, {self.time_option}, {self.uploaded_filename}"
39
+
40
+ def __repr__(self):
41
+ return f"Observation: {self.image}, {self.latitude}, {self.longitude}, {self.author_email}, {self.date}, {self.time}, {self.date_option}, {self.time_option}, {self.uploaded_filename}"
42
+
43
+ def __eq__(self, other):
44
+ return (self.image == other.image and self.latitude == other.latitude and self.longitude == other.longitude and
45
+ self.author_email == other.author_email and self.date == other.date and self.time == other.time and
46
+ self.date_option == other.date_option and self.time_option == other.time_option and self.uploaded_filename == other.uploaded_filename)
47
+
48
+ def __ne__(self, other):
49
+ return not self.__eq__(other)
50
+
51
+ def __hash__(self):
52
+ return hash((self.image, self.latitude, self.longitude, self.author_email, self.date, self.time, self.date_option, self.time_option, self.uploaded_filename))
53
+
54
+ def to_dict(self):
55
+ return {
56
+ #"image": self.image,
57
+ "image_filename": self.uploaded_filename.name if self.uploaded_filename else None,
58
+ "image_md5": hashlib.md5(self.uploaded_filename.read()).hexdigest() if self.uploaded_filename else None,
59
+ "latitude": self.latitude,
60
+ "longitude": self.longitude,
61
+ "author_email": self.author_email,
62
+ "date": self.date,
63
+ "time": self.time,
64
+ "date_option": self.date_option,
65
+ "time_option": self.time_option,
66
+ "uploaded_filename": self.uploaded_filename
67
+ }
68
+
69
+ @classmethod
70
+ def from_dict(cls, data):
71
+ return cls(data["image"], data["latitude"], data["longitude"], data["author_email"], data["date"], data["time"], data["date_option"], data["time_option"], data["uploaded_filename"])
72
+
73
+ @classmethod
74
+ def from_input(cls, input):
75
+ return cls(input.image, input.latitude, input.longitude, input.author_email, input.date, input.time, input.date_option, input.time_option, input.uploaded_filename)
76
+
77
+ @staticmethod
78
+ def from_input(input):
79
+ return InputObservation(input.image, input.latitude, input.longitude, input.author_email, input.date, input.time, input.date_option, input.time_option, input.uploaded_filename)
80
+
81
+ @staticmethod
82
+ def from_dict(data):
83
+ return InputObservation(data["image"], data["latitude"], data["longitude"], data["author_email"], data["date"], data["time"], data["date_option"], data["time_option"], data["uploaded_filename"])
84
+
85
+ # define function to validate number, allowing signed float
86
+ def is_valid_number(number:str) -> bool:
87
+ pattern = r'^[-+]?[0-9]*\.?[0-9]+$'
88
+ return re.match(pattern, number) is not None
89
+
90
+
91
+ # Function to validate email address
92
+ def is_valid_email(email):
93
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
94
+ return re.match(pattern, email) is not None
95
+
96
+ # Function to extract date and time from image metadata
97
+ def get_image_datetime(image_file):
98
+ try:
99
+ image = Image.open(image_file)
100
+ exif_data = image._getexif()
101
+ if exif_data is not None:
102
+ for tag, value in exif_data.items():
103
+ if ExifTags.TAGS.get(tag) == 'DateTimeOriginal':
104
+ return value
105
+ except Exception as e:
106
+ st.warning("Could not extract date from image metadata.")
107
+ return None
108
+
109
+
110
+ # an arbitrary set of defaults so testing is less painful...
111
+ # ideally we add in some randomization to the defaults
112
+ spoof_metadata = {
113
+ "latitude": 23.5,
114
+ "longitude": 44,
115
+ "author_email": "[email protected]",
116
+ "date": None,
117
+ "time": None,
118
+ }
119
+
120
+ #def display_whale(whale_classes:List[str], i:int, viewcontainer=None):
121
+ def setup_input(viewcontainer: st.delta_generator.DeltaGenerator=None, _allowed_image_types: list=None, ):
122
+
123
+ if viewcontainer is None:
124
+ viewcontainer = st.sidebar
125
+
126
+ if _allowed_image_types is None:
127
+ _allowed_image_types = allowed_image_types
128
+
129
+
130
+ viewcontainer.title("Input image and data")
131
+
132
+ # 1. Image Selector
133
+ uploaded_filename = viewcontainer.file_uploader("Upload an image", type=allowed_image_types)
134
+ image_datetime = None # For storing date-time from image
135
+
136
+ if uploaded_filename is not None:
137
+ # Display the uploaded image
138
+ image = Image.open(uploaded_filename)
139
+ viewcontainer.image(image, caption='Uploaded Image.', use_column_width=True)
140
+ # store the image in the session state
141
+ st.session_state.image = image
142
+
143
+
144
+ # Extract and display image date-time
145
+ image_datetime = get_image_datetime(uploaded_filename)
146
+ print(f"[D] image date extracted as {image_datetime}")
147
+ m_logger.debug(f"image date extracted as {image_datetime} (from {uploaded_filename})")
148
+
149
+
150
+ # 2. Latitude Entry Box
151
+ latitude = viewcontainer.text_input("Latitude", spoof_metadata.get('latitude', ""))
152
+ if latitude and not is_valid_number(latitude):
153
+ viewcontainer.error("Please enter a valid latitude (numerical only).")
154
+ m_logger.error(f"Invalid latitude entered: {latitude}.")
155
+ # 3. Longitude Entry Box
156
+ longitude = viewcontainer.text_input("Longitude", spoof_metadata.get('longitude', ""))
157
+ if longitude and not is_valid_number(longitude):
158
+ viewcontainer.error("Please enter a valid longitude (numerical only).")
159
+ m_logger.error(f"Invalid latitude entered: {latitude}.")
160
+
161
+ # 4. Author Box with Email Address Validator
162
+ author_email = viewcontainer.text_input("Author Email", spoof_metadata.get('author_email', ""))
163
+
164
+ if author_email and not is_valid_email(author_email):
165
+ viewcontainer.error("Please enter a valid email address.")
166
+
167
+ # 5. date/time
168
+ ## first from image metadata
169
+ if image_datetime is not None:
170
+ time_value = datetime.datetime.strptime(image_datetime, '%Y:%m:%d %H:%M:%S').time()
171
+ date_value = datetime.datetime.strptime(image_datetime, '%Y:%m:%d %H:%M:%S').date()
172
+ else:
173
+ time_value = datetime.datetime.now().time() # Default to current time
174
+ date_value = datetime.datetime.now().date()
175
+
176
+ ## if not, give user the option to enter manually
177
+ date_option = st.sidebar.date_input("Date", value=date_value)
178
+ time_option = st.sidebar.time_input("Time", time_value)
179
+
180
+ observation = InputObservation(image=uploaded_filename, latitude=latitude, longitude=longitude,
181
+ author_email=author_email, date=image_datetime, time=None,
182
+ date_option=date_option, time_option=time_option)
183
+ return observation
184
+
call_models/obs_map.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+ import logging
3
+
4
+ import pandas as pd
5
+ from datasets import load_dataset
6
+ import streamlit as st
7
+ import folium
8
+ from streamlit_folium import st_folium
9
+
10
+ import whale_viewer as sw_wv
11
+ from fix_tabrender import js_show_zeroheight_iframe
12
+
13
+ m_logger = logging.getLogger(__name__)
14
+ # we can set the log level locally for funcs in this module
15
+ #g_m_logger.setLevel(logging.DEBUG)
16
+ m_logger.setLevel(logging.INFO)
17
+
18
+ # TODO: refactor so we have richer data: a tuple or dict combining
19
+ # the dropdown label, the tileset name, the attribution - everything
20
+ # needed to make the map logic simplified
21
+ tile_sets = [
22
+ 'Open Street Map',
23
+ #'Stamen Terrain',
24
+ #'Stamen Toner',
25
+ 'Esri Ocean',
26
+ 'Esri Images',
27
+ 'Stamen Watercolor',
28
+ 'CartoDB Positron',
29
+ #'CartoDB Dark_Matter'
30
+ ]
31
+
32
+ # a list of unique colours for each whale class (for the map)
33
+ _colors = [
34
+ "#FFD700", # Gold
35
+ "#FF5733", # Red
36
+ "#33FF57", # Green
37
+ "#3357FF", # Blue
38
+ "#FFFF33", # Yellow
39
+ "#FF33FF", # Magenta
40
+ "#33FFFF", # Cyan
41
+ "#FF8C00", # Dark Orange
42
+ "#8A2BE2", # Blue Violet
43
+ "#DEB887", # Burlywood
44
+ "#5F9EA0", # Cadet Blue
45
+ "#D2691E", # Chocolate
46
+ "#FF4500", # Orange Red
47
+ "#2E8B57", # Sea Green
48
+ "#DA70D6", # Orchid
49
+ "#FF6347", # Tomato
50
+ "#7FFF00", # Chartreuse
51
+ "#DDA0DD", # Plum
52
+ "#A0522D", # Sienna
53
+ "#4682B4", # Steel Blue
54
+ "#7B68EE", # Medium Slate Blue
55
+ "#F0E68C", # Khaki
56
+ "#B22222", # Firebrick
57
+ "#FF1493", # Deep Pink
58
+ "#FFFACD", # Lemon Chiffon
59
+ "#20B2AA", # Light Sea Green
60
+ "#778899" # Light Slate Gray
61
+ ]
62
+
63
+ whale2color = {k: v for k, v in zip(sw_wv.WHALE_CLASSES, _colors)}
64
+
65
+ def create_map(tile_name:str, location:Tuple, zoom_start: int = 7):
66
+ # https://xyzservices.readthedocs.io/en/stable/gallery.html
67
+ # get teh attribtuions from here once we pick the 2-3-4 options
68
+ # make esri ocean the default
69
+ m = folium.Map(location=location, zoom_start=zoom_start,
70
+ tiles='Esri.OceanBasemap', attr="Esri")
71
+ #m = folium.Map(location=location, zoom_start=zoom_start)
72
+
73
+ attr = ""
74
+ if tile_name == 'Open Street Map':
75
+ folium.TileLayer('openstreetmap').add_to(m)
76
+ pass
77
+
78
+ #Esri.OceanBasemap
79
+ elif tile_name == 'Esri Ocean':
80
+ pass # made this one default ()
81
+ #attr = "Esri"
82
+ #folium.TileLayer('Esri.OceanBasemap', attr=attr).add_to(m)
83
+
84
+ elif tile_name == 'Esri Images':
85
+ attr = "Esri &mdash; Source: Esri, i-cubed, USDA"
86
+ #folium.TileLayer('stamenterrain', attr=attr).add_to(m)
87
+ folium.TileLayer('Esri.WorldImagery', attr=attr).add_to(m)
88
+ elif tile_name == 'Stamen Toner':
89
+ attr = "Stamen"
90
+ folium.TileLayer('stamentoner', attr=attr).add_to(m)
91
+ elif tile_name == 'Stamen Watercolor':
92
+ attr = "Stamen"
93
+ folium.TileLayer('Stadia.StamenWatercolor', attr=attr).add_to(m)
94
+ elif tile_name == 'CartoDB Positron':
95
+ folium.TileLayer('cartodb positron').add_to(m)
96
+ elif tile_name == 'CartoDB Dark_Matter':
97
+ folium.TileLayer('cartodb dark_matter').add_to(m)
98
+
99
+ #folium.LayerControl().add_to(m)
100
+ return m
101
+
102
+
103
+
104
+ def present_obs_map(dataset_id:str = "Saving-Willy/Happywhale-kaggle",
105
+ data_files:str = "data/train-00000-of-00001.parquet",
106
+ dbg_show_extra:bool = False):
107
+ '''
108
+ render a map, with a selectable tileset, and show markers for each of the whale
109
+ observations
110
+
111
+ '''
112
+ # load/download data from huggingface dataset
113
+ metadata = load_dataset(dataset_id, data_files=data_files)
114
+
115
+ # make a pandas df that is compliant with folium/streamlit maps
116
+ _df = pd.DataFrame({
117
+ 'lat': metadata["train"]["latitude"],
118
+ 'lon': metadata["train"]["longitude"],
119
+ 'species': metadata["train"]["predicted_class"],}
120
+ )
121
+ if dbg_show_extra:
122
+ # add a few samples to visualise colours
123
+ _df.loc[len(_df)] = {'lat': 0, 'lon': 0, 'species': 'rough_toothed_dolphin'}
124
+ _df.loc[len(_df)] = {'lat': -3, 'lon': 0, 'species': 'pygmy_killer_whale'}
125
+ _df.loc[len(_df)] = {'lat': 45.7, 'lon': -2.6, 'species': 'humpback_whale'}
126
+
127
+ ocean_loc = 0, 10
128
+ selected_tile = st.selectbox("Choose a tile set", tile_sets, index=None, placeholder="Choose a tile set...", disabled=False)
129
+ map_ = create_map(selected_tile, ocean_loc, zoom_start=2)
130
+
131
+ folium.Marker(
132
+ location=ocean_loc,
133
+ popup="Atlantis",
134
+ tooltip="Atlantis",
135
+ icon=folium.Icon(color='blue', icon='info-sign')
136
+ ).add_to(map_)
137
+
138
+ for _, row in _df.iterrows():
139
+ c = whale2color.get(row['species'], 'red')
140
+ msg = f"[D] color for {row['species']} is {c}"
141
+ m_logger.debug(msg) # depends on m_logger logging level (*not* the main st app's logger)
142
+ #m_logger.info(msg)
143
+
144
+ kw = {"prefix": "fa", "color": 'gray', "icon_color": c, "icon": "binoculars" }
145
+ folium.Marker(
146
+ location=[row['lat'], row['lon']],
147
+ popup=f"{row['species']} ",
148
+ tooltip=row['species'],
149
+ icon=folium.Icon(**kw)
150
+ ).add_to(map_)
151
+ #st.info(f"Added marker for {row['name']} {row['lat']} {row['lon']}")
152
+
153
+ st_data = st_folium(map_, width=725)
154
+
155
+ # workaround for correctly showing js components in tabs
156
+ js_show_zeroheight_iframe(
157
+ component_iframe_title="streamlit_folium.st_folium",
158
+ height=800,
159
+ )
160
+ # this is just debug info --
161
+ #st.info("[D]" + str(metadata.column_names))
162
+
163
+ return st_data
call_models/requirements.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ transformers
2
+ streamlit
3
+ huggingface_hub
4
+ torch
5
+
6
+ pandas
7
+ numpy
8
+
9
+ datasets
10
+
11
+ # for nice map tiles
12
+ folium
13
+ streamlit_folium
14
+
15
+ # for ceatatean
16
+ pytorch_lightning
17
+ timm
call_models/st_logs.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from datetime import datetime
3
+ import re
4
+ from collections import deque
5
+
6
+ import streamlit as st
7
+
8
+ # some discussions with code snippets from:
9
+ # https://discuss.streamlit.io/t/capture-and-display-logger-in-ui/69136
10
+
11
+ # configure log parsing (seems to need some tweaking)
12
+ _log_n_re = r'\[(\d+)\]'
13
+ _log_date_re = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})'
14
+ _log_mod_re = r'(\w+(?:\.\w+)*|__\w+__|<\w+>)'
15
+ _log_func_re = r'(\w+|<\w+>)'
16
+ _log_level_re = r'(\w+)'
17
+ _log_msg_re = '(.*)'
18
+ _sep = r' - '
19
+
20
+ log_pattern = re.compile(_log_n_re + _log_date_re + _sep + _log_mod_re + _sep +
21
+ _log_func_re + _sep + _log_level_re + _sep + _log_msg_re)
22
+
23
+
24
+ class StreamlitLogHandler(logging.Handler):
25
+ # Initializes a custom log handler with a Streamlit container for displaying logs
26
+ def __init__(self, container, maxlen:int=15, debug:bool=False):
27
+ super().__init__()
28
+ # Store the Streamlit container for log output
29
+ self.container = container
30
+ self.debug = debug
31
+ self.ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') # Regex to remove ANSI codes
32
+ self.log_area = self.container.empty() # Prepare an empty conatiner for log output
33
+
34
+ self.buffer = deque(maxlen=maxlen)
35
+ self._n = 0
36
+
37
+ def n_elems(self, verb:bool=False):
38
+ ''' return a string with num elements seen and num elements in buffer '''
39
+ if verb:
40
+ return f"total: {self._n}|| in buffer:{len(self.buffer)}"
41
+
42
+ return f"{self._n}||{len(self.buffer)}"
43
+
44
+ def emit(self, record):
45
+ self._n += 1
46
+ msg = f"[{self._n}]" + self.format(record)
47
+ self.buffer.append(msg)
48
+ clean_msg = self.ansi_escape.sub('', msg) # Strip ANSI codes
49
+ if self.debug:
50
+ self.log_area.markdown(clean_msg)
51
+
52
+ def clear_logs(self):
53
+ self.log_area.empty() # Clear previous logs
54
+ self.buffer.clear()
55
+
56
+ # Set up logging to capture all info level logs from the root logger
57
+ @st.cache_resource
58
+ def setup_logging(level: int=logging.INFO, buffer_len:int=15):
59
+ root_logger = logging.getLogger() # Get the root logger
60
+ log_container = st.container() # Create a container within which we display logs
61
+ handler = StreamlitLogHandler(log_container, maxlen=buffer_len)
62
+ handler.setLevel(level)
63
+
64
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s')
65
+ handler.setFormatter(formatter)
66
+ root_logger.addHandler(handler)
67
+
68
+ #if 'handler' not in st.session_state:
69
+ # st.session_state['handler'] = handler
70
+ return handler
71
+
72
+ def parse_log_buffer(log_contents: deque) -> list:
73
+ ''' convert log buffer to a list of dictionaries '''
74
+ j = 0
75
+ records = []
76
+ for line in log_contents:
77
+ if line: # Skip empty lines
78
+ j+=1
79
+ try:
80
+ # regex to parsse log lines, with an example line:
81
+ # '[1]2024-11-09 11:19:06,688 - task - run - INFO - 🏃 Running task '
82
+ match = log_pattern.match(line)
83
+ if match:
84
+ n, timestamp_str, name, func_name, level, message = match.groups()
85
+
86
+ # Convert timestamp string to datetime
87
+ timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S,%f')
88
+
89
+ records.append({
90
+ 'timestamp': timestamp,
91
+ 'n': n,
92
+ 'level': level,
93
+ 'module': name,
94
+ 'func': func_name,
95
+ 'message': message
96
+ })
97
+ except Exception as e:
98
+ print(f"Failed to parse line: {line}")
99
+ print(f"Error: {e}")
100
+ continue
101
+ return records
102
+
103
+ def something():
104
+ '''function to demo adding log entries'''
105
+ logger = logging.getLogger(__name__)
106
+ logger.setLevel(logging.DEBUG)
107
+ logger.debug("debug message")
108
+ logger.info("info message")
109
+ logger.warning("warning message")
110
+ logger.error("error message")
111
+ logger.critical("critical message")
112
+
113
+
114
+ if __name__ == "__main__":
115
+
116
+ # create a logging handler for streamlit + regular python logging module
117
+ handler = setup_logging()
118
+
119
+ # get buffered log data and parse, ready for display as dataframe
120
+ records = parse_log_buffer(handler.buffer)
121
+
122
+ c1, c2 = st.columns([1, 3])
123
+ with c1:
124
+ button = st.button("do something", on_click=something)
125
+ with c2:
126
+ st.info(f"Length of records: {len(records)}")
127
+ #tab = st.table(records)
128
+ tab = st.dataframe(records[::-1], use_container_width=True) # scrollable, selectable.
call_models/test_upload.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import HfApi
2
+ import json
3
+ import tempfile
4
+ import os
5
+
6
+ #huggingface_hub
7
+
8
+ submission = {'latitude': '3.5', 'longitude': '44', 'author_email':
9
+ '[email protected]', 'date': '2024-10-25', 'time': '12:07:04.487612',
10
+ 'predicted_class': 'bottlenose_dolphin', 'class_overriden': None,
11
+ 'image_filename': '000a8f2d5c316a.webp', 'image_md5':
12
+ 'd41d8cd98f00b204e9800998ecf8427e'}
13
+
14
+ imgname = submission['image_filename']
15
+
16
+ api = HfApi()
17
+
18
+
19
+ # generate a tempdirectory to store the image
20
+ #tempdir = tempfile.TemporaryDirectory()
21
+ # write a tempfile
22
+
23
+ # write submission to a tempfile in json format with the name of the image, giving the filename and path as a string
24
+
25
+ f = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
26
+ jstr = json.dumps(submission)
27
+ f.write(jstr)
28
+ f.close()
29
+
30
+
31
+
32
+ #with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
33
+ # jstr = json.dumps(submission)
34
+ # f.write(jstr)
35
+ # #print(f.path)
36
+
37
+ path_in_repo= f"metadata/{submission['author_email']}/{submission['image_md5']}.json"
38
+ print(f"fname: {f.name} | path: {path_in_repo}")
39
+ rv = api.upload_file(
40
+ path_or_fileobj=f.name,
41
+ path_in_repo=path_in_repo,
42
+ repo_id="Saving-Willy/Happywhale-kaggle",
43
+ repo_type="dataset",
44
+ )
45
+ print(rv)
46
+
47
+
48
+
49
+
call_models/whale_gallery.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import cycle
2
+ import streamlit as st
3
+
4
+ import whale_viewer as sw_wv
5
+
6
+ def render_whale_gallery(n_cols:int = 4):
7
+ """
8
+ A function to display a gallery of whale images in a grid
9
+ """
10
+ def format_whale_name(name):
11
+ return name.replace("_", " ").capitalize()
12
+
13
+ # make a grid of images, use some css to get more uniform
14
+ # https://discuss.streamlit.io/t/grid-of-images-with-the-same-height/10668/12
15
+ # nb: I think there are some community components, need to investigate their usage
16
+ st.markdown(
17
+ """
18
+ <style>
19
+
20
+ .st-key-swgallery div[data-testid="stVerticalBlock"] {
21
+ justify-content: center;
22
+ }
23
+ .st-key-swgallery div[data-testid="stVerticalBlockBorderWrapper"] {
24
+ display: flex !important;
25
+ min-height: 185px !important; //185 for image+caption or 255 with link
26
+ align-items: center;
27
+ //background-color: darkgreen;
28
+ }
29
+
30
+
31
+ /*
32
+ .st-key-swheader div[data-testid="stVerticalBlockBorderWrapper"] {
33
+ background-color: lightgreen;
34
+ min-height: 16px !important;
35
+ border: 1px solid #ccc;
36
+ }
37
+ */
38
+ .st-key-swgallery div[data-testid="stColumn"] {
39
+ flex: 1 !important; /* additionally, equal width */
40
+ padding: 1em !important;
41
+ align-items: center;
42
+ border: solid !important;
43
+ border-radius: 0px !important;
44
+ max-width: 220px !important;
45
+ border-color: #0000 !important;
46
+ }
47
+ </style>
48
+ """,
49
+ unsafe_allow_html=True,
50
+ )
51
+
52
+ cols = cycle(st.columns(n_cols))
53
+ for ix in range(len(sw_wv.df_whale_img_ref)):
54
+ img_name = sw_wv.df_whale_img_ref.iloc[ix].loc["WHALE_IMAGES"]
55
+ whale_name = format_whale_name(str(sw_wv.df_whale_img_ref.iloc[ix].name))
56
+ url = sw_wv.df_whale_img_ref.iloc[ix].loc['WHALE_REFERENCES']
57
+ image_path = f"images/references/{img_name}"
58
+ #next(cols).image(image_path, width=150, caption=f"{whale_name}")
59
+ thing = next(cols)
60
+ with thing:
61
+ with st.container(border=True):
62
+ # using the caption for name is most compact but no link.
63
+ #st.image(image_path, width=150, caption=f"{whale_name}")
64
+ st.image(image_path, width=150)
65
+ #st.markdown(f"[{whale_name}]({url})" ) # doesn't seem to allow styling, just do in raw html:w
66
+ html = f"<div style='text-align: center; font-size: 14px'><a href='{url}'>{whale_name}</a></div>"
67
+ st.markdown(html, unsafe_allow_html=True)
68
+
69
+
70
+ #next(cols).image(image_path, width=150, caption=f"{whale_name}")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ ''' example usage, with some other elements to help illustrate how
75
+ streamlit keys can be used to target specific css properties
76
+ '''
77
+ # define a container just to hold a couple of elements
78
+ header_cont = st.container(key='swheader')
79
+ with header_cont:
80
+ c1, c2 = st.columns([2, 3])
81
+ c1.markdown('left')
82
+ c2.button("Refresh Gallery (noop)")
83
+ # here we make a container to allow filtering css properties
84
+ # specific to the gallery (otherwise we get side effects)
85
+ tg_cont = st.container(key="swgallery")
86
+ with tg_cont:
87
+ render_whale_gallery(n_cols=4)
88
+
89
+ pass
call_models/whale_viewer.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from PIL import Image
4
+ import pandas as pd
5
+
6
+ WHALE_CLASSES = [
7
+ "beluga",
8
+ "blue_whale",
9
+ "bottlenose_dolphin",
10
+ "brydes_whale",
11
+ "commersons_dolphin",
12
+ "common_dolphin",
13
+ "cuviers_beaked_whale",
14
+ "dusky_dolphin",
15
+ "false_killer_whale",
16
+ "fin_whale",
17
+ "frasiers_dolphin",
18
+ "gray_whale",
19
+ "humpback_whale",
20
+ "killer_whale",
21
+ "long_finned_pilot_whale",
22
+ "melon_headed_whale",
23
+ "minke_whale",
24
+ "pantropic_spotted_dolphin",
25
+ "pygmy_killer_whale",
26
+ "rough_toothed_dolphin",
27
+ "sei_whale",
28
+ "short_finned_pilot_whale",
29
+ "southern_right_whale",
30
+ "spinner_dolphin",
31
+ "spotted_dolphin",
32
+ "white_sided_dolphin",
33
+ ]
34
+
35
+ WHALE_IMAGES = [
36
+ "beluga.webp",
37
+ "blue-whale.webp",
38
+ "bottlenose_dolphin.webp",
39
+ "brydes.webp",
40
+ "common_dolphin.webp",
41
+ "common_dolphin.webp",
42
+ "cuviers_beaked_whale.webp",
43
+ "common_dolphin.webp",
44
+ "false-killer-whale.webp",
45
+ "fin-whale.webp",
46
+ "fin-whale.webp",
47
+ "gray-whale.webp",
48
+ "Humpback.webp",
49
+ "killer_whale.webp",
50
+ "640x427-long-finned-pilot-whale.webp",
51
+ "melon.webp",
52
+ "minke-whale.webp",
53
+ "pantropical-spotted-dolphin.webp",
54
+ "pygmy-killer-whale.webp",
55
+ "rough-toothed-dolphin.webp",
56
+ "sei.webp",
57
+ "Whale_Short-Finned_Pilot-markedDW.png", ## Background
58
+ "640x427-southern-right-whale.jpg", ## background
59
+ "spinner.webp",
60
+ "pantropical-spotted-dolphin.webp", ## duplicate also used for
61
+ "640x427-atlantic-white-sided-dolphin.jpg", ##background
62
+ ]
63
+
64
+ WHALE_REFERENCES = [
65
+ "https://www.fisheries.noaa.gov/species/beluga-whale",
66
+ "https://www.fisheries.noaa.gov/species/blue-whale",
67
+ "https://www.fisheries.noaa.gov/species/common-bottlenose-dolphin",
68
+ "https://www.fisheries.noaa.gov/species/brydes-whale",
69
+ "https://en.wikipedia.org/wiki/Commerson's_dolphin",
70
+ #"commersons_dolphin - reference missing - classification to be verified", ## class matching to be verified
71
+ "https://www.fisheries.noaa.gov/species/short-beaked-common-dolphin",
72
+ "https://www.fisheries.noaa.gov/species/cuviers-beaked-whale",
73
+ "https://en.wikipedia.org/wiki/Dusky_dolphin",
74
+ #"dusky_dolphin - reference missing - classification to be verified", ## class matching to be verified
75
+ "https://www.fisheries.noaa.gov/species/false-killer-whale",
76
+ "https://www.fisheries.noaa.gov/species/fin-whale",
77
+ "https://www.fisheries.noaa.gov/species/frasers-dolphin",
78
+ #"frasiers_dolphin - reference missing - classification to be verified", ## class matching to be verified
79
+ "https://www.fisheries.noaa.gov/species/gray-whale",
80
+ "https://www.fisheries.noaa.gov/species/humpback-whale",
81
+ "https://www.fisheries.noaa.gov/species/killer-whale",
82
+ "https://www.fisheries.noaa.gov/species/long-finned-pilot-whale",
83
+ "https://www.fisheries.noaa.gov/species/melon-headed-whale",
84
+ "https://www.fisheries.noaa.gov/species/minke-whale",
85
+ "https://www.fisheries.noaa.gov/species/pantropical-spotted-dolphin",
86
+ "https://www.fisheries.noaa.gov/species/pygmy-killer-whale",
87
+ "https://www.fisheries.noaa.gov/species/rough-toothed-dolphin",
88
+ "https://www.fisheries.noaa.gov/species/sei-whale",
89
+ "https://www.fisheries.noaa.gov/species/short-finned-pilot-whale",
90
+ "https://www.fisheries.noaa.gov/species/southern-right-whale",
91
+ "https://www.fisheries.noaa.gov/species/spinner-dolphin",
92
+ "https://www.fisheries.noaa.gov/species/pantropical-spotted-dolphin",
93
+ "https://www.fisheries.noaa.gov/species/atlantic-white-sided-dolphin",
94
+ ]
95
+
96
+ # Create a dataframe
97
+ df_whale_img_ref = pd.DataFrame(
98
+ {
99
+ "WHALE_CLASSES": WHALE_CLASSES,
100
+ "WHALE_IMAGES": WHALE_IMAGES,
101
+ "WHALE_REFERENCES": WHALE_REFERENCES,
102
+ }
103
+ ).set_index("WHALE_CLASSES")
104
+
105
+ def format_whale_name(whale_class:str):
106
+ whale_name = whale_class.replace("_", " ").title()
107
+ return whale_name
108
+
109
+
110
+ def display_whale(whale_classes:List[str], i:int, viewcontainer=None):
111
+ """
112
+ Display whale image and reference to the provided viewcontainer.
113
+
114
+ Args:
115
+ whale_classes (List[str]): A list of whale class names.
116
+ i (int): The index of the whale class to display.
117
+ viewcontainer: The container to display the whale information. If
118
+ not provided, use the current streamlit context (works via
119
+ 'with <container>' syntax)
120
+ Returns:
121
+ None
122
+
123
+ TODO: how to find the object type of viewcontainer.? they are just "deltagenerators" but
124
+ we want the result of the generator.. In any case, it works ok with either call signature.
125
+ """
126
+ import streamlit as st
127
+ if viewcontainer is None:
128
+ viewcontainer = st
129
+
130
+ # validate the input i should be within the range of the whale_classes
131
+ if i >= len(whale_classes):
132
+ raise ValueError(f"Index {i} is out of range. The whale_classes list has only {len(whale_classes)} elements.")
133
+
134
+ # validate the existence of the whale class in the dataframe as a row key
135
+ if whale_classes[i] not in df_whale_img_ref.index:
136
+ raise ValueError(f"Whale class {whale_classes[i]} not found in the dataframe.")
137
+
138
+
139
+ viewcontainer.markdown(
140
+ "### :whale: #" + str(i + 1) + ": " + format_whale_name(whale_classes[i])
141
+ )
142
+ image = Image.open("images/references/" + df_whale_img_ref.loc[whale_classes[i], "WHALE_IMAGES"])
143
+
144
+ viewcontainer.image(image, caption=df_whale_img_ref.loc[whale_classes[i], "WHALE_REFERENCES"])
145
+ # link st.markdown(f"[{df.loc[whale_classes[i], 'WHALE_REFERENCES']}]({df.loc[whale_classes[i], 'WHALE_REFERENCES']})")
git ADDED
File without changes
images/references/640x427-atlantic-white-sided-dolphin.jpg ADDED
images/references/640x427-long-finned-pilot-whale.webp ADDED
images/references/640x427-southern-right-whale.jpg ADDED
images/references/Humpback.webp ADDED
images/references/Whale_Short-Finned_Pilot-markedDW.png ADDED
images/references/beluga.webp ADDED