Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/org/opensourcephysics/cabrillo/tracker/FitDataMetadata.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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 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);
}
@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions src/org/opensourcephysics/cabrillo/tracker/TrackerPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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$
Expand Down
25 changes: 25 additions & 0 deletions test/FIT_METADATA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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 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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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");
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}){
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.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);
}
}