Skip to content

Commit

Permalink
Object detection plugin mock
Browse files Browse the repository at this point in the history
  • Loading branch information
pierotofy committed Jan 23, 2025
1 parent 95ce02f commit 7f44e62
Show file tree
Hide file tree
Showing 12 changed files with 588 additions and 1 deletion.
1 change: 1 addition & 0 deletions coreplugins/objdetect/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .plugin import *
122 changes: 122 additions & 0 deletions coreplugins/objdetect/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import os

from rest_framework import status
from rest_framework.response import Response
from app.plugins.views import TaskView, CheckTask, GetTaskResult
from app.plugins.worker import run_function_async
from django.utils.translation import gettext_lazy as _

class ContoursException(Exception):
pass

def calc_contours(dem, epsg, interval, output_format, simplify, zfactor = 1):
import os
import subprocess
import tempfile
import shutil
import glob
from webodm import settings

ext = ""
if output_format == "GeoJSON":
ext = "json"
elif output_format == "GPKG":
ext = "gpkg"
elif output_format == "DXF":
ext = "dxf"
elif output_format == "ESRI Shapefile":
ext = "shp"
MIN_CONTOUR_LENGTH = 10

tmpdir = os.path.join(settings.MEDIA_TMP, os.path.basename(tempfile.mkdtemp('_contours', dir=settings.MEDIA_TMP)))
gdal_contour_bin = shutil.which("gdal_contour")
ogr2ogr_bin = shutil.which("ogr2ogr")

if gdal_contour_bin is None:
return {'error': 'Cannot find gdal_contour'}
if ogr2ogr_bin is None:
return {'error': 'Cannot find ogr2ogr'}

contours_file = f"contours.gpkg"
p = subprocess.Popen([gdal_contour_bin, "-q", "-a", "level", "-3d", "-f", "GPKG", "-i", str(interval), dem, contours_file], cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()

out = out.decode('utf-8').strip()
err = err.decode('utf-8').strip()
success = p.returncode == 0

if not success:
return {'error', f'Error calling gdal_contour: {str(err)}'}

outfile = os.path.join(tmpdir, f"output.{ext}")
p = subprocess.Popen([ogr2ogr_bin, outfile, contours_file, "-simplify", str(simplify), "-f", output_format, "-t_srs", f"EPSG:{epsg}", "-nln", "contours",
"-dialect", "sqlite", "-sql", f"SELECT ID, ROUND(level * {zfactor}, 5) AS level, GeomFromGML(AsGML(ATM_Transform(GEOM, ATM_Scale(ATM_Create(), 1, 1, {zfactor})), 10)) as GEOM FROM contour WHERE ST_Length(GEOM) >= {MIN_CONTOUR_LENGTH}"], cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()

out = out.decode('utf-8').strip()
err = err.decode('utf-8').strip()
success = p.returncode == 0

if not success:
return {'error', f'Error calling ogr2ogr: {str(err)}'}

if not os.path.isfile(outfile):
return {'error': f'Cannot find output file: {outfile}'}

if output_format == "ESRI Shapefile":
ext="zip"
shp_dir = os.path.join(tmpdir, "contours")
os.makedirs(shp_dir)
contour_files = glob.glob(os.path.join(tmpdir, "output.*"))
for cf in contour_files:
shutil.move(cf, shp_dir)

shutil.make_archive(os.path.join(tmpdir, 'output'), 'zip', shp_dir)
outfile = os.path.join(tmpdir, f"output.{ext}")

return {'file': outfile}


class TaskContoursGenerate(TaskView):
def post(self, request, pk=None):
task = self.get_and_check_task(request, pk)

layer = request.data.get('layer', None)
if layer == 'DSM' and task.dsm_extent is None:
return Response({'error': _('No DSM layer is available.')})
elif layer == 'DTM' and task.dtm_extent is None:
return Response({'error': _('No DTM layer is available.')})

try:
if layer == 'DSM':
dem = os.path.abspath(task.get_asset_download_path("dsm.tif"))
elif layer == 'DTM':
dem = os.path.abspath(task.get_asset_download_path("dtm.tif"))
else:
raise ContoursException('{} is not a valid layer.'.format(layer))

epsg = int(request.data.get('epsg', '3857'))
interval = float(request.data.get('interval', 1))
format = request.data.get('format', 'GPKG')
supported_formats = ['GPKG', 'ESRI Shapefile', 'DXF', 'GeoJSON']
if not format in supported_formats:
raise ContoursException("Invalid format {} (must be one of: {})".format(format, ",".join(supported_formats)))
simplify = float(request.data.get('simplify', 0.01))
zfactor = float(request.data.get('zfactor', 1))

celery_task_id = run_function_async(calc_contours, dem, epsg, interval, format, simplify, zfactor).task_id
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK)
except ContoursException as e:
return Response({'error': str(e)}, status=status.HTTP_200_OK)

class TaskContoursCheck(CheckTask):
def on_error(self, result):
pass

def error_check(self, result):
contours_file = result.get('file')
if not contours_file or not os.path.exists(contours_file):
return _('Could not generate contour file. This might be a bug.')

class TaskContoursDownload(GetTaskResult):
pass
13 changes: 13 additions & 0 deletions coreplugins/objdetect/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "Object Detect",
"webodmMinVersion": "2.5.8",
"description": "Detect objects using AI in orthophotos",
"version": "1.0.0",
"author": "Piero Toffanin",
"email": "pt@uav4geo.com",
"repository": "https://github.com/OpenDroneMap/WebODM",
"tags": ["object", "detect", "ai"],
"homepage": "https://github.com/OpenDroneMap/WebODM",
"experimental": false,
"deprecated": false
}
20 changes: 20 additions & 0 deletions coreplugins/objdetect/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from app.plugins import PluginBase
from app.plugins import MountPoint
# from .api import TaskContoursGenerate
# from .api import TaskContoursCheck
# from .api import TaskContoursDownload


