From a880655b0a36bd62cbaff9893adeab9d3d0369bd Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 15 Oct 2026 12:41:30 -0400 Subject: [PATCH 1/5] Feat: GPX parser, improved frame extraction from video --- opendm/video/checkers.py | 128 ------------- opendm/video/gpxparser.py | 147 +++++++++++++++ opendm/video/parameters.py | 46 ----- opendm/video/srtparser.py | 44 +++-- opendm/video/video2dataset.py | 334 ++++++++++++++++++++-------------- stages/dataset.py | 19 +- 6 files changed, 373 insertions(+), 345 deletions(-) delete mode 100644 opendm/video/checkers.py create mode 100644 opendm/video/gpxparser.py delete mode 100644 opendm/video/parameters.py diff --git a/opendm/video/checkers.py b/opendm/video/checkers.py deleted file mode 100644 index 1036689f..00000000 --- a/opendm/video/checkers.py +++ /dev/null @@ -1,128 +0,0 @@ -import cv2 -import numpy as np - -class ThresholdBlurChecker: - def __init__(self, threshold): - self.threshold = threshold - - def NeedPreProcess(self): - return False - - def PreProcess(self, video_path, start_frame, end_frame): - return - - def IsBlur(self, image_bw, id): - var = cv2.Laplacian(image_bw, cv2.CV_64F).var() - return var, var < self.threshold - -class SimilarityChecker: - def __init__(self, threshold, max_features=500): - self.threshold = threshold - self.max_features = max_features - self.last_image = None - self.last_image_id = None - self.last_image_features = None - - def IsSimilar(self, image_bw, id): - - if self.last_image is None: - self.last_image = image_bw - self.last_image_id = id - self.last_image_features = cv2.goodFeaturesToTrack(image_bw, self.max_features, 0.01, 10) - return 0, False, None - - # Detect features - features, status, _ = cv2.calcOpticalFlowPyrLK(self.last_image, image_bw, self.last_image_features, None) - - # Filter out the "bad" features (i.e. those that are not tracked successfully) - good_features = features[status == 1] - good_features2 = self.last_image_features[status == 1] - - # Calculate the difference between the locations of the good features in the two frames - distance = np.average(np.abs(good_features2 - good_features)) - - res = distance < self.threshold - - if (not res): - self.last_image = image_bw - self.last_image_id = id - self.last_image_features = cv2.goodFeaturesToTrack(image_bw, self.max_features, 0.01, 10) - - return distance, res, self.last_image_id - - -class NaiveBlackFrameChecker: - def __init__(self, threshold): - self.threshold = threshold - - def PreProcess(self, video_path, start_frame, end_frame, width=800, height=600): - return - - def NeedPreProcess(self): - return False - - def IsBlack(self, image_bw, id): - return np.average(image_bw) < self.threshold - - -class BlackFrameChecker: - def __init__(self, picture_black_ratio_th=0.98, pixel_black_th=0.30): - self.picture_black_ratio_th = picture_black_ratio_th if picture_black_ratio_th is not None else 0.98 - self.pixel_black_th = pixel_black_th if pixel_black_th is not None else 0.30 - self.luminance_minimum_value = None - self.luminance_range_size = None - self.absolute_threshold = None - - def NeedPreProcess(self): - return True - - def PreProcess(self, video_path, start_frame, end_frame): - # Open video file - cap = cv2.VideoCapture(video_path) - - # Set frame start and end indices - cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) - frame_end = end_frame - if end_frame == -1: - frame_end = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - - # Initialize luminance range size and minimum value - self.luminance_range_size = 0 - self.luminance_minimum_value = 255 - - frame_index = start_frame if start_frame is not None else 0 - - # Read and process frames from video file - while (cap.isOpened() and (end_frame is None or frame_index <= end_frame)): - - ret, frame = cap.read() - if not ret: - break - - # Convert frame to grayscale - gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - gray_frame_min = gray_frame.min() - gray_frame_max = gray_frame.max() - - # Update luminance range size and minimum value - self.luminance_range_size = max(self.luminance_range_size, gray_frame_max - gray_frame_min) - self.luminance_minimum_value = min(self.luminance_minimum_value, gray_frame_min) - - frame_index += 1 - - # Calculate absolute threshold for considering a pixel "black" - self.absolute_threshold = self.luminance_minimum_value + self.pixel_black_th * self.luminance_range_size - - # Close video file - cap.release() - - def IsBlack(self, image_bw, id): - - # Count number of pixels < self.absolute_threshold - nb_black_pixels = np.sum(image_bw < self.absolute_threshold) - - # Calculate ratio of black pixels - ratio_black_pixels = nb_black_pixels / (image_bw.shape[0] * image_bw.shape[1]) - - # Check if ratio of black pixels is above threshold - return ratio_black_pixels >= self.picture_black_ratio_th \ No newline at end of file diff --git a/opendm/video/gpxparser.py b/opendm/video/gpxparser.py new file mode 100644 index 00000000..ceddcaf9 --- /dev/null +++ b/opendm/video/gpxparser.py @@ -0,0 +1,147 @@ +from datetime import datetime, timedelta, timezone +from statistics import median +import struct +import xml.etree.ElementTree as ET +from opendm import log +from opendm.video.srtparser import SrtFileParser + +ELAPSED_ORIGIN = datetime(1900, 1, 1) + +def parse_gpx_time(s): + """Parse an ISO 8601 timestamp (e.g. 2026-07-07T17:32:51Z) into a naive UTC datetime""" + s = s.strip() + if s.endswith("Z") or s.endswith("z"): + s = s[:-1] + "+00:00" + dt = datetime.fromisoformat(s) + if dt.tzinfo is not None: + dt = dt.astimezone(timezone.utc).replace(tzinfo=None) + return dt + +def get_video_start_time(video_file): + """Read the creation time (UTC) (should work with .mp4, .mov, .lrv) + Returns a naive UTC datetime or None""" + try: + with open(video_file, 'rb') as f: + f.seek(0, 2) + file_size = f.tell() + + def find_atom(name, start, end): + pos = start + while pos + 8 <= end: + f.seek(pos) + size, atype = struct.unpack(">I4s", f.read(8)) + header = 8 + if size == 1: + size = struct.unpack(">Q", f.read(8))[0] + header = 16 + elif size == 0: + size = end - pos + if size < header: + return None + if atype == name: + return pos + header, pos + size + pos += size + return None + + moov = find_atom(b'moov', 0, file_size) + if moov is None: + return None + mvhd = find_atom(b'mvhd', *moov) + if mvhd is None: + return None + + f.seek(mvhd[0]) + version = f.read(1)[0] + f.read(3) # flags + if version == 1: + creation_time = struct.unpack(">Q", f.read(8))[0] + else: + creation_time = struct.unpack(">I", f.read(4))[0] + + if creation_time == 0: + return None + return datetime(1904, 1, 1) + timedelta(seconds=creation_time) + except Exception as e: + log.WARNING("Cannot read creation time from %s: %s" % (video_file, str(e))) + return None + +class GpxFileParser(SrtFileParser): + def __init__(self, filename, video_start=None): + super().__init__(filename) + self.video_start = video_start + + def get_entry(self, timestamp: datetime): + # GPX files do not carry camera settings + return None + + def parse(self): + points = [] + + for _, el in ET.iterparse(self.filename): + # Strip namespace + tag = el.tag.rsplit('}', 1)[-1] + if tag not in ("trkpt", "rtept"): + continue + + lat, lon = el.get('lat'), el.get('lon') + ele = tm = None + for child in el: + ctag = child.tag.rsplit('}', 1)[-1] + if ctag == "ele" and child.text: + ele = float(child.text) + elif ctag == "time" and child.text: + tm = parse_gpx_time(child.text) + + if lat is not None and lon is not None and tm is not None: + points.append((tm, float(lat), float(lon), ele)) + el.clear() + + if not points: + raise Exception("No timestamped track points found in %s" % self.filename) + + points.sort(key=lambda p: p[0]) + times = self.spread_timestamps([p[0] for p in points]) + + if self.video_start is None: + self.video_start = times[0] + log.WARNING("Video start time unknown, assuming it matches the first GPX point (%s)" % self.video_start) + + log.INFO("GPX track spans %s --> %s (%s points), video starts at %s" % (times[0], times[-1], len(points), self.video_start)) + + for tm, (_, lat, lon, ele) in zip(times, points): + # Express track time as elapsed video time + elapsed = ELAPSED_ORIGIN + (tm - self.video_start) + self.data.append({ + "start": elapsed, + "end": elapsed, + "latitude": lat, + "longitude": lon, + "altitude": ele + }) + + def spread_timestamps(self, times): + # Some devices (e.g. Insta360) log at > 1Hz, but write timestamps + # with 1 second resolution, so several points share the same time. + # We spread these evenly within their second, assuming a constant rate. + groups = [] + for tm in times: + if groups and groups[-1][0] == tm: + groups[-1][1] += 1 + else: + groups.append([tm, 1]) + + rate = median([n for _, n in groups]) + if rate <= 1: + return times + + result = [] + for i, (tm, n) in enumerate(groups): + step = 1.0 / max(n, rate) + if i == 0: + # Recording started mid-second: points belong to the end of it + offset = 1.0 - n * step + else: + offset = 0.0 + for k in range(n): + result.append(tm + timedelta(seconds=offset + k * step)) + return result diff --git a/opendm/video/parameters.py b/opendm/video/parameters.py deleted file mode 100644 index 56194f12..00000000 --- a/opendm/video/parameters.py +++ /dev/null @@ -1,46 +0,0 @@ - -import argparse -import datetime -import os - -class Parameters: - - def __init__(self, args): - - # "input" -> path to input video file(s), use ',' to separate multiple files") - # "output" -> path to output directory") - # "start" -> start frame index") - # "end" -> end frame index") - # "output-resolution" -> Override output resolution (ex. 1024)") - # "blur-threshold" -> blur measures that fall below this value will be considered 'blurry'. Good value is 300 - # "distance-threshold" -> distance measures that fall below this value will be considered 'similar'") - # "black-ratio-threshold" -> Set the threshold for considering a frame 'black'. Express the minimum value for the ratio: nb_black_pixels / nb_pixels. Default value is 0.98") - # "pixel-black-threshold" -> Set the threshold for considering a pixel 'black'. The threshold expresses the maximum pixel luminance value for which a pixel is considered 'black'. Good value is 0.30 (30%)") - # "use-srt" -> Use SRT files for extracting metadata (same name as video file with .srt extension)") - # "limit" -> Maximum number of output frames - # "frame-format" -> frame format (jpg, png, tiff, etc.)") - # "stats-file" -> Save statistics to csv file") - - if not os.path.exists(args["output"]): - os.makedirs(args["output"]) - - self.input = args["input"] - if isinstance(self.input, str): - self.input = [self.input] - - self.output = args["output"] - self.start = args.get("start", 0) - self.end = args.get("end", None) - self.limit = args.get("limit", None) - self.blur_threshold = args.get("blur_threshold", None) - self.distance_threshold = args.get("distance_threshold", None) - self.black_ratio_threshold = args.get("black_ratio_threshold", None) - self.pixel_black_threshold = args.get("pixel_black_threshold", None) - self.use_srt = "use_srt" in args - self.frame_format = args.get("frame_format", "jpg") - self.max_dimension = args.get("max_dimension", None) - - self.stats_file = args.get("stats_file", None) - - # We will resize the image to this size before processing - self.internal_resolution = 800 diff --git a/opendm/video/srtparser.py b/opendm/video/srtparser.py index 8c22eb3b..2d0da77a 100644 --- a/opendm/video/srtparser.py +++ b/opendm/video/srtparser.py @@ -3,11 +3,6 @@ import re -def float_or_none_if_zero(v): - fv = float(v) - return fv if fv != 0 else None - - def match_single(regexes, line, dtype=int): if isinstance(regexes, str): regexes = [(regexes, dtype)] @@ -23,7 +18,7 @@ def match_single(regexes, line, dtype=int): res = match.group(1) return transform(res) except Exception as e: - log.WARNING("Cannot parse SRT line \"%s\": %s", (line, str(e))) + log.WARNING("Cannot parse SRT line \"%s\": %s" % (line, str(e))) return None @@ -206,14 +201,23 @@ def parse(self): start = datetime.strptime(match.group(1), "%H:%M:%S,%f") end = datetime.strptime(match.group(2), "%H:%M:%S,%f") + def non_zero_float(v): + try: + v = float(v) + if v == 0: + return None + return v + except ValueError: + return None + iso = match_single([ "iso : (\d+)", "ISO (\d+)" ], line) shutter = match_single([ - "shutter : \d+/(\d+\.?\d*)", - "SS (\d+\.?\d*)" + ("shutter : \d+/(\d+\.?\d*)", non_zero_float), + ("SS (\d+\.?\d*)", non_zero_float) ], line) fnum = match_single([ @@ -224,22 +228,22 @@ def parse(self): focal_len = match_single("focal_len : (\d+)", line) latitude = match_single([ - ("latitude: ([\d\.\-]+)", float_or_none_if_zero), - ("latitude : ([\d\.\-]+)", float_or_none_if_zero), - ("GPS \([\d\.\-]+,? ([\d\.\-]+),? [\d\.\-]+\)", float_or_none_if_zero), - ("RTK \([-+]?\d+\.\d+, (-?\d+\.\d+), -?\d+\)", float_or_none_if_zero), + ("latitude: ([\d\.\-]+)", non_zero_float), + ("latitude : ([\d\.\-]+)", non_zero_float), + ("GPS \([\d\.\-]+,? ([\d\.\-]+),? [\d\.\-]+\)", non_zero_float), + ("RTK \([-+]?\d+\.\d+, (-?\d+\.\d+), [\d\.\-]+\)", non_zero_float), ], line) longitude = match_single([ - ("longitude: ([\d\.\-]+)", float_or_none_if_zero), - ("longtitude : ([\d\.\-]+)", float_or_none_if_zero), - ("GPS \(([\d\.\-]+),? [\d\.\-]+,? [\d\.\-]+\)", float_or_none_if_zero), - ("RTK \((-?\d+\.\d+), [-+]?\d+\.\d+, -?\d+\)", float_or_none_if_zero), + ("longitude: ([\d\.\-]+)", non_zero_float), + ("longtitude : ([\d\.\-]+)", non_zero_float), + ("GPS \(([\d\.\-]+),? [\d\.\-]+,? [\d\.\-]+\)", non_zero_float), + ("RTK \((-?\d+\.\d+), [-+]?\d+\.\d+, [\d\.\-]+\)", non_zero_float), ], line) altitude = match_single([ - ("altitude: ([\d\.\-]+)", float_or_none_if_zero), - ("GPS \([\d\.\-]+,? [\d\.\-]+,? ([\d\.\-]+)\)", float_or_none_if_zero), - ("RTK \([-+]?\d+\.\d+, [-+]?\d+\.\d+, (-?\d+\.?\d*)\)", float_or_none_if_zero), - ("abs_alt: ([\d\.\-]+)", float_or_none_if_zero), + ("altitude: ([\d\.\-]+)", non_zero_float), + ("GPS \([\d\.\-]+,? [\d\.\-]+,? ([\d\.\-]+)\)", non_zero_float), + ("RTK \([-+]?\d+\.\d+, [-+]?\d+\.\d+, ([\d\.\-]+)\)", non_zero_float), + ("abs_alt: ([\d\.\-]+)", non_zero_float), ], line) diff --git a/opendm/video/video2dataset.py b/opendm/video/video2dataset.py index 2d3fdfae..ea5cd30c 100644 --- a/opendm/video/video2dataset.py +++ b/opendm/video/video2dataset.py @@ -5,42 +5,42 @@ import time import cv2 import os +import sys +import zipfile import collections +import platform from PIL import Image import numpy as np import piexif +from opendm import system from opendm import log +from opendm.net import download from opendm.video.srtparser import SrtFileParser -from opendm.video.parameters import Parameters -from opendm.video.checkers import BlackFrameChecker, SimilarityChecker, ThresholdBlurChecker +from opendm.video.gpxparser import GpxFileParser, get_video_start_time class Video2Dataset: - - def __init__(self, parameters : Parameters): - self.parameters = parameters - - self.blur_checker = ThresholdBlurChecker(parameters.blur_threshold) if parameters.blur_threshold is not None else None - self.similarity_checker = SimilarityChecker(parameters.distance_threshold) if parameters.distance_threshold is not None else None - self.black_checker = BlackFrameChecker(parameters.black_ratio_threshold, parameters.pixel_black_threshold) if parameters.black_ratio_threshold is not None or parameters.pixel_black_threshold is not None else None - - self.frame_index = parameters.start - self.f = None - + def __init__(self, input, output, max_dimension=None, limit=None, use_loc_parsers=True): + self.frame_index = 0 + self.input = input + self.output = output + self.max_dimension = max_dimension + self.limit = limit + self.use_loc_parsers = use_loc_parsers + self.internal_resolution = 512 + self.frame_format = 'jpg' def ProcessVideo(self): + if not os.path.exists(self.output): + os.makedirs(self.output) + self.date_now = None start = time.time() - if (self.parameters.stats_file is not None): - self.f = open(self.parameters.stats_file, "w") - self.f.write("global_idx;file_name;frame_index;blur_score;is_blurry;is_black;last_frame_index;similarity_score;is_similar;written\n") - self.global_idx = 0 - output_file_paths = [] # foreach input file - for input_file in self.parameters.input: + for input_file in self.input: # get file name file_name = os.path.basename(input_file) log.INFO("Processing video: {}".format(input_file)) @@ -60,35 +60,42 @@ def ProcessVideo(self): log.INFO("Use pseudo start time: %s" % self.date_now) - if self.parameters.use_srt: + if self.use_loc_parsers: name = os.path.splitext(input_file)[0] srt_files = [name + ".srt", name + ".SRT"] - srt_parser = None + loc_parser = None for srt_file in srt_files: if os.path.exists(srt_file): log.INFO("Loading SRT file: {}".format(srt_file)) try: - srt_parser = SrtFileParser(srt_file) - srt_parser.parse() + loc_parser = SrtFileParser(srt_file) + loc_parser.parse() break except Exception as e: log.INFO("Error parsing SRT file: {}".format(e)) - srt_parser = None + loc_parser = None + + if loc_parser is None: + gpx_files = [name + ".gpx", name + ".GPX"] + + for gpx_file in gpx_files: + if os.path.exists(gpx_file): + log.INFO("Loading GPX file: {}".format(gpx_file)) + try: + loc_parser = GpxFileParser(gpx_file, get_video_start_time(input_file)) + loc_parser.parse() + break + except Exception as e: + log.INFO("Error parsing GPX file: {}".format(e)) + loc_parser = None else: - srt_parser = None - - if (self.black_checker is not None and self.black_checker.NeedPreProcess()): - start2 = time.time() - log.INFO("Preprocessing for black frame checker... this might take a bit") - self.black_checker.PreProcess(input_file, self.parameters.start, self.parameters.end) - end = time.time() - log.INFO("Preprocessing time: {:.2f}s".format(end - start2)) - log.INFO("Calculated luminance_range_size is {}".format(self.black_checker.luminance_range_size)) - log.INFO("Calculated luminance_minimum_value is {}".format(self.black_checker.luminance_minimum_value)) - log.INFO("Calculated absolute_threshold is {}".format(self.black_checker.absolute_threshold)) + loc_parser = None + + start2 = time.time() + # open video file cap = cv2.VideoCapture(input_file) @@ -96,107 +103,100 @@ def ProcessVideo(self): log.INFO("Error opening video stream or file") return - if (self.parameters.start is not None): - cap.set(cv2.CAP_PROP_POS_FRAMES, self.parameters.start) - self.frame_index = self.parameters.start - start_frame = self.parameters.start - else: - start_frame = 0 - - frames_to_process = self.parameters.end - start_frame + 1 if (self.parameters.end is not None) else video_info.total_frames - start_frame + self.frames_memory = [] + frame_index = 0 + frame_end = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + frames_to_process = video_info.total_frames + sharpness_window_size = max(round(video_info.frame_rate / 4.0), 1) + sharpness_window = [] - progress = 0 + progress = prev_progress = 0 while (cap.isOpened()): ret, frame = cap.read() if not ret: break - if (self.parameters.end is not None and self.frame_index > self.parameters.end): - break - # Calculate progress percentage - prev_progress = progress - progress = floor((self.frame_index - start_frame + 1) / frames_to_process * 100) - if progress != prev_progress: - print("[{}][{:3d}%] Processing frame {}/{}: ".format(file_name, progress, self.frame_index - start_frame + 1, frames_to_process), end="\r") + progress = floor((self.frame_index + 1) / frames_to_process * 100) + if abs(progress - prev_progress) >= 1: + prev_progress = progress + print("[{}][{:3d}%] Processing frame {}/{}: ".format(file_name, progress, self.frame_index + 1, frames_to_process), end="\r") - stats = self.ProcessFrame(frame, video_info, srt_parser) - if stats is not None and self.parameters.stats_file is not None: - self.WriteStats(input_file, stats) + frame_bw = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - # Add element to array - if stats is not None and "written" in stats.keys(): - output_file_paths.append(stats["path"]) + h, w = frame_bw.shape + resolution = self.internal_resolution + if resolution < w or resolution < h: + m = max(w, h) + factor = resolution / m + frame_bw = cv2.resize(frame_bw, (int(ceil(w * factor)), int(ceil(h * factor))), interpolation=cv2.INTER_AREA) - cap.release() + sharpness_window.append((self.frame_index, self.CalculateSharpness(frame_bw), frame, frame_bw)) + self.frame_index += 1 - if self.f is not None: - self.f.close() + if len(sharpness_window) >= sharpness_window_size: + output_file_paths.extend(self.ProcessFrame(sharpness_window, video_info, loc_parser)) + sharpness_window = [] - if self.parameters.limit is not None and self.parameters.limit > 0 and self.global_idx >= self.parameters.limit: - log.INFO("Limit of {} frames reached, trimming dataset".format(self.parameters.limit)) - output_file_paths = limit_files(output_file_paths, self.parameters.limit) + if sharpness_window: + output_file_paths.extend(self.ProcessFrame(sharpness_window, video_info, loc_parser)) + + cap.release() + + if self.limit is not None and self.limit > 0 and self.global_idx >= self.limit: + log.INFO("Limit of {} frames reached, trimming dataset".format(self.limit)) + output_file_paths = limit_files(output_file_paths, self.limit) end = time.time() log.INFO("Total processing time: {:.2f}s".format(end - start)) return output_file_paths + def CalculateSharpness(self, frame_bw): + return cv2.Laplacian(frame_bw, cv2.CV_64F).var() - def ProcessFrame(self, frame, video_info, srt_parser): - - res = {"frame_index": self.frame_index, "global_idx": self.global_idx} - - frame_bw = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - - h, w = frame_bw.shape - resolution = self.parameters.internal_resolution - if resolution < w or resolution < h: - m = max(w, h) - factor = resolution / m - frame_bw = cv2.resize(frame_bw, (int(ceil(w * factor)), int(ceil(h * factor))), interpolation=cv2.INTER_NEAREST) - - if (self.blur_checker is not None): - blur_score, is_blurry = self.blur_checker.IsBlur(frame_bw, self.frame_index) - res["blur_score"] = blur_score - res["is_blurry"] = is_blurry - - if is_blurry: - # print ("blurry, skipping") - self.frame_index += 1 - return res - - if (self.black_checker is not None): - is_black = self.black_checker.IsBlack(frame_bw, self.frame_index) - res["is_black"] = is_black - - if is_black: - # print ("black, skipping") - self.frame_index += 1 - return res - - if (self.similarity_checker is not None): - similarity_score, is_similar, last_frame_index = self.similarity_checker.IsSimilar(frame_bw, self.frame_index) - res["similarity_score"] = similarity_score - res["is_similar"] = is_similar - res["last_frame_index"] = last_frame_index - - if is_similar: - # print ("similar to {}, skipping".format(self.similarity_checker.last_image_id)) - self.frame_index += 1 - return res - - path = self.SaveFrame(frame, video_info, srt_parser) - res["written"] = True - res["path"] = path - self.frame_index += 1 + def IsBlack(self, frame_bw, picture_black_ratio_th=0.98, pixel_black_th=0.30): + abs_thresh = pixel_black_th * 255 + nb_black_pixels = np.sum(frame_bw < abs_thresh) + ratio_black_pixels = nb_black_pixels / (frame_bw.shape[0] * frame_bw.shape[1]) + return ratio_black_pixels >= picture_black_ratio_th + + def ProcessFrame(self, frames, video_info, loc_parser): + frame_index, _, frame, frame_bw = max(frames, key=lambda frame_data: frame_data[1]) + + if self.IsBlack(frame_bw): + return [] + + # Check for duplicates + small = cv2.resize(frame_bw, (9, 8), interpolation=cv2.INTER_AREA) + diff = small[:, 1:] > small[:, :-1] + frame_key = np.packbits(diff.flatten()) + + is_duplicate = False + for past_key in self.frames_memory: + hamming_distance = np.count_nonzero(frame_key != past_key) + if hamming_distance <= 2: # small tolerance, higher = more tolerant, 0 = exact + is_duplicate = True + break + + if is_duplicate: + return [] + + self.frames_memory.append(frame_key) + if len(self.frames_memory) > 10: + self.frames_memory.pop(0) + + current_frame_index = self.frame_index + self.frame_index = frame_index + path = self.SaveFrame(frame, video_info, loc_parser) + self.frame_index = current_frame_index self.global_idx += 1 + return [path] - return res - def SaveFrame(self, frame, video_info, srt_parser: SrtFileParser): - max_dim = self.parameters.max_dimension + def SaveFrame(self, frame, video_info, loc_parser: SrtFileParser): + max_dim = self.max_dimension if max_dim is not None: h, w, _ = frame.shape if max_dim < w or max_dim < h: @@ -204,10 +204,10 @@ def SaveFrame(self, frame, video_info, srt_parser: SrtFileParser): factor = max_dim / m frame = cv2.resize(frame, (int(ceil(w * factor)), int(ceil(h * factor))), interpolation=cv2.INTER_AREA) - path = os.path.join(self.parameters.output, - "{}_{}_{}.{}".format(video_info.basename, self.global_idx, self.frame_index, self.parameters.frame_format)) + path = os.path.join(self.output, + "{}_{}_{}.{}".format(video_info.basename, self.global_idx, self.frame_index, self.frame_format)) - _, buf = cv2.imencode('.' + self.parameters.frame_format, frame) + _, buf = cv2.imencode('.' + self.frame_format, frame) delta = datetime.timedelta(seconds=(self.frame_index / video_info.frame_rate)) elapsed_time = datetime.datetime(1900, 1, 1) + delta @@ -215,9 +215,9 @@ def SaveFrame(self, frame, video_info, srt_parser: SrtFileParser): img = Image.open(io.BytesIO(buf)) entry = gps_coords = None - if srt_parser is not None: - entry = srt_parser.get_entry(elapsed_time) - gps_coords = srt_parser.get_gps(elapsed_time) + if loc_parser is not None: + entry = loc_parser.get_entry(elapsed_time) + gps_coords = loc_parser.get_gps(elapsed_time) exif_time = (elapsed_time + (self.date_now - datetime.datetime(1900, 1, 1))) elapsed_time_str = exif_time.strftime("%Y:%m:%d %H:%M:%S") @@ -227,12 +227,12 @@ def SaveFrame(self, frame, video_info, srt_parser: SrtFileParser): # Set the EXIF metadata exif_dict = { "0th": { - piexif.ImageIFD.Software: "ODX", + piexif.ImageIFD.Software: "LGT", piexif.ImageIFD.DateTime: elapsed_time_str, piexif.ImageIFD.XResolution: (frame.shape[1], 1), piexif.ImageIFD.YResolution: (frame.shape[0], 1), piexif.ImageIFD.Make: "DJI" if video_info.basename.lower().startswith("dji") else "Unknown", - piexif.ImageIFD.Model: "Unknown" + piexif.ImageIFD.Model: "VideoFrame" }, "Exif": { piexif.ExifIFD.DateTimeOriginal: elapsed_time_str, @@ -261,20 +261,6 @@ def SaveFrame(self, frame, video_info, srt_parser: SrtFileParser): return path - def WriteStats(self, input_file, stats): - self.f.write("{};{};{};{};{};{};{};{};{};{}\n".format( - stats["global_idx"], - input_file, - stats["frame_index"], - stats["blur_score"] if "blur_score" in stats else "", - stats["is_blurry"] if "is_blurry" in stats else "", - stats["is_black"] if "is_black" in stats else "", - stats["last_frame_index"] if "last_frame_index" in stats else "", - stats["similarity_score"] if "similarity_score" in stats else "", - stats["is_similar"] if "is_similar" in stats else "", - stats["written"] if "written" in stats else "").replace(".", ",")) - - def get_video_info(input_file): video = cv2.VideoCapture(input_file) @@ -304,7 +290,8 @@ def limit_files(paths, limit): to_keep = list(p[keep_idxes]) for idx in remove_idxes: - os.remove(paths[idx]) + if os.path.isfile(paths[idx]): + os.remove(paths[idx]) return to_keep @@ -348,4 +335,77 @@ def get_gps_location(elapsed_time, lat, lng, altitude): gps_ifd[piexif.GPSIFD.GPSAltitudeRef] = 0 gps_ifd[piexif.GPSIFD.GPSAltitude] = float_to_rational(round(altitude * 1000) / 1000) - return gps_ifd \ No newline at end of file + return gps_ifd + +def extract_subtitles(video_file, overwrite=False): + base, ext = os.path.splitext(video_file) + srt_file = base + '.srt' + + if os.path.isfile(srt_file) and not overwrite: + return + + ffmpeg_bin = get_ffmpeg() + if ffmpeg_bin is None: + log.WARNING("Cannot extract subtitles, no ffmpeg") + return + + try: + system.run(f'"{ffmpeg_bin}" -i "{video_file}" -map 0:s:0 "{srt_file}" -y -hide_banner -loglevel panic') + except: + log.INFO(f"No subtitles found in {os.path.basename(video_file)}") + + if os.path.isfile(srt_file): + log.INFO(f"Extracted subtitles to {srt_file}") + +def get_ffmpeg(version = '7.0.2'): + base_dir = os.path.join(os.path.dirname(__file__), "..", "..") + if sys.platform == 'win32': + base_dir = os.path.join(os.getenv('PROGRAMDATA'),"LGT") + base_dir = os.path.join(os.path.abspath(base_dir), "storage", "binaries") + + if not os.path.isdir(base_dir): + os.makedirs(base_dir, exist_ok=True) + + machine = platform.machine().lower() + url = f"https://github.com/pierotofy/photogrammetry-tools/releases/download/v1.0.0/ffmpeg-{version}-amd64.zip" + + if "arm" in machine: + url = f"https://github.com/pierotofy/photogrammetry-tools/releases/download/v1.0.0/ffmpeg-{version}-arm64.zip" + + # Check if we need to download it + ffmpeg_bin = os.path.join(base_dir, "ffmpeg") + if not os.path.isfile(ffmpeg_bin): + log.INFO("Downloading ffmpeg from %s ..." % url) + + last_update = 0 + + def callback(progress): + nonlocal last_update + + time_has_elapsed = time.time() - last_update >= 2 + + if time_has_elapsed or int(progress) == 100: + log.INFO("Downloading: %s%%" % int(progress)) + last_update = time.time() + + try: + downloaded_file = download(url, base_dir, progress_callback=callback) + except Exception as e: + log.WARNING("Cannot download %s: %s" % (url, str(e))) + return None + + if os.path.basename(downloaded_file).lower().endswith(".zip"): + log.INFO("Extracting %s ..." % downloaded_file) + with zipfile.ZipFile(downloaded_file, 'r') as z: + z.extractall(base_dir) + os.remove(downloaded_file) + + if not os.path.isfile(ffmpeg_bin): + log.WARNING("Cannot find %s (is the URL correct?)" % ffmpeg_bin) + return None + else: + if not os.access(ffmpeg_bin, os.X_OK): + os.chmod(ffmpeg_bin, 0o755) + return ffmpeg_bin + else: + return ffmpeg_bin \ No newline at end of file diff --git a/stages/dataset.py b/stages/dataset.py index 9f3979b8..1e98bea9 100644 --- a/stages/dataset.py +++ b/stages/dataset.py @@ -15,7 +15,7 @@ from opendm.skyremoval.skyfilter import SkyFilter from opendm.bgfilter import BgFilter from opendm.concurrency import parallel_map -from opendm.video.video2dataset import Parameters, Video2Dataset +from opendm.video.video2dataset import Video2Dataset, extract_subtitles def save_images_database(photos, database_file): with open(database_file, 'w') as f: @@ -131,19 +131,10 @@ def find_mask(photo_path, masks): log.INFO("Found video files (%s), extracting frames" % len(video_files)) try: - params = Parameters({ - "input": video_files, - "output": images_dir, - - "blur_threshold": 200, - "distance_threshold": 10, - "black_ratio_threshold": 0.98, - "pixel_black_threshold": 0.30, - "use_srt": True, - "max_dimension": args.video_resolution, - "limit": args.video_limit, - }) - v2d = Video2Dataset(params) + v2d = Video2Dataset(input=video_files, + output=images_dir, + max_dimension=args.video_resolution, + limit=args.video_limit) frames = v2d.ProcessVideo() with open(frames_db_file, 'w') as f: From 97c30679c68dcee75dc3a79dc052abac5e3c0756 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 15 Oct 2026 12:53:36 -0400 Subject: [PATCH 2/5] Refactor: opendm --> modules --- Dockerfile | 2 +- contrib/dem-blend/dem-blend.py | 2 +- contrib/pc2dem/pc2dem.py | 2 +- contrib/resize/resize.py | 2 +- gpu.Dockerfile | 2 +- innosetup.iss | 4 +-- {opendm => modules}/__init__.py | 0 {opendm => modules}/ai.py | 4 +-- {opendm => modules}/align.py | 10 +++--- {opendm => modules}/arghelpers.py | 2 +- {opendm => modules}/bgfilter.py | 4 +-- {opendm => modules}/boundary.py | 4 +-- {opendm => modules}/camera.py | 2 +- {opendm => modules}/cogeo.py | 8 ++--- {opendm => modules}/concurrency.py | 2 +- {opendm => modules}/config.py | 6 ++-- {opendm => modules}/context.py | 0 {opendm => modules}/cropper.py | 12 +++---- {opendm => modules}/cutline.py | 8 ++--- {opendm => modules}/dem/__init__.py | 0 {opendm => modules}/dem/commands.py | 14 ++++---- {opendm => modules}/dem/merge.py | 8 ++--- {opendm => modules}/dem/pdal.py | 6 ++-- {opendm => modules}/dem/utils.py | 0 {opendm => modules}/dls.py | 0 {opendm => modules}/entwine.py | 10 +++--- {opendm => modules}/exiftool.py | 6 ++-- {opendm => modules}/gcp.py | 4 +-- {opendm => modules}/geo.py | 4 +-- {opendm => modules}/get_image_size.py | 2 +- {opendm => modules}/gltf.py | 6 ++-- {opendm => modules}/gpu.py | 2 +- {opendm => modules}/gsd.py | 4 +-- {opendm => modules}/io.py | 0 {opendm => modules}/location.py | 2 +- {opendm => modules}/log.py | 2 +- {opendm => modules}/mesh.py | 12 +++---- {opendm => modules}/multispectral.py | 6 ++-- {opendm => modules}/net.py | 0 {opendm => modules}/nvm.py | 2 +- {opendm => modules}/objpacker/__init__.py | 0 .../objpacker/imagepacker/__init__.py | 0 .../objpacker/imagepacker/imagepacker.py | 0 .../objpacker/imagepacker/utils.py | 0 {opendm => modules}/objpacker/objpacker.py | 0 {opendm => modules}/ogctiles.py | 10 +++--- {opendm => modules}/opc.py | 8 ++--- {opendm => modules}/orthophoto.py | 16 ++++----- {opendm => modules}/osfm.py | 20 +++++------ {opendm => modules}/photo.py | 6 ++-- {opendm => modules}/point_cloud.py | 24 ++++++------- {opendm => modules}/progress.py | 2 +- {opendm => modules}/pseudogeo.py | 4 +-- {opendm => modules}/remote.py | 10 +++--- {opendm => modules}/report/dsm_gradient.png | Bin .../report/overlap_color_map.txt | 0 .../report/overlap_diagram_legend.png | Bin {opendm => modules}/rollingshutter.py | 2 +- {opendm => modules}/shots.py | 6 ++-- {opendm => modules}/skyremoval/__init__.py | 0 .../skyremoval/guidedfilter.py | 0 {opendm => modules}/skyremoval/skyfilter.py | 4 +-- {opendm => modules}/system.py | 4 +-- {opendm => modules}/thermal.py | 8 ++--- {opendm => modules}/thermal_tools/__init__.py | 0 .../thermal_tools/dji_unpack.py | 6 ++-- .../thermal_tools/thermal_utils.py | 0 {opendm => modules}/tiles/color_relief.txt | 0 {opendm => modules}/tiles/hsv_merge.py | 0 {opendm => modules}/tiles/static_tiler.py | 0 {opendm => modules}/tiles/tiler.py | 6 ++-- {opendm => modules}/types.py | 16 ++++----- {opendm => modules}/utils.py | 6 ++-- {opendm => modules}/vendor/__init__.py | 0 {opendm => modules}/vendor/gdal_fillnodata.py | 0 {opendm => modules}/video/__init__.py | 0 {opendm => modules}/video/gpxparser.py | 4 +-- {opendm => modules}/video/srtparser.py | 2 +- {opendm => modules}/video/video2dataset.py | 10 +++--- portable.Dockerfile | 2 +- run.py | 14 ++++---- stages/dataset.py | 28 +++++++-------- stages/mvstex.py | 16 ++++----- stages/odm_app.py | 10 +++--- stages/odm_dem.py | 24 ++++++------- stages/odm_filterpoints.py | 16 ++++----- stages/odm_georeferencing.py | 26 +++++++------- stages/odm_meshing.py | 16 ++++----- stages/odm_orthophoto.py | 26 +++++++------- stages/odm_postprocess.py | 12 +++---- stages/odm_report.py | 26 +++++++------- stages/openmvs.py | 22 ++++++------ stages/run_opensfm.py | 26 +++++++------- stages/splitmerge.py | 32 +++++++++--------- start-dev-env.sh | 2 +- tests/test_camera.py | 2 +- tests/test_gcp.py | 2 +- tests/test_osfm.py | 4 +-- tests/test_photo.py | 2 +- tests/test_remote.py | 2 +- tests/test_types.py | 2 +- 101 files changed, 326 insertions(+), 326 deletions(-) rename {opendm => modules}/__init__.py (100%) rename {opendm => modules}/ai.py (97%) rename {opendm => modules}/align.py (96%) rename {opendm => modules}/arghelpers.py (98%) rename {opendm => modules}/bgfilter.py (97%) rename {opendm => modules}/boundary.py (97%) rename {opendm => modules}/camera.py (99%) rename {opendm => modules}/cogeo.py (94%) rename {opendm => modules}/concurrency.py (99%) rename {opendm => modules}/config.py (99%) rename {opendm => modules}/context.py (100%) rename {opendm => modules}/cropper.py (97%) rename {opendm => modules}/cutline.py (98%) rename {opendm => modules}/dem/__init__.py (100%) rename {opendm => modules}/dem/commands.py (97%) rename {opendm => modules}/dem/merge.py (98%) rename {opendm => modules}/dem/pdal.py (98%) rename {opendm => modules}/dem/utils.py (100%) rename {opendm => modules}/dls.py (100%) rename {opendm => modules}/entwine.py (96%) rename {opendm => modules}/exiftool.py (96%) rename {opendm => modules}/gcp.py (99%) rename {opendm => modules}/geo.py (98%) rename {opendm => modules}/get_image_size.py (97%) rename {opendm => modules}/gltf.py (99%) rename {opendm => modules}/gpu.py (98%) rename {opendm => modules}/gsd.py (99%) rename {opendm => modules}/io.py (100%) rename {opendm => modules}/location.py (99%) rename {opendm => modules}/log.py (98%) rename {opendm => modules}/mesh.py (97%) rename {opendm => modules}/multispectral.py (99%) rename {opendm => modules}/net.py (100%) rename {opendm => modules}/nvm.py (98%) rename {opendm => modules}/objpacker/__init__.py (100%) rename {opendm => modules}/objpacker/imagepacker/__init__.py (100%) rename {opendm => modules}/objpacker/imagepacker/imagepacker.py (100%) rename {opendm => modules}/objpacker/imagepacker/utils.py (100%) rename {opendm => modules}/objpacker/objpacker.py (100%) rename {opendm => modules}/ogctiles.py (96%) rename {opendm => modules}/opc.py (90%) rename {opendm => modules}/orthophoto.py (98%) rename {opendm => modules}/osfm.py (98%) rename {opendm => modules}/photo.py (99%) rename {opendm => modules}/point_cloud.py (96%) rename {opendm => modules}/progress.py (98%) rename {opendm => modules}/pseudogeo.py (95%) rename {opendm => modules}/remote.py (99%) rename {opendm => modules}/report/dsm_gradient.png (100%) rename {opendm => modules}/report/overlap_color_map.txt (100%) rename {opendm => modules}/report/overlap_diagram_legend.png (100%) rename {opendm => modules}/rollingshutter.py (99%) rename {opendm => modules}/shots.py (97%) rename {opendm => modules}/skyremoval/__init__.py (100%) rename {opendm => modules}/skyremoval/guidedfilter.py (100%) rename {opendm => modules}/skyremoval/skyfilter.py (97%) rename {opendm => modules}/system.py (98%) rename {opendm => modules}/thermal.py (94%) rename {opendm => modules}/thermal_tools/__init__.py (100%) rename {opendm => modules}/thermal_tools/dji_unpack.py (95%) rename {opendm => modules}/thermal_tools/thermal_utils.py (100%) rename {opendm => modules}/tiles/color_relief.txt (100%) rename {opendm => modules}/tiles/hsv_merge.py (100%) rename {opendm => modules}/tiles/static_tiler.py (100%) rename {opendm => modules}/tiles/tiler.py (97%) rename {opendm => modules}/types.py (98%) rename {opendm => modules}/utils.py (97%) rename {opendm => modules}/vendor/__init__.py (100%) rename {opendm => modules}/vendor/gdal_fillnodata.py (100%) rename {opendm => modules}/video/__init__.py (100%) rename {opendm => modules}/video/gpxparser.py (98%) rename {opendm => modules}/video/srtparser.py (99%) rename {opendm => modules}/video/video2dataset.py (98%) diff --git a/Dockerfile b/Dockerfile index a75741c6..97cc942f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ RUN bash configure.sh installruntimedeps \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ && bash run.sh --help \ - && bash -c "eval $(python3 /code/opendm/context.py) && python3 -c 'from opensfm import io, pymap'" + && bash -c "eval $(python3 /code/modules/context.py) && python3 -c 'from opensfm import io, pymap'" # Entry point ENTRYPOINT ["python3", "/code/run.py"] diff --git a/contrib/dem-blend/dem-blend.py b/contrib/dem-blend/dem-blend.py index 97127017..a9b33a8e 100755 --- a/contrib/dem-blend/dem-blend.py +++ b/contrib/dem-blend/dem-blend.py @@ -8,7 +8,7 @@ sys.path.insert(0, os.path.join("..", "..", os.path.dirname(__file__))) import argparse -from opendm.dem import merge +from modules.dem import merge parser = argparse.ArgumentParser(description='Merge and blend DEMs using ODX\'s approach.') parser.add_argument('input_dems', diff --git a/contrib/pc2dem/pc2dem.py b/contrib/pc2dem/pc2dem.py index 5a5933a7..f63e09f4 100755 --- a/contrib/pc2dem/pc2dem.py +++ b/contrib/pc2dem/pc2dem.py @@ -8,7 +8,7 @@ import argparse import multiprocessing -from opendm.dem import commands +from modules.dem import commands parser = argparse.ArgumentParser(description='Generate DEMs from point clouds using ODX\'s algorithm.') parser.add_argument('point_cloud', diff --git a/contrib/resize/resize.py b/contrib/resize/resize.py index bed6ec1b..429eed85 100644 --- a/contrib/resize/resize.py +++ b/contrib/resize/resize.py @@ -8,7 +8,7 @@ from multiprocessing.pool import ThreadPool import sys sys.path.append("../../") -from opendm.gcp import GCPFile +from modules.gcp import GCPFile parser = argparse.ArgumentParser(description='Exif Image Resize') parser.add_argument('--input', '-i', diff --git a/gpu.Dockerfile b/gpu.Dockerfile index cd104613..d06c1d81 100644 --- a/gpu.Dockerfile +++ b/gpu.Dockerfile @@ -46,7 +46,7 @@ RUN bash configure.sh installruntimedeps \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ && bash run.sh --help \ - && bash -c "eval $(python3 /code/opendm/context.py) && python3 -c 'from opensfm import io, pymap'" + && bash -c "eval $(python3 /code/modules/context.py) && python3 -c 'from opensfm import io, pymap'" # Entry point ENTRYPOINT ["python3", "/code/run.py"] diff --git a/innosetup.iss b/innosetup.iss index b4ce4425..6e530298 100644 --- a/innosetup.iss +++ b/innosetup.iss @@ -44,7 +44,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl" [Files] Source: "contrib\*"; DestDir: "{app}\contrib"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "licenses\*"; DestDir: "{app}\licenses"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "opendm\*"; DestDir: "{app}\opendm"; Excludes: "__pycache__"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "modules\*"; DestDir: "{app}\modules"; Excludes: "__pycache__"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "stages\*"; DestDir: "{app}\stages"; Excludes: "__pycache__"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "SuperBuild\install\bin\*"; DestDir: "{app}\SuperBuild\install\bin"; Excludes: "__pycache__"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "SuperBuild\install\lib\python3.12\*"; DestDir: "{app}\SuperBuild\install\lib\python3.12"; Excludes: "__pycache__"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -157,6 +157,6 @@ end; Type: filesandordirs; Name: "{app}\SuperBuild" Type: filesandordirs; Name: "{app}\contrib" Type: filesandordirs; Name: "{app}\licenses" -Type: filesandordirs; Name: "{app}\opendm" +Type: filesandordirs; Name: "{app}\modules" Type: filesandordirs; Name: "{app}\stages" Type: filesandordirs; Name: "{app}\venv" diff --git a/opendm/__init__.py b/modules/__init__.py similarity index 100% rename from opendm/__init__.py rename to modules/__init__.py diff --git a/opendm/ai.py b/modules/ai.py similarity index 97% rename from opendm/ai.py rename to modules/ai.py index 7ac09b11..1d40c575 100644 --- a/opendm/ai.py +++ b/modules/ai.py @@ -1,6 +1,6 @@ import os -from opendm.net import download -from opendm import log +from modules.net import download +from modules import log import zipfile import time import sys diff --git a/opendm/align.py b/modules/align.py similarity index 96% rename from opendm/align.py rename to modules/align.py index 349cb9a0..f8ba7ae1 100644 --- a/opendm/align.py +++ b/modules/align.py @@ -7,11 +7,11 @@ import numpy as np import rasterio from rasterio.crs import CRS -from opendm.utils import double_quote -from opendm import log -from opendm import io -from opendm import system -from opendm.concurrency import get_max_memory +from modules.utils import double_quote +from modules import log +from modules import io +from modules import system +from modules.concurrency import get_max_memory def get_point_cloud_crs(file): pipeline = pdal.Pipeline(json.dumps([ file ])) diff --git a/opendm/arghelpers.py b/modules/arghelpers.py similarity index 98% rename from opendm/arghelpers.py rename to modules/arghelpers.py index ec210b46..afa079af 100644 --- a/opendm/arghelpers.py +++ b/modules/arghelpers.py @@ -1,4 +1,4 @@ -from opendm import log +from modules import log from shlex import _find_unsafe import json import os diff --git a/opendm/bgfilter.py b/modules/bgfilter.py similarity index 97% rename from opendm/bgfilter.py rename to modules/bgfilter.py index 95f73228..503c4680 100644 --- a/opendm/bgfilter.py +++ b/modules/bgfilter.py @@ -4,8 +4,8 @@ import cv2 import os import onnxruntime as ort -from opendm import log -from opendm.ai import read_image +from modules import log +from modules.ai import read_image from threading import Lock mutex = Lock() diff --git a/opendm/boundary.py b/modules/boundary.py similarity index 97% rename from opendm/boundary.py rename to modules/boundary.py index 7ebb8dfb..ffa7bff8 100644 --- a/opendm/boundary.py +++ b/modules/boundary.py @@ -4,9 +4,9 @@ import io import json from pyproj import CRS -from opendm.location import transformer +from modules.location import transformer from osgeo import ogr -from opendm.shots import get_origin +from modules.shots import get_origin def compute_boundary_from_shots(reconstruction_json, buffer=0, reconstruction_offset=(0, 0)): if not os.path.isfile(reconstruction_json): diff --git a/opendm/camera.py b/modules/camera.py similarity index 99% rename from opendm/camera.py rename to modules/camera.py index 9687546e..7b4993db 100644 --- a/opendm/camera.py +++ b/modules/camera.py @@ -1,5 +1,5 @@ import os, json -from opendm import log +from modules import log def get_cameras_from_opensfm(reconstruction_file): """ diff --git a/opendm/cogeo.py b/modules/cogeo.py similarity index 94% rename from opendm/cogeo.py rename to modules/cogeo.py index 40c753ec..3f3e5357 100644 --- a/opendm/cogeo.py +++ b/modules/cogeo.py @@ -1,9 +1,9 @@ import os import shutil -from opendm import system -from opendm.concurrency import get_max_memory -from opendm import io -from opendm import log +from modules import system +from modules.concurrency import get_max_memory +from modules import io +from modules import log def convert_to_cogeo(src_path, blocksize=256, max_workers=1, compression="DEFLATE", deflate_predictor=2): """ diff --git a/opendm/concurrency.py b/modules/concurrency.py similarity index 99% rename from opendm/concurrency.py rename to modules/concurrency.py index 78af7e70..b9857fc4 100644 --- a/opendm/concurrency.py +++ b/modules/concurrency.py @@ -7,7 +7,7 @@ import queue import threading import time -from opendm import log +from modules import log def get_max_memory(minimum = 5, use_at_most = 0.5): """ diff --git a/opendm/config.py b/modules/config.py similarity index 99% rename from opendm/config.py rename to modules/config.py index 8a4f981c..2657bc4a 100755 --- a/opendm/config.py +++ b/modules/config.py @@ -4,9 +4,9 @@ import yaml -from opendm import context -from opendm import io -from opendm import log +from modules import context +from modules import io +from modules import log from pyodm import Node, exceptions # parse arguments diff --git a/opendm/context.py b/modules/context.py similarity index 100% rename from opendm/context.py rename to modules/context.py diff --git a/opendm/cropper.py b/modules/cropper.py similarity index 97% rename from opendm/cropper.py rename to modules/cropper.py index 2ed4ee38..04dbe21e 100644 --- a/opendm/cropper.py +++ b/modules/cropper.py @@ -1,13 +1,13 @@ -from opendm import context -from opendm.system import run -from opendm import log -from opendm.point_cloud import export_summary_json +from modules import context +from modules.system import run +from modules import log +from modules.point_cloud import export_summary_json from osgeo import ogr import fiona import fiona.crs import json, os -from opendm.concurrency import get_max_memory -from opendm.utils import double_quote +from modules.concurrency import get_max_memory +from modules.utils import double_quote class Cropper: def __init__(self, storage_dir, files_prefix = "crop"): diff --git a/opendm/cutline.py b/modules/cutline.py similarity index 98% rename from opendm/cutline.py rename to modules/cutline.py index f23a02a6..6728e0a6 100644 --- a/opendm/cutline.py +++ b/modules/cutline.py @@ -4,10 +4,10 @@ import numpy as np import math import sys -from opendm import log -from opendm import io -from opendm import concurrency -from opendm import system +from modules import log +from modules import io +from modules import concurrency +from modules import system from skimage.feature import canny from skimage.draw import line diff --git a/opendm/dem/__init__.py b/modules/dem/__init__.py similarity index 100% rename from opendm/dem/__init__.py rename to modules/dem/__init__.py diff --git a/opendm/dem/commands.py b/modules/dem/commands.py similarity index 97% rename from opendm/dem/commands.py rename to modules/dem/commands.py index 4bf790a8..5b0d7536 100755 --- a/opendm/dem/commands.py +++ b/modules/dem/commands.py @@ -5,14 +5,14 @@ import shutil import glob import re -from opendm.system import run -from opendm import point_cloud -from opendm import io -from opendm import system -from opendm.concurrency import get_max_memory +from modules.system import run +from modules import point_cloud +from modules import io +from modules import system +from modules.concurrency import get_max_memory from datetime import datetime -from opendm.vendor.gdal_fillnodata import main as gdal_fillnodata -from opendm import log +from modules.vendor.gdal_fillnodata import main as gdal_fillnodata +from modules import log from . import pdal diff --git a/opendm/dem/merge.py b/modules/dem/merge.py similarity index 98% rename from opendm/dem/merge.py rename to modules/dem/merge.py index af0e32c9..d987ecd3 100644 --- a/opendm/dem/merge.py +++ b/modules/dem/merge.py @@ -3,10 +3,10 @@ from scipy import ndimage import rasterio from rasterio.transform import Affine, rowcol -from opendm import system -from opendm.dem.commands import compute_euclidean_map -from opendm import log -from opendm import io +from modules import system +from modules.dem.commands import compute_euclidean_map +from modules import log +from modules import io import os def euclidean_merge_dems(input_dems, output_dem, creation_options={}, euclidean_map_source=None): diff --git a/opendm/dem/pdal.py b/modules/dem/pdal.py similarity index 98% rename from opendm/dem/pdal.py rename to modules/dem/pdal.py index 9bfa4452..6599db96 100644 --- a/opendm/dem/pdal.py +++ b/modules/dem/pdal.py @@ -34,9 +34,9 @@ import sys import json as jsonlib import tempfile -from opendm import system -from opendm import log -from opendm.utils import double_quote +from modules import system +from modules import log +from modules.utils import double_quote from datetime import datetime diff --git a/opendm/dem/utils.py b/modules/dem/utils.py similarity index 100% rename from opendm/dem/utils.py rename to modules/dem/utils.py diff --git a/opendm/dls.py b/modules/dls.py similarity index 100% rename from opendm/dls.py rename to modules/dls.py diff --git a/opendm/entwine.py b/modules/entwine.py similarity index 96% rename from opendm/entwine.py rename to modules/entwine.py index 900268e7..8b53f183 100644 --- a/opendm/entwine.py +++ b/modules/entwine.py @@ -1,11 +1,11 @@ import os import sys import shutil -from opendm.utils import double_quote -from opendm import io -from opendm import log -from opendm import system -from opendm import concurrency +from modules.utils import double_quote +from modules import io +from modules import log +from modules import system +from modules import concurrency def build(input_point_cloud_files, output_path, max_concurrency=8, rerun=False): diff --git a/opendm/exiftool.py b/modules/exiftool.py similarity index 96% rename from opendm/exiftool.py rename to modules/exiftool.py index 0516bedf..b72b169e 100644 --- a/opendm/exiftool.py +++ b/modules/exiftool.py @@ -3,9 +3,9 @@ import tempfile import base64 from rasterio.io import MemoryFile -from opendm.system import run -from opendm import log -from opendm.utils import double_quote +from modules.system import run +from modules import log +from modules.utils import double_quote def extract_raw_thermal_image_data(image_path): try: diff --git a/opendm/gcp.py b/modules/gcp.py similarity index 99% rename from opendm/gcp.py rename to modules/gcp.py index ea15d129..3bdebe5f 100644 --- a/opendm/gcp.py +++ b/modules/gcp.py @@ -2,8 +2,8 @@ import os import math import json -from opendm import log -from opendm import location +from modules import log +from modules import location from pyproj import CRS def fromFile(imgName): diff --git a/opendm/geo.py b/modules/geo.py similarity index 98% rename from opendm/geo.py rename to modules/geo.py index 897cd8ee..89eb98a5 100644 --- a/opendm/geo.py +++ b/modules/geo.py @@ -1,7 +1,7 @@ import os import math -from opendm import log -from opendm import location +from modules import log +from modules import location from pyproj import CRS class GeoFile: diff --git a/opendm/get_image_size.py b/modules/get_image_size.py similarity index 97% rename from opendm/get_image_size.py rename to modules/get_image_size.py index a34b74dd..e7146df4 100644 --- a/opendm/get_image_size.py +++ b/modules/get_image_size.py @@ -2,7 +2,7 @@ from PIL import Image import cv2 import rawpy -from opendm import log +from modules import log Image.MAX_IMAGE_PIXELS = None diff --git a/opendm/gltf.py b/modules/gltf.py similarity index 99% rename from opendm/gltf.py rename to modules/gltf.py index bcbbdbd4..8c4eab46 100644 --- a/opendm/gltf.py +++ b/modules/gltf.py @@ -4,9 +4,9 @@ import warnings import numpy as np import pygltflib -from opendm import system -from opendm import io -from opendm import log +from modules import system +from modules import io +from modules import log warnings.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarning) diff --git a/opendm/gpu.py b/modules/gpu.py similarity index 98% rename from opendm/gpu.py rename to modules/gpu.py index 96f79a91..5758a690 100644 --- a/opendm/gpu.py +++ b/modules/gpu.py @@ -3,7 +3,7 @@ import shutil import ctypes from functools import lru_cache -from opendm import log +from modules import log def gpu_disabled_by_user_env(): return bool(os.environ.get('ODM_NO_GPU')) or bool(os.environ.get('ODX_NO_GPU')) diff --git a/opendm/gsd.py b/modules/gsd.py similarity index 99% rename from opendm/gsd.py rename to modules/gsd.py index 3c22531e..c5645c8e 100644 --- a/opendm/gsd.py +++ b/modules/gsd.py @@ -3,8 +3,8 @@ import numpy as np import math from functools import lru_cache -from opendm import log -from opendm.shots import get_origin +from modules import log +from modules.shots import get_origin from scipy import spatial def image_max_size(photos, target_resolution, reconstruction_json, gsd_error_estimate = 0.5, ignore_gsd=False, has_gcp=False): diff --git a/opendm/io.py b/modules/io.py similarity index 100% rename from opendm/io.py rename to modules/io.py diff --git a/opendm/location.py b/modules/location.py similarity index 99% rename from opendm/location.py rename to modules/location.py index 71701c51..19cbe79d 100644 --- a/opendm/location.py +++ b/modules/location.py @@ -1,5 +1,5 @@ import math -from opendm import log +from modules import log from pyproj import Proj, Transformer, CRS from osgeo import osr diff --git a/opendm/log.py b/modules/log.py similarity index 98% rename from opendm/log.py rename to modules/log.py index f9aaec0a..1f02fe82 100644 --- a/opendm/log.py +++ b/modules/log.py @@ -7,7 +7,7 @@ import multiprocessing from functools import lru_cache -from opendm.arghelpers import double_quote, args_to_dict +from modules.arghelpers import double_quote, args_to_dict from vmem import virtual_memory if sys.platform == 'win32' or os.getenv('no_ansiesc'): diff --git a/opendm/mesh.py b/modules/mesh.py similarity index 97% rename from opendm/mesh.py rename to modules/mesh.py index dbc39446..cc781c10 100644 --- a/opendm/mesh.py +++ b/modules/mesh.py @@ -1,11 +1,11 @@ from __future__ import absolute_import import os, shutil, sys, struct, random, math, platform -from opendm.dem import commands -from opendm import system -from opendm import log -from opendm import context -from opendm import concurrency -from opendm import point_cloud +from modules.dem import commands +from modules import system +from modules import log +from modules import context +from modules import concurrency +from modules import point_cloud from scipy import signal import numpy as np diff --git a/opendm/multispectral.py b/modules/multispectral.py similarity index 99% rename from opendm/multispectral.py rename to modules/multispectral.py index 42542906..1e91c896 100644 --- a/opendm/multispectral.py +++ b/modules/multispectral.py @@ -2,10 +2,10 @@ import re import cv2 import os -from opendm import dls +from modules import dls import numpy as np -from opendm import log -from opendm.concurrency import parallel_map +from modules import log +from modules.concurrency import parallel_map from opensfm.io import imread from skimage import exposure diff --git a/opendm/net.py b/modules/net.py similarity index 100% rename from opendm/net.py rename to modules/net.py diff --git a/opendm/nvm.py b/modules/nvm.py similarity index 98% rename from opendm/nvm.py rename to modules/nvm.py index d93dfb4a..2112bb69 100644 --- a/opendm/nvm.py +++ b/modules/nvm.py @@ -1,5 +1,5 @@ import os -from opendm import log +from modules import log def replace_nvm_images(src_nvm_file, img_map, dst_nvm_file): """ diff --git a/opendm/objpacker/__init__.py b/modules/objpacker/__init__.py similarity index 100% rename from opendm/objpacker/__init__.py rename to modules/objpacker/__init__.py diff --git a/opendm/objpacker/imagepacker/__init__.py b/modules/objpacker/imagepacker/__init__.py similarity index 100% rename from opendm/objpacker/imagepacker/__init__.py rename to modules/objpacker/imagepacker/__init__.py diff --git a/opendm/objpacker/imagepacker/imagepacker.py b/modules/objpacker/imagepacker/imagepacker.py similarity index 100% rename from opendm/objpacker/imagepacker/imagepacker.py rename to modules/objpacker/imagepacker/imagepacker.py diff --git a/opendm/objpacker/imagepacker/utils.py b/modules/objpacker/imagepacker/utils.py similarity index 100% rename from opendm/objpacker/imagepacker/utils.py rename to modules/objpacker/imagepacker/utils.py diff --git a/opendm/objpacker/objpacker.py b/modules/objpacker/objpacker.py similarity index 100% rename from opendm/objpacker/objpacker.py rename to modules/objpacker/objpacker.py diff --git a/opendm/ogctiles.py b/modules/ogctiles.py similarity index 96% rename from opendm/ogctiles.py rename to modules/ogctiles.py index 351ed9cd..7b15f254 100644 --- a/opendm/ogctiles.py +++ b/modules/ogctiles.py @@ -3,11 +3,11 @@ import shutil import json import math -from opendm.utils import double_quote -from opendm import io -from opendm import log -from opendm import system -from opendm.entwine import build_entwine +from modules.utils import double_quote +from modules import io +from modules import log +from modules import system +from modules.entwine import build_entwine import fiona from shapely.geometry import shape diff --git a/opendm/opc.py b/modules/opc.py similarity index 90% rename from opendm/opc.py rename to modules/opc.py index f335d79a..37f22230 100644 --- a/opendm/opc.py +++ b/modules/opc.py @@ -1,8 +1,8 @@ import os -from opendm.ai import get_model -from opendm import log -from opendm.system import run -from opendm import io +from modules.ai import get_model +from modules import log +from modules.system import run +from modules import io def classify(point_cloud, max_threads=8): tmp_output = io.related_file_path(point_cloud, postfix=".classified") diff --git a/opendm/orthophoto.py b/modules/orthophoto.py similarity index 98% rename from opendm/orthophoto.py rename to modules/orthophoto.py index d5b14a37..5134a306 100644 --- a/opendm/orthophoto.py +++ b/modules/orthophoto.py @@ -1,9 +1,9 @@ import os import threading -from opendm import log -from opendm import system -from opendm.cropper import Cropper -from opendm.concurrency import get_max_memory, parallel_map +from modules import log +from modules import system +from modules.cropper import Cropper +from modules.concurrency import get_max_memory, parallel_map import math import numpy as np import rasterio @@ -11,10 +11,10 @@ from edt import edt from rasterio.transform import Affine, rowcol from rasterio.mask import mask -from opendm import io -from opendm.tiles.tiler import generate_orthophoto_tiles -from opendm.cogeo import convert_to_cogeo -from opendm.utils import add_raster_meta_tags +from modules import io +from modules.tiles.tiler import generate_orthophoto_tiles +from modules.cogeo import convert_to_cogeo +from modules.utils import add_raster_meta_tags from osgeo import gdal from osgeo import ogr diff --git a/opendm/osfm.py b/modules/osfm.py similarity index 98% rename from opendm/osfm.py rename to modules/osfm.py index 1b7608d5..f11812b2 100644 --- a/opendm/osfm.py +++ b/modules/osfm.py @@ -7,22 +7,22 @@ import numpy as np import pyproj from pyproj import CRS -from opendm import io -from opendm import log -from opendm import system -from opendm import context -from opendm import camera -from opendm import location -from opendm.gcp import GCPFile -from opendm.photo import find_largest_photo_dims, find_largest_photo +from modules import io +from modules import log +from modules import system +from modules import context +from modules import camera +from modules import location +from modules.gcp import GCPFile +from modules.photo import find_largest_photo_dims, find_largest_photo from opensfm.large import metadataset from opensfm.large import tools from opensfm.actions import undistort from opensfm.dataset import DataSet from opensfm.types import Reconstruction from opensfm import report -from opendm.multispectral import get_photos_by_band -from opendm.gpu import has_gpu +from modules.multispectral import get_photos_by_band +from modules.gpu import has_gpu from opensfm import multiview, exif diff --git a/opendm/photo.py b/modules/photo.py similarity index 99% rename from opendm/photo.py rename to modules/photo.py index bfaab2fe..395ec048 100644 --- a/opendm/photo.py +++ b/modules/photo.py @@ -8,10 +8,10 @@ from six import string_types from datetime import datetime, timedelta, timezone -from opendm import log -from opendm.rollingshutter import get_rolling_shutter_readout +from modules import log +from modules.rollingshutter import get_rolling_shutter_readout import xmltodict as x2d -from opendm import get_image_size +from modules import get_image_size from xml.parsers.expat import ExpatError from opensfm.sensors import sensor_data from opensfm.geo import ecef_from_lla diff --git a/opendm/point_cloud.py b/modules/point_cloud.py similarity index 96% rename from opendm/point_cloud.py rename to modules/point_cloud.py index 6c3f9a74..9e22f38f 100644 --- a/opendm/point_cloud.py +++ b/modules/point_cloud.py @@ -1,16 +1,16 @@ import os, sys, shutil, tempfile, math, json -from opendm import system -from opendm import log -from opendm import context -from opendm.system import run -from opendm import entwine -from opendm import io -from opendm.concurrency import parallel_map -from opendm.utils import double_quote -from opendm.boundary import as_polygon, as_geojson -from opendm.dem.pdal import run_pipeline -from opendm.opc import classify -from opendm.dem import commands +from modules import system +from modules import log +from modules import context +from modules.system import run +from modules import entwine +from modules import io +from modules.concurrency import parallel_map +from modules.utils import double_quote +from modules.boundary import as_polygon, as_geojson +from modules.dem.pdal import run_pipeline +from modules.opc import classify +from modules.dem import commands def ply_info(input_ply): if not os.path.exists(input_ply): diff --git a/opendm/progress.py b/modules/progress.py similarity index 98% rename from opendm/progress.py rename to modules/progress.py index 0bc700ee..7682d515 100644 --- a/opendm/progress.py +++ b/modules/progress.py @@ -1,6 +1,6 @@ import socket import os -from opendm import log +from modules import log PROGRESS_BROADCAST_PORT = 6367 #ODMR try: diff --git a/opendm/pseudogeo.py b/modules/pseudogeo.py similarity index 95% rename from opendm/pseudogeo.py rename to modules/pseudogeo.py index 1e666827..1a25a1d7 100644 --- a/opendm/pseudogeo.py +++ b/modules/pseudogeo.py @@ -1,8 +1,8 @@ from osgeo import osr from osgeo import gdal from osgeo.gdalconst import GA_Update -from opendm import io -from opendm import log +from modules import io +from modules import log def get_pseudogeo_utm(): return '+proj=utm +zone=30 +ellps=WGS84 +datum=WGS84 +units=m +no_defs' diff --git a/opendm/remote.py b/modules/remote.py similarity index 99% rename from opendm/remote.py rename to modules/remote.py index b5452eb3..049c77c0 100644 --- a/opendm/remote.py +++ b/modules/remote.py @@ -4,14 +4,14 @@ import threading import zipfile import glob -from opendm import log -from opendm import system -from opendm import config +from modules import log +from modules import system +from modules import config from pyodm import Node, exceptions from pyodm.utils import AtomicCounter from pyodm.types import TaskStatus -from opendm.osfm import OSFMContext, get_submodel_args_dict, get_submodel_argv -from opendm.utils import double_quote +from modules.osfm import OSFMContext, get_submodel_args_dict, get_submodel_argv +from modules.utils import double_quote try: import queue diff --git a/opendm/report/dsm_gradient.png b/modules/report/dsm_gradient.png similarity index 100% rename from opendm/report/dsm_gradient.png rename to modules/report/dsm_gradient.png diff --git a/opendm/report/overlap_color_map.txt b/modules/report/overlap_color_map.txt similarity index 100% rename from opendm/report/overlap_color_map.txt rename to modules/report/overlap_color_map.txt diff --git a/opendm/report/overlap_diagram_legend.png b/modules/report/overlap_diagram_legend.png similarity index 100% rename from opendm/report/overlap_diagram_legend.png rename to modules/report/overlap_diagram_legend.png diff --git a/opendm/rollingshutter.py b/modules/rollingshutter.py similarity index 99% rename from opendm/rollingshutter.py rename to modules/rollingshutter.py index 7e43e187..b2e45411 100644 --- a/opendm/rollingshutter.py +++ b/modules/rollingshutter.py @@ -1,4 +1,4 @@ -from opendm import log +from modules import log # Make Model (lowercase) --> readout time (ms) RS_DATABASE = { diff --git a/opendm/shots.py b/modules/shots.py similarity index 97% rename from opendm/shots.py rename to modules/shots.py index b6eeeb30..3bcce7fe 100644 --- a/opendm/shots.py +++ b/modules/shots.py @@ -1,7 +1,7 @@ import os, json -from opendm import log -from opendm.pseudogeo import get_pseudogeo_utm, get_pseudogeo_scale -from opendm.location import transformer +from modules import log +from modules.pseudogeo import get_pseudogeo_utm, get_pseudogeo_scale +from modules.location import transformer from pyproj import CRS from osgeo import gdal import numpy as np diff --git a/opendm/skyremoval/__init__.py b/modules/skyremoval/__init__.py similarity index 100% rename from opendm/skyremoval/__init__.py rename to modules/skyremoval/__init__.py diff --git a/opendm/skyremoval/guidedfilter.py b/modules/skyremoval/guidedfilter.py similarity index 100% rename from opendm/skyremoval/guidedfilter.py rename to modules/skyremoval/guidedfilter.py diff --git a/opendm/skyremoval/skyfilter.py b/modules/skyremoval/skyfilter.py similarity index 97% rename from opendm/skyremoval/skyfilter.py rename to modules/skyremoval/skyfilter.py index 6a79e6d0..6d5f361d 100644 --- a/opendm/skyremoval/skyfilter.py +++ b/modules/skyremoval/skyfilter.py @@ -5,8 +5,8 @@ import os import onnxruntime as ort from .guidedfilter import guided_filter -from opendm import log -from opendm.ai import read_image +from modules import log +from modules.ai import read_image from threading import Lock mutex = Lock() diff --git a/opendm/system.py b/modules/system.py similarity index 98% rename from opendm/system.py rename to modules/system.py index eaf27c8d..06fd1d19 100644 --- a/opendm/system.py +++ b/modules/system.py @@ -10,8 +10,8 @@ import shutil from collections import deque -from opendm import context -from opendm import log +from modules import context +from modules import log class SubprocessException(Exception): def __init__(self, msg, errorCode): diff --git a/opendm/thermal.py b/modules/thermal.py similarity index 94% rename from opendm/thermal.py rename to modules/thermal.py index a40889b1..ad7a2a63 100644 --- a/opendm/thermal.py +++ b/modules/thermal.py @@ -1,9 +1,9 @@ import cv2 import os -from opendm import log -from opendm.thermal_tools import dji_unpack -from opendm.exiftool import extract_raw_thermal_image_data -from opendm.thermal_tools.thermal_utils import sensor_vals_to_temp +from modules import log +from modules.thermal_tools import dji_unpack +from modules.exiftool import extract_raw_thermal_image_data +from modules.thermal_tools.thermal_utils import sensor_vals_to_temp def resize_to_match(image, match_photo = None): """ diff --git a/opendm/thermal_tools/__init__.py b/modules/thermal_tools/__init__.py similarity index 100% rename from opendm/thermal_tools/__init__.py rename to modules/thermal_tools/__init__.py diff --git a/opendm/thermal_tools/dji_unpack.py b/modules/thermal_tools/dji_unpack.py similarity index 95% rename from opendm/thermal_tools/dji_unpack.py rename to modules/thermal_tools/dji_unpack.py index 4ebf19db..050364a9 100644 --- a/opendm/thermal_tools/dji_unpack.py +++ b/modules/thermal_tools/dji_unpack.py @@ -1,9 +1,9 @@ from PIL import Image import numpy as np -from opendm import system -from opendm import log +from modules import system +from modules import log -from opendm.thermal_tools.thermal_utils import sensor_vals_to_temp +from modules.thermal_tools.thermal_utils import sensor_vals_to_temp def extract_temperatures_dji(photo, image, dataset_tree): """Extracts the DJI-encoded thermal image as 2D floating-point numpy array with temperatures in degC. diff --git a/opendm/thermal_tools/thermal_utils.py b/modules/thermal_tools/thermal_utils.py similarity index 100% rename from opendm/thermal_tools/thermal_utils.py rename to modules/thermal_tools/thermal_utils.py diff --git a/opendm/tiles/color_relief.txt b/modules/tiles/color_relief.txt similarity index 100% rename from opendm/tiles/color_relief.txt rename to modules/tiles/color_relief.txt diff --git a/opendm/tiles/hsv_merge.py b/modules/tiles/hsv_merge.py similarity index 100% rename from opendm/tiles/hsv_merge.py rename to modules/tiles/hsv_merge.py diff --git a/opendm/tiles/static_tiler.py b/modules/tiles/static_tiler.py similarity index 100% rename from opendm/tiles/static_tiler.py rename to modules/tiles/static_tiler.py diff --git a/opendm/tiles/tiler.py b/modules/tiles/tiler.py similarity index 97% rename from opendm/tiles/tiler.py rename to modules/tiles/tiler.py index e3a8954a..8ae8d6b4 100644 --- a/opendm/tiles/tiler.py +++ b/modules/tiles/tiler.py @@ -2,9 +2,9 @@ import sys import math import shutil -from opendm import log -from opendm import system -from opendm import io +from modules import log +from modules import system +from modules import io from .static_tiler import StaticTiler def generate_tiles(geotiff, output_dir, max_concurrency, resolution): diff --git a/opendm/types.py b/modules/types.py similarity index 98% rename from opendm/types.py rename to modules/types.py index f683c52e..b0f56cb3 100644 --- a/opendm/types.py +++ b/modules/types.py @@ -2,16 +2,16 @@ import shutil import warnings import numpy as np -from opendm import location -from opendm.gcp import GCPFile +from modules import location +from modules.gcp import GCPFile -from opendm import log -from opendm import io -from opendm import system -from opendm import multispectral +from modules import log +from modules import io +from modules import system +from modules import multispectral -from opendm.progress import progressbc -from opendm.photo import ODM_Photo +from modules.progress import progressbc +from modules.photo import ODM_Photo # Ignore warnings about proj information being lost warnings.filterwarnings("ignore") diff --git a/opendm/utils.py b/modules/utils.py similarity index 97% rename from opendm/utils.py rename to modules/utils.py index f2112111..3a023b01 100644 --- a/opendm/utils.py +++ b/modules/utils.py @@ -5,10 +5,10 @@ from osgeo import gdal from datetime import datetime -from opendm import log -from opendm.photo import find_largest_photo_dims, find_mean_utc_time +from modules import log +from modules.photo import find_largest_photo_dims, find_mean_utc_time from osgeo import gdal -from opendm.arghelpers import double_quote +from modules.arghelpers import double_quote class NumpyEncoder(json.JSONEncoder): def default(self, obj): diff --git a/opendm/vendor/__init__.py b/modules/vendor/__init__.py similarity index 100% rename from opendm/vendor/__init__.py rename to modules/vendor/__init__.py diff --git a/opendm/vendor/gdal_fillnodata.py b/modules/vendor/gdal_fillnodata.py similarity index 100% rename from opendm/vendor/gdal_fillnodata.py rename to modules/vendor/gdal_fillnodata.py diff --git a/opendm/video/__init__.py b/modules/video/__init__.py similarity index 100% rename from opendm/video/__init__.py rename to modules/video/__init__.py diff --git a/opendm/video/gpxparser.py b/modules/video/gpxparser.py similarity index 98% rename from opendm/video/gpxparser.py rename to modules/video/gpxparser.py index ceddcaf9..2ed15442 100644 --- a/opendm/video/gpxparser.py +++ b/modules/video/gpxparser.py @@ -2,8 +2,8 @@ from statistics import median import struct import xml.etree.ElementTree as ET -from opendm import log -from opendm.video.srtparser import SrtFileParser +from modules import log +from modules.video.srtparser import SrtFileParser ELAPSED_ORIGIN = datetime(1900, 1, 1) diff --git a/opendm/video/srtparser.py b/modules/video/srtparser.py similarity index 99% rename from opendm/video/srtparser.py rename to modules/video/srtparser.py index 2d0da77a..0de4c89b 100644 --- a/opendm/video/srtparser.py +++ b/modules/video/srtparser.py @@ -1,5 +1,5 @@ from datetime import datetime -from opendm import location, log +from modules import location, log import re diff --git a/opendm/video/video2dataset.py b/modules/video/video2dataset.py similarity index 98% rename from opendm/video/video2dataset.py rename to modules/video/video2dataset.py index ea5cd30c..5d5dc4a7 100644 --- a/opendm/video/video2dataset.py +++ b/modules/video/video2dataset.py @@ -12,11 +12,11 @@ from PIL import Image import numpy as np import piexif -from opendm import system -from opendm import log -from opendm.net import download -from opendm.video.srtparser import SrtFileParser -from opendm.video.gpxparser import GpxFileParser, get_video_start_time +from modules import system +from modules import log +from modules.net import download +from modules.video.srtparser import SrtFileParser +from modules.video.gpxparser import GpxFileParser, get_video_start_time class Video2Dataset: def __init__(self, input, output, max_dimension=None, limit=None, use_loc_parsers=True): diff --git a/portable.Dockerfile b/portable.Dockerfile index 384ad512..2a86c0d3 100644 --- a/portable.Dockerfile +++ b/portable.Dockerfile @@ -44,7 +44,7 @@ RUN bash configure.sh installruntimedeps \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ && bash run.sh --help \ - && bash -c "eval $(python3 /code/opendm/context.py) && python3 -c 'from opensfm import io, pymap'" + && bash -c "eval $(python3 /code/modules/context.py) && python3 -c 'from opensfm import io, pymap'" # Entry point ENTRYPOINT ["python3", "/code/run.py"] diff --git a/run.py b/run.py index 52b7f831..54af1872 100755 --- a/run.py +++ b/run.py @@ -7,13 +7,13 @@ sys.exit(1) import os -from opendm import log -from opendm import config -from opendm import system -from opendm import io -from opendm.progress import progressbc -from opendm.utils import get_processing_results_paths, rm_r -from opendm.arghelpers import args_to_dict, save_opts, find_rerun_stage +from modules import log +from modules import config +from modules import system +from modules import io +from modules.progress import progressbc +from modules.utils import get_processing_results_paths, rm_r +from modules.arghelpers import args_to_dict, save_opts, find_rerun_stage from stages.odm_app import ODMApp diff --git a/stages/dataset.py b/stages/dataset.py index 1e98bea9..a781f5db 100644 --- a/stages/dataset.py +++ b/stages/dataset.py @@ -1,21 +1,21 @@ import os import json -from opendm import context -from opendm import io -from opendm import types -from opendm.photo import PhotoCorruptedException -from opendm import log -from opendm import system -from opendm.geo import GeoFile +from modules import context +from modules import io +from modules import types +from modules.photo import PhotoCorruptedException +from modules import log +from modules import system +from modules.geo import GeoFile from shutil import copyfile -from opendm import progress -from opendm import boundary -from opendm import ai -from opendm.skyremoval.skyfilter import SkyFilter -from opendm.bgfilter import BgFilter -from opendm.concurrency import parallel_map -from opendm.video.video2dataset import Video2Dataset, extract_subtitles +from modules import progress +from modules import boundary +from modules import ai +from modules.skyremoval.skyfilter import SkyFilter +from modules.bgfilter import BgFilter +from modules.concurrency import parallel_map +from modules.video.video2dataset import Video2Dataset, extract_subtitles def save_images_database(photos, database_file): with open(database_file, 'w') as f: diff --git a/stages/mvstex.py b/stages/mvstex.py index de2045e3..656dd95f 100644 --- a/stages/mvstex.py +++ b/stages/mvstex.py @@ -1,13 +1,13 @@ import os, shutil -from opendm import log -from opendm import io -from opendm import system -from opendm import context -from opendm import types -from opendm.multispectral import get_primary_band_name -from opendm.photo import find_largest_photo_dim -from opendm.objpacker import obj_pack +from modules import log +from modules import io +from modules import system +from modules import context +from modules import types +from modules.multispectral import get_primary_band_name +from modules.photo import find_largest_photo_dim +from modules.objpacker import obj_pack class ODMMvsTexStage(types.ODM_Stage): def process(self, args, outputs): diff --git a/stages/odm_app.py b/stages/odm_app.py index 899be1ad..6610c693 100644 --- a/stages/odm_app.py +++ b/stages/odm_app.py @@ -1,10 +1,10 @@ import os, traceback, sys -from opendm import context -from opendm import types -from opendm import io -from opendm import system -from opendm import log +from modules import context +from modules import types +from modules import io +from modules import system +from modules import log from stages.dataset import ODMLoadDatasetStage from stages.run_opensfm import ODMOpenSfMStage diff --git a/stages/odm_dem.py b/stages/odm_dem.py index 5b363c08..23c96aa6 100755 --- a/stages/odm_dem.py +++ b/stages/odm_dem.py @@ -1,18 +1,18 @@ import os, json, math from shutil import copyfile -from opendm import io -from opendm import log -from opendm import system -from opendm import context -from opendm import types -from opendm import gsd -from opendm.dem import commands, utils -from opendm.cropper import Cropper -from opendm import pseudogeo -from opendm.tiles.tiler import generate_dem_tiles -from opendm.cogeo import convert_to_cogeo -from opendm.utils import add_raster_meta_tags +from modules import io +from modules import log +from modules import system +from modules import context +from modules import types +from modules import gsd +from modules.dem import commands, utils +from modules.cropper import Cropper +from modules import pseudogeo +from modules.tiles.tiler import generate_dem_tiles +from modules.cogeo import convert_to_cogeo +from modules.utils import add_raster_meta_tags class ODMDEMStage(types.ODM_Stage): diff --git a/stages/odm_filterpoints.py b/stages/odm_filterpoints.py index c453af87..8ad1fb07 100644 --- a/stages/odm_filterpoints.py +++ b/stages/odm_filterpoints.py @@ -1,13 +1,13 @@ import os -from opendm import log -from opendm import io -from opendm import system -from opendm import context -from opendm import point_cloud -from opendm import types -from opendm import gsd -from opendm.boundary import boundary_offset, compute_boundary_from_shots +from modules import log +from modules import io +from modules import system +from modules import context +from modules import point_cloud +from modules import types +from modules import gsd +from modules.boundary import boundary_offset, compute_boundary_from_shots class ODMFilterPoints(types.ODM_Stage): def process(self, args, outputs): diff --git a/stages/odm_georeferencing.py b/stages/odm_georeferencing.py index a55ad6c1..436ccda7 100644 --- a/stages/odm_georeferencing.py +++ b/stages/odm_georeferencing.py @@ -10,19 +10,19 @@ from collections import OrderedDict from pyproj import CRS -from opendm import io -from opendm import log -from opendm import types -from opendm import system -from opendm import context -from opendm import location -from opendm.cropper import Cropper -from opendm import point_cloud -from opendm.multispectral import get_primary_band_name -from opendm.osfm import OSFMContext -from opendm.boundary import as_polygon, export_to_bounds_files -from opendm.align import compute_alignment_matrix, transform_point_cloud, transform_obj -from opendm.utils import np_to_json +from modules import io +from modules import log +from modules import types +from modules import system +from modules import context +from modules import location +from modules.cropper import Cropper +from modules import point_cloud +from modules.multispectral import get_primary_band_name +from modules.osfm import OSFMContext +from modules.boundary import as_polygon, export_to_bounds_files +from modules.align import compute_alignment_matrix, transform_point_cloud, transform_obj +from modules.utils import np_to_json class ODMGeoreferencingStage(types.ODM_Stage): def process(self, args, outputs): diff --git a/stages/odm_meshing.py b/stages/odm_meshing.py index b5856dfe..90046597 100644 --- a/stages/odm_meshing.py +++ b/stages/odm_meshing.py @@ -1,13 +1,13 @@ import os, math -from opendm import log -from opendm import io -from opendm import system -from opendm import context -from opendm import mesh -from opendm import gsd -from opendm import types -from opendm.dem import commands +from modules import log +from modules import io +from modules import system +from modules import context +from modules import mesh +from modules import gsd +from modules import types +from modules.dem import commands class ODMeshingStage(types.ODM_Stage): def process(self, args, outputs): diff --git a/stages/odm_orthophoto.py b/stages/odm_orthophoto.py index b44c85a1..c0aa6e8c 100644 --- a/stages/odm_orthophoto.py +++ b/stages/odm_orthophoto.py @@ -1,18 +1,18 @@ import os -from opendm import io -from opendm import log -from opendm import system -from opendm import context -from opendm import types -from opendm import gsd -from opendm import orthophoto -from opendm.osfm import is_submodel -from opendm.concurrency import get_max_memory_mb -from opendm.cutline import compute_cutline -from opendm.utils import double_quote -from opendm import pseudogeo -from opendm.multispectral import get_primary_band_name +from modules import io +from modules import log +from modules import system +from modules import context +from modules import types +from modules import gsd +from modules import orthophoto +from modules.osfm import is_submodel +from modules.concurrency import get_max_memory_mb +from modules.cutline import compute_cutline +from modules.utils import double_quote +from modules import pseudogeo +from modules.multispectral import get_primary_band_name class ODMOrthoPhotoStage(types.ODM_Stage): diff --git a/stages/odm_postprocess.py b/stages/odm_postprocess.py index d417b48f..7eff879d 100644 --- a/stages/odm_postprocess.py +++ b/stages/odm_postprocess.py @@ -1,11 +1,11 @@ import os -from opendm import io -from opendm import log -from opendm import types -from opendm.utils import copy_paths, get_processing_results_paths -from opendm.ogctiles import build_3dtiles -from opendm.gltf import obj2glb +from modules import io +from modules import log +from modules import types +from modules.utils import copy_paths, get_processing_results_paths +from modules.ogctiles import build_3dtiles +from modules.gltf import obj2glb class ODMPostProcess(types.ODM_Stage): def process(self, args, outputs): diff --git a/stages/odm_report.py b/stages/odm_report.py index 2daed6f2..7cd5471f 100644 --- a/stages/odm_report.py +++ b/stages/odm_report.py @@ -3,18 +3,18 @@ import math import shutil -from opendm import log -from opendm import io -from opendm import system -from opendm import types -from opendm.shots import get_geojson_shots_from_opensfm -from opendm.osfm import OSFMContext -from opendm import gsd -from opendm.point_cloud import export_info_json -from opendm.cropper import Cropper -from opendm.orthophoto import get_orthophoto_vars, get_max_memory, generate_png -from opendm.tiles.tiler import generate_colored_hillshade -from opendm.utils import get_raster_stats, np_from_json +from modules import log +from modules import io +from modules import system +from modules import types +from modules.shots import get_geojson_shots_from_opensfm +from modules.osfm import OSFMContext +from modules import gsd +from modules.point_cloud import export_info_json +from modules.cropper import Cropper +from modules.orthophoto import get_orthophoto_vars, get_max_memory, generate_png +from modules.tiles.tiler import generate_colored_hillshade +from modules.utils import get_raster_stats, np_from_json def hms(seconds): h = seconds // 3600 @@ -173,7 +173,7 @@ def process(self, args, outputs): "--writers.gdal.output_type=max " "--writers.gdal.radius={} ".format(point_cloud_file, diagram_tiff, resolution, views_dimension, radius)) - report_assets = os.path.abspath(os.path.join(os.path.dirname(__file__), "../opendm/report")) + report_assets = os.path.abspath(os.path.join(os.path.dirname(__file__), "../modules/report")) overlap_color_map = os.path.join(report_assets, "overlap_color_map.txt") bounds_file_path = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg') diff --git a/stages/openmvs.py b/stages/openmvs.py index e40a66b6..2ce99255 100644 --- a/stages/openmvs.py +++ b/stages/openmvs.py @@ -1,16 +1,16 @@ import shutil, os, glob, math, sys -from opendm import log -from opendm import io -from opendm import system -from opendm import context -from opendm import point_cloud -from opendm import types -from opendm.gpu import has_gpu -from opendm.utils import get_depthmap_resolution -from opendm.osfm import OSFMContext -from opendm.multispectral import get_primary_band_name -from opendm.point_cloud import fast_merge_ply +from modules import log +from modules import io +from modules import system +from modules import context +from modules import point_cloud +from modules import types +from modules.gpu import has_gpu +from modules.utils import get_depthmap_resolution +from modules.osfm import OSFMContext +from modules.multispectral import get_primary_band_name +from modules.point_cloud import fast_merge_ply class ODMOpenMVSStage(types.ODM_Stage): def process(self, args, outputs): diff --git a/stages/run_opensfm.py b/stages/run_opensfm.py index 9d998a58..c36150f3 100644 --- a/stages/run_opensfm.py +++ b/stages/run_opensfm.py @@ -3,19 +3,19 @@ import shutil import glob -from opendm import log -from opendm import io -from opendm import system -from opendm import context -from opendm import gsd -from opendm import point_cloud -from opendm import types -from opendm.utils import get_depthmap_resolution -from opendm.osfm import OSFMContext -from opendm import multispectral -from opendm import thermal -from opendm import nvm -from opendm.photo import find_largest_photo +from modules import log +from modules import io +from modules import system +from modules import context +from modules import gsd +from modules import point_cloud +from modules import types +from modules.utils import get_depthmap_resolution +from modules.osfm import OSFMContext +from modules import multispectral +from modules import thermal +from modules import nvm +from modules.photo import find_largest_photo from opensfm.undistort import add_image_format_extension diff --git a/stages/splitmerge.py b/stages/splitmerge.py index e29abec1..f01fae5b 100644 --- a/stages/splitmerge.py +++ b/stages/splitmerge.py @@ -1,22 +1,22 @@ import os import shutil -from opendm import log -from opendm.osfm import OSFMContext, get_submodel_argv, get_submodel_paths, get_all_submodel_paths -from opendm import types -from opendm import io -from opendm import system -from opendm import orthophoto -from opendm.dem import utils -from opendm.dem.merge import euclidean_merge_dems +from modules import log +from modules.osfm import OSFMContext, get_submodel_argv, get_submodel_paths, get_all_submodel_paths +from modules import types +from modules import io +from modules import system +from modules import orthophoto +from modules.dem import utils +from modules.dem.merge import euclidean_merge_dems from opensfm.large import metadataset -from opendm.cropper import Cropper -from opendm.remote import LocalRemoteExecutor -from opendm.shots import merge_geojson_shots, merge_cameras -from opendm import point_cloud -from opendm.utils import double_quote, add_raster_meta_tags -from opendm.tiles.tiler import generate_dem_tiles -from opendm.cogeo import convert_to_cogeo -from opendm import multispectral +from modules.cropper import Cropper +from modules.remote import LocalRemoteExecutor +from modules.shots import merge_geojson_shots, merge_cameras +from modules import point_cloud +from modules.utils import double_quote, add_raster_meta_tags +from modules.tiles.tiler import generate_dem_tiles +from modules.cogeo import convert_to_cogeo +from modules import multispectral class ODMSplitStage(types.ODM_Stage): def process(self, args, outputs): diff --git a/start-dev-env.sh b/start-dev-env.sh index 7eef8705..3c7d8b5f 100755 --- a/start-dev-env.sh +++ b/start-dev-env.sh @@ -49,7 +49,7 @@ if [ "$1" = "--setup" ]; then echo "alias ls='ls --color=auto'" >> $HOME/.bashrc # Python paths - echo $(python3 /code/opendm/context.py) >> $HOME/.bashrc + echo $(python3 /code/modules/context.py) >> $HOME/.bashrc # Vim printf "syntax on\nset showmatch\nset ts=4\nset sts=4\nset sw=4\nset autoindent\nset smartindent\nset smarttab\nset expandtab" > $HOME/.vimrc diff --git a/tests/test_camera.py b/tests/test_camera.py index 4a929908..0903adad 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -3,7 +3,7 @@ import os import shutil -from opendm import camera +from modules import camera class TestCamera(unittest.TestCase): diff --git a/tests/test_gcp.py b/tests/test_gcp.py index 154ac4fc..b0a2cfea 100644 --- a/tests/test_gcp.py +++ b/tests/test_gcp.py @@ -3,7 +3,7 @@ import os import shutil -from opendm.gcp import GCPFile +from modules.gcp import GCPFile class TestGcp(unittest.TestCase): diff --git a/tests/test_osfm.py b/tests/test_osfm.py index 3385364f..e5e587f5 100644 --- a/tests/test_osfm.py +++ b/tests/test_osfm.py @@ -1,7 +1,7 @@ import unittest import os -from opendm.osfm import get_submodel_argv, get_submodel_args_dict -from opendm import config +from modules.osfm import get_submodel_argv, get_submodel_args_dict +from modules import config class TestOSFM(unittest.TestCase): def setUp(self): diff --git a/tests/test_photo.py b/tests/test_photo.py index 3d59b3d2..d79dd697 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -2,7 +2,7 @@ import os import shutil -from opendm.photo import ODM_Photo +from modules.photo import ODM_Photo class TestPhoto(unittest.TestCase): diff --git a/tests/test_remote.py b/tests/test_remote.py index 5483bd0d..7c4b61a5 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -2,7 +2,7 @@ import unittest import threading import random -from opendm.remote import LocalRemoteExecutor, Task, NodeTaskLimitReachedException +from modules.remote import LocalRemoteExecutor, Task, NodeTaskLimitReachedException from pyodm import Node, exceptions from pyodm.types import TaskStatus diff --git a/tests/test_types.py b/tests/test_types.py index 9427b441..3b41f502 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,5 +1,5 @@ import unittest -from opendm import types +from modules import types class ODMPhotoMock: def __init__(self, filename, band_name, band_index): From 30868a26050e4b5389fb90529cd287f7a04ff436 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 15 Oct 2026 12:54:06 -0400 Subject: [PATCH 3/5] Feat: option to pc tile by default --- stages/openmvs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stages/openmvs.py b/stages/openmvs.py index 2ce99255..c961db6f 100644 --- a/stages/openmvs.py +++ b/stages/openmvs.py @@ -103,6 +103,10 @@ def run_densify(): openmvs_scene_file, ' '.join(config + gpu_config + extra_config))) + if os.getenv("ODX_LOW_MEMORY"): + pc_tile = True + config.append("--fusion-mode 1") + try: run_densify() except system.SubprocessException as e: From ef5b3e431e7fdfc007119833cab6c915968c6b38 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 15 Oct 2026 13:12:52 -0400 Subject: [PATCH 4/5] Feat: extract subtitles automatically --- modules/video/video2dataset.py | 7 +++++-- stages/dataset.py | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/video/video2dataset.py b/modules/video/video2dataset.py index 5d5dc4a7..a95bf487 100644 --- a/modules/video/video2dataset.py +++ b/modules/video/video2dataset.py @@ -227,7 +227,7 @@ def SaveFrame(self, frame, video_info, loc_parser: SrtFileParser): # Set the EXIF metadata exif_dict = { "0th": { - piexif.ImageIFD.Software: "LGT", + piexif.ImageIFD.Software: "ODX", piexif.ImageIFD.DateTime: elapsed_time_str, piexif.ImageIFD.XResolution: (frame.shape[1], 1), piexif.ImageIFD.YResolution: (frame.shape[0], 1), @@ -360,7 +360,7 @@ def extract_subtitles(video_file, overwrite=False): def get_ffmpeg(version = '7.0.2'): base_dir = os.path.join(os.path.dirname(__file__), "..", "..") if sys.platform == 'win32': - base_dir = os.path.join(os.getenv('PROGRAMDATA'),"LGT") + base_dir = os.path.join(os.getenv('PROGRAMDATA'),"ODX") base_dir = os.path.join(os.path.abspath(base_dir), "storage", "binaries") if not os.path.isdir(base_dir): @@ -372,6 +372,9 @@ def get_ffmpeg(version = '7.0.2'): if "arm" in machine: url = f"https://github.com/pierotofy/photogrammetry-tools/releases/download/v1.0.0/ffmpeg-{version}-arm64.zip" + if sys.platform == 'win32': + url = f"https://github.com/pierotofy/photogrammetry-tools/releases/download/v1.0.0/ffmpeg-{version}-win64.zip" + # Check if we need to download it ffmpeg_bin = os.path.join(base_dir, "ffmpeg") if not os.path.isfile(ffmpeg_bin): diff --git a/stages/dataset.py b/stages/dataset.py index a781f5db..10b77636 100644 --- a/stages/dataset.py +++ b/stages/dataset.py @@ -130,6 +130,9 @@ def find_mask(photo_path, masks): if len(video_files) > 0: log.INFO("Found video files (%s), extracting frames" % len(video_files)) + for f in video_files: + extract_subtitles(f, overwrite=self.rerun()) + try: v2d = Video2Dataset(input=video_files, output=images_dir, From 2f7e41fb712aa47e3faf21eaf6f9e932c396a24e Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 15 Oct 2026 13:22:16 -0400 Subject: [PATCH 5/5] Windows fixes --- modules/video/video2dataset.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/video/video2dataset.py b/modules/video/video2dataset.py index a95bf487..83b39b2e 100644 --- a/modules/video/video2dataset.py +++ b/modules/video/video2dataset.py @@ -358,8 +358,11 @@ def extract_subtitles(video_file, overwrite=False): log.INFO(f"Extracted subtitles to {srt_file}") def get_ffmpeg(version = '7.0.2'): + is_windows = sys.platform == 'win32' + exe_name = "ffmpeg.exe" if is_windows else "ffmpeg" + base_dir = os.path.join(os.path.dirname(__file__), "..", "..") - if sys.platform == 'win32': + if is_windows: base_dir = os.path.join(os.getenv('PROGRAMDATA'),"ODX") base_dir = os.path.join(os.path.abspath(base_dir), "storage", "binaries") @@ -372,11 +375,11 @@ def get_ffmpeg(version = '7.0.2'): if "arm" in machine: url = f"https://github.com/pierotofy/photogrammetry-tools/releases/download/v1.0.0/ffmpeg-{version}-arm64.zip" - if sys.platform == 'win32': + if is_windows: url = f"https://github.com/pierotofy/photogrammetry-tools/releases/download/v1.0.0/ffmpeg-{version}-win64.zip" # Check if we need to download it - ffmpeg_bin = os.path.join(base_dir, "ffmpeg") + ffmpeg_bin = os.path.join(base_dir, exe_name) if not os.path.isfile(ffmpeg_bin): log.INFO("Downloading ffmpeg from %s ..." % url) @@ -406,7 +409,7 @@ def callback(progress): if not os.path.isfile(ffmpeg_bin): log.WARNING("Cannot find %s (is the URL correct?)" % ffmpeg_bin) return None - else: + elif not is_windows: if not os.access(ffmpeg_bin, os.X_OK): os.chmod(ffmpeg_bin, 0o755) return ffmpeg_bin