MarcSkovMadsen commited on
Commit
2c150e9
1 Parent(s): 7390946
Files changed (3) hide show
  1. Dockerfile +1 -1
  2. app.py +99 -19
  3. utils.py +23 -6
Dockerfile CHANGED
@@ -9,7 +9,7 @@ RUN python3 -m pip install --no-cache-dir --upgrade -r /code/requirements.txt
9
  COPY . .
10
  RUN python3 download_datasets.py
11
 
12
- CMD ["panel", "serve", "/code/app.py", "--address", "0.0.0.0", "--port", "7860", "--allow-websocket-origin", "*", "--num-procs", "4", "--index", "app"]
13
 
14
  RUN chmod 777 data
15
 
 
9
  COPY . .
10
  RUN python3 download_datasets.py
11
 
12
+ CMD ["panel", "serve", "/code/app.py", "--address", "0.0.0.0", "--port", "7860", "--allow-websocket-origin", "*", "--num-procs", "4", "--index", "app", "--reuse-sessions"]
13
 
14
  RUN chmod 777 data
15
 
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import dask.dataframe as dd
2
  import holoviews as hv
3
  import numpy as np
 
4
  import panel as pn
5
  import param
6
  from holoviews.operation.datashader import dynspread, rasterize
@@ -10,10 +11,13 @@ from utils import (
10
  DATASHADER_LOGO,
11
  DATASHADER_URL,
12
  DESCRIPTION,
 
 
13
  MAJOR_TOM_LOGO,
14
  MAJOR_TOM_LYRICS,
15
  MAJOR_TOM_PICTURE,
16
  MAJOR_TOM_REF_URL,
 
17
  PANEL_LOGO,
18
  PANEL_URL,
19
  get_closest_rows,
@@ -52,8 +56,11 @@ class MapInput(pn.viewable.Viewer):
52
  updating = param.Boolean()
53
 
54
  def __panel__(self):
55
- return pn.pane.HoloViews(
56
- self._plot, height=550, width=800, loading=self.param.updating
 
 
 
57
  )
58
 
59
  @param.depends("data", watch=True, on_init=True)
@@ -64,10 +71,8 @@ class MapInput(pn.viewable.Viewer):
64
  data_dask, kdims=["centre_easting", "centre_northing"], vdims=[]
65
  )
66
 
67
- mean_easting = np.mean(points.range("centre_easting"))
68
- mean_northing = np.mean(points.range("centre_northing"))
69
  rangexy = hv.streams.RangeXY(source=points)
70
- tap = hv.streams.Tap(source=points, x=mean_easting, y=mean_northing)
71
 
