From 5d39ba7572f371e6351013463fb834a9bbabbdac Mon Sep 17 00:00:00 2001 From: paulnord Date: Sun, 6 Sep 2026 19:10:09 -0400 Subject: [PATCH 1/2] Provide calibrated position and unit metadata to Data Tool fits --- .../cabrillo/tracker/FitDataMetadata.java | 48 +++++++++++++++++++ .../cabrillo/tracker/TableTrackView.java | 6 +++ .../cabrillo/tracker/TrackPlottingPanel.java | 1 + .../cabrillo/tracker/TrackerPanel.java | 5 ++ test/FIT_METADATA.md | 23 +++++++++ .../cabrillo/tracker/FitDataMetadataTest.java | 41 ++++++++++++++++ 6 files changed, 124 insertions(+) create mode 100644 src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java create mode 100644 test/FIT_METADATA.md create mode 100644 test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java diff --git a/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java b/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java new file mode 100644 index 000000000..4fe7760ff --- /dev/null +++ b/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java @@ -0,0 +1,48 @@ +package org.opensourcephysics.cabrillo.tracker; + +import org.opensourcephysics.display.Dataset; +import org.opensourcephysics.display.DatasetManager; +import org.opensourcephysics.display.DataFunction; +import org.opensourcephysics.media.core.ImageCoordSystem; +import org.opensourcephysics.tools.DataToolTab; +import org.opensourcephysics.tools.FitMetadataProvider; + +/** Live, session-only metadata adapter for OSP Data Tool. */ +final class FitDataMetadata implements FitMetadataProvider { + private final TTrack track; + private final DataToolTab tab; + private final DatasetManager source; + private FitDataMetadata(TTrack track,DataToolTab tab,DatasetManager source) { + this.track=track;this.tab=tab;this.source=source; + } + static void attach(TTrack track,DataToolTab tab,DatasetManager source) { + if(tab!=null)tab.setFitMetadataProvider(new FitDataMetadata(track,tab,source)); + } + private String variable(String column) { + for(Dataset dataset:source.getDatasetsRaw()) { + if(column.equals(tab.getColumnName(dataset.getID(),0)))return dataset.getXColumnName(); + String local=tab.getColumnName(dataset.getID(),1); + if(column.equals(local) && !(dataset instanceof DataFunction))return dataset.getYColumnName(); + } + return null; + } + @Override public String getUnits(String column) { + String variable=variable(column); + return variable==null || track.tp==null?null:track.tp.getDataUnits(track,variable); + } + @Override public double getYUnitsPerPixel(String column) { + String variable=variable(column); + if(track.tp==null || track.tp.getLengthUnit()==null || track.getClass()!=PointMass.class || !("x".equals(variable)||"y".equals(variable)))return Double.NaN; + ImageCoordSystem coords=track.tp.getCoords(); + // A common physical uncertainty requires a common conversion. A moving + // origin is harmless; changing scale or anisotropic rotation is not. + if(!coords.isFixedScale())return Double.NaN; + int n=track.tp.getFrameNumber(); + double sx=coords.getScaleX(n),sy=coords.getScaleY(n); + if(!coords.isFixedAngle() && Math.abs(sx-sy)>1e-12*Math.max(Math.abs(sx),Math.abs(sy)))return Double.NaN; + double a="x".equals(variable)?coords.imageToWorldXComponent(n,1,0):coords.imageToWorldYComponent(n,1,0); + double b="x".equals(variable)?coords.imageToWorldXComponent(n,0,1):coords.imageToWorldYComponent(n,0,1); + double scale=Math.hypot(a,b); + return Double.isNaN(scale)||Double.isInfinite(scale)||scale<=0?Double.NaN:scale; + } +} diff --git a/src/org/opensourcephysics/cabrillo/tracker/TableTrackView.java b/src/org/opensourcephysics/cabrillo/tracker/TableTrackView.java index 1262310ba..04102e630 100644 --- a/src/org/opensourcephysics/cabrillo/tracker/TableTrackView.java +++ b/src/org/opensourcephysics/cabrillo/tracker/TableTrackView.java @@ -1383,6 +1383,7 @@ public void dataToolAction() { tool.setSaveChangesOnClose(false); DataRefreshTool refresher = DataRefreshTool.getTool(trackDataManager); tool.send(new LocalJob(toSend), refresher); + FitDataMetadata.attach(track,tool.getTab(toSend),trackDataManager); tool.setVisible(true); } @@ -2269,6 +2270,11 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole } class TrackDataTable extends DataTable { + @Override public String getUnits(String name) { + TTrack track=getTrack(); + String units=track==null || track.tp==null?null:track.tp.getDataUnits(track,name); + return units==null || units.trim().length()==0?super.getUnits(name):units; + } @Override public int findLastAddedModelIndex(StringBuffer names) { diff --git a/src/org/opensourcephysics/cabrillo/tracker/TrackPlottingPanel.java b/src/org/opensourcephysics/cabrillo/tracker/TrackPlottingPanel.java index 17dfa337c..00a0c759e 100644 --- a/src/org/opensourcephysics/cabrillo/tracker/TrackPlottingPanel.java +++ b/src/org/opensourcephysics/cabrillo/tracker/TrackPlottingPanel.java @@ -1906,6 +1906,7 @@ public void showDataTool() { tab = tool.getTab(toSend); if (tab != null) { tab.setWorkingColumns(xColName, yColName); + FitDataMetadata.attach(track,tab,datasetManager); } tool.setVisible(true); } diff --git a/src/org/opensourcephysics/cabrillo/tracker/TrackerPanel.java b/src/org/opensourcephysics/cabrillo/tracker/TrackerPanel.java index 997a0886c..1dcd59d15 100644 --- a/src/org/opensourcephysics/cabrillo/tracker/TrackerPanel.java +++ b/src/org/opensourcephysics/cabrillo/tracker/TrackerPanel.java @@ -1992,6 +1992,11 @@ public void setAnglesInRadians(boolean inRadians) { public String getUnits(TTrack track, String var) { if (!isUnitsVisible()) return ""; //$NON-NLS-1$ + return getDataUnits(track,var); + } + + /** Units metadata for reports, independent of display visibility. */ + public String getDataUnits(TTrack track,String var) { String dimensions = TTrack.getVariableDimensions(track, var); if (dimensions == null) return ""; //$NON-NLS-1$ diff --git a/test/FIT_METADATA.md b/test/FIT_METADATA.md new file mode 100644 index 000000000..294972b4a --- /dev/null +++ b/test/FIT_METADATA.md @@ -0,0 +1,23 @@ +# Fit report metadata adapter + +This companion branch requires the OSP changes in OpenSourcePhysics/osp PR #9. +It connects Tracker's existing unit and ImageCoordSystem calibration metadata to +Data Tool through FitMetadataProvider. It does not change tracking or fitting. + +The adapter follows source dataset ID and source column ID through Data Tool +column names. Pixel uncertainty is available only for direct PointMass x/y with +known length units and a constant conversion. It uses the norm of the relevant +image-to-world transform row, accounting for rotation and anisotropic scale. +A varying scale, or varying rotation with anisotropic scale, is unavailable. +Derived data quantities never inherit a position conversion. + +The data-table copy path uses metadata even when displayed units are hidden. +Project serialization is unchanged; the provider is attached when Tracker opens +Data Tool from a plot or table. Generic OSP callers can continue without a provider. + +Build against the new OSP classes, then compile and run +`test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java` with the +built Tracker/OSP classpath on a graphical desktop. The fixture contains 13 checks +of fractional/custom pixel values, physical conversion, rotation, and exclusions. +The four changed Tracker source files also pass SwingJS transpilation; the actual +Tracker calibration fixture was run on macOS, not inside a browser. diff --git a/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java b/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java new file mode 100644 index 000000000..4de55dffd --- /dev/null +++ b/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java @@ -0,0 +1,41 @@ +package org.opensourcephysics.cabrillo.tracker; +import javax.swing.SwingUtilities; +import org.opensourcephysics.display.*; +import org.opensourcephysics.tools.*; + +/** Real Tracker calibration -> OSP metadata, with no inferred derived-variable scale. */ +public class FitDataMetadataTest { + static int passed; + static void check(boolean ok,String name){if(!ok)throw new AssertionError(name);passed++;} + static void near(double a,double b,String name){check(Math.abs(a-b)<1e-10,name+": "+a);} + public static void main(String[] args)throws Exception{ + try { SwingUtilities.invokeAndWait(()->{ + TFrame window=new TFrame();TrackerPanel panel=new TrackerPanel(window); + PointMass track=new PointMass();panel.addTrack(track); + panel.setLengthUnit("m",false);panel.getCoords().setScaleXY(0,100,100); + DatasetManager source=new DatasetManager();source.setName("position");source.setXPointsLinked(true); + for(int i=0;i<3;i++){ + source.setXYColumnNames(i,"t",new String[]{"x","y","v_{x}"}[i]); + source.append(i,new double[]{0,1,2,3},new double[]{1.1,2.9,4.9,7.1}); + } + DataTool tool=new DataTool(source);DataToolTab tab=tool.getTab(0);tab.checkGUI(); + FitDataMetadata.attach(track,tab,source);tab.setWorkingColumns("t","x"); + DatasetCurveFitter fitter=tab.getCurveFitter(); + near(fitter.getYUnitsPerPixel(),.01,"x scale"); + for(double p:new double[]{.5,1,1.5,2,2.5,3,.73}){ + fitter.setUncertaintyModel(FitUncertainty.PIXELS,p); + near(fitter.getUncertaintyModel().sigma(Double.NaN,fitter.getYUnitsPerPixel()),p/100,"fractional pixels"); + } + tab.setWorkingColumns("t","y");near(fitter.getYUnitsPerPixel(),.01,"y scale"); + panel.getCoords().setScaleXY(0,200,100);panel.getCoords().setAngle(0,Math.PI/4); + near(fitter.getYUnitsPerPixel(),Math.sqrt(.5/40000+.5/10000),"anisotropic rotated calibration"); + panel.getCoords().setFixedAngle(false);check(Double.isNaN(fitter.getYUnitsPerPixel()),"varying anisotropic rotation unavailable"); + panel.getCoords().setFixedAngle(true);panel.getCoords().setFixedScale(false); + check(Double.isNaN(fitter.getYUnitsPerPixel()),"varying scale unavailable"); + panel.getCoords().setFixedScale(true); + tab.setWorkingColumns("t","v_{x}");check(Double.isNaN(fitter.getYUnitsPerPixel()),"velocity unavailable"); + tool.dispose();window.dispose(); + }); } catch(Throwable t){t.printStackTrace();System.exit(1);} + System.out.println("Passed: "+passed+" Tracker calibration checks");System.exit(0); + } +} From 36ee96e1fe41b891a9316a14096321167a369490 Mon Sep 17 00:00:00 2001 From: Paul Nord Date: Mon, 7 Sep 2026 10:56:36 -0400 Subject: [PATCH 2/2] Identify position-time columns for copied motion-fit results --- .../cabrillo/tracker/FitDataMetadata.java | 5 +++++ test/FIT_METADATA.md | 4 +++- .../cabrillo/tracker/FitDataMetadataTest.java | 11 ++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java b/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java index 4fe7760ff..c440a4bc7 100644 --- a/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java +++ b/src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java @@ -26,6 +26,11 @@ private String variable(String column) { } return null; } + @Override public String getPositionComponent(String independentColumn, String dependentColumn) { + String dependent = variable(dependentColumn); + return track instanceof PointMass && "t".equals(variable(independentColumn)) + && ("x".equals(dependent) || "y".equals(dependent)) ? dependent : null; + } @Override public String getUnits(String column) { String variable=variable(column); return variable==null || track.tp==null?null:track.tp.getDataUnits(track,variable); diff --git a/test/FIT_METADATA.md b/test/FIT_METADATA.md index 294972b4a..885827f2b 100644 --- a/test/FIT_METADATA.md +++ b/test/FIT_METADATA.md @@ -17,7 +17,9 @@ Data Tool from a plot or table. Generic OSP callers can continue without a provi Build against the new OSP classes, then compile and run `test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java` with the -built Tracker/OSP classpath on a graphical desktop. The fixture contains 13 checks +built Tracker/OSP classpath on a graphical desktop. The fixture contains 17 checks of fractional/custom pixel values, physical conversion, rotation, and exclusions. The four changed Tracker source files also pass SwingJS transpilation; the actual Tracker calibration fixture was run on macOS, not inside a browser. + +The optional position-component callback identifies x(t) and y(t) through the same source-column metadata. OSP uses it to label motion results in copied fit reports. Position-versus-position and velocity-versus-time pairs are excluded; no derivative uncertainty propagation or tracking algorithm changes are introduced. diff --git a/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java b/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java index 4de55dffd..9ad0972d0 100644 --- a/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java +++ b/test/org/opensourcephysics/cabrillo/tracker/FitDataMetadataTest.java @@ -20,6 +20,15 @@ public static void main(String[] args)throws Exception{ } DataTool tool=new DataTool(source);DataToolTab tab=tool.getTab(0);tab.checkGUI(); FitDataMetadata.attach(track,tab,source);tab.setWorkingColumns("t","x"); + FitMetadataProvider metadata; + try { + java.lang.reflect.Method method=DataToolTab.class.getDeclaredMethod("getFitMetadataProvider"); + method.setAccessible(true);metadata=(FitMetadataProvider)method.invoke(tab); + } catch(Exception ex){throw new RuntimeException(ex);} + check("x".equals(metadata.getPositionComponent("t","x")),"x versus time identified"); + check("y".equals(metadata.getPositionComponent("t","y")),"y versus time identified"); + check(metadata.getPositionComponent("x","y")==null,"position versus position not velocity"); + check(metadata.getPositionComponent("t","v_{x}")==null,"derived velocity not labeled position"); DatasetCurveFitter fitter=tab.getCurveFitter(); near(fitter.getYUnitsPerPixel(),.01,"x scale"); for(double p:new double[]{.5,1,1.5,2,2.5,3,.73}){ @@ -34,7 +43,7 @@ public static void main(String[] args)throws Exception{ check(Double.isNaN(fitter.getYUnitsPerPixel()),"varying scale unavailable"); panel.getCoords().setFixedScale(true); tab.setWorkingColumns("t","v_{x}");check(Double.isNaN(fitter.getYUnitsPerPixel()),"velocity unavailable"); - tool.dispose();window.dispose(); + tool.removeTab(0,false);tool.dispose();window.dispose(); }); } catch(Throwable t){t.printStackTrace();System.exit(1);} System.out.println("Passed: "+passed+" Tracker calibration checks");System.exit(0); }