class Plugin(PluginBase):
def include_js_files(self):
return ['main.js']

def build_jsx_components(self):
return ['ObjDetect.jsx']

def api_mount_points(self):
return [
# MountPoint('task/(?P<pk>[^/.]+)/contours/generate', TaskContoursGenerate.as_view()),
# MountPoint('task/[^/.]+/contours/check/(?P<celery_task_id>.+)', TaskContoursCheck.as_view()),
# MountPoint('task/[^/.]+/contours/download/(?P<celery_task_id>.+)', TaskContoursDownload.as_view()),
]
55 changes: 55 additions & 0 deletions coreplugins/objdetect/public/ObjDetect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import L from 'leaflet';
import ReactDOM from 'ReactDOM';
import React from 'React';
import PropTypes from 'prop-types';
import './ObjDetect.scss';
import ObjDetectPanel from './ObjDetectPanel';

class ObjDetectButton extends React.Component {
static propTypes = {
tasks: PropTypes.object.isRequired,
map: PropTypes.object.isRequired
}

constructor(props){
super(props);

this.state = {
showPanel: false
};
}

handleOpen = () => {
this.setState({showPanel: true});
}

handleClose = () => {
this.setState({showPanel: false});
}

render(){
const { showPanel } = this.state;

return (<div className={showPanel ? "open" : ""}>
<a href="javascript:void(0);"
onClick={this.handleOpen}
className="leaflet-control-objdetect-button leaflet-bar-part theme-secondary"></a>
<ObjDetectPanel map={this.props.map} isShowed={showPanel} tasks={this.props.tasks} onClose={this.handleClose} />
</div>);
}
}

export default L.Control.extend({
options: {
position: 'topright'
},

onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-control-objdetect leaflet-bar leaflet-control');
L.DomEvent.disableClickPropagation(container);
ReactDOM.render(<ObjDetectButton map={this.options.map} tasks={this.options.tasks} />, container);

return container;
}
});

24 changes: 24 additions & 0 deletions coreplugins/objdetect/public/ObjDetect.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.leaflet-control-objdetect{
z-index: 999 !important;

a.leaflet-control-objdetect-button{
background: url(icon.svg) no-repeat 0 0;
background-size: 26px 26px;
border-radius: 2px;
}

div.objdetect-panel{ display: none; }

.open{
a.leaflet-control-objdetect-button{
display: none;
}

div.objdetect-panel{
display: block;
}
}
}
.leaflet-touch .leaflet-control-objdetect a {
background-position: 2px 2px;
}
Loading

0 comments on commit 7f44e62

Please sign in to comment.