72
  agg = rasterize(
73
  points, link_inputs=True, x_sampling=0.0001, y_sampling=0.0001
@@ -75,13 +80,13 @@ class MapInput(pn.viewable.Viewer):
75
  dyn = dynspread(agg)
76
  dyn.opts(cmap="kr_r", colorbar=True)
77
 
78
- pointerx = hv.streams.PointerX(x=mean_easting, source=points)
79
- pointery = hv.streams.PointerY(y=mean_northing, source=points)
80
  vline = hv.DynamicMap(lambda x: hv.VLine(x), streams=[pointerx])
81
  hline = hv.DynamicMap(lambda y: hv.HLine(y), streams=[pointery])
82
  tiles = hv.Tiles(
83
  "https://tile.openstreetmap.org/{Z}/{X}/{Y}.png", name="OSM"
84
- ).opts(width=600, height=550, xlabel="Longitude", ylabel="Latitude")
85
 
86
  self.param.update(
87
  _plot=tiles * agg * dyn * hline * vline,
@@ -106,7 +111,7 @@ class MapInput(pn.viewable.Viewer):
106
 
107
  def _update_data_in_view(self, x_range, y_range):
108
  if not x_range or not y_range:
109
- self.data_in_view = self.data.head(0)
110
  return
111
 
112
  data = self.data
@@ -114,16 +119,24 @@ class MapInput(pn.viewable.Viewer):
114
  (data.centre_easting.between(*x_range))
115
  & (data.centre_northing.between(*y_range))
116
  ]
117
- self.data_in_view = data.head(10).reset_index(drop=True)
118
 
119
  def _update_data_selected(self, tap_x, tap_y):
120
  self.data_selected = get_closest_rows(self.data, tap_x, tap_y)
121
 
 
 
 
 
122
 
123
  class ImageInput(pn.viewable.Viewer):
124
  data = param.DataFrame(allow_refs=True, allow_None=False)
125
-
 
 
126
  updating = param.Boolean()
 
 
127
  image = param.Parameter()
128
  plot = param.Parameter()
129
 
@@ -131,12 +144,19 @@ class ImageInput(pn.viewable.Viewer):
131
 
132
  def __panel__(self):
133
  return pn.Column(
134
- pn.widgets.RadioButtonGroup.from_param(
135
- self.param._timestamp, button_style="outline"
 
 
 
 
 
 
 
136
  ),
137
  pn.Tabs(
138
  pn.pane.HoloViews(
139
- hv.DynamicMap(pn.bind(lambda plot: plot, self.param.plot)),
140
  loading=self.param.updating,
141
  height=800,
142
  width=800,
@@ -148,6 +168,13 @@ class ImageInput(pn.viewable.Viewer):
148
  loading=self.param.updating,
149
  width=800,
150
  ),
 
 
 
 
 
 
 
151
  dynamic=True,
152
  ),
153
  )
@@ -167,16 +194,69 @@ class ImageInput(pn.viewable.Viewer):
167
  if not self._timestamp in options:
168
  self._timestamp = default_value
169
 
170
- @pn.depends("_timestamp", watch=True, on_init=True)
171
- def tap_image(self):
 
 
 
 
172
  if self.data.empty or not self._timestamp:
173
- self.image = hv.RGB(np.array([]))
 
 
174
  else:
175
  with self.param.update(updating=True):
176
  row = self.data[self.data.timestamp == self._timestamp].iloc[0]
177
- self.image = image = pn.cache(get_image)(row)
 
178
  image_array = np.array(image)
179
- self.plot = hv.RGB(image_array).opts(xaxis=None, yaxis=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
 
182
  class App(param.Parameterized):
 
1
  import dask.dataframe as dd
2
  import holoviews as hv
3
  import numpy as np
4
+ import pandas as pd
5
  import panel as pn
6
  import param
7
  from holoviews.operation.datashader import dynspread, rasterize
 
11
  DATASHADER_LOGO,
12
  DATASHADER_URL,
13
  DESCRIPTION,
14
+ ESA_EASTING,
15
+ ESA_NORTHING,
16
  MAJOR_TOM_LOGO,
17
  MAJOR_TOM_LYRICS,
18
  MAJOR_TOM_PICTURE,
19
  MAJOR_TOM_REF_URL,
20
+ META_DATA_COLUMNS,
21
  PANEL_LOGO,
22
  PANEL_URL,
23
  get_closest_rows,
 
56
  updating = param.Boolean()
57
 
58
  def __panel__(self):
59
+ return pn.Column(
60
+ pn.pane.HoloViews(
61
+ self._plot, height=550, width=800, loading=self.param.updating
62
+ ),
63
+ self._description,
64
  )
65
 
66
  @param.depends("data", watch=True, on_init=True)
 
71
  data_dask, kdims=["centre_easting", "centre_northing"], vdims=[]
72
  )
73
 
 
 
74
  rangexy = hv.streams.RangeXY(source=points)
75
+ tap = hv.streams.Tap(source=points, x=ESA_EASTING, y=ESA_NORTHING)
76
 
77
  agg = rasterize(
78
  points, link_inputs=True, x_sampling=0.0001, y_sampling=0.0001
 
80
  dyn = dynspread(agg)
81
  dyn.opts(cmap="kr_r", colorbar=True)
82
 
83
+ pointerx = hv.streams.PointerX(x=ESA_EASTING, source=points)
84
+ pointery = hv.streams.PointerY(y=ESA_NORTHING, source=points)
85
  vline = hv.DynamicMap(lambda x: hv.VLine(x), streams=[pointerx])
86
  hline = hv.DynamicMap(lambda y: hv.HLine(y), streams=[pointery])
87
  tiles = hv.Tiles(
88
  "https://tile.openstreetmap.org/{Z}/{X}/{Y}.png", name="OSM"
89
+ ).opts(xlabel="Longitude", ylabel="Latitude")
90
 
91
  self.param.update(
92
  _plot=tiles * agg * dyn * hline * vline,
 
111
 
112
  def _update_data_in_view(self, x_range, y_range):
113
  if not x_range or not y_range:
114
+ self.data_in_view = self.data
115
  return
116
 
117
  data = self.data
 
119
  (data.centre_easting.between(*x_range))
120
  & (data.centre_northing.between(*y_range))
121
  ]
122
+ self.data_in_view = data.reset_index(drop=True)
123
 
124
  def _update_data_selected(self, tap_x, tap_y):
125
  self.data_selected = get_closest_rows(self.data, tap_x, tap_y)
126
 
127
+ @pn.depends("data_in_view")
128
+ def _description(self):
129
+ return f"Rows: {len(self.data_in_view):,}"
130
+
131
 
132
  class ImageInput(pn.viewable.Viewer):
133
  data = param.DataFrame(allow_refs=True, allow_None=False)
134
+ column_name = param.Selector(
135
+ default="Thumbnail", objects=list(META_DATA_COLUMNS), label="Image Type"
136
+ )
137
  updating = param.Boolean()
138
+
139
+ meta_data = param.DataFrame()
140
  image = param.Parameter()
141
  plot = param.Parameter()
142
 
 
144
 
145
  def __panel__(self):
146
  return pn.Column(
147
+ pn.Row(
148
+ pn.widgets.RadioButtonGroup.from_param(
149
+ self.param._timestamp,
150
+ button_style="outline",
151
+ align="end",
152
+ ),
153
+ pn.widgets.Select.from_param(
154
+ self.param.column_name, disabled=self.param.updating
155
+ ),
156
  ),
157
  pn.Tabs(
158
  pn.pane.HoloViews(
159
+ self.param.plot,
160
  loading=self.param.updating,
161
  height=800,
162
  width=800,
 
168
  loading=self.param.updating,
169
  width=800,
170
  ),
171
+ pn.widgets.Tabulator(
172
+ self.param.meta_data,
173
+ name="Meta Data",
174
+ loading=self.param.updating,
175
+ disabled=True,
176
+ ),
177
+ pn.pane.Markdown(self.code, name="Code"),
178
  dynamic=True,
179
  ),
180
  )
 
194
  if not self._timestamp in options:
195
  self._timestamp = default_value
196
 
197
+ @property
198
+ def column(self):
199
+ return META_DATA_COLUMNS[self.column_name]
200
+
201
+ @pn.depends("_timestamp", "column_name", watch=True, on_init=True)
202
+ def _update_plot(self):
203
  if self.data.empty or not self._timestamp:
204
+ self.meta_data = self.data.T
205
+ self.image = None
206
+ self.plot = hv.RGB(np.array([]))
207
  else:
208
  with self.param.update(updating=True):
209
  row = self.data[self.data.timestamp == self._timestamp].iloc[0]
210
+ self.meta_data = pd.DataFrame(row)
211
+ self.image = image = pn.cache(get_image)(row, self.column)
212
  image_array = np.array(image)
213
+ if image_array.ndim == 2:
214
+ self.plot = hv.Image(image_array).opts(
215
+ cmap="gray_r", xaxis=None, yaxis=None, colorbar=True
216
+ )
217
+ else:
218
+ self.plot = hv.RGB(image_array).opts(xaxis=None, yaxis=None)
219
+
220
+ @pn.depends("meta_data", "column_name")
221
+ def code(self):
222
+ if self.meta_data.empty:
223
+ return ""
224
+
225
+ parquet_url = self.meta_data.T["parquet_url"].iloc[0]
226
+ parquet_row = self.meta_data.T["parquet_row"].iloc[0]
227
+ return f"""\
228
+ ```python
229
+ from io import BytesIO
230
+
231
+ import holoviews as hv
232
+ import numpy as np
233
+ import panel as pn
234
+ import pyarrow.parquet as pq
235
+ from fsspec.parquet import open_parquet_file
236
+ from PIL import Image
237
+
238
+ pn.extension()
239
+
240
+ parquet_url = "{parquet_url}"
241
+ parquet_row = {parquet_row}
242
+ column = "{self.column}"
243
+ with open_parquet_file(parquet_url, columns=[column]) as f:
244
+ with pq.ParquetFile(f) as pf:
245
+ first_row_group = pf.read_row_group(parquet_row, columns=[column])
246
+
247
+ stream = BytesIO(first_row_group[column][0].as_py())
248
+ image = Image.open(stream)
249
+ image_array = np.array(image)
250
+ if image_array.ndim==2:
251
+ plot = hv.Image(image_array).opts(cmap="gray", colorbar=True)
252
+ else:
253
+ plot = hv.RGB(image_array)
254
+
255
+ plot.opts(xaxis=None, yaxis=None)
256
+
257
+ pn.panel(plot).servable()
258
+ ```
259
+ """
260
 
261
 
262
  class App(param.Parameterized):
utils.py CHANGED
@@ -20,6 +20,24 @@ DATASHADER_LOGO = "https://datashader.org/_static/logo_horizontal.svg"
20
  DATASHADER_URL = "https://datashader.org/"
21
  REPOSITORY = "Major-TOM"
22
  DATASETS = ["Core-S2L2A", "Core-S2L1C"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  DATA_PATH = Path(__file__).parent / "data"
25
 
@@ -129,16 +147,15 @@ def get_meta_data(dataset="Core-S2L2A", repository=REPOSITORY):
129
  return data
130
 
131
 
132
- def get_image(row):
133
  parquet_url = row["parquet_url"]
134
  parquet_row = row["parquet_row"]
135
- print(parquet_url)
136
- print(parquet_row)
137
- with open_parquet_file(parquet_url, columns=["thumbnail"]) as f:
138
  with pq.ParquetFile(f) as pf:
139
- first_row_group = pf.read_row_group(parquet_row, columns=["thumbnail"])
140
 
141
- stream = BytesIO(first_row_group["thumbnail"][0].as_py())
142
  image = Image.open(stream)
143
  return image
144
 
 
20
  DATASHADER_URL = "https://datashader.org/"
21
  REPOSITORY = "Major-TOM"
22
  DATASETS = ["Core-S2L2A", "Core-S2L1C"]
23
+ ESA_EASTING = 250668.73322714816
24
+ ESA_NORTHING = 6259216.653115547
25
+ META_DATA_COLUMNS = {
26
+ "Coastal aerosol": "B01",
27
+ "Blue": "B02",
28
+ "Green": "B03",
29
+ "Red": "B04",
30
+ "Vegetation Blue": "B05",
31
+ "Vegetation Green": "B06",
32
+ "Vegetation Red": "B07",
33
+ "NIR": "B08",
34
+ "Narrow NIR": "B8A",
35
+ "Water vapour": "B09",
36
+ "SWIR, 1613.7": "B11",
37
+ "SWIR, 2202.4": "B12",
38
+ "Cloud Mask": "cloud_mask",
39
+ "Thumbnail": "thumbnail",
40
+ }
41
 
42
  DATA_PATH = Path(__file__).parent / "data"
43
 
 
147
  return data
148
 
149
 
150
+ def get_image(row, column="thumbnail"):
151
  parquet_url = row["parquet_url"]
152
  parquet_row = row["parquet_row"]
153
+ print(parquet_url, parquet_row, column)
154
+ with open_parquet_file(parquet_url, columns=[column]) as f:
 
155
  with pq.ParquetFile(f) as pf:
156
+ first_row_group = pf.read_row_group(parquet_row, columns=[column])
157
 
158
+ stream = BytesIO(first_row_group[column][0].as_py())
159
  image = Image.open(stream)
160
  return image
161