Dingyi6 commited on
Commit
4d95bf2
·
1 Parent(s): 4b80bf9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -24
app.py CHANGED
@@ -3,6 +3,7 @@ import sys
3
  import time
4
  import io
5
  import json
 
6
  from utils import print_with_line_number
7
 
8
  # Add the parent directory to the Python path
@@ -17,8 +18,10 @@ import math
17
  from typing import Optional
18
  from shiny import App, render, ui, reactive, Inputs, Outputs, Session, req
19
  import ipyleaflet as L
 
20
  from htmltools import css
21
  import numpy as np
 
22
  # from PIL import Image
23
  from shinywidgets import output_widget, reactive_read, register_widget
24
  from geopy.geocoders import Nominatim
@@ -106,19 +109,31 @@ data_to_map = {
106
  }
107
 
108
  # gradient_settings = {
109
- # "structure parameter": {0: 'blue', 0.6: 'cyan', 1.0: 'lime'},
110
- # "Chlorophylla+b content (µg/cm2)": {0: 'green', 0.6: 'lime', 1.0: 'yellow'},
111
- # "Carotenoids content (µg/cm2)": {0: 'orange', 0.6: 'red', 1.0: 'maroon'},
112
- # "Equivalent Water content (cm)": {0: 'navy', 0.6: 'blue', 1.0: 'aqua'},
113
- # "Leaf Mass per Area (g/cm2)": {0: 'purple', 0.6: 'fuchsia', 1.0: 'pink'}
114
  # }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  gradient_settings = {
117
- "structure parameter": {0.2: 'rgba(0, 0, 255, 1.0)', 0.6: 'rgba(0, 255, 255, 1.0)', 1.0: 'rgba(0, 255, 0, 1.0)'},
118
- "Chlorophylla+b content (µg/cm2)": {0.2: 'rgba(0, 128, 0, 1.0)', 0.6: 'rgba(127, 255, 0, 1.0)', 1.0: 'rgba(255, 255, 0, 1.0)'},
119
- "Carotenoids content (µg/cm2)": {0.2: 'rgba(255, 69, 0, 1.0)', 0.6: 'rgba(255, 0, 0, 1.0)', 1.0: 'rgba(139, 0, 0, 1.0)'},
120
- "Equivalent Water content (cm)": {0.2: 'rgba(0, 0, 139, 1.0)', 0.6: 'rgba(65, 105, 225, 1.0)', 1.0: 'rgba(0, 191, 255, 1.0)'},
121
- "Leaf Mass per Area (g/cm2)": {0.2: 'rgba(75, 0, 130, 1.0)', 0.6: 'rgba(148, 0, 211, 1.0)', 1.0: 'rgba(255, 20, 147, 1.0)'}
122
  }
123
 
124
  print_with_line_number("Finish loading the ANN model!")
@@ -174,11 +189,18 @@ app_ui = ui.page_fluid(
174
  ui.span("3.Currently, the analysis does not support multiple polygons. The application will only recognize the last polygoned area."),
175
  ui.br(),
176
  ui.span("4.After analyzing the data of the drawn area, the webpage may experience slower loading speeds and delays. Please be patient and wait after performing an operation."),
 
 
 
 
177
  ),
