forrestfwilliams commited on
Commit
ce90b3f
1 Parent(s): 39b33d2

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +244 -134
  2. requirements.txt +4 -6
app.py CHANGED
@@ -1,147 +1,257 @@
1
- import io
2
- import random
3
- from typing import List, Tuple
4
-
5
- import aiohttp
6
  import panel as pn
7
- from PIL import Image
8
- from transformers import CLIPModel, CLIPProcessor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- pn.extension(design="bootstrap", sizing_mode="stretch_width")
11
 
12
- ICON_URLS = {
13
- "brand-github": "https://github.com/holoviz/panel",
14
- "brand-twitter": "https://twitter.com/Panel_Org",
15
- "brand-linkedin": "https://www.linkedin.com/company/panel-org",
16
- "message-circle": "https://discourse.holoviz.org/",
17
- "brand-discord": "https://discord.gg/AXRHnJU6sP",
18
- }
19
 
20
 
21
- async def random_url(_):
22
- pet = random.choice(["cat", "dog"])
23
- api_url = f"https://api.the{pet}api.com/v1/images/search"
24
- async with aiohttp.ClientSession() as session:
25
- async with session.get(api_url) as resp:
26
- return (await resp.json())[0]["url"]
27
 
28
 
29
- @pn.cache
30
- def load_processor_model(
31
- processor_name: str, model_name: str
32
- ) -> Tuple[CLIPProcessor, CLIPModel]:
33
- processor = CLIPProcessor.from_pretrained(processor_name)
34
- model = CLIPModel.from_pretrained(model_name)
35
- return processor, model
36
 
 
 
 
 
 
 
37
 
38
- async def open_image_url(image_url: str) -> Image:
39
- async with aiohttp.ClientSession() as session:
40
- async with session.get(image_url) as resp:
41
- return Image.open(io.BytesIO(await resp.read()))
42
 
 
 
 
43
 
44
- def get_similarity_scores(class_items: List[str], image: Image) -> List[float]:
45
- processor, model = load_processor_model(
46
- "openai/clip-vit-base-patch32", "openai/clip-vit-base-patch32"
 
 
 
47
  )
