diff --git a/modules/base/pymod/table.py b/modules/base/pymod/table.py index 8795799ce95aa0aa1a0e5d1579cfd20790732cf7..3bd8c750e8bb7653cf693e8815673f8bd3674687 100644 --- a/modules/base/pymod/table.py +++ b/modules/base/pymod/table.py @@ -90,6 +90,9 @@ class BinaryColExpr: def __mul__(self, rhs): return BinaryColExpr(operator.mul, self, rhs) + def __div__(self, rhs): + return BinaryColExpr(operator.div, self, rhs) + class TableCol: def __init__(self, table, col): self._table=table @@ -119,6 +122,7 @@ class TableCol: def __mul__(self, rhs): return BinaryColExpr(operator.mul, self, rhs) + def __div__(self, rhs): return BinaryColExpr(operator.div, self, rhs) @@ -411,6 +415,31 @@ class Table(object): def __str__(self): return self.ToString() + def Stats(self, col): + idx = self.GetColIndex(col) + text =''' +Statistics for column %(col)s + + Number of Rows : %(num)d + Number of Rows Not None: %(num_non_null)d + Mean : %(mean)f + Median : %(median)f + Standard Deviation : %(stddev)f + Min : %(min)f + Max : %(max)f +''' + data = { + 'col' : col, + 'num' : len(self.rows), + 'num_non_null' : self.Count(col), + 'median' : self.Median(col), + 'mean' : self.Mean(col), + 'stddev' : self.StdDev(col), + 'min' : self.Min(col), + 'max' : self.Max(col), + } + return text % data + def _AddRowsFromDict(self, d, overwrite=None): ''' Add one or more rows from a :class:`dictionary <dict>`. @@ -469,6 +498,27 @@ class Table(object): if not overwrite or not added: self.rows.append(new_row) + def PairedTTest(self, col_a, col_b): + """ + Two-sided test for the null-hypothesis that two related samples + have the same average (expected values) + + :param col_a: First column + :param col_b: Second column + + :returns: P-value between 0 and 1 that the two columns have the + same average. The smaller the value, the less related the two + columns are. + """ + from scipy.stats import ttest_rel + xs = [] + ys = [] + for x, y in self.Zip(col_a, col_b): + if x!=None and y!=None: + xs.append(x) + ys.append(y) + result = ttest_rel(xs, ys) + return result[1] def AddRow(self, data, overwrite=None): """ @@ -645,6 +695,10 @@ class Table(object): As a special case, if there are no previous rows, and data is not None, rows are added for every item in data. """ + + if col_name in self.col_names: + raise ValueError('Column with name %s already exists'%col_name) + col_type = self._ParseColTypes(col_type, exp_num=1)[0] self.col_names.append(col_name) self.col_types.append(col_type) @@ -654,10 +708,15 @@ class Table(object): for row in self.rows: row.append(data) else: + if hasattr(data, '__len__') and len(data)!=len(self.rows): + self.col_names.pop() + self.col_types.pop() + raise ValueError('Length of data (%i) must correspond to number of '%len(data) +\ + 'existing rows (%i)'%len(self.rows)) for row, d in zip(self.rows, data): row.append(d) - elif data!=None and len(self.col_names)==0: + elif data!=None and len(self.col_names)==1: if IsScalar(data): self.AddRow({col_name : data}) else: @@ -1506,6 +1565,45 @@ class Table(object): self.AddCol(mean_col_name, 'f', mean_rows) + def Percentiles(self, col, nths): + """ + returns the percentiles of column *col* given in *nths*. + + The percentils are calculated as + + .. code-block:: python + + values[min(len(values), int(round(len(values)*p/100+0.5)-1))] + + where values are the sorted values of *col* not equal to none + :param: nths: list of percentiles to be calculated. Each percentil is a number + between 0 and 100. + + :raises: :class:`TypeError` if column type is ``string`` + :returns: List of percentils in the same order as given in *nths* + """ + idx = self.GetColIndex(col) + col_type = self.col_types[idx] + if col_type!='int' and col_type!='float' and col_type!='bool': + raise TypeError("Median can only be used on numeric column types") + + for nth in nths: + if nth < 0 or nth > 100: + raise ValueError("percentiles must be between 0 and 100") + vals=[] + for v in self[col]: + if v!=None: + vals.append(v) + vals=sorted(vals) + if len(vals)==0: + return [None]*len(nths) + percentiles=[] + + for nth in nths: + p=vals[min(len(vals)-1, int(round(len(vals)*nth/100.0+0.5)-1))] + percentiles.append(p) + return percentiles + def Median(self, col): """ Returns the median of the given column. Cells with None are ignored. Returns diff --git a/modules/base/tests/test_table.py b/modules/base/tests/test_table.py index 7a10f4ee131ac625e24d33a4cca634ca53d57803..7337dfbe63baf40cb74e85de3448887ecff020db 100644 --- a/modules/base/tests/test_table.py +++ b/modules/base/tests/test_table.py @@ -196,7 +196,14 @@ class TestTable(unittest.TestCase): self.assertEquals(type(z[0][1]),int) self.assertEquals(type(z[1][1]),int) self.assertRaises(ValueError, tab.Zip, 'col5', 'col3') - + def testPercentiles(self): + tab = Table(['nums'], 'i') + self.assertEqual(tab.Percentiles('nums', [0,100]), [None, None]) + self.assertRaises(ValueError, tab.Percentiles, 'nums', [101]) + self.assertRaises(ValueError, tab.Percentiles, 'nums', [-1]) + for i in (35,15,50,40,20): + tab.AddRow([i]) + self.assertEqual(tab.Percentiles('nums', [0,30,40,100]), [15,20,35,50]) def testTableInitEmpty(self): ''' empty table @@ -592,6 +599,15 @@ class TestTable(unittest.TestCase): 'foo': [True, None, True], 'bar': [1, 2, 3]}) + def testRaiseErrorOnWrongDataLengthAddCol(self): + tab = Table() + tab.AddCol('a','f',[4.2,4.2,4.2]) + self.assertRaises(ValueError, tab.AddCol, 'b', 'f', [4.2,4.2]) + + def testRaiseErrorColNameAlreadyExists(self): + tab = Table() + tab.AddCol('awesome','f') + self.assertRaises(ValueError, tab.AddCol, 'awesome', 'f') def testRaiseErrorOnWrongColumnTypes(self): # wrong columns types in init @@ -1371,7 +1387,52 @@ class TestTable(unittest.TestCase): self.assertRaises(RuntimeError, tab.GetOptimalPrefactors, 'c','a','b',weight='d') self.assertRaises(RuntimeError, tab.GetOptimalPrefactors, 'c',weights='d') - + + def testGaussianSmooth(self): + tab = Table(['a','b','c','d','e','f'],'fffffi', + a=[0.5,1.0,2.0,3.0,2.5,1.0,0.5,2.3,1.0], + b=[0.5,1.0,2.0,3.0,2.5,1.0,0.5,2.3,1.0], + c=[0.5,1.0,2.0,3.0,2.5,1.0,0.5,2.3,1.0], + d=[0.5,1.0,2.0,3.0,2.5,1.0,0.5,2.3,1.0], + e=[0.5,1.0,2.0,3.0,2.5,1.0,0.5,2.3,1.0], + f=[2,6,5,3,8,7,4,4,4]) + + tab.GaussianSmooth('a') + tab.GaussianSmooth('b', std=2.0) + tab.GaussianSmooth('c', padding='wrap') + tab.GaussianSmooth('d', padding='constant') + tab.GaussianSmooth('e', padding='constant',c=3.0) + tab.GaussianSmooth('f') + + ref_list=[] + + ref_list.append([0.74729766,1.20875404,1.93459464,2.39849076,2.11504816, + 1.42457403,1.20524937,1.41025075,1.3557406]) + ref_list.append([1.23447249,1.41295267,1.65198705,1.79959835,1.78131778, + 1.64501718,1.49728102,1.40589715,1.37147629]) + ref_list.append([0.9315564,1.24131027,1.93698455,2.39855767,2.11504816, + 1.42450711,1.20285946,1.37769451,1.17148186]) + ref_list.append([0.5630556,1.17705895,1.93224488,2.39842384,2.11504816, + 1.4244402,1.2005097,1.34599942,0.9872398 ]) + ref_list.append([1.46464039,1.35272941,1.94594196,2.39882533,2.11504816, + 1.42484169,1.21420677,1.52166988,1.88882459]) + ref_list.append([3,4,4,5,6,6,4,4,4]) + + tab_list=[[],[],[],[],[],[]] + + for row in tab.rows: + for i,v in enumerate(row): + tab_list[i].append(v) + + for i in range(len(ref_list[0])): + self.assertAlmostEquals(tab_list[0][i],ref_list[0][i]) + self.assertAlmostEquals(tab_list[1][i],ref_list[1][i]) + self.assertAlmostEquals(tab_list[2][i],ref_list[2][i]) + self.assertAlmostEquals(tab_list[3][i],ref_list[3][i]) + self.assertAlmostEquals(tab_list[4][i],ref_list[4][i]) + self.assertAlmostEquals(tab_list[5][i],ref_list[5][i]) + + def testIsEmpty(self): tab = Table() self.assertTrue(tab.IsEmpty()) diff --git a/modules/img/base/src/image_state/image_state_algorithm.hh b/modules/img/base/src/image_state/image_state_algorithm.hh index b601345196af9c1daec20fc6ec486846aa2d015a..e5546583a2208bd600c66d73667783aa3acb8338 100644 --- a/modules/img/base/src/image_state/image_state_algorithm.hh +++ b/modules/img/base/src/image_state/image_state_algorithm.hh @@ -34,7 +34,7 @@ namespace ost { namespace img { namespace image_state { /* one-time definition of the constructor adapters, allowing - zero to 10 ctor parameters to be automagically used. There + zero to 12 ctor parameters to be automagically used. There is probably a recursive way to do this more elegantly... this version includes a call to a base class to allow a name @@ -52,14 +52,14 @@ namespace ost { namespace img { namespace image_state { template <class P0, \ class P1> \ CLASS (const P0& p0, \ - const P1& p1): \ + const P1& p1): \ FNC(p0,p1), BASE (FNC::GetAlgorithmName()) {} \ /* 3 params */ \ template <class P0, \ class P1, \ class P2> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2): \ FNC(p0,p1,p2), BASE (FNC::GetAlgorithmName()) {} \ /* 4 params */ \ @@ -68,7 +68,7 @@ namespace ost { namespace img { namespace image_state { class P2, \ class P3> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3): \ FNC(p0,p1,p2,p3), BASE (FNC::GetAlgorithmName()) {} \ @@ -79,7 +79,7 @@ namespace ost { namespace img { namespace image_state { class P3, \ class P4> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4): \ @@ -92,7 +92,7 @@ namespace ost { namespace img { namespace image_state { class P4, \ class P5> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -107,7 +107,7 @@ namespace ost { namespace img { namespace image_state { class P5, \ class P6> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -124,7 +124,7 @@ namespace ost { namespace img { namespace image_state { class P6, \ class P7> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -143,7 +143,7 @@ namespace ost { namespace img { namespace image_state { class P7, \ class P8> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -164,7 +164,7 @@ namespace ost { namespace img { namespace image_state { class P8, \ class P9> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -187,7 +187,7 @@ namespace ost { namespace img { namespace image_state { class P9, \ class PA> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -212,7 +212,7 @@ namespace ost { namespace img { namespace image_state { class PA, \ class PB> \ CLASS (const P0& p0, \ - const P1& p1, \ + const P1& p1, \ const P2& p2, \ const P3& p3, \ const P4& p4, \ @@ -221,8 +221,8 @@ namespace ost { namespace img { namespace image_state { const P7& p7, \ const P8& p8, \ const P9& p9, \ - const P9& pa, \ - const PA& pb): \ + const PA& pa, \ + const PB& pb): \ FNC(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pa,pb), BASE (FNC::GetAlgorithmName()) {} @@ -356,8 +356,8 @@ private: */ template <class FNC> class TEMPLATE_EXPORT ImageStateConstModIPAlgorithm: public FNC, - public ConstModIPAlgorithm, - public ImageStateConstModIPVisitorBase { + public ConstModIPAlgorithm, + public ImageStateConstModIPVisitorBase { public: IMAGE_STATE_VISITOR_CTOR_ADAPTERS(ImageStateConstModIPAlgorithm, ConstModIPAlgorithm) @@ -409,8 +409,8 @@ private: //! out-of-place modifying image state const visitor plus op algorithm template <class FNC> class TEMPLATE_EXPORT ImageStateConstModOPAlgorithm: public FNC, - public ConstModOPAlgorithm, - public ImageStateConstModOPVisitorBase { + public ConstModOPAlgorithm, + public ImageStateConstModOPVisitorBase { public: IMAGE_STATE_VISITOR_CTOR_ADAPTERS(ImageStateConstModOPAlgorithm, ConstModOPAlgorithm)