178
  ui.layout_sidebar(
179
  ui.panel_sidebar(
180
  ui.div(
181
- ui.input_slider("zoom", "Map zoom level", value=12, min=1, max=18),
 
 
 
182
  ui.div(
183
  ui.input_numeric("lat", "Latitude", value=38.53667742),
184
  ui.input_numeric("long", "Longitude", value=-121.75387309),
@@ -195,14 +217,15 @@ app_ui = ui.page_fluid(
195
  ui.output_text("Cm_range"),
196
  style=css(display="flex", justify_content="center", align_items="center", gap="2rem"),
197
  ),
198
- ui.strong("If you are unable to zoom in or out of the map using the mouse scroll wheel, please use the slide bar provided above to zoom directly."),
199
  ),
200
  ),
201
  output_widget("map"),
202
  ui.strong("Must analyze (to renew the image information) before downloading polygoned area tif file."),
203
  ui.div(
204
  ui.input_action_button("analyze", "Analyze", class_="btn-success"),
205
- ui.download_button("download_polygon", "Download polygoned area data as tif", class_="btn-success"),
 
206
  style=css(display="flex", justify_content="center", align_items="center", gap="2rem"),
207
  ),
208
  )
@@ -212,10 +235,11 @@ def server(input, output, session):
212
  # Initialize Earth Engine
213
  ee.Initialize(credentials)
214
 
215
- global address_line, polygoned_image
216
  address_line = None
217
  polygoned_image = None
218
  polygon_data = reactive.Value([])
 
219
  N = reactive.Value("structure parameter")
220
  Cab = reactive.Value("Chlorophylla+b content (µg/cm2)")
221
  Ccx = reactive.Value("Carotenoids content (µg/cm2)")
@@ -278,8 +302,7 @@ def server(input, output, session):
278
  # Check API status
279
  asset_roots = ee.data.getAssetRoots()
280
  if asset_roots:
281
- print("Active Project ID:", asset_roots[0]['id'])
282
- print("API is connected and working: ", asset_roots)
283
  else:
284
  print("API is not connected or not working.")
285
 
@@ -295,6 +318,7 @@ def server(input, output, session):
295
 
296
  # Initialize and display when the session starts (1)
297
  map = L.Map(center=(current_gps['location']['lat'], current_gps['location']['lng']), zoom=12, scroll_wheel_zoom=True)
 
298
 
299
  @reactive.isolate()
300
  def update_text_inputs(lat: Optional[float], long: Optional[float]) -> None:
@@ -413,7 +437,12 @@ def server(input, output, session):
413
 
414
  heatmap_data = []
415
  data_values = output_datasets[data_to_map[layer_name]]
 
 
 
416
  min_value = min(data_values)
 
 
417
  max_value = max(data_values)
418
 
419
  if (data_to_map[layer_name] == "N"):
@@ -428,7 +457,13 @@ def server(input, output, session):
428
  Cm.set(layer_name + ": " + str(min_value) + " ~ " + str(max_value))
429
 
430
  for coord, n in zip(output_datasets["Coordinates"], data_values):
431
- normalized_value = (n - min_value) / (max_value - min_value)
 
 
 
 
 
 
432
  heatmap_data.append([coord[1], coord[0], normalized_value])
433
 
434
  # Generate new heatmap for this dataset
@@ -447,6 +482,7 @@ def server(input, output, session):
447
  @reactive.Effect
448
  @reactive.event(input.analyze, ignore_none=True, ignore_init=True)
449
  def _():
 
450
  if not polygon_data.get():
451
  return
452
  ui.modal_show(m)
@@ -456,7 +492,8 @@ def server(input, output, session):
456
  print("Polygon: " , polygon)
457
 
458
  # Define Sentinel-2 image collection ("2021-01-01", "2021-12-31")
459
- today = ee.Date(datetime.today().strftime('%Y-%m-%d')) # 获取当天日期并转换为ee.Date格式
 
460
  start_date = today.advance(-15, 'day')
461
 
462
  print("Start Date: ", start_date.format('YYYY-MM-dd').getInfo(), "| End Date: ", today.format('YYYY-MM-dd').getInfo())
@@ -477,6 +514,17 @@ def server(input, output, session):
477
  retry = 5
478
  while(sentinel2.size().getInfo() == 0):
479
  if(retry == 0):
 
 
 
 
 
 
 
 
 
 
 
480
  print("fail to fecth image.")
481
  return
482
  print("wait for fetching.")
@@ -499,7 +547,7 @@ def server(input, output, session):
499
  polygon_area = polygon.area().getInfo()
500
  num = math.ceil(polygon_area / scale / scale)
501
  if (num > 4999):
502
- per_area = math.ceil(polygon_area / 4999)
503
  scale = math.ceil(math.pow(per_area, 1.0/2))
504
 
505
  print("polygon_area(m2): ", polygon_area, "scale: ", scale)
@@ -531,12 +579,17 @@ def server(input, output, session):
531
 
532
  # Convert to NumPy arrays
533
  coords = np.array(coords)
534
- print("coords: ", coords)
535
  input_data = np.array(input_data)
536
- print("input_bands: ", input_data)
537
 
538
  output_datasets = runModel(input_data, loaded_scaler_X, loaded_scaler_Y, model)
539
 
 
 
 
 
 
540
  print_with_line_number("Add a dataset for the coordinates")
541
  output_datasets['Coordinates'] = coords
542
 
@@ -549,7 +602,7 @@ def server(input, output, session):
549
  print("Current navbar page: ", input.navbar_id())
550
 
551
  @session.download(
552
- filename=lambda: f"image-{date.today().isoformat()}-{np.random.randint(100, 999)}.tif"
553
  )
554
  async def download_polygon():
555
  # # Replace this with your ee.Image object
@@ -587,5 +640,29 @@ def server(input, output, session):
587
  yield buf.getvalue()
588
 
589
  print("Image downloaded successfully!")
590
-
591
- app = App(app_ui, server)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import time
4
  import io
5
  import json
6
+ from pathlib import Path
7
  from utils import print_with_line_number
8
 
9
  # Add the parent directory to the Python path
 
18
  from typing import Optional
19
  from shiny import App, render, ui, reactive, Inputs, Outputs, Session, req
20
  import ipyleaflet as L
21
+ from ipywidgets import Layout
22
  from htmltools import css
23
  import numpy as np
24
+ import pandas as pd
25
  # from PIL import Image
26
  from shinywidgets import output_widget, reactive_read, register_widget
27
  from geopy.geocoders import Nominatim
 
109
  }
