feat(spectro): add -u real-time JSON stream output to chartread #8
+17
-3
@@ -32,15 +32,29 @@ and of Argyll was in October 2000. Code development commenced in 1995. See
|
||||
Changes Summary for an overview of changes since the last release. Changes
|
||||
between revisions is detailed in the log.txt file that accompanies the source code.
|
||||
|
||||
It is licensed under the Affero GNU Version 3 license.
|
||||
Modifications in this fork:
|
||||
--------------------------
|
||||
- Added `-u` command-line switch to `chartread` to emit real-time row-level
|
||||
and patch-level colour measurement events in JSON format (`ROW_COLORS_JSON: ...`)
|
||||
to `stdout` for external UI / subprocess integration.
|
||||
|
||||
Original Author: Graeme W. Gill
|
||||
Modified source code repository: https://git.i3omb.com/gronod/argyllcms
|
||||
|
||||
License:
|
||||
--------
|
||||
ArgyllCMS is licensed under the GNU Affero General Public License (AGPL) Version 3.
|
||||
In compliance with the AGPLv3, the complete corresponding source code for all
|
||||
modifications is publicly available at https://git.i3omb.com/gronod/argyllcms.
|
||||
|
||||
For more detailed information, please consult the HTML documentation in
|
||||
<doc/ArgyllDoc.html>, or <http://www.argyllcms.com/doc/ArgyllDoc.html>.
|
||||
|
||||
For the most recent source code start at <http://www.argyllcms.com/index.html>.
|
||||
For the upstream source code start at <http://www.argyllcms.com/index.html>.
|
||||
|
||||
Contact me in regards to Argyll, icclib or cgatslib at:
|
||||
Contact Graeme in regards to original Argyll, icclib or cgatslib at:
|
||||
|
||||
Graeme at argyllcms dot com
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ Verbose
|
||||
|
||||
|
||||
mode</tt><tt><br>
|
||||
</tt> <tt> </tt><tt><a href="#u">-u</a></tt><tt> Emit real-time JSON stream of row patch data to stdout</tt><tt><br>
|
||||
</tt><tt> </tt><tt><a href="#c">-c listno</a></tt><tt>
|
||||
Set communication port from the
|
||||
following list (default 1)</tt><tt><br>
|
||||
@@ -656,6 +657,8 @@ Override
|
||||
<a name="v"></a>The <b>-v</b> flag causes extra information to be
|
||||
printed out during chartread operation.<br>
|
||||
<br>
|
||||
<a name="u"></a>The <b>-u</b> flag enables real-time JSON streaming of row patch data to stdout. Each time a row is completed (either forward or reverse), a JSON object containing the expected and measured patch data, along with padding indicators, is emitted prefixed by <code>ROW_COLORS_JSON:</code>. This is designed for GUI applications wrapping chartread to display live swatches and calculate color differences during reading.<br>
|
||||
<br>
|
||||
<a name="c"></a>Normally instruments are connected via a serial
|
||||
communication port, and the port used should be selected by
|
||||
supplying the correct parameter to the <b>-c</b> flag. If you
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
# ArgyllCMS `chartread -u` Subprocess Integration Specification & License Isolation Guide
|
||||
|
||||
This document specifies the communication protocol, JSON payload schema, process lifecycle, and licensing isolation architecture for integrating `chartread -u` into external user interfaces and control software.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architectural & Licensing Boundary (AGPLv3 Isolation)
|
||||
|
||||
ArgyllCMS is licensed under the **GNU Affero General Public License (AGPL) Version 3**. To ensure a separate host project (proprietary, MIT, Apache, etc.) is **not tainted** by the AGPLv3 copyleft terms, the integration must maintain a strict, arm's-length inter-process communication (IPC) boundary:
|
||||
|
||||
### Core Isolation Rules
|
||||
1. **No Library Linking**: The host application **must never** statically or dynamically link (`#include`, `.so`, `.dylib`, `.dll`, `.a`) against any ArgyllCMS C libraries (`libinst`, `libicc`, `libcgats`, `libyajl`, etc.).
|
||||
2. **Subprocess Isolation via Standard OS Pipes**: `chartread` must run strictly as a standalone, decoupled child process.
|
||||
3. **Standard IPC Only**: Communication is conducted exclusively over standard POSIX / Win32 file descriptors (`stdin`, `stdout`, `stderr`).
|
||||
4. **Independent Binary Distribution**: The `chartread` binary should be treated as an external utility tool invoked by the OS shell or process manager.
|
||||
|
||||
```
|
||||
+-------------------------------------------------------------+
|
||||
| Host Application |
|
||||
| (Electron, Web App, Qt, Python, Rust, etc.) |
|
||||
| |
|
||||
| +-----------------------------------------------------+ |
|
||||
| | Line-by-Line Subprocess Stream Reader | |
|
||||
| +-----------------------------------------------------+ |
|
||||
+------------------------------|------------------------------+
|
||||
|
|
||||
Standard Pipes | (stdin / stdout / stderr)
|
||||
|
|
||||
+------------------------------v------------------------------+
|
||||
| Isolated Child Process |
|
||||
| `chartread -u ...` |
|
||||
| (AGPLv3 Licensed Binary) |
|
||||
+-------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Command Invocation
|
||||
|
||||
### Command Syntax
|
||||
```bash
|
||||
chartread [options] -u <target_basename>
|
||||
```
|
||||
|
||||
* **`-u` Flag**: Enables real-time emission of JSON records to `stdout`.
|
||||
* `<target_basename>`: The base name of the input `.ti2` target file and output `.ti3` measurement file (without extension).
|
||||
|
||||
### Key Command Options
|
||||
| Flag | Description |
|
||||
| :--- | :--- |
|
||||
| `-u` | **Required for streaming**: Emits `ROW_COLORS_JSON: ...` payloads on `stdout`. |
|
||||
| `-p` | Patch-by-patch spot mode (emits JSON after each individual patch). |
|
||||
| `-n` | Disable spectral readings (omits `spectral` field from JSON). |
|
||||
| `-c <port>` | Select communication port / instrument index. |
|
||||
| `-d` | Display measurement mode. |
|
||||
| `-t` | Transmission measurement mode. |
|
||||
| `-e` | Emissive measurement mode. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Stream Protocol & Framing Specification
|
||||
|
||||
When `-u` is provided, `chartread` outputs two types of lines to `stdout`:
|
||||
|
||||
1. **Human-Readable Status / Prompt Lines**: Unprefixed text intended for user prompts or diagnostics (e.g. `Hit [Space] to read strip A`).
|
||||
2. **Structured JSON Events**: Single-line JSON objects strictly prefixed by the marker:
|
||||
```
|
||||
ROW_COLORS_JSON: <json_object>\n
|
||||
```
|
||||
|
||||
### Stream Guarantees
|
||||
* **Line-Delimited**: Every JSON event is serialized as a single, uninterrupted line terminated with `\n`.
|
||||
* **Immediate Flush**: `chartread` explicitly invokes `fflush(stdout)` immediately after emitting each JSON line, guaranteeing low latency without OS-level output buffering.
|
||||
* **Deterministic Ordering**: Patches within a row are always indexed in canonical left-to-right strip order (bi-directional scan reversals are normalized internally before emission).
|
||||
|
||||
---
|
||||
|
||||
## 4. JSON Payload Schema
|
||||
|
||||
### Top-Level Object Schema
|
||||
|
||||
```json
|
||||
ROW_COLORS_JSON: {
|
||||
"event": "row_complete",
|
||||
"row_id": "A",
|
||||
"row_index": 0,
|
||||
"total_rows": 12,
|
||||
"patch_count": 21,
|
||||
"patches": [
|
||||
/* Array of Patch Objects */
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `event` | `string` | Event identifier. Currently always `"row_complete"`. |
|
||||
| `row_id` | `string` | Human-readable label of the row/strip (e.g. `"A"`, `"B"`, `"1"`). |
|
||||
| `row_index` | `integer` | 0-based index of the row within the target chart (`0 ... total_rows - 1`). |
|
||||
| `total_rows` | `integer` | Total number of rows/passes defined in the target chart. |
|
||||
| `patch_count`| `integer` | Number of patch elements contained in this row payload. |
|
||||
| `patches` | `array` | List of individual patch data objects. |
|
||||
|
||||
---
|
||||
|
||||
### Patch Object Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"loc": "A1",
|
||||
"is_pad": false,
|
||||
"device": [0.0, 50.0, 100.0, 0.0],
|
||||
"expected": {
|
||||
"XYZ": [18.4210, 20.1234, 15.6789],
|
||||
"Lab": [51.98, -8.45, 12.32]
|
||||
},
|
||||
"measured": {
|
||||
"XYZ": [18.5120, 20.0451, 15.7100],
|
||||
"Lab": [51.89, -8.31, 12.15],
|
||||
"spectral": {
|
||||
"bands": 36,
|
||||
"start_nm": 380.0,
|
||||
"end_nm": 730.0,
|
||||
"norm": 100.0,
|
||||
"values": [0.0120, 0.0135, 0.0180, 0.0245]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `id` | `string` | Patch ID string from `.ti2` file. If `"0"`, indicates a spacer/padding patch. |
|
||||
| `loc` | `string` | Physical location coordinate string (e.g. `"A1"`, `"B12"`). |
|
||||
| `is_pad` | `boolean` | `true` if patch is an alignment/lead-in spacer patch (`id == "0"`). UIs should typically render these distinctly or skip them in analysis. |
|
||||
| `device` | `array[float]` | Device colorant drive values scaled to percentage `0.0 ... 100.0%` (e.g. `[C, M, Y, K]` or `[R, G, B]`). |
|
||||
| `expected` | `object` *(optional)* | Expected reference values from `.ti2` (omitted if no reference data is present). |
|
||||
| `expected.XYZ` | `array[float][3]` | Reference CIE XYZ values on reference scale `0.0 ... 100.0`. |
|
||||
| `expected.Lab` | `array[float][3]` | Reference D50 $L^*a^*b^*$ computed via standard CIE transformation. |
|
||||
| `measured` | `object` | Actual instrument readings. |
|
||||
| `measured.XYZ` | `array[float][3]` | Measured CIE XYZ values on reference scale `0.0 ... 100.0`. |
|
||||
| `measured.Lab` | `array[float][3]` | Measured D50 $L^*a^*b^*$ ($L^* \in [0, 100]$, $a^*, b^* \in [-128, 127]$). |
|
||||
| `measured.spectral` | `object` *(optional)* | Spectral reflection/emission data (omitted if instrument is colorimeter-only or `-n` passed). |
|
||||
| `measured.spectral.bands` | `integer` | Number of spectral sample bands. |
|
||||
| `measured.spectral.start_nm` | `float` | Starting wavelength in nanometres (e.g. `380.0` or `400.0`). |
|
||||
| `measured.spectral.end_nm` | `float` | Ending wavelength in nanometres (e.g. `700.0` or `730.0`). |
|
||||
| `measured.spectral.norm` | `float` | Normalization scale factor (typically `100.0`). |
|
||||
| `measured.spectral.values` | `array[float]` | Array of spectral reflectance / radiance values per band. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration Implementation Examples
|
||||
|
||||
### Node.js / Electron / TypeScript Integration
|
||||
|
||||
```typescript
|
||||
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
|
||||
import * as readline from 'readline';
|
||||
|
||||
export interface PatchColorEvent {
|
||||
event: string;
|
||||
row_id: string;
|
||||
row_index: number;
|
||||
total_rows: number;
|
||||
patch_count: number;
|
||||
patches: Array<{
|
||||
id: string;
|
||||
loc: string;
|
||||
is_pad: boolean;
|
||||
device: number[];
|
||||
expected?: {
|
||||
XYZ: [number, number, number];
|
||||
Lab: [number, number, number];
|
||||
};
|
||||
measured: {
|
||||
XYZ: [number, number, number];
|
||||
Lab: [number, number, number];
|
||||
spectral?: {
|
||||
bands: number;
|
||||
start_nm: number;
|
||||
end_nm: number;
|
||||
norm: number;
|
||||
values: number[];
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export class ChartreadRunner {
|
||||
private process: ChildProcessWithoutNullStreams | null = null;
|
||||
private readonly JSON_PREFIX = 'ROW_COLORS_JSON: ';
|
||||
|
||||
public startSession(
|
||||
targetBasename: string,
|
||||
onRowData: (data: PatchColorEvent) => void,
|
||||
onConsoleMessage: (msg: string) => void,
|
||||
onError: (err: string) => void
|
||||
): void {
|
||||
// Spawn isolated process over standard pipes (No library linking)
|
||||
this.process = spawn('chartread', ['-u', targetBasename]);
|
||||
|
||||
// Parse stdout line by line
|
||||
const rlOut = readline.createInterface({ input: this.process.stdout });
|
||||
rlOut.on('line', (line: string) => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith(this.JSON_PREFIX)) {
|
||||
try {
|
||||
const jsonStr = trimmed.substring(this.JSON_PREFIX.length);
|
||||
const payload: PatchColorEvent = JSON.parse(jsonStr);
|
||||
onRowData(payload);
|
||||
} catch (err) {
|
||||
onError(`Failed to parse JSON row payload: ${err}`);
|
||||
}
|
||||
} else if (trimmed.length > 0) {
|
||||
onConsoleMessage(trimmed);
|
||||
}
|
||||
});
|
||||
|
||||
// Capture stderr for warnings / errors
|
||||
const rlErr = readline.createInterface({ input: this.process.stderr });
|
||||
rlErr.on('line', (errLine: string) => {
|
||||
onError(errLine);
|
||||
});
|
||||
|
||||
this.process.on('close', (code) => {
|
||||
onConsoleMessage(`chartread exited with code ${code}`);
|
||||
this.process = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send user keyboard triggers or confirmations to chartread (e.g. Spacebar or Enter)
|
||||
*/
|
||||
public sendInput(input: string): void {
|
||||
if (this.process && this.process.stdin.writable) {
|
||||
this.process.stdin.write(input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort measurement session
|
||||
*/
|
||||
public abort(): void {
|
||||
if (this.process) {
|
||||
this.sendInput('q\n'); // Send standard quit character
|
||||
setTimeout(() => {
|
||||
if (this.process) {
|
||||
this.process.kill('SIGTERM');
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Python Subprocess Integration
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
import json
|
||||
import threading
|
||||
|
||||
class ChartreadClient:
|
||||
JSON_PREFIX = "ROW_COLORS_JSON: "
|
||||
|
||||
def __init__(self, target_basename: str, on_row_callback, on_status_callback):
|
||||
self.target_basename = target_basename
|
||||
self.on_row_callback = on_row_callback
|
||||
self.on_status_callback = on_status_callback
|
||||
self.process = None
|
||||
|
||||
def start(self):
|
||||
# Arms-length subprocess invocation maintaining AGPL isolation
|
||||
self.process = subprocess.Popen(
|
||||
["chartread", "-u", self.target_basename],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1 # Line buffered
|
||||
)
|
||||
|
||||
threading.Thread(target=self._read_stdout, daemon=True).start()
|
||||
threading.Thread(target=self._read_stderr, daemon=True).start()
|
||||
|
||||
def _read_stdout(self):
|
||||
for line in iter(self.process.stdout.readline, ''):
|
||||
line_str = line.strip()
|
||||
if line_str.startswith(self.JSON_PREFIX):
|
||||
payload_str = line_str[len(self.JSON_PREFIX):]
|
||||
try:
|
||||
data = json.loads(payload_str)
|
||||
self.on_row_callback(data)
|
||||
except json.JSONDecodeError as ex:
|
||||
print(f"JSON decode error: {ex}")
|
||||
elif line_str:
|
||||
self.on_status_callback(line_str)
|
||||
|
||||
def _read_stderr(self):
|
||||
for line in iter(self.process.stderr.readline, ''):
|
||||
if line.strip():
|
||||
print(f"[chartread stderr] {line.strip()}")
|
||||
|
||||
def send_key(self, key: str):
|
||||
if self.process and self.process.stdin:
|
||||
self.process.stdin.write(f"{key}\n")
|
||||
self.process.stdin.flush()
|
||||
|
||||
def terminate(self):
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Real-Time UI Visualisation Best Practices
|
||||
|
||||
1. **Rendering Device Colors**:
|
||||
* For RGB targets: scale `device[0..2]` from $0..100$ to $0..255$ (`rgb(r%, g%, b%)`).
|
||||
* For CMYK targets: use device simulation or convert `measured.Lab` / `expected.Lab` to sRGB for color swatch display.
|
||||
2. **Delta E Calculation ($\Delta E_{00}$ or $\Delta E_{ab}$)**:
|
||||
* When `expected.Lab` and `measured.Lab` are both present, calculate the color difference $\Delta E$.
|
||||
* The basic Euclidean distance ($\Delta E_{ab}$) is simple to implement directly:
|
||||
$$\Delta E_{ab} = \sqrt{(L_m^* - L_e^*)^2 + (a_m^* - a_e^*)^2 + (b_m^* - b_e^*)^2}$$
|
||||
* **Recommendation**: For professional color work, use the modern $\Delta E_{00}$ (CIEDE2000) formula. Due to its complexity, it is highly recommended to use an established color math library (e.g., `colorjs.io` in JavaScript, or `colormath` in Python) rather than implementing it from scratch.
|
||||
* Display green/amber/red indicator lights next to each patch in real-time based on your accepted $\Delta E$ tolerance.
|
||||
3. **Handling Alignment / Spacer Patches (`is_pad == true`)**:
|
||||
* Omit `is_pad == true` patches from quality score computations, or render them with dashed neutral borders to maintain layout grid accuracy without skewing statistics.
|
||||
@@ -201,6 +201,76 @@ typedef struct {
|
||||
xspect sp; /* Spectrum. sp.spec_n > 0 if valid, 100 scaled for ref. */
|
||||
} chcol;
|
||||
|
||||
extern int json_ui_out;
|
||||
int json_ui_out = 0;
|
||||
|
||||
static void compute_patch_metrics(chcol *scb, double *eLab, double *mLab) {
|
||||
/* Expected Lab */
|
||||
if (scb->eXYZ[0] != 0.0 || scb->eXYZ[1] != 0.0 || scb->eXYZ[2] != 0.0) {
|
||||
double scaled_exyz[3];
|
||||
scaled_exyz[0] = scb->eXYZ[0] / 100.0;
|
||||
scaled_exyz[1] = scb->eXYZ[1] / 100.0;
|
||||
scaled_exyz[2] = scb->eXYZ[2] / 100.0;
|
||||
icmXYZ2Lab(&icmD50, eLab, scaled_exyz);
|
||||
} else {
|
||||
eLab[0] = eLab[1] = eLab[2] = 0.0;
|
||||
}
|
||||
|
||||
/* Measured Lab */
|
||||
{
|
||||
double scaled_mxyz[3];
|
||||
scaled_mxyz[0] = scb->XYZ[0] / 100.0;
|
||||
scaled_mxyz[1] = scb->XYZ[1] / 100.0;
|
||||
scaled_mxyz[2] = scb->XYZ[2] / 100.0;
|
||||
icmXYZ2Lab(&icmD50, mLab, scaled_mxyz);
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_row_json_colors(const char *row_id, int row_index, int total_rows, int patch_count, int nchan, chcol **scbs) {
|
||||
int i, j;
|
||||
if (!json_ui_out) return;
|
||||
fprintf(stdout, "ROW_COLORS_JSON: {\"event\": \"row_complete\", \"row_id\": \"%s\", \"row_index\": %d, \"total_rows\": %d, \"patch_count\": %d, \"patches\": [", row_id ? row_id : "", row_index, total_rows, patch_count);
|
||||
|
||||
for (i = 0; i < patch_count; i++) {
|
||||
chcol *scb = scbs[i];
|
||||
int is_pad = 0;
|
||||
double eLab[3], mLab[3];
|
||||
|
||||
if (scb->id && strcmp(scb->id, "0") == 0)
|
||||
is_pad = 1;
|
||||
|
||||
compute_patch_metrics(scb, eLab, mLab);
|
||||
|
||||
fprintf(stdout, "%s{\"id\": \"%s\", \"loc\": \"%s\", \"is_pad\": %s, \"device\": [", i == 0 ? "" : ", ", scb->id ? scb->id : "", scb->loc ? scb->loc : "", is_pad ? "true" : "false");
|
||||
for (j = 0; j < nchan; j++) {
|
||||
fprintf(stdout, "%s%.4f", j == 0 ? "" : ", ", scb->dev[j] * 100.0);
|
||||
}
|
||||
fprintf(stdout, "]");
|
||||
|
||||
if (scb->eXYZ[0] != 0.0 || scb->eXYZ[1] != 0.0 || scb->eXYZ[2] != 0.0) {
|
||||
fprintf(stdout, ", \"expected\": {\"XYZ\": [%.4f, %.4f, %.4f], \"Lab\": [%.4f, %.4f, %.4f]}",
|
||||
scb->eXYZ[0], scb->eXYZ[1], scb->eXYZ[2],
|
||||
eLab[0], eLab[1], eLab[2]);
|
||||
}
|
||||
|
||||
fprintf(stdout, ", \"measured\": {\"XYZ\": [%.4f, %.4f, %.4f], \"Lab\": [%.4f, %.4f, %.4f]",
|
||||
scb->XYZ[0], scb->XYZ[1], scb->XYZ[2],
|
||||
mLab[0], mLab[1], mLab[2]);
|
||||
|
||||
if (scb->sp.spec_n > 0) {
|
||||
fprintf(stdout, ", \"spectral\": {\"bands\": %d, \"start_nm\": %.1f, \"end_nm\": %.1f, \"norm\": %.1f, \"values\": [",
|
||||
scb->sp.spec_n, scb->sp.spec_wl_short, scb->sp.spec_wl_long, scb->sp.norm);
|
||||
for (j = 0; j < scb->sp.spec_n; j++) {
|
||||
fprintf(stdout, "%s%.4f", j == 0 ? "" : ", ", scb->sp.spec[j]);
|
||||
}
|
||||
fprintf(stdout, "]}");
|
||||
}
|
||||
fprintf(stdout, "}}");
|
||||
}
|
||||
fprintf(stdout, "]}\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Convert a base 62 character into a number */
|
||||
/* (This is used for converting the PASSES_IN_STRIPS string */
|
||||
/* (Could convert this to using an alphix("0-9A-Za-Z")) */
|
||||
@@ -1040,6 +1110,16 @@ a1log *log /* verb, debug & error log */
|
||||
scols[i]->mcond = vals[i].mcond;
|
||||
scols[i]->rr = 1; /* Has been read */
|
||||
}
|
||||
|
||||
if (json_ui_out) {
|
||||
int k;
|
||||
for (k = 0; k < totpa; k++) {
|
||||
char row_id[32];
|
||||
sprintf(row_id, "%s", paix->aix(paix, k));
|
||||
emit_row_json_colors(row_id, k, totpa, stipa, scols[0]->n, &scols[k * stipa]);
|
||||
}
|
||||
}
|
||||
|
||||
free(vals);
|
||||
|
||||
|
||||
@@ -1340,6 +1420,15 @@ a1log *log /* verb, debug & error log */
|
||||
scols[sti]->rr = 1; /* Has been read */
|
||||
}
|
||||
|
||||
if (json_ui_out) {
|
||||
int k;
|
||||
for (k = 0; k < paist; k++) {
|
||||
char row_id[32];
|
||||
sprintf(row_id, "%s", paix->aix(paix, pai + k));
|
||||
emit_row_json_colors(row_id, pai + k, totpa, stipa, scols[0]->n, &scols[(pai + k) * stipa]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (cap2 & inst2_xy_holdrel) {
|
||||
|
||||
@@ -1948,6 +2037,13 @@ a1log *log /* verb, debug & error log */
|
||||
}
|
||||
scb[i]->rr = 1; /* Has been read */
|
||||
}
|
||||
|
||||
if (json_ui_out) {
|
||||
char row_id[32];
|
||||
sprintf(row_id, "%s", paix->aix(paix, oroi));
|
||||
emit_row_json_colors(row_id, oroi, totpa, stipa, scb[0]->n, scb);
|
||||
}
|
||||
|
||||
incflag = 2; /* Skip to next unread */
|
||||
|
||||
|
||||
@@ -2419,6 +2515,14 @@ a1log *log /* verb, debug & error log */
|
||||
scols[pix]->sp = val.sp;
|
||||
}
|
||||
scols[pix]->rr = 1; /* Has been read */
|
||||
|
||||
if (json_ui_out) {
|
||||
char row_id[32];
|
||||
int oroi = pix / stipa;
|
||||
sprintf(row_id, "%s", paix->aix(paix, oroi));
|
||||
emit_row_json_colors(row_id, oroi, totpa, 1, scols[pix]->n, &scols[pix]);
|
||||
}
|
||||
|
||||
printf(" Patch read OK\n");
|
||||
|
||||
/* Advance to next patch. */
|
||||
@@ -2481,6 +2585,7 @@ usage() {
|
||||
fprintf(stderr," ** No ports found **\n");
|
||||
}
|
||||
fprintf(stderr," -t Use transmission measurement mode\n");
|
||||
fprintf(stderr," -u Emit real-time JSON updates to stdout\n");
|
||||
fprintf(stderr," -d Use display measurement mode (white Y relative results)\n");
|
||||
cap2 = inst_show_disptype_options(stderr, " -y ", icmps, 0, 0);
|
||||
fprintf(stderr," -e Emissive for transparency on a light box\n");
|
||||
@@ -2733,6 +2838,10 @@ int main(int argc, char *argv[]) {
|
||||
trans = 0;
|
||||
displ = 2;
|
||||
|
||||
/* Enable UI JSON output */
|
||||
} else if (argv[fa][1] == 'u') {
|
||||
json_ui_out = 1;
|
||||
|
||||
/* Request emissive measurement */
|
||||
} else if (argv[fa][1] == 'e') {
|
||||
emis = 1;
|
||||
|
||||
Reference in New Issue
Block a user