package pl.edu.fuw.fid.signalanalysis.waveform; import java.awt.image.BufferedImage; import javafx.scene.chart.Axis; import org.apache.commons.math.complex.Complex; import org.signalml.app.view.book.wignermap.WignerMapPalette; import pl.edu.fuw.fid.signalanalysis.AsyncStatus; import pl.edu.fuw.fid.signalanalysis.SingleSignal; /** * Base class for object computing time-frequency maps * for parameters selected by the user. * * @author ptr@mimuw.edu.pl */ public abstract class ImageRenderer<P> { protected final SingleSignal signal; protected final double sampling; private volatile boolean inverted_ = false; private volatile WignerMapPalette palette_ = WignerMapPalette.RAINBOW; private volatile CachedImageResult<PreferencesWithAxes<P>> cache_; public ImageRenderer(SingleSignal signal) { this.signal = signal; this.sampling = signal.getSamplingFrequency(); } protected abstract ImageResult compute(PreferencesWithAxes<P> preferences, AsyncStatus status) throws Exception; public boolean isInverted() { return inverted_; } public WignerMapPalette getPaletteType() { return palette_; } public TimeFrequency getTimeFrequency(int x, int y) { TimeFrequency result = null; CachedImageResult<PreferencesWithAxes<P>> cache = cache_; if (cache != null && x >= 0 && x < cache.result.t.length && y >= 0 && y < cache.result.f.length) { double t = cache.result.t[x]; double f = cache.result.f[y]; Complex v = cache.result.values[x][y]; result = new TimeFrequency(t, f, v); } return result; } public void setInverted(boolean inverted) { inverted_ = inverted; } public void setPaletteType(WignerMapPalette palette) { palette_ = palette; } protected abstract P getPreferences(); /** * Render image consisting of computation result. * This method will be run in background thread, so it can have a while. * Implementation should periodically check if status.isCancelled() * and abort (return null) if so. * * @param xAxis values for x dimension * @param yAxis values for y dimension * @param status status to be checked periodically * @return computation result as two-dimensional array * @throws Exception if computational error occurs */ public BufferedImage renderImage(Axis<Number> xAxis, Axis<Number> yAxis, ImageRendererStatus status) throws Exception { ImageResult result; int width = (int) xAxis.getWidth(); int height = (int) yAxis.getHeight(); double xMin = xAxis.getValueForDisplay(0.0).doubleValue(); double xMax = xAxis.getValueForDisplay(width-1).doubleValue(); double yMin = yAxis.getValueForDisplay(0.0).doubleValue(); double yMax = yAxis.getValueForDisplay(height-1).doubleValue(); PreferencesWithAxes<P> pax = new PreferencesWithAxes<P>(getPreferences(), width, height, xMin, xMax, yMin, yMax); CachedImageResult<PreferencesWithAxes<P>> cache = cache_; if (cache != null && cache.preferences.equals(pax)) { result = cache.result; } else { result = compute(pax, status); if (result == null) { return null; } cache_ = new CachedImageResult<PreferencesWithAxes<P>>(pax, result); } BufferedImage image = new BufferedImage(pax.width, pax.height, BufferedImage.TYPE_INT_RGB); if (result == null) { return null; } double max = 0; for (int ix=0; ix<pax.width; ++ix) { for (int iy=0; iy<pax.height; ++iy) { max = Math.max(max, result.values[ix][iy].abs()); } } int[] palette = palette_.getPalette(); boolean inverted = inverted_; for (int ix=0; ix<pax.width; ++ix) { if (status.isCancelled()) { return null; } for (int iy=0; iy<pax.height; ++iy) { double t = result.values[ix][iy].abs() / max; if (inverted) { t = 1.0 - t; } int value = palette[Math.min( (int) Math.floor(t * palette.length), palette.length-1 )]; image.setRGB(ix, iy, value); } } return image; } }