110
 
111
  # gradient_settings = {
112
+ # "structure parameter": {0.2: 'rgba(0, 0, 255, 1.0)', 0.6: 'rgba(0, 255, 255, 1.0)', 1.0: 'rgba(0, 255, 0, 1.0)'},
113
+ # "Chlorophylla+b content (µg/cm2)": {0.2: 'rgba(0, 128, 0, 1.0)', 0.6: 'rgba(127, 255, 0, 1.0)', 1.0: 'rgba(255, 255, 0, 1.0)'},
114
+ # "Carotenoids content (µg/cm2)": {0.2: 'rgba(255, 69, 0, 1.0)', 0.6: 'rgba(255, 0, 0, 1.0)', 1.0: 'rgba(139, 0, 0, 1.0)'},
115
+ # "Equivalent Water content (cm)": {0.2: 'rgba(0, 0, 139, 1.0)', 0.6: 'rgba(65, 105, 225, 1.0)', 1.0: 'rgba(0, 191, 255, 1.0)'},
116
+ # "Leaf Mass per Area (g/cm2)": {0.2: 'rgba(75, 0, 130, 1.0)', 0.6: 'rgba(148, 0, 211, 1.0)', 1.0: 'rgba(255, 20, 147, 1.0)'}
117
  # }
118
 
119
+ gradient_parula = {
120
+ 0.0: 'rgba(128, 0, 128, 1.0)', # purple
121
+ 0.2: 'rgba(0, 0, 255, 1.0)', # blue
122
+ 0.4: 'rgba(0, 255, 255, 1.0)', # cyan
123
+ 0.5: 'rgba(0, 250, 154, 1.0)', # mediumspringgreen
124
+ 0.6: 'rgba(50, 205, 50, 1.0)', # lime
125
+ 0.7: 'rgba(173, 255, 47, 1.0)', # greenyellow
126
+ 0.8: 'rgba(255, 255, 0, 1.0)', # yellow
127
+ 0.9: 'rgba(255, 165, 0, 1.0)', # orange
128
+ 1.0: 'rgba(255, 0, 0, 1.0)' # red
129
+ }
130
+
131
  gradient_settings = {
132
+ "structure parameter": gradient_parula,
133
+ "Chlorophylla+b content (µg/cm2)": gradient_parula,
134
+ "Carotenoids content (µg/cm2)": gradient_parula,
135
+ "Equivalent Water content (cm)": gradient_parula,
136
+ "Leaf Mass per Area (g/cm2)": gradient_parula
137
  }
