#!/usr/bin/env python
"""`HBins.py` histogram-style bin parameters holder
Usage::
from pyimgalgos.HBins import HBins
# Equal bins constructor
hb = HBins((1,6), nbins=5)
# Variable bins constructor
hb = HBins((1,2,4,6,10))
# Access methods
nbins = hb.nbins() # returns int input parameter - number of bins
edges = hb.edges() # returns np.array input list of bin edges
vmin = hb.vmin() # returns vtype minimal value of bin edges
vmax = hb.vmax() # returns vtype maximal value of bin edges
vtype = hb.vtype() # returns np.dtype - type of bin edge values
equalbins = hb.equalbins() # returns bool True/False for equal/variable size bins
limits = hb.limits() # returns np.array of limits (vmin, vmax)
binedges = hb.binedges() # returns np.array with bin edges of size nbins+1
binedgesleft = hb.binedgesleft() # returns np.array with bin left edges of size nbins
binedgesright = hb.binedgesright() # returns np.array with bin rignt edges of size nbins
bincenters = hb.bincenters() # returns np.array with bin centers of size nbins
binwidth = hb.binwidth() # returns np.array with bin widths of size nbins or scalar bin width for equal bins
halfbinw = hb.halfbinw() # returns np.array with half-bin widths of size nbins or scalar bin half-width for equal bins
strrange = hb.strrange(fmt) # returns str of formatted vmin, vmax, nbins ex: 1-6-5
ind = hb.bin_index(value, edgemode=0) # returns bin index [0,nbins) for value.
indarr = hb.bin_indexes(valarr, edgemode=0) # returns array of bin index [0,nbins) for array of values
# edgemode - defines what to do with underflow overflow indexes;
# = 0 - use indexes 0 and nbins-1 for underflow overflow, respectively
# = 1 - use extended indexes -1 and nbins for underflow overflow, respectively
# Print methods
hb.print_attrs_defined()
hb.print_attrs()
hb.print_attrs_and_methods()
@see :py:class:`pyimgalgos.HPolar`,
:py:class:`pyimgalgos.HSpectrum`,
:py:class:`pyimgalgos.NDArrSpectrum`,
:py:class:`pyimgalgos.RadialBkgd`,
`Radial background <https://confluence.slac.stanford.edu/display/PSDMInternal/Radial+background+subtraction+algorithm>`_.
This software was developed for the SIT project. If you use all or
part of it, please give an appropriate acknowledgment.
@version $Id: HBins.py 11999 2016-06-01 21:16:06Z dubrovin@SLAC.STANFORD.EDU $
Created on 2016-01-15
@author Mikhail S. Dubrovin
"""
#------------------------------
__version__ = "$Revision: 11999 $"
#------------------------------
import math
import numpy as np
#------------------------------
class HBins() :
"""Hystogram-style bin parameters holder
"""
def __init__(self, edges, nbins=None, vtype=np.float32):
"""Class constructor for
- equal bins, ex: hb = HBins((1,6), nbins=5)
- or variable bins, ex: hb = HBins((1,2,4,6,10))
Parameters:
- edges - sequence of two or more bin edges
- nbins - (int) number of bins for equal size bins
- vtype - numpy type of bin values (optional parameter)
"""
self._name = self.__class__.__name__
self._vtype = vtype
self._set_valid_edges(edges)
self._set_valid_nbins(nbins)
self._vmin = min(self._edges)
self._vmax = max(self._edges)
self._equalbins = len(self._edges)==2 and nbins is not None
self._ascending = self._edges[0] < self._edges[-1]
# dynamic parameters
self._limits = None
self._binwidth = None
self._halfbinw = None
self._binedges = None
self._bincenters = None
self._inds = None
self._indedges = None
self._indcenters = None
self._strrange = None
def _set_valid_edges(self, edges) :
if not isinstance(edges,(tuple,list,np.array)) :
raise ValueError('Parameter edges is not a tuple or list: '\
'edges=%s' % str(edges))
if len(edges)<2 :
raise ValueError('Sequence of edges should have at least two values: '\
'edges=%s' % str(edges))
if not all([isinstance(v,(int, float)) for v in tuple(edges)]) :
raise ValueError('Sequence of edges has a wrong type value: '\
'edges=%s' % str(edges))
if edges[0]==edges[-1] :
raise ValueError('Sequence of edges has equal limits: '\
'edges=%s' % str(edges))
if len(edges)>2 :
if edges[0]<edges[-1] and not all([x<y for x,y in zip(edges[:-1], edges[1:])]) :
raise ValueError('Sequence of edges is not monotonically ascending: '\
'edges=%s' % str(edges))
if edges[0]>edges[-1] and not all([x>y for x,y in zip(edges[:-1], edges[1:])]) :
raise ValueError('Sequence of edges is not monotonically descending: '\
'edges=%s' % str(edges))
self._edges = np.array(edges, dtype=self._vtype)
def _set_valid_nbins(self, nbins) :
if nbins is None :
self._nbins = len(self._edges)-1
return
if not isinstance(nbins, int) :
raise ValueError('nbins=%s has a wrong type. Expected integer.' % str(nbins))
if nbins < 1 :
raise ValueError('nbins=%d should be positive.' % nbins)
self._nbins = nbins
def edges(self) :
"""Returns input sequence of edges"""
return self._edges
def vmin(self) :
"""Returns minimal value of the range"""
return self._vmin
def vmax(self) :
"""Returns miximal value of the range"""
return self._vmax
def nbins(self) :
"""Returns number of bins"""
return self._nbins
def vtype(self) :
"""Returns npumpy datatype for bin values"""
return self._vtype
def equalbins(self) :
return self._equalbins
def ascending(self) :
return self._ascending
def limits(self) :
"""Returns np.array of two ordered limits (vmin, vmax)"""
if self._limits is None :
self._limits = np.array((self._edges[0], self._edges[-1]), dtype=self._vtype)
return self._limits
def binedges(self) :
"""Returns np.array of nbins+1 values of bin edges"""
if self._binedges is None :
if self._equalbins :
self._binedges = np.linspace(self._edges[0], self._edges[-1], self._nbins+1, endpoint=True, dtype=self._vtype)
else :
self._binedges = self._edges
return self._binedges
def binedgesleft(self) :
"""Returns np.array of nbins values of bin left edges"""
return self.binedges()[:-1]
def binedgesright(self) :
"""Returns np.array of nbins values of bin right edges"""
return self.binedges()[1:]
def binwidth(self) :
"""Returns np.array of nbins values of bin widths"""
if self._binwidth is None :
if self._equalbins :
self._binwidth = float(self._edges[-1]-self._edges[0])/self._nbins
else :
self._binwidth = self.binedgesright() - self.binedgesleft()
return self._binwidth
def halfbinw(self) :
"""Returns np.array of nbins values of bin half-widths"""
if self._halfbinw is None :
self._halfbinw = 0.5 * self.binwidth()
return self._halfbinw
def bincenters(self) :
"""Returns np.array of nbins values of bin centers"""
if self._bincenters is None :
self._bincenters = self.binedgesleft() + self.halfbinw()
return self._bincenters
def _set_limit_indexes(self, edgemode) :
"""Returns limit bin indexes for underflow and overflow values"""
if edgemode==0 : return 0, self._nbins-1
elif edgemode==1 : return -1, self._nbins
def bin_index(self, v, edgemode=0) :
"""Returns bin index for scalar value"""
indmin, indmax = self._set_limit_indexes(edgemode)
if self._ascending :
if v< self._edges[0] : return indmin
if v>=self._edges[-1] : return indmax
else :
if v> self._edges[0] : return indmin
if v<=self._edges[-1] : return indmax
if self._equalbins :
return math.floor((v-self._edges[0])/self.binwidth())
if self._ascending :
for ind, edgeright in enumerate(self.binedgesright()) :
if v<edgeright :
return ind
else :
for ind, edgeright in enumerate(self.binedgesright()) :
if v>edgeright :
return ind
def bin_indexes(self, arr, edgemode=0) :
indmin, indmax = self._set_limit_indexes(edgemode)
if self._equalbins :
factor = float(self._nbins)/(self._edges[-1]-self._edges[0])
nbins1 = self._nbins-1
nparr = (np.array(arr, dtype=self._vtype)-self._edges[0])*factor
ind = np.array(np.floor(nparr), dtype=np.int32)
return np.select((ind<0, ind>nbins1), (indmin, indmax), default=ind)
else :
conds = None
if self._ascending :
conds = np.array([arr<edge for edge in self.binedges()], dtype=np.bool)
else :
conds = np.array([arr>edge for edge in self.binedges()], dtype=np.bool)
inds1d = range(-1, self._nbins)
inds1d[0] = indmin # re-define index for underflow
inds = np.array(len(arr)*inds1d, dtype=np.int32)
inds.shape = (len(arr),self._nbins+1)
inds = inds.transpose()
#print 'indmin, indmax = ', indmin, indmax
#print 'XXX conds:\n', conds
#print 'XXX inds:\n', inds
return np.select(conds, inds, default=indmax)
def strrange(self, fmt='%.0f-%.0f-%d') :
"""Returns string of range parameters"""
if self._strrange is None :
self._strrange =fmt % (self._edges[0], self._edges[-1], self._nbins)
return self._strrange
def print_attrs(self) :
print 'Attributes of the %s object' % self._name
for k,v in self.__dict__.items() :
print ' %s : %s' % (k.ljust(16), str(v))
def print_attrs_defined(self) :
print 'Attributes (not None) of the %s object' % self._name
for k,v in self.__dict__.items() :
if v is None : continue
print ' %s : %s' % (k.ljust(16), str(v))
def print_attrs_and_methods(self) :
print 'Methods & attributes of the %s object' % self._name
for m in dir(self) :
print ' %s' % (str(m).ljust(16))
#------------------------------
[docs]def test_bin_indexes(o, vals, edgemode=0, cmt='') :
print '%s\n%s, edgemode=%d :' % (80*'_', cmt, edgemode)
print 'nbins = %d' % o.nbins()
print 'binedges', o.binedges()
print 'equalbins', o.equalbins()
print 'Test of o.bin_index:'
for v in vals : print 'value=%5.1f index=%2d' % (v, o.bin_index(v, edgemode))
print 'Test of o.bin_indexes:'
inds = o.bin_indexes(vals, edgemode)
for v,i in zip(vals,inds) : print 'value=%5.1f index=%2d' % (v, i)
#------------------------------
[docs]def test(o, cmt='') :
print '%s\n%s\n' % (80*'_', cmt)
o.print_attrs_and_methods()
o.print_attrs_defined()
print 'nbins = %d' % o.nbins()
print 'limits', o.limits()
print 'binedges', o.binedges()
print 'binedgesleft', o.binedgesleft()
print 'binedgesright', o.binedgesright()
print 'bincenters', o.bincenters()
print 'binwidth', o.binwidth()
print 'halfbinw', o.halfbinw()
print 'strrange', o.strrange()
print 'equalbins', o.equalbins()
o.print_attrs_defined()
print '%s' % (80*'_')
#------------------------------
if __name__ == "__main__" :
o1 = HBins((1,6), 5); test(o1, 'Test HBins for EQUAL BINS')
o2 = HBins((1, 2, 4, 8)); test(o2, 'Test HBins for VARIABLE BINS')
try : o = HBins((1,6), 5.5)
except Exception as e : print 'Test Exception non-int nbins:', e
try : o = HBins((1,6), -5)
except Exception as e : print 'Test Exception nbins<1:', e
try : o = HBins((1,6), 0)
except Exception as e : print 'Test Exception nbins<1:', e
try : o = HBins((1,6,3))
except Exception as e : print 'Test Exception non-monotonic edges:', e
try : o = HBins((3,6,1))
except Exception as e : print 'Test Exception non-monotonic edges:', e
try : o = HBins((3,2,2,1))
except Exception as e : print 'Test Exception non-monotonic edges:', e
try : o = HBins((3,'s',1))
except Exception as e : print 'Test Exception wrong type value in edges:', e
try : o = HBins(3)
except Exception as e : print 'Test Exception not-sequence in edges:', e
try : o = HBins((3,))
except Exception as e : print 'Test Exception sequence<2 in edges:', e
vals=(-3, 0, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)
test_bin_indexes(o1, vals, edgemode=0, cmt='Test for EQUAL BINS')
test_bin_indexes(o1, vals, edgemode=1, cmt='Test for EQUAL BINS')
test_bin_indexes(o2, vals, edgemode=0, cmt='Test for VARIABLE BINS')
test_bin_indexes(o2, vals, edgemode=1, cmt='Test for VARIABLE BINS')
#------------------------------