48
- inputs = processor(
49
- text=class_items,
50
- images=[image],
51
- return_tensors="pt", # pytorch tensors
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  )
53
- outputs = model(**inputs)
54
- logits_per_image = outputs.logits_per_image
55
- class_likelihoods = logits_per_image.softmax(dim=1).detach().numpy()
56
- return class_likelihoods[0]
57
-
58
-
59
- async def process_inputs(class_names: List[str], image_url: str):
60
- """
61
- High level function that takes in the user inputs and returns the
62
- classification results as panel objects.
63
- """
64
- try:
65
- main.disabled = True
66
- if not image_url:
67
- yield "##### ⚠️ Provide an image URL"
68
- return
69
-
70
- yield "##### ��� Fetching image and running model..."
71
- try:
72
- pil_img = await open_image_url(image_url)
73
- img = pn.pane.Image(pil_img, height=400, align="center")
74
- except Exception as e:
75
- yield f"##### 😔 Something went wrong, please try a different URL!"
76
- return
77
-
78
- class_items = class_names.split(",")
79
- class_likelihoods = get_similarity_scores(class_items, pil_img)
80
-
81
- # build the results column
82
- results = pn.Column("##### 🎉 Here are the results!", img)
83
-
84
- for class_item, class_likelihood in zip(class_items, class_likelihoods):
85
- row_label = pn.widgets.StaticText(
86
- name=class_item.strip(), value=f"{class_likelihood:.2%}", align="center"
87
- )
88
- row_bar = pn.indicators.Progress(
89
- value=int(class_likelihood * 100),
90
- sizing_mode="stretch_width",
91
- bar_color="secondary",
92
- margin=(0, 10),
93
- design=pn.theme.Material,
94
- )
95
- results.append(pn.Column(row_label, row_bar))
96
- yield results
97
- finally:
98
- main.disabled = False
99
-
100
-
101
- # create widgets
102
- randomize_url = pn.widgets.Button(name="Randomize URL", align="end")
103
-
104
- image_url = pn.widgets.TextInput(
105
- name="Image URL to classify",
106
- value=pn.bind(random_url, randomize_url),
107
- )
108
- class_names = pn.widgets.TextInput(
109
- name="Comma separated class names",
110
- placeholder="Enter possible class names, e.g. cat, dog",
111
- value="cat, dog, parrot",
112
- )
113
-
114
- input_widgets = pn.Column(
115
- "##### 😊 Click randomize or paste a URL to start classifying!",
116
- pn.Row(image_url, randomize_url),
117
- class_names,
118
- )
119
-
120
- # add interactivity
121
- interactive_result = pn.panel(
122
- pn.bind(process_inputs, image_url=image_url, class_names=class_names),
123
- height=600,
124
- )
125
-
126
- # add footer
127
- footer_row = pn.Row(pn.Spacer(), align="center")
128
- for icon, url in ICON_URLS.items():
129
- href_button = pn.widgets.Button(icon=icon, width=35, height=35)
130
- href_button.js_on_click(code=f"window.open('{url}')")
131
- footer_row.append(href_button)
132
- footer_row.append(pn.Spacer())
133
-
134
- # create dashboard
135
- main = pn.WidgetBox(
136
- input_widgets,
137
- interactive_result,
138
- footer_row,
139
- )
140
-
141
- title = "Panel Demo - Image Classification"
142
- pn.template.BootstrapTemplate(
143
- title=title,
144
- main=main,
145
- main_max_width="min(50%, 698px)",
146
- header_background="#F08080",
147
- ).servable(title=title)
 
 
 
 
 
 
1
  import panel as pn
2
+ import numpy as np
3
+
4
+ from matplotlib.figure import Figure
5
+ from matplotlib.colors import LinearSegmentedColormap
6
+ from matplotlib.markers import MarkerStyle
7
+
8
+ # pn.extension(design='material')
9
+ WIDTH = 800
10
+ BG_COLOR = '#646464'
11
+
12
+
13
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
14
+ def angles_to_unit_vector(heading_angle_degrees, incidence_angle_degrees, left_looking=True):
15
+ # Convert angles to radians
16
+ heading_angle_start_at_east = 90 - heading_angle_degrees
17
+ look_offset = 90 if left_looking else -90
18
+ heading_los = heading_angle_start_at_east + look_offset
19
+ heading_angle_radians = np.radians(heading_los)
20
+
21
+ incidence_angle_sensor_to_ground = -(90 - incidence_angle_degrees)
22
+ incidence_angle_radians = np.radians(incidence_angle_sensor_to_ground)
23
+
24
+ # Calculate the vector components
25
+ x_component = np.cos(heading_angle_radians) * np.cos(incidence_angle_radians)
26
+ y_component = np.sin(heading_angle_radians) * np.cos(incidence_angle_radians)
27
+ z_component = np.sin(incidence_angle_radians)
28
+
29
+ # Create a NumPy array for the vector
30
+ vector = np.array([x_component, y_component, z_component])
31
+
32
+ # Normalize the vector to obtain the unit vector
33
+ unit_vector = (vector / np.linalg.norm(vector)).round(5)
34
+
35
+ return unit_vector
36
+
37
+
38
+ def unit_vector_to_hex(unit_vector):
39
+ centered_rgb = (unit_vector * 127.5) + 127.5
40
+ # r, g, b = centered_rgb.round(0).astype(int)
41
+ r, b, g = centered_rgb.round(0).astype(int)
42
+ hex_color = f'#{r:02X}{g:02X}{b:02X}'
43
+ return hex_color
44
 
 
45
 
