Skip to content

Commit

Permalink
Merge pull request #1345 from pierotofy/flir
Browse files Browse the repository at this point in the history
Adds support for single band thermal datasets
  • Loading branch information
pierotofy committed May 19, 2023
2 parents a18c1d3 + 92a016b commit d8825e2
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 10 deletions.
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

0 comments on commit d8825e2

Please sign in to comment.