Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for single band thermal datasets #1345

Merged
merged 5 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions app/api/formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@
'help': _('Atmospherically Resistant Vegetation Index. Useful when working with imagery for regions with high atmospheric aerosol content.'),
'range': (-1, 1)
},
'Thermal C': {
'Celsius': {
'expr': 'L',
'help': _('Thermal temperature in Celsius degrees.')
'help': _('Temperature in Celsius degrees.')
},
'Thermal K': {
'Kelvin': {
'expr': 'L * 100 + 27315',
'help': _('Thermal temperature in Centikelvin degrees.')
'help': _('Temperature in Centikelvin degrees.')
},

# more?
Expand Down Expand Up @@ -154,6 +154,8 @@
'BGRNReL',
'BGRReNL',

'L', # FLIR camera has a single LWIR band

# more?
# TODO: certain cameras have only two bands? eg. MAPIR NDVI BLUE+NIR
]
Expand Down
44 changes: 44 additions & 0 deletions app/migrations/0035_task_orthophoto_bands.py
Original file line number Diff line number Diff line change
@@ -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),
]
26 changes: 23 additions & 3 deletions app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -878,6 +879,7 @@ def extract_assets_and_complete(self):

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"
Expand All @@ -899,8 +901,9 @@ def get_tile_base_url(self, tile_type):

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')

Expand All @@ -919,7 +922,8 @@ def get_map_items(self):
'public': self.public,
'camera_shots': camera_shots,
'ground_control_points': ground_control_points,
'epsg': self.epsg
'epsg': self.epsg,
'orthophoto_bands': self.orthophoto_bands,
}
}
}
Expand Down Expand Up @@ -993,6 +997,22 @@ def update_epsg_field(self, commit=False):
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
Expand Down
13 changes: 11 additions & 2 deletions app/static/app/js/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
9 changes: 9 additions & 0 deletions app/tests/test_api_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ def test_task(self):
# 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']
Expand Down Expand Up @@ -378,6 +381,9 @@ def test_task(self):
res = client.get("/api/projects/{}/tasks/{}/assets/odm_orthophoto/odm_orthophoto.tif".format(project.id, task.id))
self.assertTrue(res.status_code == status.HTTP_200_OK)

# Orthophoto bands field should be populated
self.assertEqual(len(task.orthophoto_bands), 4)

# Can export orthophoto (when formula and bands are specified)
res = client.post("/api/projects/{}/tasks/{}/orthophoto/export".format(project.id, task.id), {
'formula': 'NDVI'
Expand Down Expand Up @@ -916,6 +922,9 @@ def connTimeout(*args, **kwargs):
# EPSG should be populated
self.assertEqual(task.epsg, 32615)

# Orthophoto bands should not be populated
self.assertEqual(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)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "WebODM",
"version": "2.0.2",
"version": "2.0.3",
"description": "User-friendly, extendable application and API for processing aerial imagery.",
"main": "index.js",
"scripts": {
Expand Down