46
+ def angles_to_hex(heading_angle_degrees, incidence_angle_degrees, left_looking=True):
47
+ unit_vector = angles_to_unit_vector(heading_angle_degrees, incidence_angle_degrees, left_looking)
48
+ hex = unit_vector_to_hex(unit_vector)
49
+ return hex
 
 
 
50
 
51
 
52
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
 
 
 
 
 
53
 
54
 
55
+ def get_heading_line(vector):
56
+ if vector[0] == 0 and vector[1] == 0:
57
+ return [0, 0], [0, 0]
 
 
 
 
58
 
59
+ projected_vector = vector.copy()
60
+ projected_vector[2] = 0
61
+ unit_vector = (projected_vector / np.linalg.norm(projected_vector)).round(5)
62
+ x = [0, unit_vector[0]]
63
+ y = [0, unit_vector[1]]
64
+ return x, y
65
 
 
 
 
 
66
 
67
+ def get_azimuth_line(vector, left_looking=True):
68
+ if vector[0] == 0 and vector[1] == 0:
69
+ return [0, 0], [0, 0]
70
 
71
+ projected_vector = vector.copy()
72
+ projected_vector[2] = 0
73
+ look_offset = 90 if left_looking else -90
74
+ angle_rad = np.deg2rad(-look_offset)
75
+ rotation_matrix = np.array(
76
+ [[np.cos(angle_rad), -np.sin(angle_rad), 0], [np.sin(angle_rad), np.cos(angle_rad), 0], [0, 0, 1]]
77
  )
78
+ rotated_vector = np.dot(rotation_matrix, projected_vector)
79
+ unit_vector = (rotated_vector / np.linalg.norm(projected_vector)).round(5)
80
+ half_vector = unit_vector * 0.5
81
+ x = [0, half_vector[0]]
82
+ y = [0, half_vector[1]]
83
+ return x, y
84
+
85
+
86
+ def satellite_marker(axis, angle=0, center=(0, 0)):
87
+ # TODO: update to below when pyodide matplotlib version>=3.7.2
88
+ # t = Affine2D().rotate_deg(angle)
89
+ # marker = MarkerStyle('_', transform=t)
90
+ marker = MarkerStyle('_')
91
+ marker._transform.rotate_deg(angle)
92
+
93
+ axis.scatter(center[0], center[1], marker=marker, s=3000, lw=4, color='black', zorder=1000)
94
+ axis.scatter(center[0], center[1], marker=MarkerStyle('o'), s=200, color='black', zorder=1001)
95
+
96
+
97
+ def get_params(heading_angle, incidence_angle, look_direction):
98
+ left_looking = look_direction == 'Left Looking'
99
+ away_vector = angles_to_unit_vector(heading_angle, incidence_angle, left_looking)
100
+ away_color = unit_vector_to_hex(away_vector)
101
+ towards_vector = angles_to_unit_vector(heading_angle, 180 + incidence_angle, left_looking)
102
+ towards_color = unit_vector_to_hex(towards_vector)
103
+ return away_vector, left_looking, (away_color, towards_color)
104
+
105
+
106
+ def plot_look_direction(params):
107
+ away_vector, left_looking, (away_color, _) = params
108
+
109
+ x, y = get_heading_line(away_vector)
110
+ az_x, az_y = get_azimuth_line(away_vector, left_looking)
111
+ unit_circle = np.linspace(0, np.pi * 2, 500)
112
+
113
+ fig = Figure(figsize=(6, 6))
114
+ ax = fig.subplots()
115
+ ax.plot(np.cos(unit_circle), np.sin(unit_circle), linewidth=1, color=BG_COLOR, zorder=2)
116
+ ax.plot(az_x, az_y, color='lightgray', linestyle='--', label='Azimuth Direction', zorder=4)
117
+ ax.plot(x, y, color=away_color, linestyle='--', label='Look Direction', zorder=3)
118
+ angle = np.rad2deg(np.arctan2(away_vector[1], away_vector[0]))
119
+ satellite_marker(ax, angle)
120
+
121
+ ax.set(xlabel=None, ylabel=None, xlim=(-1.1, 1.1), ylim=(-1.1, 1.1), aspect='equal', facecolor=(0, 0, 0, 0))
122
+ ax.spines['left'].set(position='center', color=BG_COLOR, zorder=0)
123
+ ax.spines['bottom'].set(position='center', color=BG_COLOR, zorder=1)
124
+ ax.spines['top'].set_visible(False)
125
+ ax.spines['right'].set_visible(False)
126
+ ax.xaxis.set_ticks([-1, 1], labels=[270, 90], zorder=5)
127
+ ax.yaxis.set_ticks([-1, 1], labels=[180, 0])
128
+ ax.legend(loc='upper left')
129
+
130
+ fig.patch.set_alpha(0.0)
131
+ fig.tight_layout()
132
+ mpl_pane = pn.pane.Matplotlib(fig, format='svg', dpi=150, width=WIDTH // 2)
133
+ return mpl_pane
134
+
135
+
136
+ def get_incidence_line(vector, left_looking, vertical_offset=1):
137
+ if vector[2] == 0:
138
+ x = np.array([100, 0, -100])
139
+ y = np.array([0, 0, 0])
140
+ elif vector[0] == 0 and vector[1] == 0:
141
+ x = np.array([0, 0, 0])
142
+ y = np.array([-1, 0, 1])
143
+ else:
144
+ slope = vector[2] / np.sqrt(vector[0] ** 2 + vector[1] ** 2)
145
+ y = np.array([-1, 0, 1])
146
+ x = y / slope
147
+
148
+ if left_looking:
149
+ x *= -1
150
+
151
+ y += vertical_offset
152
+ return x, y
153
+
154
+
155
+ def plot_incidence_angle(params):
156
+ away_vector, left_looking, (away_color, towards_color) = params
157
+ x, y = get_incidence_line(away_vector, left_looking)
158
+
159
+ fig = Figure(figsize=(6, 6))
160
+ ax = fig.subplots()
161
+ ax.plot(x[:2], y[:2], linewidth=2, linestyle='--', color=away_color, label='Away from Satellite', zorder=2)
162
+ ax.plot(x[1:], y[1:], linewidth=2, linestyle='--', color=towards_color, label='Towards Satellite', zorder=3)
163
+ angle = np.rad2deg(np.arccos(away_vector[2]))
164
+ angle = angle if left_looking else angle + (2 * (180 - angle))
165
+ satellite_marker(ax, angle, (0, 1))
166
+
167
+ ax.set(xlabel=None, ylabel=None, xlim=(-1.25, 1.25), ylim=(0, 2.5), aspect='equal', facecolor=(0, 0, 0, 0))
168
+ ax.spines['left'].set(position='center', color=BG_COLOR, zorder=0)
169
+ ax.spines['bottom'].set(color=BG_COLOR, zorder=1)
170
+ ax.spines['top'].set_visible(False)
171
+ ax.spines['right'].set_visible(False)
172
+ ax.xaxis.set_ticks([])
173
+ ax.yaxis.set_ticks([])
174
+ ax.legend(loc='upper left')
175
+
176
+ fig.patch.set_alpha(0.0)
177
+ fig.tight_layout()
178
+ mpl_pane = pn.pane.Matplotlib(fig, format='svg', dpi=150, width=WIDTH // 2)
179
+ return mpl_pane
180
+
181
+
182
+ def plot_color_gradient(params):
183
+ _, _, (away_color, towards_color) = params
184
+ custom_cmap = LinearSegmentedColormap.from_list('custom diverging', [towards_color, 'white', away_color], N=256)
185
+
186
+ gradient = np.linspace(0, 1, 256)
187
+ gradient = np.vstack((gradient, gradient))
188
+
189
+ fontsize = 14
190
+ fig = Figure(figsize=(12, 1.5))
191
+ ax = fig.subplots()
192
+ inset = ax.inset_axes([0.05, 0.05, 0.9, 0.5])
193
+ inset.imshow(gradient, aspect='auto', cmap=custom_cmap)
194
+ ax.annotate(
195
+ f'Towards satellite\n{towards_color}',
196
+ xy=[0.05, 0.6],
197
+ fontsize=fontsize,
198
+ horizontalalignment='left',
199
+ verticalalignment='bottom',
200
+ )
201
+ ax.annotate('#FFFFFF', xy=[0.5, 0.6], fontsize=fontsize, horizontalalignment='center', verticalalignment='bottom')
202
+ ax.annotate(
203
+ f'Away from satellite\n{away_color}',
204
+ fontsize=fontsize,
205
+ xy=[0.95, 0.6],
206
+ horizontalalignment='right',
207
+ verticalalignment='bottom',
208
  )
209
+ inset.set_axis_off()
210
+ ax.set_axis_off()
211
+ ax.set(facecolor=(0, 0, 0, 0))
212
+
213
+ fig.patch.set_alpha(0.0)
214
+ fig.tight_layout()
215
+ mpl_pane = pn.pane.Matplotlib(fig, format='svg', dpi=150, width=WIDTH)
216
+ return mpl_pane
217
+
218
+
219
+ def reset_widgets(menu_value):
220
+ options = {
221
+ 's1a': (348, 34, 'Left Looking'),
222
+ 's1d': (193, 34, 'Left Looking'),
223
+ 'vert': (0, 0, 'Left Looking'),
224
+ 'we': (0, 90, 'Left Looking'),
225
+ 'sn': (90, 90, 'Left Looking'),
226
+ }
227
+ heading_slider.value, incidence_slider.value, look_switch.value = options[menu_value]
228
+
229
+
230
+ def on_menu_change(event):
231
+ selected_option = event.new
232
+ reset_widgets(selected_option)
233
+
234
+
235
+ opts = dict(align=('center', 'center'), width=int(WIDTH / 4.5))
236
+ heading_slider = pn.widgets.IntSlider(name='Satellite Heading', start=0, end=360, step=1, value=360 - 12, **opts)
237
+ incidence_slider = pn.widgets.IntSlider(name='Incidence Angle', start=0, end=90, step=1, value=34, **opts)
238
+ look_switch = pn.widgets.ToggleGroup(options=['Left Looking', 'Right Looking'], behavior='radio', **opts)
239
+ menu_items = [
240
+ ('Sentinel-1 Ascending', 's1a'),
241
+ ('Sentinel-1 Descending', 's1d'),
242
+ ('Vertical', 'vert'),
243
+ ('West-East', 'we'),
244
+ ('South-North', 'sn'),
245
+ ]
246
+ menu = pn.widgets.MenuButton(name='Presets', items=menu_items, button_type='primary', **opts)
247
+ menu.on_click(on_menu_change)
248
+
249
+ params = pn.bind(get_params, heading_slider, incidence_slider, look_switch)
250
+ interactive_look = pn.bind(plot_look_direction, params)
251
+ interactive_incidence = pn.bind(plot_incidence_angle, params)
252
+ interactive_color = pn.bind(plot_color_gradient, params)
253
+ pn.Column(
254
+ pn.Row(menu, heading_slider, incidence_slider, look_switch, height=100),
255
+ pn.Row(interactive_look, interactive_incidence),
256
+ pn.Row(interactive_color),
257
+ ).servable()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,6 +1,4 @@
1
- panel
2
- jupyter
3
- transformers
4
- numpy
5
- torch
6
- aiohttp
 
1
+ panel;
2
+ ipykernel;
3
+ ipywidgets-bokeh;
4
+ matplotlib;