If you still have the source files in the media dir folder, but you forgot to make a backup according to the instructions in the main readme - you can perform a hard recovery process.
You need to have a setup WebODM admin account before starting recovery. If you already create admin account - skip this step.
From inside the webodm_webapp container:
List the container of WebODM with usage of docker ps
command, or using Docker Desktop.
Connect into the webodm container using:
docker exec -ti webodm_webapp bash
python manage.py shell
# START COPY FIRST PART
from django.contrib.auth.models import User
from app.models import Project, Task
import os
from django.contrib.gis.gdal import GDALRaster
from django.contrib.gis.gdal import OGRGeometry
from django.contrib.gis.geos import GEOSGeometry
from django.db import connection
from django.utils.translation import gettext_lazy as _, gettext
from nodeodm import status_codes
from app.cogeo import assure_cogeo
import json
def process_task(creating_task):
images_json = creating_task.assets_path("images.json")
if os.path.exists(images_json):
try:
with open(images_json) as f:
images = json.load(f)
creating_task.images_count = len(images)
except:
print("Cannot read images count from imported task {}".format(creating_task))
pass
extent_fields = [
(os.path.realpath(creating_task.assets_path("odm_orthophoto", "odm_orthophoto.tif")),
'orthophoto_extent'),
(os.path.realpath(creating_task.assets_path("odm_dem", "dsm.tif")),
'dsm_extent'),
(os.path.realpath(creating_task.assets_path("odm_dem", "dtm.tif")),
'dtm_extent'),
]
for raster_path, field in extent_fields:
if os.path.exists(raster_path):
# Make sure this is a Cloud Optimized GeoTIFF
# if not, it will be created
try:
assure_cogeo(raster_path)
except IOError as e:
print(
"Cannot create Cloud Optimized GeoTIFF for %s (%s). This will result in degraded visualization performance." % (
raster_path, str(e)))
# Read extent and SRID
raster = GDALRaster(raster_path)
extent = OGRGeometry.from_bbox(raster.extent)
# Make sure PostGIS supports it
with connection.cursor() as cursor:
cursor.execute("SELECT SRID FROM spatial_ref_sys WHERE SRID = %s", [raster.srid])
if cursor.rowcount == 0:
raise Exception()
# It will be implicitly transformed into the SRID of the model’s field
# task.field = GEOSGeometry(...)
setattr(creating_task, field, GEOSGeometry(extent.wkt, srid=raster.srid))
print("Populated extent field with {} for {}".format(raster_path, creating_task))
creating_task.update_available_assets_field()
creating_task.potree_scene = {}
creating_task.running_progress = 1.0
creating_task.status = status_codes.COMPLETED
creating_task.save()
def create_project(project_id, user):
project = Project()
project.name = project_id
project.owner = user
project.id = int(project_id)
return project
# END COPY FIRST PART
# START COPY SECOND PART
user = User.objects.get(username="YOUR NEW CREATED ADMIN USERNAME HERE")
# END COPY COPY SECOND PART
Step 3. This is the main part of script which make the main magic of the project. It will read media dir and create tasks and projects from the sources
# START COPY THIRD PART
for project_id in os.listdir("/webodm/app/media/project"):
if not Project.objects.filter(id=int(project_id)).exists():
project = create_project(project_id, user)
project.save()
else:
project = Project.objects.get(pk=(project_id))
for task_id in os.listdir(f"/webodm/app/media/project/{project_id}/task"):
if not Task.objects.filter(id=task_id).exists():
task = Task(project=project)
task.id = task_id
process_task(task)
# END COPY THIRD PART
with connection.cursor() as cursor:
cursor.execute("SELECT setval('app_project_id_seq', (SELECT MAX(id) FROM app_project)+1)")
If all is ok - you will get recreated structure of projects inside WebODM database, and WebODM GUI. Congratulations - you are great!