From 88e735dd51a46a030b8f23bbf05bd07c6ae3aae7 Mon Sep 17 00:00:00 2001 From: iferencik <92793531+iferencik@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:45:08 +0200 Subject: [PATCH] update RCA algo and set RELOAD in docker-compose to --reload (#78) Co-authored-by: janf --- docker-compose.yml | 2 + src/cogserver/algorithms/rca.py | 116 +++++++++++++++++++------------- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7604edf..1c94672 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: - "./src/cogserver:/opt/server/cogserver" env_file: - ./gdal_rio.env + environment: + - RELOAD=--reload # the above env file contains the $PORT variable. ports: - "${PORT}:${PORT}" diff --git a/src/cogserver/algorithms/rca.py b/src/cogserver/algorithms/rca.py index ba49e9b..279ebed 100644 --- a/src/cogserver/algorithms/rca.py +++ b/src/cogserver/algorithms/rca.py @@ -1,79 +1,101 @@ from typing import Sequence - import numpy +from typing import List from pydantic import Field from rio_tiler.models import ImageData - from titiler.core.algorithm.base import BaseAlgorithm + + + + + class RapidChangeAssessment(BaseAlgorithm): title: str = "Rapid Change Assessment Tool" - description: str = "Quick assessment to detect changes by comparing two bands" + description: str = "Detect changes by subtracting relative radiances between two images taken between and after an event" """Rapid change assessment.""" # parameters - threshold: float = Field( - 0.8, ge=0.0, le=1.0, - title="Threshold(%)", - description="Threshold (%) to mask changes which is ranged between 0 and 1" - ) + # threshold: float = Field( + # default=0, ge=0.0, le=1.0, + # title="Threshold(%)", + # description="Only pixels with change/decrease above this threshold will be supplied" + # ) + + only_negative: bool = Field( + default=True, + title='Only negative', + description='Compute only pixels whose values have decreased between the two dates' - cloud_mask: bool = Field( - False, - title="Cloud mask", - description="If enabled, cloud will be removed masked by band 3 and band 4." ) - cloud_mask_threshold: int = Field( - 1, + # cloud_mask: bool = Field( + # False, + # title="Cloud mask", + # description="If enabled, cloud will be removed masked by band 3 and band 4." + # ) + + cloud_mask_value: int = Field( + default=1, + ge=0, + le=5, title="Cloud mask threshold", - description="Remove cloud if band value is greater than this threshold" + description="Inclusive cloud threshold. Specifying a value results in considering all lower values", + options_descriptions = ['No clouds', 'Almost no clouds', 'Very few clouds', 'Partially cloudy', 'Cloudy', 'Very cloudy' ] ) + + # metadata input_nbands: int = 2 - input_description: str = "the first two bands will be used to compute change detection. " \ - "the last two bands will be used to mask the result. " \ - "The first two bands are required. " + input_description: str = "The bands that will be used to detect changes" + input_bands: List = [ + {'title': 'Start date image', 'description': 'The image before the event', 'required':True}, + {'title': 'End date image', 'description': 'The image after the event', 'required':True}, + {'title': 'Start date cloud mask', 'description': 'The cloud mask of the image before the event', + 'required': True}, + {'title': 'End date cloud mask', 'description': 'The cloud mask of the image after the event', + 'required': True}, + ] + + # input_second_image_title: str = 'Second image' + # input_second_image_description: str = 'The image after the event' output_nbands: int = 1 - output_dtype: int = "uint8" - output_min: Sequence[int] = [0] - output_max: Sequence[int] = [1] - output_description: str = "Masked result which has changed greater than threshold" + output_dtype: int = "int8" + output_min: Sequence[int] = [-100] + output_max: Sequence[int] = [100] + output_description: str = "Pixels/locations whose values have decreased/changed" def __call__(self, img: ImageData) -> ImageData: """Rapid change assessment.""" - b1 = img.array[0].astype("float16") - b2 = img.array[1].astype("float16") - - diff = numpy.abs(b1 - b2) - total = numpy.abs(b1) + numpy.abs(b2) - - # If the difference is more than threshold, return 1. Otherwise return 0 - data = (diff / total > self.threshold) - - # add additional mask condition to remove cloud - if self.cloud_mask and len(img.array) > 2: - # add the third band to mask - b3 = img.array[2].astype("uint8") - valid_mask = (b3 > self.cloud_mask_threshold) - - if len(img.array) > 3: - # add the fourth band to mask if available - b4 = img.array[3].astype("uint8") - valid_mask = valid_mask | (b4 > self.cloud_mask_threshold) - arr = numpy.ma.masked_array(data, dtype=self.output_dtype, mask=valid_mask) - else: - arr = numpy.ma.masked_array(data, dtype=self.output_dtype) - - bnames = img.band_names + b1 = img.array[0] + b2 = img.array[1] + b2 = b2 / b2.max() + b1 = b1 / b1.max() + valid_mask = (img.array[2].astype('uint8') > self.cloud_mask_value) | (img.array[3].astype('uint8') > self.cloud_mask_value) + diff = b2-b1 + data = diff + v = .1 + #v = data.ptp()*.1 + + datam = (data > -v) & (data < v) + + if self.only_negative: + datam |= data>0 + + + arr = numpy.ma.masked_array(data*100, dtype=self.output_dtype, mask=valid_mask | datam ) + + word = 'changes' if not self.only_negative else 'decrease' + return ImageData( arr, assets=img.assets, crs=img.crs, bounds=img.bounds, - band_names=[f"(abs({bnames[1]} - {bnames[0]}) / ({bnames[1]} + {bnames[0]})) > {self.threshold}"], + band_names=[f"Relative {word} in pixels value"], ) +