Spaces:
Sleeping
Sleeping
feat: sync github with huggingface
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .github/workflows/push_to_hf.yml +20 -0
- .gitignore +3 -0
- README.md +39 -2
- app.py +1 -0
- basic_map/app.py +21 -0
- basic_map/app1.py +42 -0
- basic_map/requirements.txt +4 -0
- call_models/alps_map.py +171 -0
- call_models/click_map.py +18 -0
- call_models/d_entry.py +108 -0
- call_models/entry_and_hotdog.py +304 -0
- call_models/fix_tabrender.py +69 -0
- call_models/hotdogs.py +24 -0
- call_models/images/references/640x427-atlantic-white-sided-dolphin.jpg +0 -0
- call_models/images/references/640x427-long-finned-pilot-whale.webp +0 -0
- call_models/images/references/640x427-southern-right-whale.jpg +0 -0
- call_models/images/references/Humpback.webp +0 -0
- call_models/images/references/Whale_Short-Finned_Pilot-markedDW.png +0 -0
- call_models/images/references/beluga.webp +0 -0
- call_models/images/references/blue-whale.webp +0 -0
- call_models/images/references/bottlenose_dolphin.webp +0 -0
- call_models/images/references/brydes.webp +0 -0
- call_models/images/references/common_dolphin.webp +0 -0
- call_models/images/references/cuviers_beaked_whale.webp +0 -0
- call_models/images/references/false-killer-whale.webp +0 -0
- call_models/images/references/fin-whale.webp +0 -0
- call_models/images/references/gray-whale.webp +0 -0
- call_models/images/references/killer_whale.webp +0 -0
- call_models/images/references/melon.webp +0 -0
- call_models/images/references/minke-whale.webp +0 -0
- call_models/images/references/pantropical-spotted-dolphin.webp +0 -0
- call_models/images/references/pygmy-killer-whale.webp +0 -0
- call_models/images/references/rough-toothed-dolphin.webp +0 -0
- call_models/images/references/sei.webp +0 -0
- call_models/images/references/spinner.webp +0 -0
- call_models/imgs/cakes.jpg +0 -0
- call_models/input_handling.py +184 -0
- call_models/obs_map.py +163 -0
- call_models/requirements.txt +17 -0
- call_models/st_logs.py +128 -0
- call_models/test_upload.py +49 -0
- call_models/whale_gallery.py +89 -0
- call_models/whale_viewer.py +145 -0
- git +0 -0
- images/references/640x427-atlantic-white-sided-dolphin.jpg +0 -0
- images/references/640x427-long-finned-pilot-whale.webp +0 -0
- images/references/640x427-southern-right-whale.jpg +0 -0
- images/references/Humpback.webp +0 -0
- images/references/Whale_Short-Finned_Pilot-markedDW.png +0 -0
- 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 |
-
|
2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 — 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 — 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
![]() |