OpenDroneMap-WebODM/app/api/formulas.py

269 wiersze
8.6 KiB
Python
Czysty Zwykły widok Historia

2019-11-19 16:15:33 +00:00
# Algos from https://github.com/dirceup/tiled-vegetation-indices/blob/master/app/lib/vegetation_index.rb
2019-11-19 21:27:42 +00:00
# Functions can use all of the supported functions and operators from
# https://numexpr.readthedocs.io/en/latest/user_guide.html#supported-operators
2019-11-19 16:15:33 +00:00
import re
2019-11-19 17:51:17 +00:00
from functools import lru_cache
from django.utils.translation import gettext_lazy as _
2019-11-19 16:15:33 +00:00
algos = {
2019-11-27 16:41:17 +00:00
'NDVI': {
'expr': '(N - R) / (N + R)',
2022-10-01 16:09:42 +00:00
'help': _('Normalized Difference Vegetation Index shows the amount of green vegetation.'),
'range': (-1, 1)
2019-11-27 16:41:17 +00:00
},
2022-07-24 15:59:43 +00:00
'NDYI': {
'expr': '(G - B) / (G + B)',
2022-10-01 16:09:42 +00:00
'help': _('Normalized difference yellowness index (NDYI), best model variability in relative yield potential in Canola.'),
'range': (-1, 1)
2022-07-24 15:59:43 +00:00
},
2022-02-03 20:58:17 +00:00
'NDRE': {
'expr': '(N - Re) / (N + Re)',
2022-10-01 16:09:42 +00:00
'help': _('Normalized Difference Red Edge Index shows the amount of green vegetation of permanent or later stage crops.'),
'range': (-1, 1)
2022-02-03 20:58:17 +00:00
},
2022-02-03 19:46:38 +00:00
'NDWI': {
'expr': '(G - N) / (G + N)',
2022-10-01 16:09:42 +00:00
'help': _('Normalized Difference Water Index shows the amount of water content in water bodies.'),
'range': (-1, 1)
2022-02-03 19:46:38 +00:00
},
'NDVI (Blue)': {
'expr': '(N - B) / (N + B)',
2022-10-01 16:09:42 +00:00
'help': _('Normalized Difference Vegetation Index shows the amount of green vegetation.'),
'range': (-1, 1)
},
'ENDVI':{
'expr': '((N + G) - (2 * B)) / ((N + G) + (2 * B))',
'help': _('Enhanced Normalized Difference Vegetation Index is like NDVI, but uses Blue and Green bands instead of only Red to isolate plant health.')
},
'vNDVI':{
'expr': '0.5268*((R ** -0.1294) * (G ** 0.3389) * (B ** -0.3118))',
'help': _('Visible NDVI is an un-normalized index for RGB sensors using constants derived from citrus, grape, and sugarcane crop data.')
2022-02-03 19:46:38 +00:00
},
2019-11-19 17:51:17 +00:00
'VARI': {
'expr': '(G - R) / (G + R - B)',
'help': _('Visual Atmospheric Resistance Index shows the areas of vegetation.'),
'range': (-1, 1)
},
'MPRI': {
'expr': '(G - R) / (G + R)',
'help': _('Modified Photochemical Reflectance Index'),
'range': (-1, 1)
2019-11-19 17:51:17 +00:00
},
2020-02-19 13:47:30 +00:00
'EXG': {
'expr': '(2 * G) - (R + B)',
2021-08-27 12:56:09 +00:00
'help': _('Excess Green Index (derived from only the RGB bands) emphasizes the greenness of leafy crops such as potatoes.')
2021-08-27 09:39:48 +00:00
},
2019-11-19 17:51:17 +00:00
'BAI': {
'expr': '1.0 / (((0.1 - R) ** 2) + ((0.06 - N) ** 2))',
'help': _('Burn Area Index hightlights burned land in the red to near-infrared spectrum.')
2019-11-19 17:51:17 +00:00
},
'GLI': {
'expr': '((G * 2) - R - B) / ((G * 2) + R + B)',
'help': _('Green Leaf Index shows greens leaves and stems.'),
'range': (-1, 1)
2019-11-19 17:51:17 +00:00
},
'GNDVI':{
'expr': '(N - G) / (N + G)',
2022-10-01 16:09:42 +00:00
'help': _('Green Normalized Difference Vegetation Index is similar to NDVI, but measures the green spectrum instead of red.'),
'range': (-1, 1)
2019-11-19 16:15:33 +00:00
},
2019-11-19 18:35:59 +00:00
'GRVI':{
'expr': 'N / G',
'help': _('Green Ratio Vegetation Index is sensitive to photosynthetic rates in forests.')
2019-11-19 18:35:59 +00:00
},
'SAVI':{
'expr': '(1.5 * (N - R)) / (N + R + 0.5)',
'help': _('Soil Adjusted Vegetation Index is similar to NDVI but attempts to remove the effects of soil areas using an adjustment factor (0.5).')
2019-11-19 18:35:59 +00:00
},
'MNLI':{
'expr': '((N ** 2 - R) * 1.5) / (N ** 2 + R + 0.5)',
'help': _('Modified Non-Linear Index improves the Non-Linear Index algorithm to account for soil areas.')
2019-11-19 18:35:59 +00:00
},
'MSR': {
'expr': '((N / R) - 1) / (sqrt(N / R) + 1)',
'help': _('Modified Simple Ratio is an improvement of the Simple Ratio (SR) index to be more sensitive to vegetation.')
2019-11-19 18:35:59 +00:00
},
'RDVI': {
'expr': '(N - R) / sqrt(N + R)',
'help': _('Renormalized Difference Vegetation Index uses the difference between near-IR and red, plus NDVI to show areas of healthy vegetation.')
2019-11-19 18:35:59 +00:00
},
'TDVI': {
'expr': '1.5 * ((N - R) / sqrt(N ** 2 + R + 0.5))',
'help': _('Transformed Difference Vegetation Index highlights vegetation cover in urban environments.')
2019-11-19 18:35:59 +00:00
},
'OSAVI': {
'expr': '(N - R) / (N + R + 0.16)',
'help': _('Optimized Soil Adjusted Vegetation Index is based on SAVI, but tends to work better in areas with little vegetation where soil is visible.')
2019-11-19 18:35:59 +00:00
},
'LAI': {
'expr': '3.618 * (2.5 * (N - R) / (N + 6*R - 7.5*B + 1)) * 0.118',
'help': _('Leaf Area Index estimates foliage areas and predicts crop yields.'),
2019-12-10 20:31:54 +00:00
'range': (-1, 1)
2019-11-19 18:35:59 +00:00
},
'EVI': {
'expr': '2.5 * (N - R) / (N + 6*R - 7.5*B + 1)',
'help': _('Enhanced Vegetation Index is useful in areas where NDVI might saturate, by using blue wavelengths to correct soil signals.'),
2019-12-10 20:31:54 +00:00
'range': (-1, 1)
2019-11-19 18:35:59 +00:00
},
2023-02-21 07:26:52 +00:00
'ARVI': {
'expr': '(N - (2 * R) + B) / (N + (2 * R) + B)',
'help': _('Atmospherically Resistant Vegetation Index. Useful when working with imagery for regions with high atmospheric aerosol content.'),
'range': (-1, 1)
},
2023-05-18 19:24:39 +00:00
'Celsius': {
'expr': 'L',
2023-05-18 19:24:39 +00:00
'help': _('Temperature in Celsius degrees.')
2022-02-14 20:43:48 +00:00
},
2023-05-18 19:24:39 +00:00
'Kelvin': {
'expr': 'L * 100 + 27315',
2023-05-18 19:24:39 +00:00
'help': _('Temperature in Centikelvin degrees.')
2022-05-11 02:50:01 +00:00
},
2019-11-19 16:15:33 +00:00
2019-11-19 21:27:42 +00:00
# more?
2019-11-19 17:51:17 +00:00
'_TESTRB': {
'expr': 'R + B',
'range': (0,1)
2019-11-19 18:35:59 +00:00
},
'_TESTFUNC': {
'expr': 'R + (sqrt(B) )'
2019-11-19 16:15:33 +00:00
}
}
2019-11-19 21:27:42 +00:00
camera_filters = [
'RGB',
2019-11-27 16:41:17 +00:00
'RGN',
2019-11-19 21:27:42 +00:00
'NGB',
'NRG',
2019-11-27 16:41:17 +00:00
'NRB',
2021-07-08 06:25:09 +00:00
'RGBN',
2023-10-02 14:19:16 +00:00
'RGNRe',
2021-07-08 00:34:54 +00:00
'GRReN',
2023-10-02 14:10:48 +00:00
'RGBNRe',
'BGRNRe',
'BGRReN',
'RGBReN',
2023-10-02 14:10:48 +00:00
'RGBNReL',
'BGRNReL',
'BGRReNL',
2022-02-14 20:43:48 +00:00
'RGBNRePL',
2023-05-18 19:24:39 +00:00
'L', # FLIR camera has a single LWIR band
2019-11-19 21:27:42 +00:00
# more?
2019-11-27 16:41:17 +00:00
# TODO: certain cameras have only two bands? eg. MAPIR NDVI BLUE+NIR
2019-11-19 21:27:42 +00:00
]
@lru_cache(maxsize=20)
2019-11-19 16:15:33 +00:00
def lookup_formula(algo, band_order = 'RGB'):
if algo is None:
return None, None
2019-11-19 16:15:33 +00:00
if band_order is None:
band_order = 'RGB'
if algo not in algos:
raise ValueError("Cannot find algorithm " + algo)
input_bands = tuple(b for b in re.split(r"([A-Z][a-z]*)", band_order) if b != "")
2022-10-01 15:43:26 +00:00
2019-11-19 16:15:33 +00:00
def repl(matches):
b = matches.group(1)
2019-11-19 18:35:59 +00:00
try:
return 'b' + str(input_bands.index(b) + 1)
except ValueError:
raise ValueError("Cannot find band \"" + b + "\" from \"" + band_order + "\". Choose a proper band order.")
2019-11-19 16:15:33 +00:00
expr = re.sub("([A-Z]+?[a-z]*)", repl, re.sub("\s+", "", algos[algo]['expr']))
hrange = algos[algo].get('range', None)
return expr, hrange
2019-11-19 21:27:42 +00:00
2019-11-27 16:41:17 +00:00
@lru_cache(maxsize=2)
def get_algorithm_list(max_bands=3):
2022-07-07 04:44:06 +00:00
res = []
for k in algos:
if k.startswith("_"):
continue
cam_filters = get_camera_filters_for(algos[k]['expr'], max_bands)
2022-07-07 04:44:06 +00:00
if len(cam_filters) == 0:
continue
res.append({
'id': k,
'filters': cam_filters,
**algos[k]
})
return res
2019-11-27 16:41:17 +00:00
@lru_cache(maxsize=100)
def get_camera_filters_for(expr, max_bands=3):
2019-11-27 16:41:17 +00:00
result = []
pattern = re.compile("([A-Z]+?[a-z]*)")
bands = list(set(re.findall(pattern, expr)))
2019-11-27 16:41:17 +00:00
for f in camera_filters:
# Count bands that show up in the filter
count = 0
fbands = list(set(re.findall(pattern, f)))
for b in fbands:
2019-11-27 16:41:17 +00:00
if b in bands:
count += 1
# If all bands are accounted for, this is a valid filter for this algo
if count >= len(bands) and len(fbands) <= max_bands:
2019-11-27 16:41:17 +00:00
result.append(f)
2019-11-19 21:27:42 +00:00
2019-11-27 16:41:17 +00:00
return result
2019-11-19 21:27:42 +00:00
@lru_cache(maxsize=1)
def get_bands_lookup():
bands_aliases = {
'R': ['red', 'r'],
'G': ['green', 'g'],
'B': ['blue', 'b'],
'N': ['nir', 'n'],
'Re': ['rededge', 're'],
'P': ['panchro', 'p'],
'L': ['lwir', 'l']
}
bands_lookup = {}
for band in bands_aliases:
for a in bands_aliases[band]:
bands_lookup[a] = band
return bands_lookup
def get_auto_bands(orthophoto_bands, formula):
algo = algos.get(formula)
if not algo:
raise ValueError("Cannot find formula: " + formula)
max_bands = len(orthophoto_bands) - 1 # minus alpha
filters = get_camera_filters_for(algo['expr'], max_bands)
if not filters:
raise ValueError(f"Cannot find filters for {algo} with max bands {max_bands}")
bands_lookup = get_bands_lookup()
band_order = ""
for band in orthophoto_bands:
if band['name'] == 'alpha' or (not band['description']):
continue
f_band = bands_lookup.get(band['description'].lower())
if f_band is not None:
band_order += f_band
if band_order in filters:
return band_order, True
else:
return filters[0], False # Fallback