NOTICE! This is a static HTML version of a legacy ImageJ Trac ticket.

The ImageJ project now uses GitHub Issues for issue tracking.

Please file all new issues there.

Ticket #2012 (closed defect: moved)

Opened 2013-10-15T16:01:51-05:00

Last modified 2013-10-16T09:44:38-05:00

Support general text specified axis class

Reported by: bdezonia Owned by: bdezonia
Priority: major Milestone: imagej-2.0.0
Component: ImgLib2 Version:
Severity: serious Keywords:
Cc: Blocked By:
Blocking: #1400

Description (last modified by bdezonia)

There are good reasons to support one kind of axis that has it's equation defined by a string. I have mocked up a class below. This is not a simple endeavor but would eliminate a lot of (sometimes redundant) concrete CalibratedAxis implementations. See notes in code below for pointers.

package net.imglib2.meta.axis;

import net.imglib2.meta.AxisType;

/**
 * A very general text driven axis class.
 * 
 * @author Barry DeZonia
 */
public class UberAsciiAxis extends VariableAxis {

	// I might be getting carried away but think it would be nice to define axes
	// by equation alone. IJ1 has lots of one off definitions in CurveFitter that
	// have offsets or not or powers or not. I've generalized some of this by
	// always having an offset that can be 0. But really we'd like the flexibility
	// of not needing a new concrete axis class every time someone comes up with a
	// new formula. This class would parse the equation and build an appropriate
	// function that can scale coords as needed. The nice thing here is that the
	// parsed axis can be queried for variables and then a curve fitting algo can
	// determine appropriate variable values. Curtis' idea of the EditAxes plugin
	// directly tweaking the variables of a displayed equation is correct. There
	// remains the trick of determining when an axis is linear. This can be done
	// by testing that an axis.equals(some general linear axis string) as noted
	// below.

	// TODO - bad name: Equation is more general than our 1d case

	private interface Equation {

		// This might be involved to determine but it would be fun to write!
		Equation inverse(); // if doesn't exist we will return a NullEquation

		double eval(double input);

		// TODO - maybe one String method rather than two.

		String generalEquation();

		String particularEquation(); // likely in an abstract class
	}

	// equation string can be things like:
	// y = m*x + b
	// y = 74*x + b
	// y = q*sin(slope*x+19.4)^(33*power)+fred (here only x & y are predefined)
	// constants and vars automatically detected

	private final String equationString;
	private final Equation eqn;
	private final Equation eqnInverse;
	
	public UberAsciiAxis(AxisType type, String unit, String equationString) {
		super(type, unit);
		this.eqn = parse(equationString);
		this.eqnInverse = eqn.inverse();
		this.equationString = equationString;
	}

	public UberAsciiAxis(AxisType type, String unit, Equation equation) {
		super(type, unit);
		this.eqn = equation;
		this.eqnInverse = eqn.inverse();
		this.equationString = equation.generalEquation(); // or particular?
	}

	@Override
	public double calibratedValue(double rawValue) {
		return eqn.eval(rawValue);
	}

	@Override
	public double rawValue(double calibratedValue) {
		return eqnInverse.eval(calibratedValue);
	}

	@Override
	public String generalEquation() {
		return equationString;
	}

	@Override
	public UberAsciiAxis copy() {
		return new UberAsciiAxis(type(), unit(), equationString);
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof UberAsciiAxis)) return false;
		UberAsciiAxis other = (UberAsciiAxis) o;

		// TODO: do something really cool. compare syntax trees and variables of the
		// two UberAxes. Even trickier: detect when vars of two eqns are the same
		// due to coeffs being 1 or 0. For example y = 1*x + 0 is same as y = x
		// though they would have different syntax trees.

		// TEMP: only equal to self
		return other == this;
	}

	// For generality and reuse elsewhere maybe make parser return multidim
	// equation. We'd need to test that it has a single dim of input to qualify as
	// a valid axis equation

	private Equation parse(String equationString) {
		// TODO
		return new NullEquation();
	}

	// return one of these as an Equation::inverse() when it is not invertible

	private class NullEquation implements Equation {

		@Override
		public double eval(double input) {
			return Double.NaN;
		}

		@Override
		public Equation inverse() {
			return this;
		}

		@Override
		public String generalEquation() {
			return "y = NaN";
		}

		@Override
		public String particularEquation() {
			return "y = NaN";
		}

	}
}

Change History

comment:1 Changed 2013-10-15T16:03:30-05:00 by bdezonia

  • Description modified

comment:2 Changed 2013-10-15T16:05:03-05:00 by bdezonia

  • Blocking 1400 added

comment:3 Changed 2013-10-16T09:44:38-05:00 by bdezonia

AFter discussing with Mark we determined this has some other benefits. Currently CalibratedAxis has no means of setting values. But this implementation as a base could have the equivalent of axis.setEquation("y=1.3*x + 43"). So it would be easier to calibrate things at runtime.

Last edited 2013-10-16T09:45:18-05:00 by bdezonia

comment:4 Changed 2014-12-11T14:30:17-06:00 by curtis

  • Status changed from new to closed
  • Resolution set to moved