From b36db20699b266ee5dff68396bb1571e6904a4ee Mon Sep 17 00:00:00 2001 From: Steve Varner Date: Wed, 4 May 2016 16:46:23 -0400 Subject: [PATCH 1/2] Adding code to correctly handle NaN values in images. --- thunder/images/images.py | 147 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/thunder/images/images.py b/thunder/images/images.py index 9c1872ef..215ca60a 100644 --- a/thunder/images/images.py +++ b/thunder/images/images.py @@ -1,11 +1,57 @@ import logging from numpy import ndarray, arange, amax, amin, size, asarray, random, prod, \ - apply_along_axis + apply_along_axis, nanmean, nanstd, nanmin, nanmax, nansum, nanmedian, inf, subtract + from itertools import product from ..base import Data from ..blocks.local import LocalBlocks +class Dimensions(object): + """ Class for estimating and storing dimensions of data based on the keys """ + + def __init__(self, values=[], n=3): + self.min = tuple(map(lambda i: inf, range(0, n))) + self.max = tuple(map(lambda i: -inf, range(0, n))) + + for v in values: + self.merge(v) + + def merge(self, value): + self.min = tuple(map(min, self.min, value)) + self.max = tuple(map(max, self.max, value)) + return self + + def mergeDims(self, other): + self.min = tuple(map(min, self.min, other.min)) + self.max = tuple(map(max, self.max, other.max)) + return self + + @property + def count(self): + return tuple(map(lambda x: x + 1, map(subtract, self.max, self.min))) + + @classmethod + def fromTuple(cls, tup): + """ Generates a Dimensions object from the passed tuple. """ + mx = [v-1 for v in tup] + mn = [0] * len(tup) + return cls(values=[mx, mn], n=len(tup)) + + def __str__(self): + return str(self.count) + + def __repr__(self): + return str(self.count) + + def __len__(self): + return len(self.min) + + def __iter__(self): + return iter(self.count) + + def __getitem__(self, item): + return self.count[item] class Images(Data): """ @@ -173,6 +219,56 @@ def sample(self, nsamples=100, seed=None): return self._constructor(result) + + + def crop(self, minbound, maxbound): + """ + Crop a spatial region from 2D or 3D data. + + Parameters + ---------- + minbound : list or tuple + Minimum of crop region (x,y) or (x,y,z) + + maxbound : list or tuple + Maximum of crop region (x,y) or (x,y,z) + + Returns + ------- + Images object with cropped images / volume + """ + dims = self.dims + dims = Dimensions.fromTuple(dims) + ndims = len(dims) + dimsCount = dims.count + + if ndims < 2 or ndims > 3: + raise Exception("Cropping only supported on 2D or 3D image data.") + + dimMinMaxTuples = zip(dimsCount, minbound, maxbound) + if len(dimMinMaxTuples) != ndims: + raise ValueError("Number of specified bounds (%d) must equal image dimensionality (%d)" % + (len(dimMinMaxTuples), ndims)) + slices = [] + newdims = [] + for dim, minb, maxb in dimMinMaxTuples: + if maxb > dim: + raise ValueError("Maximum bound (%d) may not exceed image size (%d)" % (maxb, dim)) + if minb < 0: + raise ValueError("Minumum bound (%d) must be positive" % minb) + if minb < maxb: + slise = slice(minb, maxb) + newdims.append(maxb - minb) + elif minb == maxb: + slise = minb # just an integer index, not a slice; this squeezes out singleton dimensions + # don't append to newdims, this dimension will be squeezed out + else: + raise ValueError("Minimum bound (%d) must be <= max bound (%d)" % (minb, maxb)) + slices.append(slise) + + return self.map(lambda v: v[slices], dims=newdims) + + def map(self, func, dims=None, with_keys=False): """ Map an array -> array function over each image. @@ -237,6 +333,55 @@ def min(self): """ return self._constructor(self.values.min(axis=0, keepdims=True)) + + def nanmean(self): + """ + Compute the mean across images ignoring the NaNs + """ + return self._constructor(self.values.nanmean(axis=0, keepdims=True)) + + def nancount(self): + """ + Compute the mean across images ignoring the NaNs + """ + return self._constructor(self.values.nancount(axis=0, keepdims=True)) + + def nanmax(self): + """ + Compute the max across images ignoring the NaNs + """ + return self._constructor(self.values.nanmax(axis=0, keepdims=True)) + + def nanmin(self): + """ + Compute the min across images ignoring the NaNs + """ + return self._constructor(self.values.nanmin(axis=0, keepdims=True)) + + def nanstd(self): + """ + Compute the standard deviation across images ignoring the NaNs + """ + return self._constructor(self.values.nanstd(axis=0, keepdims=True)) + + def nansum(self): + """ + Compute the sum across images ignoring the NaNs + """ + return self._constructor(self.values.nansum(axis=0, keepdims=True)) + + def nanvariance(self): + """ + Compute the sum across images ignoring the NaNs + """ + return self._constructor(self.values.nanvar(axis=0, keepdims=True)) + + def nanmedian(self): + """ + Compute the median across images ignoring the NaNs + """ + return self._constructor(nanmedian(self.values, axis=0)) + def squeeze(self): """ Remove single-dimensional axes from images. From ae713a695a14b0ef913e46535a9814c8f8e8bac4 Mon Sep 17 00:00:00 2001 From: Steve Varner Date: Wed, 4 May 2016 16:58:11 -0400 Subject: [PATCH 2/2] Removing crop function that was mistakenly added back in. --- thunder/images/images.py | 48 ---------------------------------------- 1 file changed, 48 deletions(-) diff --git a/thunder/images/images.py b/thunder/images/images.py index 215ca60a..17637944 100644 --- a/thunder/images/images.py +++ b/thunder/images/images.py @@ -221,54 +221,6 @@ def sample(self, nsamples=100, seed=None): - def crop(self, minbound, maxbound): - """ - Crop a spatial region from 2D or 3D data. - - Parameters - ---------- - minbound : list or tuple - Minimum of crop region (x,y) or (x,y,z) - - maxbound : list or tuple - Maximum of crop region (x,y) or (x,y,z) - - Returns - ------- - Images object with cropped images / volume - """ - dims = self.dims - dims = Dimensions.fromTuple(dims) - ndims = len(dims) - dimsCount = dims.count - - if ndims < 2 or ndims > 3: - raise Exception("Cropping only supported on 2D or 3D image data.") - - dimMinMaxTuples = zip(dimsCount, minbound, maxbound) - if len(dimMinMaxTuples) != ndims: - raise ValueError("Number of specified bounds (%d) must equal image dimensionality (%d)" % - (len(dimMinMaxTuples), ndims)) - slices = [] - newdims = [] - for dim, minb, maxb in dimMinMaxTuples: - if maxb > dim: - raise ValueError("Maximum bound (%d) may not exceed image size (%d)" % (maxb, dim)) - if minb < 0: - raise ValueError("Minumum bound (%d) must be positive" % minb) - if minb < maxb: - slise = slice(minb, maxb) - newdims.append(maxb - minb) - elif minb == maxb: - slise = minb # just an integer index, not a slice; this squeezes out singleton dimensions - # don't append to newdims, this dimension will be squeezed out - else: - raise ValueError("Minimum bound (%d) must be <= max bound (%d)" % (minb, maxb)) - slices.append(slise) - - return self.map(lambda v: v[slices], dims=newdims) - - def map(self, func, dims=None, with_keys=False): """ Map an array -> array function over each image.