mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 10:07:08 +00:00
Compare commits
12 Commits
2fcfcb4f84
...
cf8b0937d9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf8b0937d9 | ||
|
|
d737f5d676 | ||
|
|
5fe19da586 | ||
|
|
0af0cf4b42 | ||
|
|
ccb8205d26 | ||
|
|
9b3fffdcfc | ||
|
|
dea1e9ee0d | ||
|
|
d45ec767ec | ||
|
|
67520ffc48 | ||
|
|
22a296ba26 | ||
|
|
f89435d80f | ||
|
|
a3f1dd490c |
109
etc/db/upgrades/upgrade41-v0.6.3-add-comparisons.sql
Normal file
109
etc/db/upgrades/upgrade41-v0.6.3-add-comparisons.sql
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
-- Add procedure to decimate old nav data
|
||||||
|
--
|
||||||
|
-- New schema version: 0.6.3
|
||||||
|
--
|
||||||
|
-- ATTENTION:
|
||||||
|
--
|
||||||
|
-- ENSURE YOU HAVE BACKED UP THE DATABASE BEFORE RUNNING THIS SCRIPT.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- NOTE: This upgrade creates a new schema called `comparisons`.
|
||||||
|
-- NOTE: Each application starts a transaction, which must be committed
|
||||||
|
-- or rolled back.
|
||||||
|
--
|
||||||
|
-- This update adds a `comparisons` table to a `comparisons` schema.
|
||||||
|
-- The `comparisons.comparisons` table holds 4D prospect comparison data.
|
||||||
|
--
|
||||||
|
-- To apply, run as the dougal user:
|
||||||
|
--
|
||||||
|
-- psql <<EOF
|
||||||
|
-- \i $THIS_FILE
|
||||||
|
-- COMMIT;
|
||||||
|
-- EOF
|
||||||
|
--
|
||||||
|
-- NOTE: It can be applied multiple times without ill effect.
|
||||||
|
--
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.show_notice (notice text) AS $$
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE '%', notice;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.upgrade_database () AS $outer$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
RAISE NOTICE 'Updating schema %', 'public';
|
||||||
|
SET search_path TO public;
|
||||||
|
|
||||||
|
-- BEGIN
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS comparisons
|
||||||
|
AUTHORIZATION postgres;
|
||||||
|
|
||||||
|
COMMENT ON SCHEMA comparisons
|
||||||
|
IS 'Holds 4D comparison data and logic';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS comparisons.comparisons
|
||||||
|
(
|
||||||
|
type text COLLATE pg_catalog."default" NOT NULL,
|
||||||
|
baseline_pid text COLLATE pg_catalog."default" NOT NULL,
|
||||||
|
monitor_pid text COLLATE pg_catalog."default" NOT NULL,
|
||||||
|
data bytea,
|
||||||
|
meta jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
CONSTRAINT comparisons_pkey PRIMARY KEY (baseline_pid, monitor_pid, type)
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS comparisons.comparisons
|
||||||
|
OWNER to postgres;
|
||||||
|
|
||||||
|
-- END
|
||||||
|
|
||||||
|
END;
|
||||||
|
$outer$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.upgrade () AS $outer$
|
||||||
|
DECLARE
|
||||||
|
row RECORD;
|
||||||
|
current_db_version TEXT;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
SELECT value->>'db_schema' INTO current_db_version FROM public.info WHERE key = 'version';
|
||||||
|
|
||||||
|
IF current_db_version >= '0.6.3' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
USING MESSAGE='Patch already applied';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF current_db_version != '0.6.2' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
USING MESSAGE='Invalid database version: ' || current_db_version,
|
||||||
|
HINT='Ensure all previous patches have been applied.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CALL pg_temp.upgrade_database();
|
||||||
|
END;
|
||||||
|
$outer$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CALL pg_temp.upgrade();
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('Cleaning up');
|
||||||
|
DROP PROCEDURE pg_temp.upgrade_database ();
|
||||||
|
DROP PROCEDURE pg_temp.upgrade ();
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('Updating db_schema version');
|
||||||
|
INSERT INTO public.info VALUES ('version', '{"db_schema": "0.6.3"}')
|
||||||
|
ON CONFLICT (key) DO UPDATE
|
||||||
|
SET value = public.info.value || '{"db_schema": "0.6.3"}' WHERE public.info.key = 'version';
|
||||||
|
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('All done. You may now run "COMMIT;" to persist the changes');
|
||||||
|
DROP PROCEDURE pg_temp.show_notice (notice text);
|
||||||
|
|
||||||
|
--
|
||||||
|
--NOTE Run `COMMIT;` now if all went well
|
||||||
|
--
|
||||||
169
etc/db/upgrades/upgrade42-v0.6.4-notify-exclude-columns.sql
Normal file
169
etc/db/upgrades/upgrade42-v0.6.4-notify-exclude-columns.sql
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
-- Add procedure to decimate old nav data
|
||||||
|
--
|
||||||
|
-- New schema version: 0.6.4
|
||||||
|
--
|
||||||
|
-- ATTENTION:
|
||||||
|
--
|
||||||
|
-- ENSURE YOU HAVE BACKED UP THE DATABASE BEFORE RUNNING THIS SCRIPT.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- NOTE: This upgrade affects the public schema only.
|
||||||
|
-- NOTE: Each application starts a transaction, which must be committed
|
||||||
|
-- or rolled back.
|
||||||
|
--
|
||||||
|
-- This update modifies notify() to accept, as optional arguments, the
|
||||||
|
-- names of columns that are to be *excluded* from the notification.
|
||||||
|
-- It is intended for tables with large columns which are however of
|
||||||
|
-- no particular interest in a notification.
|
||||||
|
--
|
||||||
|
-- To apply, run as the dougal user:
|
||||||
|
--
|
||||||
|
-- psql <<EOF
|
||||||
|
-- \i $THIS_FILE
|
||||||
|
-- COMMIT;
|
||||||
|
-- EOF
|
||||||
|
--
|
||||||
|
-- NOTE: It can be applied multiple times without ill effect.
|
||||||
|
--
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.show_notice (notice text) AS $$
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE '%', notice;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.upgrade_database () AS $outer$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
RAISE NOTICE 'Updating schema %', 'public';
|
||||||
|
SET search_path TO public;
|
||||||
|
|
||||||
|
-- BEGIN
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.notify()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE 'plpgsql'
|
||||||
|
COST 100
|
||||||
|
VOLATILE NOT LEAKPROOF
|
||||||
|
AS $BODY$
|
||||||
|
DECLARE
|
||||||
|
channel text := TG_ARGV[0];
|
||||||
|
pid text;
|
||||||
|
payload text;
|
||||||
|
notification text;
|
||||||
|
payload_id integer;
|
||||||
|
old_json jsonb;
|
||||||
|
new_json jsonb;
|
||||||
|
excluded_col text;
|
||||||
|
i integer;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- Fetch pid
|
||||||
|
SELECT projects.pid INTO pid FROM projects WHERE schema = TG_TABLE_SCHEMA;
|
||||||
|
|
||||||
|
-- Build old and new as jsonb, excluding specified columns if provided
|
||||||
|
IF OLD IS NOT NULL THEN
|
||||||
|
old_json := row_to_json(OLD)::jsonb;
|
||||||
|
FOR i IN 1 .. TG_NARGS - 1 LOOP
|
||||||
|
excluded_col := TG_ARGV[i];
|
||||||
|
old_json := old_json - excluded_col;
|
||||||
|
END LOOP;
|
||||||
|
ELSE
|
||||||
|
old_json := NULL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NEW IS NOT NULL THEN
|
||||||
|
new_json := row_to_json(NEW)::jsonb;
|
||||||
|
FOR i IN 1 .. TG_NARGS - 1 LOOP
|
||||||
|
excluded_col := TG_ARGV[i];
|
||||||
|
new_json := new_json - excluded_col;
|
||||||
|
END LOOP;
|
||||||
|
ELSE
|
||||||
|
new_json := NULL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Build payload
|
||||||
|
payload := json_build_object(
|
||||||
|
'tstamp', CURRENT_TIMESTAMP,
|
||||||
|
'operation', TG_OP,
|
||||||
|
'schema', TG_TABLE_SCHEMA,
|
||||||
|
'table', TG_TABLE_NAME,
|
||||||
|
'old', old_json,
|
||||||
|
'new', new_json,
|
||||||
|
'pid', pid
|
||||||
|
)::text;
|
||||||
|
|
||||||
|
-- Handle large payloads
|
||||||
|
IF octet_length(payload) < 1000 THEN
|
||||||
|
PERFORM pg_notify(channel, payload);
|
||||||
|
ELSE
|
||||||
|
-- Store large payload and notify with ID (as before)
|
||||||
|
INSERT INTO notify_payloads (payload) VALUES (payload) RETURNING id INTO payload_id;
|
||||||
|
|
||||||
|
notification := json_build_object(
|
||||||
|
'tstamp', CURRENT_TIMESTAMP,
|
||||||
|
'operation', TG_OP,
|
||||||
|
'schema', TG_TABLE_SCHEMA,
|
||||||
|
'table', TG_TABLE_NAME,
|
||||||
|
'pid', pid,
|
||||||
|
'payload_id', payload_id
|
||||||
|
)::text;
|
||||||
|
|
||||||
|
PERFORM pg_notify(channel, notification);
|
||||||
|
RAISE INFO 'Payload over limit';
|
||||||
|
END IF;
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$BODY$;
|
||||||
|
|
||||||
|
ALTER FUNCTION public.notify()
|
||||||
|
OWNER TO postgres;
|
||||||
|
|
||||||
|
-- END
|
||||||
|
|
||||||
|
END;
|
||||||
|
$outer$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.upgrade () AS $outer$
|
||||||
|
DECLARE
|
||||||
|
row RECORD;
|
||||||
|
current_db_version TEXT;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
SELECT value->>'db_schema' INTO current_db_version FROM public.info WHERE key = 'version';
|
||||||
|
|
||||||
|
IF current_db_version >= '0.6.4' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
USING MESSAGE='Patch already applied';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF current_db_version != '0.6.3' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
USING MESSAGE='Invalid database version: ' || current_db_version,
|
||||||
|
HINT='Ensure all previous patches have been applied.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CALL pg_temp.upgrade_database();
|
||||||
|
END;
|
||||||
|
$outer$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CALL pg_temp.upgrade();
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('Cleaning up');
|
||||||
|
DROP PROCEDURE pg_temp.upgrade_database ();
|
||||||
|
DROP PROCEDURE pg_temp.upgrade ();
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('Updating db_schema version');
|
||||||
|
INSERT INTO public.info VALUES ('version', '{"db_schema": "0.6.4"}')
|
||||||
|
ON CONFLICT (key) DO UPDATE
|
||||||
|
SET value = public.info.value || '{"db_schema": "0.6.4"}' WHERE public.info.key = 'version';
|
||||||
|
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('All done. You may now run "COMMIT;" to persist the changes');
|
||||||
|
DROP PROCEDURE pg_temp.show_notice (notice text);
|
||||||
|
|
||||||
|
--
|
||||||
|
--NOTE Run `COMMIT;` now if all went well
|
||||||
|
--
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
-- Add procedure to decimate old nav data
|
||||||
|
--
|
||||||
|
-- New schema version: 0.6.5
|
||||||
|
--
|
||||||
|
-- ATTENTION:
|
||||||
|
--
|
||||||
|
-- ENSURE YOU HAVE BACKED UP THE DATABASE BEFORE RUNNING THIS SCRIPT.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- NOTE: This upgrade affects the public schema only.
|
||||||
|
-- NOTE: Each application starts a transaction, which must be committed
|
||||||
|
-- or rolled back.
|
||||||
|
--
|
||||||
|
-- This update modifies notify() to accept, as optional arguments, the
|
||||||
|
-- names of columns that are to be *excluded* from the notification.
|
||||||
|
-- It is intended for tables with large columns which are however of
|
||||||
|
-- no particular interest in a notification.
|
||||||
|
--
|
||||||
|
-- To apply, run as the dougal user:
|
||||||
|
--
|
||||||
|
-- psql <<EOF
|
||||||
|
-- \i $THIS_FILE
|
||||||
|
-- COMMIT;
|
||||||
|
-- EOF
|
||||||
|
--
|
||||||
|
-- NOTE: It can be applied multiple times without ill effect.
|
||||||
|
--
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.show_notice (notice text) AS $$
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE '%', notice;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.upgrade_database () AS $outer$
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
RAISE NOTICE 'Updating schema %', 'public';
|
||||||
|
SET search_path TO public;
|
||||||
|
|
||||||
|
-- BEGIN
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER comparisons_tg
|
||||||
|
AFTER INSERT OR DELETE OR UPDATE
|
||||||
|
ON comparisons.comparisons
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.notify('comparisons', 'data');
|
||||||
|
|
||||||
|
-- END
|
||||||
|
|
||||||
|
END;
|
||||||
|
$outer$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PROCEDURE pg_temp.upgrade () AS $outer$
|
||||||
|
DECLARE
|
||||||
|
row RECORD;
|
||||||
|
current_db_version TEXT;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
SELECT value->>'db_schema' INTO current_db_version FROM public.info WHERE key = 'version';
|
||||||
|
|
||||||
|
IF current_db_version >= '0.6.5' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
USING MESSAGE='Patch already applied';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF current_db_version != '0.6.4' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
USING MESSAGE='Invalid database version: ' || current_db_version,
|
||||||
|
HINT='Ensure all previous patches have been applied.';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CALL pg_temp.upgrade_database();
|
||||||
|
END;
|
||||||
|
$outer$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CALL pg_temp.upgrade();
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('Cleaning up');
|
||||||
|
DROP PROCEDURE pg_temp.upgrade_database ();
|
||||||
|
DROP PROCEDURE pg_temp.upgrade ();
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('Updating db_schema version');
|
||||||
|
INSERT INTO public.info VALUES ('version', '{"db_schema": "0.6.5"}')
|
||||||
|
ON CONFLICT (key) DO UPDATE
|
||||||
|
SET value = public.info.value || '{"db_schema": "0.6.5"}' WHERE public.info.key = 'version';
|
||||||
|
|
||||||
|
|
||||||
|
CALL pg_temp.show_notice('All done. You may now run "COMMIT;" to persist the changes');
|
||||||
|
DROP PROCEDURE pg_temp.show_notice (notice text);
|
||||||
|
|
||||||
|
--
|
||||||
|
--NOTE Run `COMMIT;` now if all went well
|
||||||
|
--
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Metric</th>
|
<th>Metric</th>
|
||||||
<th>X (m)</th>
|
<th>I (m)</th>
|
||||||
<th>Y (m)</th>
|
<th>J (m)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -34,21 +34,24 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-simple-table>
|
</v-simple-table>
|
||||||
|
|
||||||
<h3 class="mt-4">Counts</h3>
|
<h3 class="mt-4">Error distribution</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Common Points: {{ comparison.common }}</li>
|
<li title="Relative to I-axis positive direction">Primary Direction: {{ (comparison.primaryDirection * 180 / Math.PI).toFixed(2) }}°</li>
|
||||||
<li>Comparison Length: {{ comparison.length }}</li>
|
<li>Anisotropy: {{ comparison.anisotropy.toFixed(2) }}</li>
|
||||||
<li>Baseline Length: {{ comparison.baselineLength }} (Unique: {{ comparison.baselineUniqueLength }})</li>
|
<li title="Length of the semi-major axis of the error ellipse">Semi-Major Axis: {{ semiMajorAxis.toFixed(2) }} m</li>
|
||||||
<li>Monitor Length: {{ comparison.monitorLength }} (Unique: {{ comparison.monitorUniqueLength }})</li>
|
<li title="Length of the semi-minor axis of the error ellipse">Semi-Minor Axis: {{ semiMinorAxis.toFixed(2) }} m</li>
|
||||||
<li>Compared Points: {{ comparison.compared }}</li>
|
<li title="Area of the error ellipse">Error Ellipse Area: {{ ellipseArea.toFixed(2) }} m²</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 class="mt-4">Other Metrics</h3>
|
<h3 class="mt-4">Counts</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Anisotropy: {{ comparison.anisotropy.toFixed(2) }}</li>
|
<li title="Unique line / point pairs found in both projects">Common Points: {{ comparison.common }}</li>
|
||||||
<li title="Relative to I-axis positive direction">Primary Direction: {{ (comparison.primaryDirection * 180 / Math.PI).toFixed(2) }}°</li>
|
<li title="Total number of points compared, including reshoots, infills, etc.">Comparison Length: {{ comparison.length }}</li>
|
||||||
<li>Timestamp: {{ new Date(comparison.tstamp).toLocaleString() }}</li>
|
<li title="Number of points in the baseline project">Baseline Points: {{ comparison.baselineLength }} (Unique: {{ comparison.baselineUniqueLength }})</li>
|
||||||
|
<li title="Number of points in the monitor project">Monitor Points: {{ comparison.monitorLength }} (Unique: {{ comparison.monitorUniqueLength }})</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<p class="mt-3" title="Date and time when the comparison was last performed">Computation timestamp: {{ new Date(comparison.tstamp).toLocaleString() }}</p>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
@@ -139,10 +142,34 @@ export default {
|
|||||||
Math.abs(this.comparison['μ'][0]),
|
Math.abs(this.comparison['μ'][0]),
|
||||||
Math.abs(this.comparison['μ'][1])
|
Math.abs(this.comparison['μ'][1])
|
||||||
);
|
);
|
||||||
const maxExtent = maxMu + 3 * maxSigma;
|
//const maxExtent = maxMu + 3 * maxSigma;
|
||||||
|
const maxExtent = 20;
|
||||||
return 100 / maxExtent; // Adjust scale to fit within ~200 pixels diameter
|
return 100 / maxExtent; // Adjust scale to fit within ~200 pixels diameter
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ellipseArea () {
|
||||||
|
if (!this.comparison) return 0;
|
||||||
|
const a = Math.sqrt(this.comparison.eigenvalues[0]);
|
||||||
|
const b = Math.sqrt(this.comparison.eigenvalues[1]);
|
||||||
|
return Math.PI * a * b;
|
||||||
|
},
|
||||||
|
|
||||||
|
semiMajorAxis () {
|
||||||
|
if (!this.comparison) return 0;
|
||||||
|
return Math.max(
|
||||||
|
Math.sqrt(this.comparison.eigenvalues[0]),
|
||||||
|
Math.sqrt(this.comparison.eigenvalues[1])
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
semiMinorAxis () {
|
||||||
|
if (!this.comparison) return 0;
|
||||||
|
return Math.min(
|
||||||
|
Math.sqrt(this.comparison.eigenvalues[0]),
|
||||||
|
Math.sqrt(this.comparison.eigenvalues[1])
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
meanX () {
|
meanX () {
|
||||||
return this.comparison ? this.comparison['μ'][0] : 0;
|
return this.comparison ? this.comparison['μ'][0] : 0;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-card class="ma-1">
|
<v-card class="ma-1">
|
||||||
<v-card-title>Group Repeatability Summary</v-card-title>
|
<v-card-title>Group Repeatability Summary</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p>Inline standard deviation (σ_x) for each baseline-monitor pair. Lower values indicate better repeatability. Colors range from green (best) to red (worst).</p>
|
<p>Error ellipse area for each baseline-monitor pair. Lower values indicate better repeatability. Colors range from green (best) to red (worst).</p>
|
||||||
<v-simple-table dense>
|
<v-simple-table dense>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -17,18 +17,19 @@
|
|||||||
<v-tooltip v-if="colIndex > rowIndex" top>
|
<v-tooltip v-if="colIndex > rowIndex" top>
|
||||||
<template v-slot:activator="{ on, attrs }">
|
<template v-slot:activator="{ on, attrs }">
|
||||||
<div
|
<div
|
||||||
:style="{ backgroundColor: getSigmaXColor(baselineProject.pid, monitorProject.pid), color: 'white', textAlign: 'center', padding: '4px' }"
|
:style="{ backgroundColor: getEllipseAreaColor(baselineProject.pid, monitorProject.pid), color: 'white', textAlign: 'center', padding: '4px' }"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
@click="emitInput(baselineProject, monitorProject)"
|
@click="emitInput(baselineProject, monitorProject)"
|
||||||
>
|
>
|
||||||
{{ formatSigmaX(baselineProject.pid, monitorProject.pid) }}
|
{{ formatEllipseArea(baselineProject.pid, monitorProject.pid) }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<span v-if="getComp(baselineProject.pid, monitorProject.pid)">
|
<span v-if="getComp(baselineProject.pid, monitorProject.pid)">
|
||||||
<div>σ_x: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][0].toFixed(2) }} m</div>
|
<div>σ_i: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][0].toFixed(2) }} m</div>
|
||||||
<div>σ_y: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][1].toFixed(2) }} m</div>
|
<div>σ_j: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][1].toFixed(2) }} m</div>
|
||||||
<div>Anisotropy: {{ getComp(baselineProject.pid, monitorProject.pid).meta.anisotropy.toFixed(0) }}</div>
|
<div>Anisotropy: {{ getComp(baselineProject.pid, monitorProject.pid).meta.anisotropy.toFixed(0) }}</div>
|
||||||
|
<div>Ellipse Area: {{ getEllipseArea(baselineProject.pid, monitorProject.pid).toFixed(2) }} m²</div>
|
||||||
<div>Primary Direction: {{ formatPrimaryDirection(getComp(baselineProject.pid, monitorProject.pid)) }}°</div>
|
<div>Primary Direction: {{ formatPrimaryDirection(getComp(baselineProject.pid, monitorProject.pid)) }}°</div>
|
||||||
</span>
|
</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
@@ -64,30 +65,42 @@ export default {
|
|||||||
compMap () {
|
compMap () {
|
||||||
return new Map(this.comparisons.map(c => [`${c.baseline_pid}-${c.monitor_pid}`, c]));
|
return new Map(this.comparisons.map(c => [`${c.baseline_pid}-${c.monitor_pid}`, c]));
|
||||||
},
|
},
|
||||||
minSigmaX () {
|
minEllipseArea () {
|
||||||
if (!this.comparisons.length) return 0;
|
if (!this.comparisons.length) return 0;
|
||||||
return Math.min(...this.comparisons.map(c => c.meta['σ'][0]));
|
return Math.min(...this.comparisons.map(c => {
|
||||||
|
const a = Math.sqrt(c.meta.eigenvalues[0]);
|
||||||
|
const b = Math.sqrt(c.meta.eigenvalues[1]);
|
||||||
|
return Math.PI * a * b;
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
maxSigmaX () {
|
maxEllipseArea () {
|
||||||
if (!this.comparisons.length) return 0;
|
if (!this.comparisons.length) return 0;
|
||||||
return Math.max(...this.comparisons.map(c => c.meta['σ'][0]));
|
return Math.max(...this.comparisons.map(c => {
|
||||||
|
const a = Math.sqrt(c.meta.eigenvalues[0]);
|
||||||
|
const b = Math.sqrt(c.meta.eigenvalues[1]);
|
||||||
|
return Math.PI * a * b;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getComp (basePid, monPid) {
|
getComp (basePid, monPid) {
|
||||||
return this.compMap.get(`${basePid}-${monPid}`);
|
return this.compMap.get(`${basePid}-${monPid}`);
|
||||||
},
|
},
|
||||||
getSigmaX (basePid, monPid) {
|
getEllipseArea (basePid, monPid) {
|
||||||
return this.getComp(basePid, monPid)?.meta?.['σ']?.[0] ?? null;
|
const comp = this.getComp(basePid, monPid);
|
||||||
|
if (!comp) return null;
|
||||||
|
const a = Math.sqrt(comp.meta.eigenvalues[0]);
|
||||||
|
const b = Math.sqrt(comp.meta.eigenvalues[1]);
|
||||||
|
return Math.PI * a * b;
|
||||||
},
|
},
|
||||||
formatSigmaX (basePid, monPid) {
|
formatEllipseArea (basePid, monPid) {
|
||||||
const val = this.getSigmaX(basePid, monPid);
|
const val = this.getEllipseArea(basePid, monPid);
|
||||||
return val !== null ? val.toFixed(1) : '';
|
return val !== null ? val.toFixed(1) : '';
|
||||||
},
|
},
|
||||||
getSigmaXColor (basePid, monPid) {
|
getEllipseAreaColor (basePid, monPid) {
|
||||||
const val = this.getSigmaX(basePid, monPid);
|
const val = this.getEllipseArea(basePid, monPid);
|
||||||
if (val === null) return '';
|
if (val === null) return '';
|
||||||
const ratio = (val - this.minSigmaX) / (this.maxSigmaX - this.minSigmaX);
|
const ratio = (val - this.minEllipseArea) / (this.maxEllipseArea - this.minEllipseArea);
|
||||||
const hue = (1 - ratio) * 120;
|
const hue = (1 - ratio) * 120;
|
||||||
return `hsl(${hue}, 70%, 70%)`;
|
return `hsl(${hue}, 70%, 70%)`;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid fill-height class="ma-0 pa-0">
|
<v-container fluid fill-height class="ma-0 pa-0">
|
||||||
|
|
||||||
|
<v-overlay :value="loading && !comparisons.length" absolute>
|
||||||
|
<v-progress-circular
|
||||||
|
indeterminate
|
||||||
|
size="64"
|
||||||
|
></v-progress-circular>
|
||||||
|
</v-overlay>
|
||||||
|
|
||||||
|
<v-overlay :value="!loading && !groupFound" absolute opacity="0.8">
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-alert
|
||||||
|
type="error"
|
||||||
|
>
|
||||||
|
Group not found
|
||||||
|
</v-alert>
|
||||||
|
</v-row>
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-btn color="primary" @click="refreshProjects">Retry</v-btn>
|
||||||
|
</v-row>
|
||||||
|
</v-overlay>
|
||||||
|
|
||||||
|
|
||||||
<v-row no-gutters align="stretch" class="fill-height">
|
<v-row no-gutters align="stretch" class="fill-height">
|
||||||
<v-col cols="12" v-if="groupFound">
|
<v-col cols="12" v-if="groupFound">
|
||||||
|
|
||||||
@@ -7,7 +29,6 @@
|
|||||||
:headers="projectHeaders"
|
:headers="projectHeaders"
|
||||||
:items="projects"
|
:items="projects"
|
||||||
dense
|
dense
|
||||||
hide-default-footer
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<template v-slot:item.baseline="{item, value, index}">
|
<template v-slot:item.baseline="{item, value, index}">
|
||||||
@@ -51,6 +72,15 @@
|
|||||||
{{ (value/1000).toFixed(1) }} km
|
{{ (value/1000).toFixed(1) }} km
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:footer.prepend>
|
||||||
|
<v-btn v-if="comparison"
|
||||||
|
text
|
||||||
|
color="primary"
|
||||||
|
title="Back to summary"
|
||||||
|
@click="clearComparison"
|
||||||
|
>Back</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
|
|
||||||
<!-- BEGIN TEST -->
|
<!-- BEGIN TEST -->
|
||||||
@@ -206,7 +236,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearComparison () {
|
||||||
|
this.baseline = null;
|
||||||
|
this.monitor = null;
|
||||||
|
},
|
||||||
|
|
||||||
setComparison (baseline, monitor) {
|
setComparison (baseline, monitor) {
|
||||||
|
this.clearComparison();
|
||||||
this.setBaseline(baseline);
|
this.setBaseline(baseline);
|
||||||
this.setMonitor(monitor);
|
this.setMonitor(monitor);
|
||||||
},
|
},
|
||||||
@@ -216,6 +252,14 @@ export default {
|
|||||||
this.comparisons = await this.api([url]);
|
this.comparisons = await this.api([url]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO Should this go in a Vuex action rather?
|
||||||
|
async refreshComparisons () {
|
||||||
|
await this.getGroups();
|
||||||
|
if (this.groupFound) {
|
||||||
|
await this.getComparisons();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
async getComparison () {
|
async getComparison () {
|
||||||
if (this.baseline && this.monitor) {
|
if (this.baseline && this.monitor) {
|
||||||
@@ -228,27 +272,34 @@ export default {
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
...mapActions(["api", "getGroups"])
|
handleComparisons (context, {payload}) {
|
||||||
|
this.refreshComparisons();
|
||||||
|
},
|
||||||
|
|
||||||
|
registerNotificationHandlers (action = "registerHandler") {
|
||||||
|
|
||||||
|
this.$store.dispatch(action, {
|
||||||
|
table: 'comparisons',
|
||||||
|
handler: this.handleComparisons
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
unregisterNotificationHandlers () {
|
||||||
|
return this.registerNotificationHandlers("unregisterHandler");
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
...mapActions(["api", "getGroups", "refreshProjects"])
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted () {
|
async mounted () {
|
||||||
await this.getGroups();
|
this.registerNotificationHandlers();
|
||||||
if (this.groupFound) {
|
this.refreshComparisons()
|
||||||
this.getComparisons();
|
|
||||||
/*
|
|
||||||
this.registerNotificationHandlers();
|
|
||||||
|
|
||||||
this.refreshLines();
|
|
||||||
this.refreshSequences();
|
|
||||||
this.refreshEvents();
|
|
||||||
this.refreshLabels();
|
|
||||||
this.refreshPlan();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
//this.unregisterNotificationHandlers();
|
this.unregisterNotificationHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -365,9 +365,9 @@ app.map({
|
|||||||
// FIXME no authentication yet!
|
// FIXME no authentication yet!
|
||||||
|
|
||||||
'/comparison/group': {
|
'/comparison/group': {
|
||||||
get: [ mw.comparisons.groups.list ],
|
get: [ mw.etag.noSave, mw.comparisons.groups.list ],
|
||||||
'/:group': {
|
'/:group': {
|
||||||
get: [ mw.comparisons.groups.get ],
|
get: [ mw.etag.noSave, mw.comparisons.groups.get ],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ async function save (baselineProjectID, monitorProjectID, bundle, meta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function saveSample (baselineProjectID, monitorProjectID, opts = {}) {
|
||||||
|
DEBUG("Not bothering to save samples. This feature will be removed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
async function saveSample (baselineProjectID, monitorProjectID, opts = {}) {
|
async function saveSample (baselineProjectID, monitorProjectID, opts = {}) {
|
||||||
let sample = opts.sample;
|
let sample = opts.sample;
|
||||||
let populationStats = opts.populationStats;
|
let populationStats = opts.populationStats;
|
||||||
@@ -213,6 +219,7 @@ async function saveSample (baselineProjectID, monitorProjectID, opts = {}) {
|
|||||||
client.release();
|
client.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
async function get (baselineProjectID, monitorProjectID, type = 'geometric_difference') {
|
async function get (baselineProjectID, monitorProjectID, type = 'geometric_difference') {
|
||||||
|
|
||||||
@@ -439,6 +446,14 @@ async function saveGroup (group, opts = {}) {
|
|||||||
|
|
||||||
for (const [ baselineProjectID, monitorProjectID ] of combinations(pids, 2)) {
|
for (const [ baselineProjectID, monitorProjectID ] of combinations(pids, 2)) {
|
||||||
try {
|
try {
|
||||||
|
if (!opts.overwrite) {
|
||||||
|
const exists = await get(baselineProjectID, monitorProjectID);
|
||||||
|
if (exists) {
|
||||||
|
DEBUG("Not overwriting existing comparison between %s and %s. Skipping", baselineProjectID, monitorProjectID);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isSaved = await save(baselineProjectID, monitorProjectID);
|
const isSaved = await save(baselineProjectID, monitorProjectID);
|
||||||
if (isSaved) {
|
if (isSaved) {
|
||||||
await saveSample(baselineProjectID, monitorProjectID, opts.sampleOpts);
|
await saveSample(baselineProjectID, monitorProjectID, opts.sampleOpts);
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ function computePCA(deviations) {
|
|||||||
// Compute mean for centering (1 x 2 matrix)
|
// Compute mean for centering (1 x 2 matrix)
|
||||||
const mean = math.mean(D, 0);
|
const mean = math.mean(D, 0);
|
||||||
|
|
||||||
// Explicitly repeat mean to match D's shape (n x 2)
|
// Manually repeat-mean to match D's shape (n x 2)
|
||||||
const n = deviationMatrix.length;
|
const n = deviationMatrix.length;
|
||||||
const meanRepeated = math.repmat(mean, n, 1);
|
const meanArr = mean.toArray();
|
||||||
|
const meanRepeated = math.matrix(
|
||||||
|
Array(n).fill().map(() => [meanArr[0], meanArr[1]])
|
||||||
|
);
|
||||||
|
|
||||||
// Center the data
|
// Center the data
|
||||||
const centered = math.subtract(D, meanRepeated);
|
const centered = math.subtract(D, meanRepeated);
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ module.exports = [
|
|||||||
"planned_lines",
|
"planned_lines",
|
||||||
"raw_lines", "raw_shots",
|
"raw_lines", "raw_shots",
|
||||||
"final_lines", "final_shots", "info",
|
"final_lines", "final_shots", "info",
|
||||||
"queue_items"
|
"queue_items",
|
||||||
|
"comparisons",
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user