kopia lustrzana https://github.com/OpenDroneMap/WebODM
Adds support for automatically selecting the proper band filter
rodzic
474e2d844b
commit
530720b699
|
@ -156,6 +156,8 @@ camera_filters = [
|
|||
'BGRNReL',
|
||||
'BGRReNL',
|
||||
|
||||
'RGBNRePL',
|
||||
|
||||
'L', # FLIR camera has a single LWIR band
|
||||
|
||||
# more?
|
||||
|
@ -171,7 +173,7 @@ def lookup_formula(algo, 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 != "")
|
||||
|
||||
def repl(matches):
|
||||
|
@ -193,7 +195,7 @@ def get_algorithm_list(max_bands=3):
|
|||
if k.startswith("_"):
|
||||
continue
|
||||
|
||||
cam_filters = get_camera_filters_for(algos[k], max_bands)
|
||||
cam_filters = get_camera_filters_for(algos[k]['expr'], max_bands)
|
||||
|
||||
if len(cam_filters) == 0:
|
||||
continue
|
||||
|
@ -206,9 +208,9 @@ def get_algorithm_list(max_bands=3):
|
|||
|
||||
return res
|
||||
|
||||
def get_camera_filters_for(algo, max_bands=3):
|
||||
@lru_cache(maxsize=100)
|
||||
def get_camera_filters_for(expr, max_bands=3):
|
||||
result = []
|
||||
expr = algo['expr']
|
||||
pattern = re.compile("([A-Z]+?[a-z]*)")
|
||||
bands = list(set(re.findall(pattern, expr)))
|
||||
for f in camera_filters:
|
||||
|
@ -226,3 +228,45 @@ def get_camera_filters_for(algo, max_bands=3):
|
|||
|
||||
return result
|
||||
|
||||
@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
|
||||
|
|
|
@ -23,7 +23,7 @@ from .custom_colormaps_helper import custom_colormaps
|
|||
from app.raster_utils import extension_for_export_format, ZOOM_EXTRA_LEVELS
|
||||
from .hsvblend import hsv_blend
|
||||
from .hillshade import LightSource
|
||||
from .formulas import lookup_formula, get_algorithm_list
|
||||
from .formulas import lookup_formula, get_algorithm_list, get_auto_bands
|
||||
from .tasks import TaskNestedView
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.response import Response
|
||||
|
@ -141,6 +141,12 @@ class Metadata(TaskNestedView):
|
|||
if boundaries_feature == '': boundaries_feature = None
|
||||
if boundaries_feature is not None:
|
||||
boundaries_feature = json.loads(boundaries_feature)
|
||||
|
||||
is_auto_bands_match = False
|
||||
is_auto_bands = False
|
||||
if bands == 'auto' and formula:
|
||||
is_auto_bands = True
|
||||
bands, is_auto_bands_match = get_auto_bands(task.orthophoto_bands, formula)
|
||||
try:
|
||||
expr, hrange = lookup_formula(formula, bands)
|
||||
if defined_range is not None:
|
||||
|
@ -224,6 +230,8 @@ class Metadata(TaskNestedView):
|
|||
|
||||
colormaps = []
|
||||
algorithms = []
|
||||
auto_bands = {'filter': '', 'match': None}
|
||||
|
||||
if tile_type in ['dsm', 'dtm']:
|
||||
colormaps = ['viridis', 'jet', 'terrain', 'gist_earth', 'pastel1']
|
||||
elif formula and bands:
|
||||
|
@ -231,9 +239,14 @@ class Metadata(TaskNestedView):
|
|||
'better_discrete_ndvi',
|
||||
'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'jet', 'jet_r']
|
||||
algorithms = *get_algorithm_list(band_count),
|
||||
if is_auto_bands:
|
||||
auto_bands['filter'] = bands
|
||||
auto_bands['match'] = is_auto_bands_match
|
||||
|
||||
info['color_maps'] = []
|
||||
info['algorithms'] = algorithms
|
||||
info['auto_bands'] = auto_bands
|
||||
|
||||
if colormaps:
|
||||
for cmap in colormaps:
|
||||
try:
|
||||
|
@ -254,6 +267,7 @@ class Metadata(TaskNestedView):
|
|||
info['maxzoom'] += ZOOM_EXTRA_LEVELS
|
||||
info['minzoom'] -= ZOOM_EXTRA_LEVELS
|
||||
info['bounds'] = {'value': src.bounds, 'crs': src.dataset.crs}
|
||||
|
||||
return Response(info)
|
||||
|
||||
|
||||
|
@ -296,6 +310,8 @@ class Tiles(TaskNestedView):
|
|||
if color_map == '': color_map = None
|
||||
if hillshade == '' or hillshade == '0': hillshade = None
|
||||
if tilesize == '' or tilesize is None: tilesize = 256
|
||||
if bands == 'auto' and formula:
|
||||
bands, _ = get_auto_bands(task.orthophoto_bands, formula)
|
||||
|
||||
try:
|
||||
tilesize = int(tilesize)
|
||||
|
@ -611,4 +627,4 @@ class Export(TaskNestedView):
|
|||
else:
|
||||
celery_task_id = export_pointcloud.delay(url, epsg=epsg,
|
||||
format=export_format).task_id
|
||||
return Response({'celery_task_id': celery_task_id, 'filename': filename})
|
||||
return Response({'celery_task_id': celery_task_id, 'filename': filename})
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# Generated by Django 2.2.27 on 2023-10-02 10:21
|
||||
|
||||
import rasterio
|
||||
import os
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations
|
||||
from webodm import settings
|
||||
|
||||
def update_orthophoto_bands_fields(apps, schema_editor):
|
||||
Task = apps.get_model('app', 'Task')
|
||||
|
||||
for t in Task.objects.all():
|
||||
|
||||
bands = []
|
||||
orthophoto_path = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_orthophoto", "odm_orthophoto.tif")
|
||||
|
||||
if os.path.isfile(orthophoto_path):
|
||||
try:
|
||||
with rasterio.open(orthophoto_path) as f:
|
||||
names = [c.name for c in f.colorinterp]
|
||||
for i, n in enumerate(names):
|
||||
bands.append({
|
||||
'name': n,
|
||||
'description': f.descriptions[i]
|
||||
})
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
print("Updating {} (with orthophoto bands: {})".format(t, str(bands)))
|
||||
|
||||
t.orthophoto_bands = bands
|
||||
t.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('app', '0038_remove_task_console_output'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_orthophoto_bands_fields),
|
||||
]
|
|
@ -1024,7 +1024,12 @@ class Task(models.Model):
|
|||
|
||||
if os.path.isfile(orthophoto_path):
|
||||
with rasterio.open(orthophoto_path) as f:
|
||||
bands = [c.name for c in f.colorinterp]
|
||||
names = [c.name for c in f.colorinterp]
|
||||
for i, n in enumerate(names):
|
||||
bands.append({
|
||||
'name': n,
|
||||
'description': f.descriptions[i]
|
||||
})
|
||||
|
||||
self.orthophoto_bands = bands
|
||||
if commit: self.save()
|
||||
|
|
|
@ -134,7 +134,7 @@ export default class LayersControlLayer extends React.Component {
|
|||
|
||||
// Check if bands need to be switched
|
||||
const algo = this.getAlgorithm(e.target.value);
|
||||
if (algo && algo['filters'].indexOf(bands) === -1) bands = algo['filters'][0]; // Pick first
|
||||
if (algo && algo['filters'].indexOf(bands) === -1 && bands !== "auto") bands = algo['filters'][0]; // Pick first
|
||||
|
||||
this.setState({formula: e.target.value, bands});
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ export default class LayersControlLayer extends React.Component {
|
|||
render(){
|
||||
const { colorMap, bands, hillshade, formula, histogramLoading, exportLoading } = this.state;
|
||||
const { meta, tmeta } = this;
|
||||
const { color_maps, algorithms } = tmeta;
|
||||
const { color_maps, algorithms, auto_bands } = tmeta;
|
||||
const algo = this.getAlgorithm(formula);
|
||||
|
||||
let cmapValues = null;
|
||||
|
@ -298,13 +298,17 @@ export default class LayersControlLayer extends React.Component {
|
|||
|
||||
{bands !== "" && algo ?
|
||||
<div className="row form-group form-inline">
|
||||
<label className="col-sm-3 control-label">{_("Filter:")}</label>
|
||||
<label className="col-sm-3 control-label">{_("Bands:")}</label>
|
||||
<div className="col-sm-9 ">
|
||||
{histogramLoading ?
|
||||
<i className="fa fa-circle-notch fa-spin fa-fw" /> :
|
||||
<select className="form-control" value={bands} onChange={this.handleSelectBands}>
|
||||
[<select className="form-control" value={bands} onChange={this.handleSelectBands} title={auto_bands.filter !== "" && bands == "auto" ? auto_bands.filter : ""}>
|
||||
<option key="auto" value="auto">{_("Automatic")}</option>
|
||||
{algo.filters.map(f => <option key={f} value={f}>{f}</option>)}
|
||||
</select>}
|
||||
</select>,
|
||||
bands == "auto" && !auto_bands.match ?
|
||||
<i style={{marginLeft: '4px'}} title={interpolate(_("Not every band for %(name)s could be automatically identified."), {name: algo.id}) + "\n" + _("Your sensor might not have the proper bands for using this algorithm.")} className="fa fa-exclamation-circle info-button"></i>
|
||||
: ""]}
|
||||
</div>
|
||||
</div> : ""}
|
||||
|
||||
|
|
|
@ -94,6 +94,16 @@ class Map extends React.Component {
|
|||
return "";
|
||||
}
|
||||
|
||||
hasBands = (bands, orthophoto_bands) => {
|
||||
if (!orthophoto_bands) return false;
|
||||
console.log(orthophoto_bands)
|
||||
for (let i = 0; i < bands.length; i++){
|
||||
if (orthophoto_bands.find(b => b.description !== null && b.description.toLowerCase() === bands[i].toLowerCase()) === undefined) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
loadImageryLayers(forceAddLayers = false){
|
||||
// Cancel previous requests
|
||||
if (this.tileJsonRequests) {
|
||||
|
@ -131,7 +141,11 @@ class Map extends React.Component {
|
|||
// Single band, probably thermal dataset, in any case we can't render NDVI
|
||||
// because it requires 3 bands
|
||||
metaUrl += "?formula=Celsius&bands=L&color_map=magma";
|
||||
}else if (meta.task && meta.task.orthophoto_bands){
|
||||
let formula = this.hasBands(["red", "green", "nir"], meta.task.orthophoto_bands) ? "NDVI" : "VARI";
|
||||
metaUrl += `?formula=${formula}&bands=auto&color_map=rdylgn`;
|
||||
}else{
|
||||
// This should never happen?
|
||||
metaUrl += "?formula=NDVI&bands=RGN&color_map=rdylgn";
|
||||
}
|
||||
}else if (type == "dsm" || type == "dtm"){
|
||||
|
|
|
@ -679,7 +679,7 @@ class TestApiTask(BootTransactionTestCase):
|
|||
|
||||
for k in algos:
|
||||
a = algos[k]
|
||||
filters = get_camera_filters_for(a)
|
||||
filters = get_camera_filters_for(a['expr'])
|
||||
|
||||
for f in filters:
|
||||
params.append(("orthophoto", "formula={}&bands={}&color_map=rdylgn".format(k, f), status.HTTP_200_OK))
|
||||
|
|
|
@ -38,7 +38,7 @@ class TestFormulas(TestCase):
|
|||
bands = list(set(re.findall(pattern, f)))
|
||||
self.assertTrue(len(bands) <= 3)
|
||||
|
||||
self.assertTrue(get_camera_filters_for(algos['VARI']) == ['RGB'])
|
||||
self.assertTrue(get_camera_filters_for(algos['VARI']['expr']) == ['RGB'])
|
||||
|
||||
# Request algorithms with more band filters
|
||||
al = get_algorithm_list(max_bands=5)
|
||||
|
|
2
locale
2
locale
|
@ -1 +1 @@
|
|||
Subproject commit d253dd5770c42a0705d0f861db3314b08f230a68
|
||||
Subproject commit 0a68f6ed5172a8838571a9872bcc3cb4f310794c
|
Ładowanie…
Reference in New Issue