magland / figurl-timeseries-views

0 stars 0 forks source link

Optimize TimeseriesGraph worker using matrix operations #5

Open magland opened 1 year ago

magland commented 1 year ago

Here's the section that could use optimization:

https://github.com/magland/figurl-timeseries-views/blob/5e1d9d63e66600fcc40d53970ba23c331a64f76c/test-gui/src/package/view-timeseries-graph/worker.ts#L61-L78

I suggest do something like:

const transformCoordsToPixels = (() => {
    // define the matrix here
    // ....
   return (inputData: {t: number[], y: number[]}): {x: number[], y: number[]} => {
       // perform the transformation here
   }
})()
jsoules commented 1 year ago

So I think the most recent version of the point-transforming matrix math code that I use is in https://github.com/magland/figurl-spike-sorting-views/blob/master/test-gui/src/package/util-point-projection/pointProjection.ts in the figurl-spike-sorting-views repo. (There's a slight variation in the TrackPositionAnimationView in figurl-franklab-views (https://github.com/magland/figurl-franklab-views/blob/master/test-gui/src/package/view-track-position-animation/TrackPositionAnimationView.tsx), I think because it just never got centralized.)

I believe this version is used in the PositionPlotView and SpikeAmplitudesView, as well as in computing y-axis ticks. It doesn't actually make any assumptions about a React (or FigurlCanvas) context.

Within the util-point-projection version, the model for callers looks like this:

const data = [[1, 3, 5], [1, 9, 25]] // more generally, [[x], [y]]
const scalingMatrixProps = {
  totalPixelWidth: 250,
  totalPixelHeight: 250,
  pixelMargins: { top: 20, bottom: 30, left: 25, right: 25 },
  // the values below refer to the view window in native units;
  // that's an input, rather than computed from data, since we can't
  // know how much whitespace the caller wants
  dataXMin: 0,
  dataXMax: 10,
  dataYMin: 0,
  dataYMax: 50
}
const transform = use2dScalingMatrix(scalingMatrixProps)
const pixelData = convert2dDataSeries(data, transform)
assert( pixelData === [[45, 85, 125], [216, 184, 120]] )

The ScalingProps type also has parameters for user-defined y- and x-scale factors, as well as a toggle for letting y grow downward from the top of the window, and an option to intelligently preserve the aspect ratio of the data. The convert2dDataSeries function also has a parameter to return the values as a list of pairs [[x1, y1], [x2, y2], ...] rather than a pair of lists.

Similar functions exist for 1d in the same file.

Now, this does wind up providing the caller with a matrix that gets passed to the conversion function, rather than wrapping the matrix in a closure. I think the advantage of keeping the conversion matrix is allowing it to be used for two other functions in the same file, getYAxisPixelZero(transformMatrix2d) and convertBaseDistanceToPixels(xDistance, yDistance, transformMatrix2d). The first one returns the pixel height corresponding to y = 0 in the data. The second function converts distances using the same transformation matrix.

If you'd prefer a conversion function that's closed over the matrix so that the user doesn't have to track it, I can wrap the distance-measurement functionality into the conversion function by adding a flag to tell it whether to treat the inputs as distances rather than points; and it's easy enough to pass x = [0], y = [0] if you want the y-axis pixel zero.

I do think this utility code probably belongs in figurl-core-utils (as does several other things, like the color management code from figurl-franklab-views, but that's obviously out of scope for this issue).

magland commented 1 year ago

Looks good, noting that the hooks can't be used in the worker thread.