138
 
139
  print_with_line_number("Finish loading the ANN model!")
 
189
  ui.span("3.Currently, the analysis does not support multiple polygons. The application will only recognize the last polygoned area."),
190
  ui.br(),
191
  ui.span("4.After analyzing the data of the drawn area, the webpage may experience slower loading speeds and delays. Please be patient and wait after performing an operation."),
192
+ ui.br(),
193
+ ui.strong("If you are unable to zoom in or out of the map using the mouse scroll wheel, please use the slide bar provided above to zoom directly.", style="color: green;"),
194
+ ui.br(),
195
+ ui.strong("We strongly recommend that you use a smaller scale to view the heat map (17 or 18 zoom level), as it will retain more details.", style="color: red;")
196
  ),
197
  ui.layout_sidebar(
198
  ui.panel_sidebar(
199
  ui.div(
200
+ ui.div(
201
+ ui.input_date("date", "Date:"),
202
+ ui.input_slider("zoom", "Map zoom level", value=12, min=1, max=18),
203
+ ),
204
  ui.div(
205
  ui.input_numeric("lat", "Latitude", value=38.53667742),
206
  ui.input_numeric("long", "Longitude", value=-121.75387309),
 
217
  ui.output_text("Cm_range"),
218
  style=css(display="flex", justify_content="center", align_items="center", gap="2rem"),
219
  ),
220
+ ui.img(src="legend.png"),
221
  ),
222
  ),
223
  output_widget("map"),
224
  ui.strong("Must analyze (to renew the image information) before downloading polygoned area tif file."),
225
  ui.div(
226
  ui.input_action_button("analyze", "Analyze", class_="btn-success"),
227
+ ui.download_button("download_polygon", "Download spectral data as tif", class_="btn-success"),
228
+ ui.download_button("download_output", "Download spectral and output data as csv", class_="btn-success"),
229
  style=css(display="flex", justify_content="center", align_items="center", gap="2rem"),
230
  ),
231
  )
 
235
  # Initialize Earth Engine
236
  ee.Initialize(credentials)
237
 
238
+ global address_line, polygoned_image, output_df
239
  address_line = None
240
  polygoned_image = None
241
  polygon_data = reactive.Value([])
242
+ output_df = pd.DataFrame()
243
  N = reactive.Value("structure parameter")
244
  Cab = reactive.Value("Chlorophylla+b content (µg/cm2)")
245
  Ccx = reactive.Value("Carotenoids content (µg/cm2)")
 
302
  # Check API status
303
  asset_roots = ee.data.getAssetRoots()
304
  if asset_roots:
305
+ print("API is connected and working.")
 
306
  else:
307
  print("API is not connected or not working.")
308
 
 
318
 
319
  # Initialize and display when the session starts (1)
320
  map = L.Map(center=(current_gps['location']['lat'], current_gps['location']['lng']), zoom=12, scroll_wheel_zoom=True)
321
+ map.layout = Layout(height='600px')
322
 
323
  @reactive.isolate()
324
  def update_text_inputs(lat: Optional[float], long: Optional[float]) -> None:
 
437
 
438
  heatmap_data = []
439
  data_values = output_datasets[data_to_map[layer_name]]
440
+
441
+ q03 = np.percentile(data_values, 3)
442
+ q97 = np.percentile(data_values, 97)
443
  min_value = min(data_values)
444
+ if min_value < 0:
445
+ min_value = 0
446
  max_value = max(data_values)
447
 
448
  if (data_to_map[layer_name] == "N"):
 
457
  Cm.set(layer_name + ": " + str(min_value) + " ~ " + str(max_value))
458
 
459
  for coord, n in zip(output_datasets["Coordinates"], data_values):
460
+ # normalized_value = (n - min_value) / (max_value - min_value)
461
+ if n <= q03:
462
+ normalized_value = 0
463
+ elif n >= q97:
464
+ normalized_value = 1
465
+ else:
466
+ normalized_value = (n - q03) / (q97 - q03)
467
  heatmap_data.append([coord[1], coord[0], normalized_value])
