kopia lustrzana https://github.com/OpenDroneMap/WebODM
Tiler metadata unit tests
rodzic
2bbcefa77a
commit
fdebc8f157
|
@ -135,7 +135,10 @@ class Metadata(TaskNestedView):
|
||||||
if formula == '': formula = None
|
if formula == '': formula = None
|
||||||
if bands == '': bands = None
|
if bands == '': bands = None
|
||||||
|
|
||||||
|
try:
|
||||||
expr, hrange = lookup_formula(formula, bands)
|
expr, hrange = lookup_formula(formula, bands)
|
||||||
|
except ValueError as e:
|
||||||
|
raise exceptions.ValidationError(str(e))
|
||||||
|
|
||||||
pmin, pmax = 2.0, 98.0
|
pmin, pmax = 2.0, 98.0
|
||||||
raster_path = get_raster_path(task, tile_type)
|
raster_path = get_raster_path(task, tile_type)
|
||||||
|
@ -167,13 +170,16 @@ class Metadata(TaskNestedView):
|
||||||
}
|
}
|
||||||
|
|
||||||
colormaps = []
|
colormaps = []
|
||||||
|
algorithms = []
|
||||||
if tile_type in ['dsm', 'dtm']:
|
if tile_type in ['dsm', 'dtm']:
|
||||||
colormaps = ['jet_r', 'terrain', 'gist_earth', 'pastel1']
|
colormaps = ['jet_r', 'terrain', 'gist_earth', 'pastel1']
|
||||||
elif formula and bands:
|
elif formula and bands:
|
||||||
colormaps = ['rdylgn', 'spectral', 'rdylgn_r', 'spectral_r']
|
colormaps = ['rdylgn', 'spectral', 'rdylgn_r', 'spectral_r']
|
||||||
info['algorithms'] = *get_algorithm_list(),
|
algorithms = *get_algorithm_list(),
|
||||||
|
|
||||||
info['color_maps'] = []
|
info['color_maps'] = []
|
||||||
|
info['algorithms'] = algorithms
|
||||||
|
|
||||||
if colormaps:
|
if colormaps:
|
||||||
for cmap in colormaps:
|
for cmap in colormaps:
|
||||||
try:
|
try:
|
||||||
|
@ -257,7 +263,10 @@ class Tiles(TaskNestedView):
|
||||||
if color_map == '': color_map = None
|
if color_map == '': color_map = None
|
||||||
if hillshade == '' or hillshade == '0': hillshade = None
|
if hillshade == '' or hillshade == '0': hillshade = None
|
||||||
|
|
||||||
|
try:
|
||||||
expr, _ = lookup_formula(formula, bands)
|
expr, _ = lookup_formula(formula, bands)
|
||||||
|
except ValueError as e:
|
||||||
|
raise exceptions.ValidationError(str(e))
|
||||||
|
|
||||||
if tile_type in ['dsm', 'dtm'] and rescale is None:
|
if tile_type in ['dsm', 'dtm'] and rescale is None:
|
||||||
rescale = "0,1000"
|
rescale = "0,1000"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ import worker
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from app import pending_actions
|
from app import pending_actions
|
||||||
|
from app.api.formulas import algos
|
||||||
from app.models import Project, Task, ImageUpload
|
from app.models import Project, Task, ImageUpload
|
||||||
from app.models.task import task_directory_path, full_task_directory_path, TaskInterruptedException
|
from app.models.task import task_directory_path, full_task_directory_path, TaskInterruptedException
|
||||||
from app.plugins.signals import task_completed, task_removed, task_removing
|
from app.plugins.signals import task_completed, task_removed, task_removing
|
||||||
|
@ -235,10 +237,12 @@ class TestApiTask(BootTransactionTestCase):
|
||||||
# No processing node is set
|
# No processing node is set
|
||||||
self.assertTrue(task.processing_node is None)
|
self.assertTrue(task.processing_node is None)
|
||||||
|
|
||||||
# tiles.json should not be accessible at this point
|
# tiles.json, bounds, metadata should not be accessible at this point
|
||||||
tile_types = ['orthophoto', 'dsm', 'dtm']
|
tile_types = ['orthophoto', 'dsm', 'dtm']
|
||||||
|
endpoints = ['tiles.json', 'bounds', 'metadata']
|
||||||
|
for ep in endpoints:
|
||||||
for tile_type in tile_types:
|
for tile_type in tile_types:
|
||||||
res = client.get("/api/projects/{}/tasks/{}/{}/tiles.json".format(project.id, task.id, tile_type))
|
res = client.get("/api/projects/{}/tasks/{}/{}/{}".format(project.id, task.id, tile_type, ep))
|
||||||
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
# Neither should an individual tile
|
# Neither should an individual tile
|
||||||
|
@ -345,22 +349,128 @@ class TestApiTask(BootTransactionTestCase):
|
||||||
res = client.get("/api/projects/{}/tasks/{}/assets/odm_orthophoto/odm_orthophoto.tif".format(project.id, task.id))
|
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)
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
|
||||||
# Can access tiles.json
|
# Can access tiles.json, bounds and metadata
|
||||||
|
for ep in endpoints:
|
||||||
for tile_type in tile_types:
|
for tile_type in tile_types:
|
||||||
res = client.get("/api/projects/{}/tasks/{}/{}/tiles.json".format(project.id, task.id, tile_type))
|
res = client.get("/api/projects/{}/tasks/{}/{}/{}".format(project.id, task.id, tile_type, ep))
|
||||||
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
self.assertTrue(res.status_code == status.HTTP_200_OK)
|
||||||
|
|
||||||
# Bounds are what we expect them to be
|
# Bounds are what we expect them to be
|
||||||
# (4 coords in lat/lon)
|
# (4 coords in lat/lon)
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles.json".format(project.id, task.id))
|
||||||
tiles = json.loads(res.content.decode("utf-8"))
|
tiles = json.loads(res.content.decode("utf-8"))
|
||||||
self.assertTrue(len(tiles['bounds']) == 4)
|
self.assertTrue(len(tiles['bounds']) == 4)
|
||||||
self.assertTrue(round(tiles['bounds'][0], 7) == -91.9945132)
|
self.assertTrue(round(tiles['bounds'][0], 7) == -91.9945132)
|
||||||
|
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/bounds".format(project.id, task.id))
|
||||||
|
bounds = json.loads(res.content.decode("utf-8"))
|
||||||
|
self.assertTrue(len(bounds['bounds']) == 4)
|
||||||
|
self.assertTrue(round(bounds['bounds'][0], 7) == -91.9945132)
|
||||||
|
|
||||||
|
# Metadata checks for orthophoto
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/metadata".format(project.id, task.id))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||||
|
metadata = json.loads(res.content.decode("utf-8"))
|
||||||
|
fields = ['bounds', 'minzoom', 'maxzoom', 'statistics', 'algorithms', 'color_maps', 'tiles', 'scheme', 'name']
|
||||||
|
for f in fields:
|
||||||
|
self.assertTrue(f in metadata)
|
||||||
|
|
||||||
|
# Colormaps and algorithms should be empty lists
|
||||||
|
self.assertEqual(metadata['algorithms'], [])
|
||||||
|
self.assertEqual(metadata['color_maps'], [])
|
||||||
|
|
||||||
|
# Address key is removed
|
||||||
|
self.assertFalse('address' in metadata)
|
||||||
|
|
||||||
|
# Scheme is xyz
|
||||||
|
self.assertEqual(metadata['scheme'], 'xyz')
|
||||||
|
|
||||||
|
# Tiles URL has no extra params
|
||||||
|
self.assertTrue(metadata['tiles'][0].endswith('.png'))
|
||||||
|
|
||||||
|
# Histogram stats are available (3 bands for orthophoto)
|
||||||
|
self.assertTrue(len(metadata['statistics']) == 3)
|
||||||
|
for b in ['1', '2', '3']:
|
||||||
|
self.assertEqual(len(metadata['statistics'][b]['histogram']), 255) # bins
|
||||||
|
self.assertEqual(metadata['statistics'][b]['min'], 0)
|
||||||
|
self.assertEqual(metadata['statistics'][b]['max'], 255)
|
||||||
|
|
||||||
|
# Metadata with invalid formula
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/metadata?formula=INVALID".format(project.id, task.id))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Metadata with a valid formula but invalid bands
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/metadata?formula=NDVI&bands=ABC".format(project.id, task.id))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Medatata with valid formula and bands
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/metadata?formula=NDVI&bands=RGN".format(project.id, task.id))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||||
|
metadata = json.loads(res.content.decode("utf-8"))
|
||||||
|
|
||||||
|
# Colormaps and algorithms are populated
|
||||||
|
self.assertTrue(len(metadata['algorithms']) > 0)
|
||||||
|
self.assertTrue(len(metadata['color_maps']) > 0)
|
||||||
|
|
||||||
|
# Colormap is for algorithms
|
||||||
|
self.assertTrue('rdylgn' in metadata['color_maps'])
|
||||||
|
self.assertFalse('jet_r' in metadata['color_maps'])
|
||||||
|
|
||||||
|
# Formula parameters are copied to tile URL
|
||||||
|
self.assertTrue(metadata['tiles'][0].endswith('?formula=NDVI&bands=RGN'))
|
||||||
|
|
||||||
|
# Histogram stats are available (1 band)
|
||||||
|
self.assertTrue(len(metadata['statistics']) == 1)
|
||||||
|
|
||||||
|
# Medatata with valid formula and bands that specifies a scale range
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/orthophoto/metadata?formula=VARI".format(project.id, task.id))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||||
|
metadata = json.loads(res.content.decode("utf-8"))
|
||||||
|
self.assertTrue(len(metadata['statistics']) == 1)
|
||||||
|
|
||||||
|
# Min/max values have been replaced
|
||||||
|
self.assertEqual(metadata['statistics']['1']['min'], algos['VARI']['range'][0])
|
||||||
|
self.assertEqual(metadata['statistics']['1']['max'], algos['VARI']['range'][1])
|
||||||
|
|
||||||
|
# Metadata for DSM/DTM
|
||||||
|
for tile_type in ['dsm', 'dtm']:
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/{}/metadata".format(project.id, task.id, tile_type))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||||
|
metadata = json.loads(res.content.decode("utf-8"))
|
||||||
|
|
||||||
|
# Colormaps are populated
|
||||||
|
self.assertTrue(len(metadata['color_maps']) > 0)
|
||||||
|
|
||||||
|
# Colormaps are for elevation
|
||||||
|
self.assertTrue('jet_r' in metadata['color_maps'])
|
||||||
|
self.assertFalse('rdylgn' in metadata['color_maps'])
|
||||||
|
|
||||||
|
# Algorithms are empty
|
||||||
|
self.assertEqual(len(metadata['algorithms']), 0)
|
||||||
|
|
||||||
|
# Min/max values are what we expect them to be
|
||||||
|
self.assertEqual(len(metadata['statistics']), 1)
|
||||||
|
self.assertEqual(round(metadata['statistics']['1']['min'], 2), 156.91)
|
||||||
|
self.assertEqual(round(metadata['statistics']['1']['max'], 2), 164.94)
|
||||||
|
|
||||||
# Can access individual tiles
|
# Can access individual tiles
|
||||||
for tile_type in tile_types:
|
for tile_type in tile_types:
|
||||||
res = client.get("/api/projects/{}/tasks/{}/{}/tiles/17/32042/46185.png".format(project.id, task.id, tile_type))
|
res = client.get("/api/projects/{}/tasks/{}/{}/tiles/17/32042/46185.png".format(project.id, task.id, tile_type))
|
||||||
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
with Image.open(io.BytesIO(res.data)) as i:
|
||||||
|
self.assertEqual(i.width, 256)
|
||||||
|
self.assertEqual(i.height, 256)
|
||||||
|
|
||||||
|
# Can access retina tiles
|
||||||
|
for tile_type in tile_types:
|
||||||
|
res = client.get("/api/projects/{}/tasks/{}/{}/tiles/17/32042/46185@2x.png".format(project.id, task.id, tile_type))
|
||||||
|
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
with Image.open(io.BytesIO(res.data)) as i:
|
||||||
|
self.assertEqual(i.width, 512)
|
||||||
|
self.assertEqual(i.height, 512)
|
||||||
|
|
||||||
# Another user does not have access to the resources
|
# Another user does not have access to the resources
|
||||||
other_client = APIClient()
|
other_client = APIClient()
|
||||||
other_client.login(username="testuser2", password="test1234")
|
other_client.login(username="testuser2", password="test1234")
|
||||||
|
|
Ładowanie…
Reference in New Issue