Commit 479b64e0 authored by Andrey Filippov's avatar Andrey Filippov

feat: Implement CuasTargetsAnalyze MCP endpoint and fix calcMatchingTargetsLengths bug

parent ca8ad02a
#!/usr/bin/env python3
"""Compare CUAS target sections through the ImageJ MCP HTTP endpoint."""
import argparse
import json
import math
import sys
from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple
import requests
DEFAULT_URL = "http://127.0.0.1:48888/mcp/cuas/targets"
PARAMS: List[Tuple[str, int]] = [
("RSLT_X", 0),
("RSLT_Y", 1),
("RSLT_VX", 16),
("RSLT_VY", 17),
("RSLT_VSTR", 18),
("RSLT_BX", 20),
("RSLT_BY", 21),
("RSLT_AX", 22),
("RSLT_AY", 23),
("RSLT_MISMATCH_BEFORE", 24),
("RSLT_MISMATCH_AFTER", 25),
("RSLT_MISMATCH_DIRS", 26),
("RSLT_MATCH_LENGTH", 27),
("RSLT_BEFORE_LENGTH", 28),
("RSLT_AFTER_LENGTH", 29),
("RSLT_SEQ_TRAVEL", 30),
("RSLT_MSCORE", 31),
("RSLT_QMATCH", 36),
("RSLT_QMATCH_LEN", 37),
("RSLT_QTRAVEL", 38),
("RSLT_QSCORE", 39),
("RSLT_SLOW", 41),
("RSLT_FAIL", 43),
("RSLT_GLOBAL", 48),
]
PARAM_BY_INDEX = {index: name for name, index in PARAMS}
CORE_FIELDS = [
"RSLT_MATCH_LENGTH",
"RSLT_BEFORE_LENGTH",
"RSLT_AFTER_LENGTH",
"RSLT_MISMATCH_BEFORE",
"RSLT_MISMATCH_AFTER",
"RSLT_SEQ_TRAVEL",
"RSLT_QMATCH",
"RSLT_QMATCH_LEN",
"RSLT_QTRAVEL",
"RSLT_QSCORE",
"RSLT_GLOBAL",
]
TargetKey = Tuple[int, int, int, int]
TargetRecord = Dict[str, Any]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Query BEFORE and AFTER CUAS target TIFFs through /mcp/cuas/targets "
"and print per-target matching length changes."
)
)
parser.add_argument("--file-before", required=True, help="Path to TARGETS_SINGLE-FIRST TIFF")
parser.add_argument("--file-after", required=True, help="Path to MATCHING_LENGTHS2 TIFF")
parser.add_argument("--tx", type=int, required=True, help="Tile X coordinate")
parser.add_argument("--ty", type=int, required=True, help="Tile Y coordinate")
parser.add_argument("--seq0", type=int, required=True, help="First sequence index")
parser.add_argument("--seq1", type=int, required=True, help="Last sequence index")
parser.add_argument("--tw", type=int, default=1, help="Tile-section width; default: 1")
parser.add_argument("--th", type=int, default=1, help="Tile-section height; default: 1")
parser.add_argument("--url", default=DEFAULT_URL, help=f"MCP endpoint URL; default: {DEFAULT_URL}")
parser.add_argument("--timeout", type=float, default=60.0, help="HTTP timeout in seconds")
parser.add_argument(
"--json",
action="store_true",
help="Print normalized JSON instead of the text report",
)
return parser.parse_args()
def query_targets(
url: str,
target_path: str,
args: argparse.Namespace,
) -> Mapping[str, Any]:
payload = {
"target_path": target_path,
"queries": [
{
"mode": "section",
"tx": args.tx,
"ty": args.ty,
"tw": args.tw,
"th": args.th,
"nseq0": args.seq0,
"nseq1": args.seq1,
"skip_empty": False,
"params": [index for _, index in PARAMS],
}
],
}
response = requests.post(url, json=payload, timeout=args.timeout)
response.raise_for_status()
data = response.json()
if not data.get("ok", False):
raise RuntimeError(f"MCP request failed for {target_path}: {data}")
return data
def normalize_response(data: Mapping[str, Any]) -> Dict[TargetKey, TargetRecord]:
results = data.get("results") or []
if not results:
return {}
section = results[0]
if "error" in section:
raise RuntimeError(str(section["error"]))
records: Dict[TargetKey, TargetRecord] = {}
for seq in section.get("sequences", []):
nseq = int(seq["nseq"])
for tile in seq.get("tiles", []):
tx = int(tile["tx"])
ty = int(tile["ty"])
for ntarg, target in enumerate(tile.get("targets", [])):
named = rename_params(target)
named.update({"nseq": nseq, "tx": tx, "ty": ty, "ntarg": ntarg})
records[(nseq, tx, ty, ntarg)] = named
return records
def rename_params(target: Mapping[str, Any]) -> TargetRecord:
renamed: TargetRecord = {}
for key, value in target.items():
try:
numeric_key = int(key)
except (TypeError, ValueError):
renamed[key] = value
continue
renamed[PARAM_BY_INDEX.get(numeric_key, key)] = value
return renamed
def fmt_value(value: Any) -> str:
if value is None:
return "-"
if isinstance(value, float):
if math.isnan(value):
return "NaN"
if math.isinf(value):
return "Inf" if value > 0 else "-Inf"
return f"{value:.6g}"
return str(value)
def fmt_delta(before: Any, after: Any) -> str:
if before is None or after is None:
return ""
if isinstance(before, (int, float)) and isinstance(after, (int, float)):
delta = after - before
if delta == 0:
return ""
return f" ({delta:+.6g})"
if before != after:
return " (changed)"
return ""
def iter_changed_fields(before: Optional[TargetRecord], after: Optional[TargetRecord]) -> Iterable[str]:
for field in CORE_FIELDS:
bval = before.get(field) if before else None
aval = after.get(field) if after else None
if bval != aval:
yield f"{field}: {fmt_value(bval)} -> {fmt_value(aval)}{fmt_delta(bval, aval)}"
def print_report(
before: Mapping[TargetKey, TargetRecord],
after: Mapping[TargetKey, TargetRecord],
args: argparse.Namespace,
) -> None:
keys = sorted(set(before) | set(after))
print(f"Endpoint: {args.url}")
print(f"BEFORE: {args.file_before}")
print(f"AFTER: {args.file_after}")
print(f"Section: tx={args.tx}, ty={args.ty}, tw={args.tw}, th={args.th}, seq={args.seq0}..{args.seq1}")
print(f"Targets: before={len(before)}, after={len(after)}, union={len(keys)}")
print()
if not keys:
print("No targets returned for this section.")
return
for key in keys:
nseq, tx, ty, ntarg = key
btarget = before.get(key)
atarget = after.get(key)
status = "changed"
if btarget is None:
status = "after-only"
elif atarget is None:
status = "before-only"
elif not list(iter_changed_fields(btarget, atarget)):
status = "unchanged"
print(f"nseq={nseq} tile=({tx},{ty}) ntarg={ntarg} [{status}]")
print(" kinematics:")
for field in ("RSLT_BX", "RSLT_BY", "RSLT_AX", "RSLT_AY", "RSLT_VX", "RSLT_VY", "RSLT_VSTR", "RSLT_SLOW", "RSLT_FAIL"):
bval = btarget.get(field) if btarget else None
aval = atarget.get(field) if atarget else None
print(f" {field}: {fmt_value(bval)} -> {fmt_value(aval)}{fmt_delta(bval, aval)}")
changed = list(iter_changed_fields(btarget, atarget))
if changed:
print(" matching:")
for line in changed:
print(f" {line}")
else:
print(" matching: no core-field changes")
print()
def jsonable_records(records: Mapping[TargetKey, TargetRecord]) -> Dict[str, TargetRecord]:
return {
f"{nseq}:{tx}:{ty}:{ntarg}": record
for (nseq, tx, ty, ntarg), record in sorted(records.items())
}
def main() -> int:
args = parse_args()
try:
before_raw = query_targets(args.url, args.file_before, args)
after_raw = query_targets(args.url, args.file_after, args)
before = normalize_response(before_raw)
after = normalize_response(after_raw)
except requests.RequestException as exc:
print(f"HTTP error querying MCP endpoint: {exc}", file=sys.stderr)
return 2
except (ValueError, RuntimeError, KeyError, TypeError) as exc:
print(f"Error processing MCP response: {exc}", file=sys.stderr)
return 3
if args.json:
print(
json.dumps(
{"before": jsonable_records(before), "after": jsonable_records(after)},
indent=2,
sort_keys=True,
default=str,
)
)
else:
print_report(before, after, args)
return 0
if __name__ == "__main__":
raise SystemExit(main())
......@@ -13503,9 +13503,10 @@ public class Eyesis_Correction implements PlugIn, ActionListener {
String pluginsDir = url.substring(5, url.length() - clazz.getName().length() - 6);
System.setProperty("plugins.dir", pluginsDir);
// start ImageJ
if (!GraphicsEnvironment.isHeadless()) {
new ImageJ();
}
// run the plugin
IJ.runPlugIn(clazz.getName(), "");
}
}
......@@ -1066,6 +1066,198 @@ public class CuasMotion {
return vf;
}
@SuppressWarnings("unchecked")
public static org.json.simple.JSONArray targetsAnalyzeMCP(String target_path, org.json.simple.JSONArray queries) {
String [][] pvf_top_titles = new String[1][];
String [][] pvf_titles = new String[1][];
int [] err_num = new int [1];
int [] wh = new int[2];
double [][][][] multi_targets = readSingleMultiTargets(
target_path,
wh,
pvf_top_titles,
pvf_titles,
err_num);
org.json.simple.JSONArray results = new org.json.simple.JSONArray();
if (multi_targets == null) {
org.json.simple.JSONObject err = new org.json.simple.JSONObject();
err.put("error", "Failed to read target file. Error code: " + err_num[0]);
results.add(err);
return results;
}
int tilesX = wh[0];
int tilesY = wh[1];
final int tileSize = GPUTileProcessor.DTT_SIZE; // 8
final int PAR_UPXY=-3,PAR_VPXY=-2,PAR_PXY=-1;
for (Object qObj : queries) {
org.json.simple.JSONObject q = (org.json.simple.JSONObject) qObj;
org.json.simple.JSONObject res = new org.json.simple.JSONObject();
String modeStr = (String) q.get("mode");
if (modeStr == null) modeStr = "section";
int mode = 0;
for (int i = 0; i < ANLZ_MODES.length; i++) {
if (ANLZ_MODES[i].equals(modeStr)) {
mode = i; break;
}
}
long tx_l = q.get("tx") != null ? (Long) q.get("tx") : 0;
long ty_l = q.get("ty") != null ? (Long) q.get("ty") : 0;
long tw_l = q.get("tw") != null ? (Long) q.get("tw") : 1;
long th_l = q.get("th") != null ? (Long) q.get("th") : 1;
long nseq0_l = q.get("nseq0") != null ? (Long) q.get("nseq0") : 0;
long nseq1_l = q.get("nseq1") != null ? (Long) q.get("nseq1") : multi_targets.length - 1;
int tx = Math.max(Math.min((int)tx_l, tilesX - 1), 0);
int ty = Math.max(Math.min((int)ty_l, tilesY - 1), 0);
int tw = Math.max(1, (int)tw_l);
int th = Math.max(1, (int)th_l);
int nseq0 = Math.max(Math.min((int)nseq0_l, multi_targets.length - 1), 0);
int nseq1 = Math.max(Math.min((int)nseq1_l, multi_targets.length - 1), nseq0);
res.put("mode", modeStr);
if (mode == ANLZ_SECT) {
boolean skip_empty = q.get("skip_empty") != null ? (Boolean) q.get("skip_empty") : true;
org.json.simple.JSONArray paramsJson = (org.json.simple.JSONArray) q.get("params");
int[] params = new int[paramsJson != null ? paramsJson.size() : 1];
if (paramsJson != null) {
for (int i=0; i<paramsJson.size(); i++) {
params[i] = ((Long)paramsJson.get(i)).intValue();
}
} else {
params[0] = PAR_PXY;
}
org.json.simple.JSONArray seqResults = new org.json.simple.JSONArray();
for (int nseq = nseq0; nseq <= nseq1; nseq++) {
if (multi_targets[nseq] == null) continue;
org.json.simple.JSONObject seqObj = new org.json.simple.JSONObject();
seqObj.put("nseq", nseq);
seqObj.put("title", pvf_titles[0] != null && nseq < pvf_titles[0].length ? pvf_titles[0][nseq] : "");
org.json.simple.JSONArray tilesArr = new org.json.simple.JSONArray();
for (int x = tx; x < Math.min(tx + tw, tilesX); x++) {
for (int y = ty; y < Math.min(ty + th, tilesY); y++) {
int ntile = x + y * tilesX;
boolean has_targets = (multi_targets[nseq][ntile] != null) && (multi_targets[nseq][ntile].length > 0);
if (!has_targets && skip_empty) continue;
org.json.simple.JSONObject tileObj = new org.json.simple.JSONObject();
tileObj.put("tx", x);
tileObj.put("ty", y);
org.json.simple.JSONArray targsArr = new org.json.simple.JSONArray();
if (has_targets) {
for (int ntarg = 0; ntarg < multi_targets[nseq][ntile].length; ntarg++) {
if (multi_targets[nseq][ntile][ntarg] == null) continue;
org.json.simple.JSONObject targObj = new org.json.simple.JSONObject();
for (int npar : params) {
switch (npar) {
case PAR_UPXY:
targObj.put("upx", multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_FL_PX]);
targObj.put("upy", multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_FL_PY]);
targObj.put("range", multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_FL_RANGE]);
break;
case PAR_VPXY:
targObj.put("vx", multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_VX]);
targObj.put("vy", multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_VY]);
targObj.put("vconf", multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_VSTR]);
break;
case PAR_PXY:
targObj.put("px", tileSize * x + tileSize/2.0 + multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_X]);
targObj.put("py", tileSize * y + tileSize/2.0 + multi_targets[nseq][ntile][ntarg][CuasMotionLMA.RSLT_Y]);
break;
default:
if (npar >= 0 && npar < multi_targets[nseq][ntile][ntarg].length) {
targObj.put(String.valueOf(npar), multi_targets[nseq][ntile][ntarg][npar]);
}
}
}
targsArr.add(targObj);
}
}
tileObj.put("targets", targsArr);
tilesArr.add(tileObj);
}
}
if (!tilesArr.isEmpty() || !skip_empty) {
seqObj.put("tiles", tilesArr);
seqResults.add(seqObj);
}
}
res.put("sequences", seqResults);
} else {
// discover modes
int max_targets = q.get("max_targets") != null ? ((Long)q.get("max_targets")).intValue() : 50;
int par_index = q.get("par_index") != null ? ((Long)q.get("par_index")).intValue() : 0;
double par_val = q.get("par_val") != null ? ((Number)q.get("par_val")).doubleValue() : 0.0;
org.json.simple.JSONArray foundTargets = new org.json.simple.JSONArray();
int count = 0;
for (int nseq = nseq0; nseq <= nseq1 && count < max_targets; nseq++) {
if (multi_targets[nseq] == null) continue;
for (int x = tx; x < Math.min(tx + tw, tilesX) && count < max_targets; x++) {
for (int y = ty; y < Math.min(ty + th, tilesY) && count < max_targets; y++) {
int ntile = x + y * tilesX;
if (multi_targets[nseq][ntile] == null || multi_targets[nseq][ntile].length == 0) continue;
for (int ntarg = 0; ntarg < multi_targets[nseq][ntile].length && count < max_targets; ntarg++) {
if (multi_targets[nseq][ntile][ntarg] == null) continue;
boolean match = false;
double val = 0;
if (par_index >= 0 && par_index < multi_targets[nseq][ntile][ntarg].length) {
val = multi_targets[nseq][ntile][ntarg][par_index];
}
switch (mode) {
case ANLZ_NEMPTY:
match = true;
break;
case ANLZ_DEFINED:
match = !Double.isNaN(val);
break;
case ANLZ_EQ:
match = (val == par_val);
break;
case ANLZ_GE:
match = (val >= par_val);
break;
case ANLZ_LE:
match = (val <= par_val);
break;
}
if (match) {
org.json.simple.JSONObject matchObj = new org.json.simple.JSONObject();
matchObj.put("nseq", nseq);
matchObj.put("tx", x);
matchObj.put("ty", y);
matchObj.put("ntarg", ntarg);
if (mode != ANLZ_NEMPTY) {
matchObj.put("val", val);
}
foundTargets.add(matchObj);
count++;
}
}
}
}
}
res.put("matches", foundTargets);
}
results.add(res);
}
return results;
}
public static void targetsAnalyze() {
int mode = 0;
double par_val = 0.0;
......@@ -6317,6 +6509,8 @@ public class CuasMotion {
final int num_tiles = targets_multi[0].length;
final int [][][][] len_ba = new int [2][num_seq][num_tiles][]; // first index is before/after
final int [][][][] target_grp= new int [2][num_seq][num_tiles][]; // [ba][nseq][ntile][ntarg] target group (connected), separately for each direction
final double [][][][] best_mismatch_ba = new double [2][num_seq][num_tiles][];
final double [][][][] best_dirs_ba = new double [2][num_seq][num_tiles][];
final Thread[] threads = ImageDtt.newThreadArray();
final AtomicInteger ai = new AtomicInteger(0);
final AtomicInteger agrp = new AtomicInteger(1);
......@@ -6354,6 +6548,9 @@ public class CuasMotion {
}
}
target_grp[ba][fnseq][nTile] = new int [targets.length];
best_mismatch_ba[ba][fnseq][nTile] = new double [targets.length];
best_dirs_ba[ba][fnseq][nTile] = new double [targets.length];
Arrays.fill(best_mismatch_ba[ba][fnseq][nTile], Double.POSITIVE_INFINITY);
if (first) {
for (int ntarg = 0; ntarg < targets.length; ntarg++) {
target_grp[ba][fnseq][nTile][ntarg] = agrp.getAndIncrement(); // unique group index
......@@ -6377,7 +6574,7 @@ public class CuasMotion {
for (int ntarg_other = 0; ntarg_other < targets_other.length; ntarg_other++) {
double [] target_other = targets_other[ntarg_other];
if ((target_other != null) && (target_other[CuasMotionLMA.RSLT_FAIL]==CuasMotionLMA.FAIL_NONE)){
if (len_ba[ba][fnseq_other][ntile_other][ntarg_other] > (lengths[ntarg] -1)) { // can potentially improve
if (len_ba[ba][fnseq_other][ntile_other][ntarg_other] >= (lengths[ntarg] -1)) { // can potentially improve
double other_x = target_other[CuasMotionLMA.RSLT_BX + 2 * (1 - ba)]; // opposite direction
double other_y = target_other[CuasMotionLMA.RSLT_BY + 2 * (1 - ba)]; // opposite direction
boolean other_slow = target_other[CuasMotionLMA.RSLT_SLOW] > 0;
......@@ -6387,8 +6584,26 @@ public class CuasMotion {
double mismatch_y = other_y - this_y;
double mismatch2 = mismatch_x * mismatch_x + mismatch_y * mismatch_y;
if (mismatch2 <= mm2) { // max_mismatch2) {
lengths[ntarg] = len_ba[ba][fnseq_other][ntile_other][ntarg_other] + 1;
double mismatch = Math.sqrt(mismatch2);
int proposed_len = len_ba[ba][fnseq_other][ntile_other][ntarg_other] + 1;
if ((proposed_len > lengths[ntarg]) ||
((proposed_len == lengths[ntarg]) &&
(mismatch < best_mismatch_ba[ba][fnseq][nTile][ntarg]))) {
int dir = 8;
for (int ndir = 0; ndir < TileNeibs.DIR_XY.length; ndir++) {
if ((TileNeibs.DIR_XY[ndir][0] == dx) && (TileNeibs.DIR_XY[ndir][1] == dy)) {
dir = ndir;
break;
}
}
int [] mm_dirs = {-1, -1};
int [] mm_alts = {0, 0};
mm_dirs[ba] = dir;
mm_alts[ba] = ntarg_other;
lengths[ntarg] = proposed_len;
target_grp[ba][fnseq][nTile][ntarg] = target_grp[ba][fnseq_other][ntile_other][ntarg_other]; // same group
best_mismatch_ba[ba][fnseq][nTile][ntarg] = mismatch;
best_dirs_ba[ba][fnseq][nTile][ntarg] = encodeDirs(mm_dirs, mm_alts);
// targets are ordered, starting with strongest.
// combine bbox-es
bbox_ba[ba][fnseq][nTile][ntarg] = getBbox(
......@@ -6404,6 +6619,7 @@ public class CuasMotion {
bbox_ba[ba][fnseq][nTile][ntarg][2]+","+
bbox_ba[ba][fnseq][nTile][ntarg][3]+"}");
}
}
} else {
if (nTile==dbg_tile) {
System.out.println("calcMathingTargetsLengths().3 ba="+ba+", fnseq = "+fnseq+", nTile="+nTile+", ntarg="+ntarg+
......@@ -6504,8 +6720,15 @@ public class CuasMotion {
target[CuasMotionLMA.RSLT_QMATCH_LEN] = Math.pow(target[CuasMotionLMA.RSLT_MATCH_LENGTH], match_len_pwr);// 1.0 if in every scene;
target[CuasMotionLMA.RSLT_QTRAVEL] = Math.max((target[CuasMotionLMA.RSLT_SEQ_TRAVEL] - seq_travel)/seq_travel, 0);
double MM_BEFORE = target[CuasMotionLMA.RSLT_MISMATCH_BEFORE];
double MM_AFTER = target[CuasMotionLMA.RSLT_MISMATCH_AFTER];
double MM_BEFORE = best_mismatch_ba[0][nSeq][ntile][ntarg];
double MM_AFTER = best_mismatch_ba[1][nSeq][ntile][ntarg];
int [] dirs_before = decodeDirs(best_dirs_ba[0][nSeq][ntile][ntarg]);
int [] dirs_after = decodeDirs(best_dirs_ba[1][nSeq][ntile][ntarg]);
target[CuasMotionLMA.RSLT_MISMATCH_BEFORE] = MM_BEFORE;
target[CuasMotionLMA.RSLT_MISMATCH_AFTER] = MM_AFTER;
target[CuasMotionLMA.RSLT_MISMATCH_DIRS] = encodeDirs(
new int [] {dirs_before[0], dirs_after[1]},
new int [] {dirs_before[2], dirs_after[3]});
target[CuasMotionLMA.RSLT_QMATCH] = 0;
if (MM_BEFORE <= good_mismatch) {
target[CuasMotionLMA.RSLT_QMATCH] += Math.max(0,(good_mismatch - MM_BEFORE)/good_mismatch); // 0 .. 1
......
......@@ -105,6 +105,7 @@ public class McpServer {
server.createContext("/mcp/fs/glob", new FsGlobHandler());
server.createContext("/mcp/fs/csvcol", new FsCsvColHandler());
server.createContext("/mcp/rag/query", new RagQueryHandler());
server.createContext("/mcp/cuas/targets", new CuasTargetsHandler());
server.setExecutor(null);
server.start();
if (Eyesis_Correction.MCP_DEBUG_LEVEL >= Eyesis_Correction.MINIMAL_DEBUG_MCP) {
......@@ -831,6 +832,43 @@ public class McpServer {
}
}
private class CuasTargetsHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
try {
java.io.InputStream is = exchange.getRequestBody();
java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
int nRead;
byte[] data = new byte[1024];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
String requestBody = new String(buffer.toByteArray(), StandardCharsets.UTF_8);
org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();
org.json.simple.JSONObject req = (org.json.simple.JSONObject) parser.parse(requestBody);
String targetPath = (String) req.get("target_path");
org.json.simple.JSONArray queries = (org.json.simple.JSONArray) req.get("queries");
if (targetPath == null || queries == null) {
sendJson(exchange, 400, "{\"ok\":false,\"error\":\"Missing target_path or queries\"}");
return;
}
org.json.simple.JSONArray results = com.elphel.imagej.cuas.CuasMotion.targetsAnalyzeMCP(targetPath, queries);
org.json.simple.JSONObject response = new org.json.simple.JSONObject();
response.put("ok", true);
response.put("results", results);
sendJson(exchange, 200, response.toJSONString());
} catch (Exception e) {
String detail = jsonEscape(e.getMessage());
sendJson(exchange, 500, "{\"ok\":false,\"error\":\"Failed to process request\",\"detail\":\"" + detail + "\"}");
}
}
}
private static String jsonEscape(String value) {
if (value == null) {
return "";
......
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