diff --git a/app/migrations/0035_task_orthophoto_bands.py b/app/migrations/0035_task_orthophoto_bands.py new file mode 100644 index 00000000..2a99ee10 --- /dev/null +++ b/app/migrations/0035_task_orthophoto_bands.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.27 on 2023-05-19 15:38 + +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: + bands = [c.name for c in f.colorinterp] + 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', '0034_delete_imageupload'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='orthophoto_bands', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, help_text='List of orthophoto bands', verbose_name='Orthophoto Bands'), + ), + + migrations.RunPython(update_orthophoto_bands_fields), + ] diff --git a/app/models/task.py b/app/models/task.py index 23426d09..497bb74a 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -278,6 +278,7 @@ class Task(models.Model): potree_scene = fields.JSONField(default=dict, blank=True, help_text=_("Serialized potree scene information used to save/load measurements and camera view angle"), verbose_name=_("Potree Scene")) epsg = models.IntegerField(null=True, default=None, blank=True, help_text=_("EPSG code of the dataset (if georeferenced)"), verbose_name="EPSG") tags = models.TextField(db_index=True, default="", blank=True, help_text=_("Task tags"), verbose_name=_("Tags")) + orthophoto_bands = fields.JSONField(default=list, blank=True, help_text=_("List of orthophoto bands"), verbose_name=_("Orthophoto Bands")) class Meta: verbose_name = _("Task") @@ -878,6 +879,7 @@ class Task(models.Model): self.update_available_assets_field() self.update_epsg_field() + self.update_orthophoto_bands_field() self.potree_scene = {} self.running_progress = 1.0 self.console_output += gettext("Done!") + "\n" @@ -899,8 +901,9 @@ class Task(models.Model): def get_map_items(self): types = [] - if 'orthophoto.tif' in self.available_assets: types.append('orthophoto') - if 'orthophoto.tif' in self.available_assets: types.append('plant') + if 'orthophoto.tif' in self.available_assets: + types.append('orthophoto') + types.append('plant') if 'dsm.tif' in self.available_assets: types.append('dsm') if 'dtm.tif' in self.available_assets: types.append('dtm') @@ -919,7 +922,8 @@ class Task(models.Model): 'public': self.public, 'camera_shots': camera_shots, 'ground_control_points': ground_control_points, - 'epsg': self.epsg + 'epsg': self.epsg, + 'orthophoto_bands': self.orthophoto_bands, } } } @@ -993,6 +997,22 @@ class Task(models.Model): if commit: self.save() + def update_orthophoto_bands_field(self, commit=False): + """ + Updates the orthophoto bands field with the correct value + :param commit: when True also saves the model, otherwise the user should manually call save() + """ + bands = [] + orthophoto_path = self.assets_path(self.ASSETS_MAP['orthophoto.tif']) + + if os.path.isfile(orthophoto_path): + with rasterio.open(orthophoto_path) as f: + bands = [c.name for c in f.colorinterp] + + self.orthophoto_bands = bands + if commit: self.save() + + def delete(self, using=None, keep_parents=False): task_id = self.id from app.plugins import signals as plugin_signals diff --git a/app/static/app/js/components/Map.jsx b/app/static/app/js/components/Map.jsx index ff17a591..7cf2a248 100644 --- a/app/static/app/js/components/Map.jsx +++ b/app/static/app/js/components/Map.jsx @@ -126,8 +126,17 @@ class Map extends React.Component { let metaUrl = url + "metadata"; - if (type == "plant") metaUrl += "?formula=NDVI&bands=RGN&color_map=rdylgn"; - if (type == "dsm" || type == "dtm") metaUrl += "?hillshade=6&color_map=viridis"; + if (type == "plant"){ + if (meta.task && meta.task.orthophoto_bands && meta.task.orthophoto_bands.length === 2){ + // 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{ + metaUrl += "?formula=NDVI&bands=RGN&color_map=rdylgn"; + } + }else if (type == "dsm" || type == "dtm"){ + metaUrl += "?hillshade=6&color_map=viridis"; + } this.tileJsonRequests.push($.getJSON(metaUrl) .done(mres => { diff --git a/app/tests/test_api_task.py b/app/tests/test_api_task.py index dbbf8fe8..a6d4c3a2 100644 --- a/app/tests/test_api_task.py +++ b/app/tests/test_api_task.py @@ -246,6 +246,9 @@ class TestApiTask(BootTransactionTestCase): # EPSG should be null self.assertTrue(task.epsg is None) + # Orthophoto bands field should be an empty list + self.assertEqual(len(task.orthophoto_bands), 0) + # tiles.json, bounds, metadata should not be accessible at this point tile_types = ['orthophoto', 'dsm', 'dtm'] endpoints = ['tiles.json', 'bounds', 'metadata'] @@ -916,6 +919,9 @@ class TestApiTask(BootTransactionTestCase): # EPSG should be populated self.assertEqual(task.epsg, 32615) + # Orthophoto bands should be populated + self.assertTrue(len(task.orthophoto_bands) > 0) + # Can access only tiles of available assets res = client.get("/api/projects/{}/tasks/{}/dsm/tiles.json".format(project.id, task.id)) self.assertEqual(res.status_code, status.HTTP_200_OK)