Commit 3dbc0525 authored by Andrey Filippov's avatar Andrey Filippov

Implementing compatible flight log from DJI mini4 pro SRT files

parent f84524bd
......@@ -2545,7 +2545,17 @@ public class EyesisCorrectionParameters {
if (this.cuasUasLogs.length() == 0) {
return "";
}
if (!this.cuasUasLogs.endsWith(".json")) {
String lowerPath = this.cuasUasLogs.toLowerCase();
boolean explicitExt =
lowerPath.endsWith(".json") ||
lowerPath.endsWith(".srt") ||
lowerPath.contains("*.srt") ||
lowerPath.contains(",") ||
lowerPath.contains(";") ||
lowerPath.contains("*") ||
lowerPath.contains("?") ||
lowerPath.endsWith(Prefs.getFileSeparator());
if (!explicitExt) {
this.cuasUasLogs = this.cuasUasLogs+".json";
}
return this.cuasUasLogs;
......
......@@ -48,7 +48,7 @@ public class DjiSrt {
int iso = -1;
double shutter = Double.NaN;
double fnum = Double.NaN;
int ev = -1;
double ev = Double.NaN;
String color_md = "";
double focal_len = Double.NaN;
double latitude = Double.NaN;
......@@ -132,25 +132,33 @@ public class DjiSrt {
for (int k = 0; k < SRT_KEYWORDS.length; k++) {
if (s.startsWith(SRT_KEYWORDS[k])){
String sv = s.substring(SRT_KEYWORDS[k].length()+1,iclos).trim();
switch (k) {
case I_iso: djiSrt.iso = Integer.parseInt(sv); break;
case I_shutter: djiSrt.shutter = 1.0/Double.parseDouble(sv.substring(2)); break;
case I_fnum: djiSrt.fnum = Double.parseDouble(sv); break;
case I_ev: djiSrt.ev = Integer.parseInt(sv); break;
case I_color_md: djiSrt.color_md = sv; break;
case I_focal_len:djiSrt.focal_len = Double.parseDouble(sv); break;
case I_latitude: djiSrt.latitude = Double.parseDouble(sv); break;
case I_longitude:djiSrt.longitude = Double.parseDouble(sv); break;
case I_rel_alt:
int ispilt = sv.indexOf(" ");
djiSrt.rel_alt = Double.parseDouble(sv.substring(0,ispilt));
String sv2 = sv.substring(ispilt).trim();
if (sv2.startsWith(SRT_KEYWORDS[I_abs_alt])) {
sv2 = sv2.substring(SRT_KEYWORDS[I_abs_alt].length() + 1).trim();
djiSrt.abs_alt = Double.parseDouble(sv2);
try {
switch (k) {
case I_iso: djiSrt.iso = Integer.parseInt(sv); break;
case I_shutter: djiSrt.shutter = 1.0/Double.parseDouble(sv.substring(2)); break;
case I_fnum: djiSrt.fnum = Double.parseDouble(sv); break;
case I_ev: djiSrt.ev = Double.parseDouble(sv); break;
case I_color_md: djiSrt.color_md = sv; break;
case I_focal_len:djiSrt.focal_len = Double.parseDouble(sv); break;
case I_latitude: djiSrt.latitude = Double.parseDouble(sv); break;
case I_longitude:djiSrt.longitude = Double.parseDouble(sv); break;
case I_rel_alt:
int ispilt = sv.indexOf(" ");
if (ispilt > 0) {
djiSrt.rel_alt = Double.parseDouble(sv.substring(0,ispilt));
String sv2 = sv.substring(ispilt).trim();
if (sv2.startsWith(SRT_KEYWORDS[I_abs_alt])) {
sv2 = sv2.substring(SRT_KEYWORDS[I_abs_alt].length() + 1).trim();
djiSrt.abs_alt = Double.parseDouble(sv2);
}
} else {
djiSrt.rel_alt = Double.parseDouble(sv);
}
break;
case I_ct: djiSrt.ct = Integer.parseInt(sv); break;
}
break;
case I_ct: djiSrt.ct = Integer.parseInt(sv); break;
} catch (RuntimeException e) {
// Keep NaN/default values for malformed fields and continue parsing.
}
}
}
......
package com.elphel.imagej.ims;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
public class DjiSrtReader {
private final String pathSpec;
private final ArrayList<String> sourceFiles = new ArrayList<String>();
private UasLogRecord[] rec_arr = null;
private double firstEpochSeconds = Double.NaN;
private double lastEpochSeconds = Double.NaN;
private static class SrtRow {
double epochSeconds;
double latitude;
double longitude;
double relAlt;
}
public DjiSrtReader(String path) {
this.pathSpec = path;
load();
}
public UasLogRecord[] getRecords() {
return rec_arr;
}
public double getFirstEpochSeconds() {
return firstEpochSeconds;
}
public double getLastEpochSeconds() {
return lastEpochSeconds;
}
public String[] getSourceFiles() {
return sourceFiles.toArray(new String[0]);
}
public String getPathSpec() {
return pathSpec;
}
public static boolean looksLikeSrtPath(String path) {
if (path == null) {
return false;
}
String lower = path.toLowerCase();
try {
if (Files.isDirectory(Paths.get(path))) {
return true;
}
} catch (RuntimeException e) {
// ignore malformed paths and continue pattern-based checks
}
return lower.contains(".srt")
|| lower.contains("*.srt")
|| lower.contains("?")
|| lower.contains("*")
|| lower.contains(";")
|| lower.contains(",");
}
private void load() {
ArrayList<Path> files = resolveSrtPaths(pathSpec);
if (files.isEmpty()) {
throw new IllegalArgumentException("No DJI SRT files found for path: " + pathSpec);
}
ArrayList<SrtRow> rows = new ArrayList<SrtRow>();
for (Path filePath : files) {
ArrayList<DjiSrt> parsed = DjiSrt.parseDjiSrt(filePath.toString());
if ((parsed == null) || parsed.isEmpty()) {
continue;
}
sourceFiles.add(filePath.toString());
for (DjiSrt djiSrt : parsed) {
if (djiSrt.date == null) {
continue;
}
if (Double.isNaN(djiSrt.latitude) || Double.isNaN(djiSrt.longitude)) {
continue;
}
SrtRow row = new SrtRow();
row.epochSeconds = djiSrt.date.getTime() * 1.0E-3;
row.latitude = djiSrt.latitude;
row.longitude = djiSrt.longitude;
row.relAlt = Double.isNaN(djiSrt.rel_alt) ? 0.0 : djiSrt.rel_alt;
rows.add(row);
}
}
if (rows.isEmpty()) {
throw new IllegalArgumentException("No valid DJI SRT records found for path: " + pathSpec);
}
rows.sort(new Comparator<SrtRow>() {
@Override
public int compare(SrtRow lhs, SrtRow rhs) {
return (lhs.epochSeconds > rhs.epochSeconds) ? 1 : (lhs.epochSeconds < rhs.epochSeconds) ? -1 : 0;
}
});
firstEpochSeconds = rows.get(0).epochSeconds;
lastEpochSeconds = rows.get(rows.size() - 1).epochSeconds;
double homeLat = rows.get(0).latitude;
double homeLon = rows.get(0).longitude;
ArrayList<UasLogRecord> recList = new ArrayList<UasLogRecord>(rows.size());
for (SrtRow row : rows) {
double[] ned = Imx5.nedFromLla(
new double[] {row.latitude, row.longitude, row.relAlt},
new double[] {homeLat, homeLon, 0.0});
double distance = Math.sqrt(ned[0] * ned[0] + ned[1] * ned[1] + ned[2] * ned[2]);
double relTs = row.epochSeconds - firstEpochSeconds;
recList.add(new UasLogRecord(
relTs,
distance,
row.latitude,
row.longitude,
row.relAlt,
homeLat,
homeLon));
}
rec_arr = UasLogRecord.getArray(recList);
UasLogRecord.fillUndefined(rec_arr);
System.out.println("DjiSrtReader: loaded " + rec_arr.length + " records from " + sourceFiles.size() + " file(s)");
}
private static ArrayList<Path> resolveSrtPaths(String pathSpec) {
ArrayList<Path> files = new ArrayList<Path>();
if (pathSpec == null) {
return files;
}
String[] tokens = splitPathSpec(pathSpec);
LinkedHashSet<String> unique = new LinkedHashSet<String>();
for (String token : tokens) {
if (token == null) {
continue;
}
String trimmed = token.trim();
if (trimmed.isEmpty()) {
continue;
}
collectPaths(trimmed, unique);
}
for (String s : unique) {
files.add(Paths.get(s));
}
files.sort(new Comparator<Path>() {
@Override
public int compare(Path lhs, Path rhs) {
return lhs.toString().compareTo(rhs.toString());
}
});
return files;
}
private static String[] splitPathSpec(String pathSpec) {
if ((pathSpec.indexOf(',') < 0) && (pathSpec.indexOf(';') < 0) && (pathSpec.indexOf('\n') < 0)) {
return new String[] {pathSpec};
}
return pathSpec.split("[,;\\n]+");
}
private static boolean hasWildcard(String path) {
return (path.indexOf('*') >= 0) || (path.indexOf('?') >= 0);
}
private static void collectPaths(String token, LinkedHashSet<String> unique) {
Path path = Paths.get(token);
if (Files.isDirectory(path)) {
addDirectorySrt(path, unique);
return;
}
if (hasWildcard(token)) {
Path dir = path.getParent();
if (dir == null) {
dir = Paths.get(".");
}
String pattern = path.getFileName().toString();
addDirectoryPattern(dir, pattern, unique);
return;
}
if (Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".srt")) {
unique.add(path.toAbsolutePath().normalize().toString());
}
}
private static void addDirectorySrt(Path dir, LinkedHashSet<String> unique) {
addDirectoryPattern(dir, "*.srt", unique);
addDirectoryPattern(dir, "*.SRT", unique);
}
private static void addDirectoryPattern(Path dir, String glob, LinkedHashSet<String> unique) {
if (!Files.isDirectory(dir)) {
return;
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, glob)) {
for (Path p : stream) {
if (Files.isRegularFile(p) && p.toString().toLowerCase().endsWith(".srt")) {
unique.add(p.toAbsolutePath().normalize().toString());
}
}
} catch (IOException e) {
System.err.println("DjiSrtReader: failed to list " + dir + " (" + glob + "): " + e.getMessage());
}
}
}
......@@ -84,10 +84,40 @@ public class UasLogReader {
QuadCLT parentCLT) throws JSONException {
this.parentCLT = parentCLT;
setCameraLLA(camera_lla);
setStartTimestamp(timestamp);
// StringBuffer sb = new StringBuffer();
// sb.append(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime())+"\n");
// UasLogRecord[] rec_arr = null;
if (DjiSrtReader.looksLikeSrtPath(filePath)) {
loadDjiSrt(filePath, timestamp);
} else {
setStartTimestamp(timestamp);
loadJson(filePath);
}
if ((rec_arr == null) || (rec_arr.length == 0)) {
throw new IllegalArgumentException("No UAS log records loaded from " + filePath);
}
}
private void loadDjiSrt(
String filePath,
double timestamp) {
DjiSrtReader djiSrtReader = new DjiSrtReader(filePath);
rec_arr = djiSrtReader.getRecords();
double startTs = timestamp;
if (startTs == 0.0) {
startTs = djiSrtReader.getFirstEpochSeconds();
System.out.println(String.format(
"UasLogReader: using DJI SRT first timestamp %.3f as cuasUasTimeStamp (input was 0)",
startTs));
}
setStartTimestamp(startTs);
System.out.println(String.format(
"UasLogReader: loaded DJI SRT path \"%s\": %d records, startTimestamp=%f, source files=%d",
filePath,
rec_arr.length,
start_timestamp,
djiSrtReader.getSourceFiles().length));
}
private void loadJson(
String filePath) throws JSONException {
JSONObject logData = null;
JSONArray flight_logging_items = null;
JSONArray flight_logging_keys = null;
......@@ -103,7 +133,7 @@ public class UasLogReader {
System.err.println("Error reading file: " + e.getMessage());
}
if (log_indices == null) {
return;
throw new IllegalArgumentException("Failed to parse UAS JSON log: " + filePath);
}
for (int i = 0; i < flight_logging_keys.length(); i++) {
......@@ -111,7 +141,12 @@ public class UasLogReader {
}
int [] indices = new int[UasLogRecord.FIELDS_USED.length];
for (int i = 0; i < UasLogRecord.FIELDS_USED.length; i++) {
indices[i] = log_indices.get(UasLogRecord.FIELDS_USED[i]);
Integer idx = log_indices.get(UasLogRecord.FIELDS_USED[i]);
if (idx == null) {
throw new IllegalArgumentException(
"Missing field \"" + UasLogRecord.FIELDS_USED[i] + "\" in UAS JSON log " + filePath);
}
indices[i] = idx;
}
ArrayList<UasLogRecord> uas_log_list = new ArrayList<UasLogRecord>();
double[] rec = new double [indices.length];
......@@ -135,8 +170,7 @@ public class UasLogReader {
uas_log_list); // ArrayList<UasLogRecord> rec_list)
UasLogRecord.fillUndefined(rec_arr);
System.out.println(rec_arr.length+" items");
return;
System.out.println("UasLogReader: loaded JSON log " + filePath + ", records=" + rec_arr.length);
}
public UasLogRecord interpolate(double timestamp) { // relative
......
......@@ -136,11 +136,16 @@ public class UasLogRecord {
double ts1 = rec_arr[last].timestamp;
if (timestamp <= ts0) {
return rec_arr[0];
} else if ( timestamp >= last) {
} else if ( timestamp >= ts1) {
return rec_arr[last];
} else {
// assuming timestamps are uniform, but not requiring that
int indx = (int) Math.round((timestamp-ts0)/(ts1-ts0));
int indx = (int) Math.round(last * (timestamp-ts0)/(ts1-ts0));
if (indx < 0) {
indx = 0;
} else if (indx > (last - 1)) {
indx = last - 1;
}
while ((indx < (last-1)) && (rec_arr[indx+1].timestamp < timestamp)) indx++; // next record timestamp > requested
while ((indx >=0) && (rec_arr[indx].timestamp > timestamp)) indx--; // this record timestamp <= requested
double k = (timestamp-rec_arr[indx].timestamp) / (rec_arr[indx+1].timestamp-rec_arr[indx].timestamp);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment