Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
|
2 |
import streamlit as st
|
3 |
import json
|
4 |
import ee
|
@@ -229,77 +228,38 @@ def aggregate_data_yearly(collection):
|
|
229 |
yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
|
230 |
return ee.ImageCollection(yearly_images)
|
231 |
|
232 |
-
#
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
# reducer=ee.Reducer.count(),
|
240 |
-
# geometry=image.geometry(),
|
241 |
-
# scale=60,
|
242 |
-
# maxPixels=1e13
|
243 |
-
# ).get(cloud_band)
|
244 |
-
# cloudy_pixels = cloud_mask.reduceRegion(
|
245 |
-
# reducer=ee.Reducer.sum(),
|
246 |
-
# geometry=image.geometry(),
|
247 |
-
# scale=60,
|
248 |
-
# maxPixels=1e13
|
249 |
-
# ).get(cloud_band)
|
250 |
-
# if total_pixels == 0:
|
251 |
-
# return 0
|
252 |
-
# return ee.Number(cloudy_pixels).divide(ee.Number(total_pixels)).multiply(100)
|
253 |
-
|
254 |
-
def calculate_cloud_percentage(image):
|
255 |
-
# Check if QA60 band exists (Level-1C)
|
256 |
-
bands = image.bandNames().getInfo()
|
257 |
-
if 'QA60' in bands:
|
258 |
-
qa60 = image.select('QA60')
|
259 |
-
opaque_clouds = qa60.bitwiseAnd(1 << 10)
|
260 |
-
cirrus_clouds = qa60.bitwiseAnd(1 << 11)
|
261 |
-
cloud_mask = opaque_clouds.Or(cirrus_clouds)
|
262 |
-
elif 'SCL' in bands: # Check if SCL band exists (Level-2A)
|
263 |
-
scl = image.select('SCL')
|
264 |
-
cloud_mask = scl.eq(8).Or(scl.eq(9)).Or(scl.eq(10)) # Cloud, cirrus, snow/ice
|
265 |
-
else:
|
266 |
-
raise ValueError("Cloud masking bands (QA60 or SCL) not found in the dataset.")
|
267 |
-
|
268 |
-
total_pixels = cloud_mask.reduceRegion(
|
269 |
reducer=ee.Reducer.count(),
|
270 |
geometry=image.geometry(),
|
271 |
scale=60,
|
272 |
maxPixels=1e13
|
273 |
-
).get(
|
274 |
-
|
275 |
cloudy_pixels = cloud_mask.reduceRegion(
|
276 |
reducer=ee.Reducer.sum(),
|
277 |
geometry=image.geometry(),
|
278 |
scale=60,
|
279 |
maxPixels=1e13
|
280 |
-
).get(
|
281 |
-
|
282 |
if total_pixels == 0:
|
283 |
return 0
|
284 |
return ee.Number(cloudy_pixels).divide(ee.Number(total_pixels)).multiply(100)
|
285 |
-
|
|
|
286 |
def preprocess_collection(collection, pixel_cloud_threshold):
|
287 |
def mask_cloudy_pixels(image):
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
cirrus_clouds = qa60.bitwiseAnd(1 << 11)
|
293 |
-
cloud_mask = opaque_clouds.Or(cirrus_clouds)
|
294 |
-
elif 'SCL' in bands: # Level-2A
|
295 |
-
scl = image.select('SCL')
|
296 |
-
cloud_mask = scl.eq(8).Or(scl.eq(9)).Or(scl.eq(10)) # Cloud, cirrus, snow/ice
|
297 |
-
else:
|
298 |
-
raise ValueError("Cloud masking bands (QA60 or SCL) not found in the dataset.")
|
299 |
-
|
300 |
clear_pixels = cloud_mask.Not()
|
301 |
return image.updateMask(clear_pixels)
|
302 |
-
|
303 |
if pixel_cloud_threshold > 0:
|
304 |
return collection.map(mask_cloudy_pixels)
|
305 |
return collection
|
@@ -329,18 +289,19 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
329 |
roi = roi.buffer(-30).bounds()
|
330 |
except ValueError:
|
331 |
return None
|
332 |
-
|
333 |
# Filter collection by date and area first
|
334 |
collection = ee.ImageCollection(dataset_id) \
|
335 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
336 |
.filterBounds(roi)
|
337 |
-
|
338 |
-
|
|
|
|
|
339 |
if pixel_cloud_threshold > 0:
|
340 |
collection = preprocess_collection(collection, pixel_cloud_threshold)
|
341 |
st.write(f"After cloud masking: {collection.size().getInfo()} images")
|
342 |
-
|
343 |
-
# Apply temporal aggregation
|
344 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
345 |
collection = aggregate_data_custom(collection)
|
346 |
elif aggregation_period.lower() == 'daily':
|
@@ -351,8 +312,7 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
351 |
collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
|
352 |
elif aggregation_period.lower() == 'yearly':
|
353 |
collection = aggregate_data_yearly(collection)
|
354 |
-
|
355 |
-
# Process the filtered collection
|
356 |
image_list = collection.toList(collection.size())
|
357 |
processed_weeks = set()
|
358 |
aggregated_results = []
|
@@ -383,7 +343,7 @@ def process_single_geometry(row, start_date_str, end_date_str, dataset_id, selec
|
|
383 |
timestamp = image.get('year')
|
384 |
period_label = 'Year'
|
385 |
date = ee.Date(timestamp).format('YYYY').getInfo()
|
386 |
-
|
387 |
index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, dataset_id, user_scale=user_scale)
|
388 |
try:
|
389 |
index_value = index_image.reduceRegion(
|
@@ -415,23 +375,16 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
415 |
progress_bar = st.progress(0)
|
416 |
progress_text = st.empty()
|
417 |
start_time = time.time()
|
418 |
-
|
419 |
-
# Fetch the original collection
|
420 |
raw_collection = ee.ImageCollection(dataset_id) \
|
421 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str))
|
422 |
-
|
423 |
st.write(f"Original Collection Size: {raw_collection.size().getInfo()}")
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
st.write(f"Filtered Collection Size (After Cloud Masking): {raw_collection.size().getInfo()}")
|
430 |
-
except Exception as e:
|
431 |
-
st.error(f"Cloud masking failed: {str(e)}")
|
432 |
-
st.stop()
|
433 |
-
|
434 |
-
# Use ThreadPoolExecutor to process each geometry
|
435 |
with ThreadPoolExecutor(max_workers=10) as executor:
|
436 |
futures = []
|
437 |
for idx, row in locations_df.iterrows():
|
@@ -453,7 +406,6 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
453 |
user_scale=user_scale
|
454 |
)
|
455 |
futures.append(future)
|
456 |
-
|
457 |
completed = 0
|
458 |
for future in as_completed(futures):
|
459 |
result = future.result()
|
@@ -463,10 +415,10 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
463 |
progress_percentage = completed / total_steps
|
464 |
progress_bar.progress(progress_percentage)
|
465 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
466 |
-
|
467 |
end_time = time.time()
|
468 |
processing_time = end_time - start_time
|
469 |
-
|
470 |
if aggregated_results:
|
471 |
result_df = pd.DataFrame(aggregated_results)
|
472 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
@@ -484,8 +436,8 @@ def process_aggregation(locations_df, start_date_str, end_date_str, dataset_id,
|
|
484 |
else:
|
485 |
return result_df.to_dict(orient='records'), processing_time
|
486 |
return [], processing_time
|
487 |
-
|
488 |
-
#
|
489 |
st.markdown("<h5>Image Collection</h5>", unsafe_allow_html=True)
|
490 |
imagery_base = st.selectbox("Select Imagery Base", ["Sentinel", "Landsat", "MODIS", "VIIRS", "Custom Input"], index=0)
|
491 |
data = {}
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import json
|
3 |
import ee
|
|
|
228 |
yearly_images = ee.List(grouped_by_year.map(calculate_yearly_mean))
|
229 |
return ee.ImageCollection(yearly_images)
|
230 |
|
231 |
+
# Cloud percentage calculation
|
232 |
+
def calculate_cloud_percentage(image, cloud_band='QA60'):
|
233 |
+
qa60 = image.select(cloud_band)
|
234 |
+
opaque_clouds = qa60.bitwiseAnd(1 << 10)
|
235 |
+
cirrus_clouds = qa60.bitwiseAnd(1 << 11)
|
236 |
+
cloud_mask = opaque_clouds.Or(cirrus_clouds)
|
237 |
+
total_pixels = qa60.reduceRegion(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
reducer=ee.Reducer.count(),
|
239 |
geometry=image.geometry(),
|
240 |
scale=60,
|
241 |
maxPixels=1e13
|
242 |
+
).get(cloud_band)
|
|
|
243 |
cloudy_pixels = cloud_mask.reduceRegion(
|
244 |
reducer=ee.Reducer.sum(),
|
245 |
geometry=image.geometry(),
|
246 |
scale=60,
|
247 |
maxPixels=1e13
|
248 |
+
).get(cloud_band)
|
|
|
249 |
if total_pixels == 0:
|
250 |
return 0
|
251 |
return ee.Number(cloudy_pixels).divide(ee.Number(total_pixels)).multiply(100)
|
252 |
+
|
253 |
+
# Preprocessing function
|
254 |
def preprocess_collection(collection, pixel_cloud_threshold):
|
255 |
def mask_cloudy_pixels(image):
|
256 |
+
qa60 = image.select('QA60')
|
257 |
+
opaque_clouds = qa60.bitwiseAnd(1 << 10)
|
258 |
+
cirrus_clouds = qa60.bitwiseAnd(1 << 11)
|
259 |
+
cloud_mask = opaque_clouds.Or(cirrus_clouds)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
clear_pixels = cloud_mask.Not()
|
261 |
return image.updateMask(clear_pixels)
|
262 |
+
|
263 |
if pixel_cloud_threshold > 0:
|
264 |
return collection.map(mask_cloudy_pixels)
|
265 |
return collection
|
|
|
289 |
roi = roi.buffer(-30).bounds()
|
290 |
except ValueError:
|
291 |
return None
|
292 |
+
|
293 |
# Filter collection by date and area first
|
294 |
collection = ee.ImageCollection(dataset_id) \
|
295 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str)) \
|
296 |
.filterBounds(roi)
|
297 |
+
|
298 |
+
st.write(f"After initial filtering: {collection.size().getInfo()} images")
|
299 |
+
|
300 |
+
# Apply pixel cloud masking if threshold > 0
|
301 |
if pixel_cloud_threshold > 0:
|
302 |
collection = preprocess_collection(collection, pixel_cloud_threshold)
|
303 |
st.write(f"After cloud masking: {collection.size().getInfo()} images")
|
304 |
+
|
|
|
305 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
306 |
collection = aggregate_data_custom(collection)
|
307 |
elif aggregation_period.lower() == 'daily':
|
|
|
312 |
collection = aggregate_data_monthly(collection, start_date_str, end_date_str)
|
313 |
elif aggregation_period.lower() == 'yearly':
|
314 |
collection = aggregate_data_yearly(collection)
|
315 |
+
|
|
|
316 |
image_list = collection.toList(collection.size())
|
317 |
processed_weeks = set()
|
318 |
aggregated_results = []
|
|
|
343 |
timestamp = image.get('year')
|
344 |
period_label = 'Year'
|
345 |
date = ee.Date(timestamp).format('YYYY').getInfo()
|
346 |
+
|
347 |
index_image = calculate_custom_formula(image, roi, selected_bands, custom_formula, reducer_choice, dataset_id, user_scale=user_scale)
|
348 |
try:
|
349 |
index_value = index_image.reduceRegion(
|
|
|
375 |
progress_bar = st.progress(0)
|
376 |
progress_text = st.empty()
|
377 |
start_time = time.time()
|
378 |
+
|
|
|
379 |
raw_collection = ee.ImageCollection(dataset_id) \
|
380 |
.filterDate(ee.Date(start_date_str), ee.Date(end_date_str))
|
381 |
+
|
382 |
st.write(f"Original Collection Size: {raw_collection.size().getInfo()}")
|
383 |
+
|
384 |
+
if tile_cloud_threshold > 0 or pixel_cloud_threshold > 0:
|
385 |
+
raw_collection = preprocess_collection(raw_collection, pixel_cloud_threshold)
|
386 |
+
st.write(f"Preprocessed Collection Size: {raw_collection.size().getInfo()}")
|
387 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
with ThreadPoolExecutor(max_workers=10) as executor:
|
389 |
futures = []
|
390 |
for idx, row in locations_df.iterrows():
|
|
|
406 |
user_scale=user_scale
|
407 |
)
|
408 |
futures.append(future)
|
|
|
409 |
completed = 0
|
410 |
for future in as_completed(futures):
|
411 |
result = future.result()
|
|
|
415 |
progress_percentage = completed / total_steps
|
416 |
progress_bar.progress(progress_percentage)
|
417 |
progress_text.markdown(f"Processing: {int(progress_percentage * 100)}%")
|
418 |
+
|
419 |
end_time = time.time()
|
420 |
processing_time = end_time - start_time
|
421 |
+
|
422 |
if aggregated_results:
|
423 |
result_df = pd.DataFrame(aggregated_results)
|
424 |
if aggregation_period.lower() == 'custom (start date to end date)':
|
|
|
436 |
else:
|
437 |
return result_df.to_dict(orient='records'), processing_time
|
438 |
return [], processing_time
|
439 |
+
|
440 |
+
# Streamlit App Logic
|
441 |
st.markdown("<h5>Image Collection</h5>", unsafe_allow_html=True)
|
442 |
imagery_base = st.selectbox("Select Imagery Base", ["Sentinel", "Landsat", "MODIS", "VIIRS", "Custom Input"], index=0)
|
443 |
data = {}
|