/* File: RebinDescriptor.java * Copyright (C) 2002-2003 The University of Iowa * Created by: Jeremy Faden <jbf@space.physics.uiowa.edu> * Jessica Swanner <jessica@space.physics.uiowa.edu> * Edward E. West <eew@space.physics.uiowa.edu> * * This file is part of the das2 library. * * das2 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.das2.dataset; import java.util.logging.Level; import java.util.logging.Logger; import org.das2.datum.DatumVector; import org.das2.datum.UnitsConverter; import org.das2.datum.Datum; import org.das2.datum.LoggerManager; import org.das2.datum.Units; import org.das2.qds.DDataSet; import org.das2.qds.DataSetOps; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.QDataSet; import org.das2.qds.SemanticOps; /** * The RebinDescriptor will quickly look up which 1-D bin a Datum is * in. This is not thread-safe, and must be used by only one thread during its * lifetime. * @author jbf */ public final class RebinDescriptor { private static final Logger logger= LoggerManager.getLogger("das2.data.rebinner"); Units units; protected double start; protected double end; protected int nBin; protected boolean isLog = false; public static final int FIRSTORLAST=-2; // return the closest valid bin, first or last public static final int MINUSONE= -3; // return sentinel -1. public static final int EXTRAPOLATE= -4; // return negative or >nBin. private int outOfBoundsAction= EXTRAPOLATE; /** Creates a new instance of RebinDescriptor */ private RebinDescriptor() { } public RebinDescriptor(double start, double end, Units units, int nBin, boolean isLog) { this.units= units; if (isLog) { this.start= Math.log(start); this.end= Math.log(end); } else { this.start= start; this.end= end; } this.nBin= nBin; this.isLog= isLog; } public RebinDescriptor( Datum start, Datum end, int nBin, boolean isLog) { this(start.doubleValue(start.getUnits()),end.doubleValue(end.getUnits()),start.getUnits(),nBin,isLog); if (start.getUnits()!=end.getUnits()) throw new IllegalArgumentException( "start and end units differ: \""+start.getUnits()+ "\" \"" +end.getUnits()+ "\"" ); } public int numberOfBins() { return nBin; } private UnitsConverter uc; // cache UnitsConverter private Units inUnits=null; // cache units. public int whichBin( double x, Units units ) { if ( units!=this.units ) { if ( uc==null || units!=inUnits ) { // small optimization doesn't seem to have a large effect. uc= Units.getConverter(units,this.units); inUnits= units; } x= uc.convert(x); } int result=0; if (isLog) x= Math.log(x); boolean outOfBounds= start < end ? (x<start || x>=end) : (x<end || x>=start) ; if ( outOfBounds && outOfBoundsAction!=EXTRAPOLATE) { switch (outOfBoundsAction) { case FIRSTORLAST: result= start < end ? ( x<start ? 0 : nBin-1 ) : ( x<end ? nBin-1 : 0 ); break; case MINUSONE: result= -1; break; default: logger.log(Level.INFO, "outOfBoundsAction not supported: {0}", outOfBoundsAction); } } else { result= (int)((x-start)*nBin/(end-start)); } return result; } public DatumVector binCentersDV() { double [] result= binCenters(); return DatumVector.newDatumVector(result, units); } public double[] binCenters() { double [] result= new double[nBin]; for (int i=0; i<nBin; i++) { result[i]= start+((i+0.5)/(float)(nBin)*(end-start)); } if (isLog) { for (int i=0; i<nBin; i++) result[i]= Math.exp(result[i]); } return result; } public double binCenter(int ibin,Units units) { UnitsConverter cu= this.units.getConverter(units); double result= start+((ibin+0.5)/(double)(nBin)*(end-start)); if ( isLog ) return cu.convert( Math.exp(result) ); else return cu.convert( result ); } public Datum binCenter(int ibin) { return Datum.create( binCenter( ibin, units ), units ); } public Datum binStart( int ibin ) { return Datum.create( binStart( ibin, units ), units ); } /** * return the smaller boundary of the bin. * @param ibin the bin number * @param units the units for the result. * @return the smaller boundary of the bin in the desired units. */ public double binStart( int ibin, Units units ) { if ( this.outOfBoundsAction!=RebinDescriptor.EXTRAPOLATE ) { if ( ibin<0 || ibin >= numberOfBins() ) { throw new IllegalArgumentException("bin "+ibin+" is out of bounds"); } } double result= start+((ibin)/(double)(nBin)*(end-start)); UnitsConverter cu= this.units.getConverter(units); if ( isLog ) { return cu.convert(Math.exp(result)); } else { return cu.convert(result); } } public Datum binStop( int ibin ) { return Datum.create( binStop( ibin, units ), units ); } /** * return the bigger boundary of the bin. * @param ibin the bin number * @param units the units for the result. * @return the bigger boundary of the bin in the desired units. */ public double binStop( int ibin, Units units ) { if ( this.outOfBoundsAction!=RebinDescriptor.EXTRAPOLATE ) { if ( ibin<0 || ibin >= numberOfBins() ) { throw new IllegalArgumentException("bin "+ibin+" is out of bounds"); } } double result= start+((ibin+1)/(double)(nBin)*(end-start)); UnitsConverter cu= this.units.getConverter(units); if ( isLog ) { return cu.convert(Math.exp(result)); } else { return cu.convert(result); } } /** * return the bin starts of all bins, in units of <tt>getUnits()</tt> * @return the bin starts of all bins */ public double[] binStarts() { double [] result= new double[nBin]; for (int i=0; i<nBin; i++) { result[i]= start+((i)/(float)(nBin)*(end-start)); } if (isLog) { for (int i=0; i<nBin; i++) result[i]= Math.exp(result[i]); } return result; } /** * return the bin stops of all bins, in units of <tt>getUnits()</tt> * @return the bin stops of all bins */ public double[] binStops() { double [] result= new double[nBin]; for (int i=0; i<nBin; i++) { result[i]= start+((i+1)/(float)(nBin)*(end-start)); } if (isLog) { for (int i=0; i<nBin; i++) result[i]= Math.exp(result[i]); } return result; } public void setOutOfBoundsAction(int action) { outOfBoundsAction= action; } private Object clone( int outOfBoundsAction ) { RebinDescriptor result= new RebinDescriptor(); result.units= this.units; result.start= this.start; result.end= this.end; result.nBin= this.nBin; result.isLog= this.isLog; result.outOfBoundsAction= outOfBoundsAction; return result; } /* create new rebinDescriptor that includes ddY plus additional channels to include ymin to ymax */ public static RebinDescriptor createSubsumingRebinDescriptor( RebinDescriptor ddY, Datum ymin, Datum ymax ) { if ( ddY==null ) return null; RebinDescriptor dd= (RebinDescriptor)ddY.clone( RebinDescriptor.EXTRAPOLATE ); Units units= ddY.getUnits(); int i0= dd.whichBin( ymin.doubleValue(units), units ); if ( i0>0 ) { i0= 0; ymin= units.createDatum(ddY.binStart(0, units)); } if ( i0< -10000000 ) { throw new IllegalArgumentException( "ymin would result in impossibly large rebin descriptor (ymin="+ymin+" falls in bin number "+i0+")" ); } int i1= dd.whichBin( ymax.doubleValue(units), units ); if ( i1<dd.numberOfBins() ) { i1= dd.numberOfBins(); ymax= units.createDatum(dd.binStop(dd.numberOfBins()-1,units)); } if ( i0> 10000000 ) { throw new IllegalArgumentException( "ymax would result in impossibly large rebin descriptor (ymax="+ymax+" falls in bin number "+i0+")" ); } int nbins= i1-i0+1; return new RebinDescriptor( units.createDatum(dd.binStart(i0,units)), units.createDatum(dd.binStop(i1,units)), nbins, dd.isLog() ); } public double binWidth() { return (end-start)/(double)nBin; } public Datum binWidthDatum() { return Datum.create( binWidth(), getUnits().getOffsetUnits() ); } public boolean isLog() { return isLog; } public Units getUnits() { return units; } /** * taken from AverageTableRebinner * @param ds the original dataset * @param result the rank 2 rebin target. * @param ddX the descriptor, or null. * @param ddY the descriptor, or null. */ public static void putDepDataSet( QDataSet ds, MutablePropertyDataSet result, RebinDescriptor ddX, RebinDescriptor ddY ) { QDataSet xds= SemanticOps.xtagsDataSet(ds); MutablePropertyDataSet xx; if ( ddX!=null ) { DDataSet xxx= DDataSet.createRank1( ddX.numberOfBins() ); for ( int i=0; i<xxx.length(); i++ ) xxx.putValue(i, ddX.binCenter(i,ddX.units)); xxx.putProperty( QDataSet.UNITS, ddX.units ); xx= xxx; } else { xx= DataSetOps.makePropertiesMutable(xds); //TODO: untested branch } QDataSet yds= SemanticOps.ytagsDataSet(ds); MutablePropertyDataSet yy; if ( ddY!=null ) { DDataSet yyy= DDataSet.createRank1( ddY.numberOfBins() ); for ( int i=0; i<yyy.length(); i++ ) yyy.putValue(i, ddY.binCenter(i,ddY.units)); yyy.putProperty( QDataSet.UNITS, ddY.units ); yy= yyy; } else { yy= DataSetOps.makePropertiesMutable( yds ); } String[] props= new String[] { QDataSet.NAME, QDataSet.LABEL, QDataSet.TITLE }; for ( String s: props ) { if ( xds!=null && xds.property(s)!=null ) xx.putProperty(s,xds.property(s)); if ( yds!=null && yds.property(s)!=null ) yy.putProperty(s,yds.property(s)); } for ( String s: org.das2.qds.DataSetUtil.dimensionProperties() ) { if ( ds.property(s)!=null ) result.putProperty(s,ds.property(s)); } if (ddX != null) { xx.putProperty(QDataSet.CADENCE, org.das2.qds.DataSetUtil.asDataSet(ddX.binWidthDatum()) ); } if (ddY != null) { yy.putProperty(QDataSet.CADENCE, org.das2.qds.DataSetUtil.asDataSet(ddY.binWidthDatum()) ); } result.putProperty( QDataSet.DEPEND_0, xx ); result.putProperty( QDataSet.DEPEND_1, yy ); } @Override public String toString() { if ( isLog() ) { return "["+units.createDatum(Math.exp(start))+" - "+units.createDatum(Math.exp(end))+" in "+nBin+" bins "+(isLog?"Log":"")+"]"; } else { return "["+units.createDatum(start)+" - "+units.createDatum(end)+" in "+nBin+" bins "+(isLog?"Log":"")+"]"; } } }