From 6dbe51dea7dd572a524cb79240bc0dd00957e7e9 Mon Sep 17 00:00:00 2001 From: Thuhaa Date: Wed, 19 Jun 2024 16:39:16 +0300 Subject: [PATCH 1/5] feat: flood detection algorithm --- Dockerfile | 2 +- requirements.txt | 3 +- src/cogserver/algorithms/__init__.py | 4 +- src/cogserver/algorithms/flood_detection.py | 65 +++++++++++++++++++++ src/cogserver/dependencies.py | 11 ++-- 5 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 src/cogserver/algorithms/flood_detection.py diff --git a/Dockerfile b/Dockerfile index dd58dc1..ff13e40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY src/cogserver cogserver ENV HOST=0.0.0.0 ENV PORT=8000 -ENV LOG_LEVEL=info +ENV LOG_LEVEL=debug ENV RELOAD=--reload ENV WORKERS=1 ENV THREADS=1 diff --git a/requirements.txt b/requirements.txt index 3493a83..3673a3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ postgis==1.0.4 uvicorn==0.29.0 boto3 pyyaml -gunicorn \ No newline at end of file +gunicorn +scikit-image \ No newline at end of file diff --git a/src/cogserver/algorithms/__init__.py b/src/cogserver/algorithms/__init__.py index 713275b..1a11334 100644 --- a/src/cogserver/algorithms/__init__.py +++ b/src/cogserver/algorithms/__init__.py @@ -1,8 +1,10 @@ from titiler.core.algorithm import Algorithms, algorithms as default_algorithms from .rca import RapidChangeAssessment +from .flood_detection import DetectFlood algorithms: Algorithms = default_algorithms.register( { - "rca": RapidChangeAssessment + "rca": RapidChangeAssessment, + "flooding": DetectFlood, } ) diff --git a/src/cogserver/algorithms/flood_detection.py b/src/cogserver/algorithms/flood_detection.py new file mode 100644 index 0000000..57ab4ab --- /dev/null +++ b/src/cogserver/algorithms/flood_detection.py @@ -0,0 +1,65 @@ +from typing import List, Sequence + +import numpy as np +from titiler.core.algorithm import BaseAlgorithm +from rio_tiler.models import ImageData +from skimage.filters import threshold_otsu + + +## Credit: Sashka Warner (https://github.com/sashkaw) + +class DetectFlood(BaseAlgorithm): + title: str = "Flood detection " + description: str = "Algorithm to calculate Modified Normalized Difference Water Index (MNDWI), and apply Otsu thresholding algorithm to identify surface water" + + """ + Desc: Algorithm to calculate Modified Normalized Difference Water Index (MNDWI), + and apply Otsu thresholding algorithm to identify surface water. + """ + + input_bands: List = [ + {'title': 'Green band', 'description': 'The green band with the wavelength between 0.53µm - 0.59µm', + 'required': True, + 'keywords': ['Green band']}, + {'title': 'Short wave infrared band', 'description': 'The SWIR band with wavelength between 0.9μ – 1.7μm', + 'required': True, + 'keywords': ['Shortwave infrared band']}, + ] + input_description: str = "The bands that will be used to make this calculation" + + # Metadata + input_nbands: int = 2 + output_nbands: int = 1 + output_min: Sequence[int] = [-1] + output_max: Sequence[int] = [1] + output_colormap_name: str = 'viridis' + + def __call__(self, img: ImageData, *args, **kwargs): + # Extract bands of interest + green_band = img.data[0].astype("float32") + swir_band = img.data[1].astype("float32") + + # Calculate Modified Normalized Difference Water Index (MNDWI) + numerator = (green_band - swir_band) + denominator = (green_band + swir_band) + # Use np.divide to avoid divide by zero errors + mndwi_arr = np.divide(numerator, denominator, np.zeros_like(numerator), where=denominator != 0) + + # Apply Otsu thresholding method + otsu_threshold = threshold_otsu(mndwi_arr) + + # Use Otsu threshold to classify the computed MNDWI + classified_arr = mndwi_arr >= otsu_threshold + + # Reshape data -> ImageData only accepts image in form of (count, height, width) + # classified_arr = np.around(classified_arr).astype(int) + # classified_arr = np.expand_dims(classified_arr, axis=0).astype(self.output_dtype) + classified_arr = np.expand_dims(classified_arr, axis=0).astype(int) + + return ImageData( + classified_arr, + img.mask, + assets=img.assets, + crs=img.crs, + bounds=img.bounds, + ) diff --git a/src/cogserver/dependencies.py b/src/cogserver/dependencies.py index 7e00366..83308d0 100644 --- a/src/cogserver/dependencies.py +++ b/src/cogserver/dependencies.py @@ -3,17 +3,17 @@ from typing import List import base64 -def parse_signed_url(url:str=None): +def parse_signed_url(url: str = None): if '?' in url: furl, b64token = url.split('?') try: decoded_token = base64.b64decode(b64token).decode() except Exception: decoded_token = b64token - decoded_url = f'{furl}?{decoded_token}' + decoded_url = f'{furl}?{decoded_token}' else: - decoded_url = f'{url}' + decoded_url = f'{url}' return decoded_url @@ -42,6 +42,7 @@ def SignedDatasetPath(url: Annotated[str, Query(description="Unsigned/signed dat """ return parse_signed_url(url=url) + def SignedDatasetPaths(url: Annotated[List[str], Query(description="Unsigned/signed dataset URLs")]) -> str: """ FastAPI dependency function that enables @@ -61,8 +62,8 @@ def SignedDatasetPaths(url: Annotated[List[str], Query(description="Unsigned/sig The returned value is a str representing a RAM stored GDAL VRT file which Titiler will use to resolve the request - Obviously the rasters need to spatially overlap. Additionaly, the VRT can be created with various params - (spatial align, resolution, resmapling) that, to some extent can influence the performace of the server + Obviously the rasters need to spatially overlap. Additionally, the VRT can be created with various params + (spatial align, resolution, resampling) that, to some extent can influence the performance of the server """ decoded_urls = list() From 6de45b8b0f34c6423c89d10999b72fc26d0b7cb2 Mon Sep 17 00:00:00 2001 From: Thuhaa Date: Wed, 19 Jun 2024 16:52:34 +0300 Subject: [PATCH 2/5] added LICENSE text --- src/cogserver/algorithms/flood_detection.py | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/cogserver/algorithms/flood_detection.py b/src/cogserver/algorithms/flood_detection.py index 57ab4ab..17744e5 100644 --- a/src/cogserver/algorithms/flood_detection.py +++ b/src/cogserver/algorithms/flood_detection.py @@ -5,8 +5,31 @@ from rio_tiler.models import ImageData from skimage.filters import threshold_otsu - ## Credit: Sashka Warner (https://github.com/sashkaw) +""" +MIT License + +Copyright (c) 2023 Sashka Warner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + class DetectFlood(BaseAlgorithm): title: str = "Flood detection " From eaf39bcc91a7735c6971f8d984ccaf256d5a197d Mon Sep 17 00:00:00 2001 From: Thuhaa Date: Wed, 19 Jun 2024 16:55:49 +0300 Subject: [PATCH 3/5] refactor: license --- src/cogserver/algorithms/flood_detection.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/cogserver/algorithms/flood_detection.py b/src/cogserver/algorithms/flood_detection.py index 17744e5..22f1f0c 100644 --- a/src/cogserver/algorithms/flood_detection.py +++ b/src/cogserver/algorithms/flood_detection.py @@ -1,10 +1,3 @@ -from typing import List, Sequence - -import numpy as np -from titiler.core.algorithm import BaseAlgorithm -from rio_tiler.models import ImageData -from skimage.filters import threshold_otsu - ## Credit: Sashka Warner (https://github.com/sashkaw) """ MIT License @@ -30,6 +23,14 @@ SOFTWARE. """ +from typing import List, Sequence + +import numpy as np +from titiler.core.algorithm import BaseAlgorithm +from rio_tiler.models import ImageData +from skimage.filters import threshold_otsu + + class DetectFlood(BaseAlgorithm): title: str = "Flood detection " From f25f3e1d84400c00784018cb18d0d58803a795c6 Mon Sep 17 00:00:00 2001 From: Thuhaa Date: Wed, 19 Jun 2024 17:45:55 +0300 Subject: [PATCH 4/5] fix: comments --- Dockerfile | 2 +- src/cogserver/algorithms/__init__.py | 2 +- src/cogserver/algorithms/flood_detection.py | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index ff13e40..dd58dc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY src/cogserver cogserver ENV HOST=0.0.0.0 ENV PORT=8000 -ENV LOG_LEVEL=debug +ENV LOG_LEVEL=info ENV RELOAD=--reload ENV WORKERS=1 ENV THREADS=1 diff --git a/src/cogserver/algorithms/__init__.py b/src/cogserver/algorithms/__init__.py index 1a11334..5a0d33c 100644 --- a/src/cogserver/algorithms/__init__.py +++ b/src/cogserver/algorithms/__init__.py @@ -5,6 +5,6 @@ algorithms: Algorithms = default_algorithms.register( { "rca": RapidChangeAssessment, - "flooding": DetectFlood, + "flood_detection": DetectFlood, } ) diff --git a/src/cogserver/algorithms/flood_detection.py b/src/cogserver/algorithms/flood_detection.py index 22f1f0c..6a3ce1a 100644 --- a/src/cogserver/algorithms/flood_detection.py +++ b/src/cogserver/algorithms/flood_detection.py @@ -44,12 +44,13 @@ class DetectFlood(BaseAlgorithm): input_bands: List = [ {'title': 'Green band', 'description': 'The green band with the wavelength between 0.53µm - 0.59µm', 'required': True, - 'keywords': ['Green band']}, + 'keywords': ['green', 'b3']}, {'title': 'Short wave infrared band', 'description': 'The SWIR band with wavelength between 0.9μ – 1.7μm', 'required': True, - 'keywords': ['Shortwave infrared band']}, + 'keywords': ['swir', 'b6']}, ] input_description: str = "The bands that will be used to make this calculation" + output_description: str = "The output is a binary image where 1 represents water and 0 represents non-water" # Metadata input_nbands: int = 2 From 98e6a99bad18916aa4b7141bdabd12a7f712e65c Mon Sep 17 00:00:00 2001 From: Thuhaa Date: Wed, 19 Jun 2024 18:29:15 +0300 Subject: [PATCH 5/5] fix: descrition and credit --- src/cogserver/algorithms/flood_detection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cogserver/algorithms/flood_detection.py b/src/cogserver/algorithms/flood_detection.py index 6a3ce1a..8f4a610 100644 --- a/src/cogserver/algorithms/flood_detection.py +++ b/src/cogserver/algorithms/flood_detection.py @@ -1,4 +1,4 @@ -## Credit: Sashka Warner (https://github.com/sashkaw) +# Credit: Sashka Warner (https://github.com/sashkaw/flood-data-api) """ MIT License @@ -31,7 +31,6 @@ from skimage.filters import threshold_otsu - class DetectFlood(BaseAlgorithm): title: str = "Flood detection " description: str = "Algorithm to calculate Modified Normalized Difference Water Index (MNDWI), and apply Otsu thresholding algorithm to identify surface water" @@ -50,7 +49,6 @@ class DetectFlood(BaseAlgorithm): 'keywords': ['swir', 'b6']}, ] input_description: str = "The bands that will be used to make this calculation" - output_description: str = "The output is a binary image where 1 represents water and 0 represents non-water" # Metadata input_nbands: int = 2 @@ -58,6 +56,7 @@ class DetectFlood(BaseAlgorithm): output_min: Sequence[int] = [-1] output_max: Sequence[int] = [1] output_colormap_name: str = 'viridis' + output_description: str = "The output is a binary image where 1 represents water and 0 represents non-water" def __call__(self, img: ImageData, *args, **kwargs): # Extract bands of interest