468
 
469
  # Generate new heatmap for this dataset
 
482
  @reactive.Effect
483
  @reactive.event(input.analyze, ignore_none=True, ignore_init=True)
484
  def _():
485
+ global output_df
486
  if not polygon_data.get():
487
  return
488
  ui.modal_show(m)
 
492
  print("Polygon: " , polygon)
493
 
494
  # Define Sentinel-2 image collection ("2021-01-01", "2021-12-31")
495
+ current_date = input.date()
496
+ today = ee.Date(input.date().strftime('%Y-%m-%d'))
497
  start_date = today.advance(-15, 'day')
498
 
499
  print("Start Date: ", start_date.format('YYYY-MM-dd').getInfo(), "| End Date: ", today.format('YYYY-MM-dd').getInfo())
 
514
  retry = 5
515
  while(sentinel2.size().getInfo() == 0):
516
  if(retry == 0):
517
+ ui.update_date("date", label="Date:", value=current_date)
518
+ ui.modal_remove()
519
+ error_modal = ui.modal(
520
+ "Please do not choose a date that is too far in the future. This application will search for remote sensing data within the two weeks prior to the selected date.",
521
+ title="Something wrong happened, try \"Analyze\" again ",
522
+ easy_close=True,
523
+ size="xl",
524
+ footer=None,
525
+ fade=True
526
+ )
527
+ ui.modal_show(error_modal)
528
  print("fail to fecth image.")
529
  return
530
  print("wait for fetching.")
 
547
  polygon_area = polygon.area().getInfo()
548
  num = math.ceil(polygon_area / scale / scale)
549
  if (num > 4999):
550
+ per_area = math.ceil(polygon_area / 4998)
551
  scale = math.ceil(math.pow(per_area, 1.0/2))
552
 
553
  print("polygon_area(m2): ", polygon_area, "scale: ", scale)
 
579
 
580
  # Convert to NumPy arrays
581
  coords = np.array(coords)
582
+ # print("coords: ", coords)
583
  input_data = np.array(input_data)
584
+ # print("input_bands: ", input_data)
585
 
586
  output_datasets = runModel(input_data, loaded_scaler_X, loaded_scaler_Y, model)
587
 
588
+ # Combine all data
589
+ data_combined = np.column_stack((coords, input_data, output_datasets['N'], output_datasets['Cab'], output_datasets['Ccx'], output_datasets['Cw'], output_datasets['Cm']))
590
+ # Convert to DataFrame
591
+ output_df = pd.DataFrame(data_combined, columns=labels)
592
+
593
  print_with_line_number("Add a dataset for the coordinates")
594
  output_datasets['Coordinates'] = coords
595
 
 
602
  print("Current navbar page: ", input.navbar_id())
603
 
604
  @session.download(
605
+ filename=lambda: f"image-{input.date().isoformat()}-{np.random.randint(100, 999)}.tif"
606
  )
607
  async def download_polygon():
608
  # # Replace this with your ee.Image object
 
640
  yield buf.getvalue()
641
 
642
  print("Image downloaded successfully!")
643
+
644
+ @session.download(
645
+ filename=lambda: f"data-{input.date().isoformat()}-{np.random.randint(100, 999)}.csv"
646
+ )
647
+ def download_output():
648
+ global output_df
649
+ # Check if data is available
650
+ if output_df.empty:
651
+ print("No data available for download.")
652
+ return
653
+
654
+ # Convert dataframe to CSV and encode to bytes
655
+ csv_data = output_df.to_csv(index=False).encode()
656
+
657
+ # Create a StringIO buffer for textual data
658
+ with io.BytesIO() as buf:
659
+ buf.write(csv_data)
660
+ # Reset the buffer's position to the beginning
661
+ buf.seek(0)
662
+ # Create and return a streaming response
663
+ yield buf.getvalue()
664
+
665
+ print("Data downloaded successfully!")
666
+
667
+ static_dir = Path(__file__).parent / "assets"
668
+ app = App(app_ui, server, static_assets=static_dir)