def finto(x, ii, n, null=0): ''' finto(x,ii,n) yields a vector u of length n such that u[ii] = x. Notes: * The ii index may be a tuple (as can be passed to numpy arrays' getitem method) in order to specify that the specific elements of a multidimensional output be set. In this case, the argument n should be a tuple of sizes (a single integer is taken to be a square/cube/etc). * x may be a sparse-array, but in it will be reified by this function. The following optional arguments are allowed: * null (defaut: 0) specifies the value that should appear in the elements of u that are not set. ''' x = x.toarray() if sps.issparse(x) else np.asarray(x) shx = x.shape if isinstance(ii, tuple): if not pimms.is_vector(n): n = tuple([n for _ in ii]) if len(n) != len(ii): raise ValueError('%d-dim index but %d-dim output' % (len(ii),len(n))) sh = n + shx[1:] elif pimms.is_int(ii): sh = (n,) + shx else: sh = (n,) + shx[1:] u = np.zeros(sh, dtype=x.dtype) if null == 0 else np.full(sh, null, dtype=x.dtype) u[ii] = x return u
def label(l): ''' prf.label is the visual area label of the pRF. ''' if pimms.is_str(l): return l.lower() elif not pimms.is_int(l) or l < 0: raise ValueError('Labels must be positive integers or strings') else: return l
def name_lookup(self, ii): ''' lblidx.name_lookup(ii) yields the names associated with the labels with the given ids. If ii is a list of ids, then yields an array of names. ''' if pimms.is_int(ii): return self.by_id[ii].name if ii in self.by_id else None elif pimms.is_str(ii): return self.by_name[ii].name if ii in self.by_name else None else: return np.asarray([tbl[ii].name if ii in tbl else None for ii in ii for tbl in [self.by_name if pimms.is_str(ii) else self.by_id]])
def id_lookup(self, names): ''' lblidx.id_lookup(names) yields the ids associated with the labels with the given names. If names is a list of names, then yields an array of ids. ''' if pimms.is_str(names): return self.by_name[names].id if names in self.by_name else None elif pimms.is_int(names): return self.by_id[names].id if names in self.by_id else None else: return np.asarray([tbl[ii].id if ii in tbl else None for ii in names for tbl in [self.by_name if pimms.is_str(ii) else self.by_id]])
def __getitem__(self, k): if pimms.is_int(k): return self.by_id.get(k, None) elif pimms.is_str(k): return self.by_name.get(k, None) elif pimms.is_vector(k, 'int'): return np.asarray([self.by_id.get(k, None) for k in k]) else: return np.asarray([ (self.by_name if pimms.is_str(k) else self.by_id).get(k, None) for k in k ])
def __contains__(self, sub): if pimms.is_int(sub): sub = str(sub) # first check the item straight-up: sd = os.path.join(self.path, sub) if os.path.isdir(sd) and (not check_path or is_hcp_subject_path(sd)): return True if not bids: return False if sub.startswith('sub-'): sd = os.path.join(self.path, sub[4:]) else: sd = os.path.join(self.path, 'sub-' + sub) return os.path.isdir(sd) and (not check_path or is_hcp_subject_path(sd))
def color_lookup(self, ii): ''' lblidx.color_lookup(ids) yields the color(s) associated with the labels with the given ids. If ids is a list of ids, then yields a matrix of colors. lblidx.color_lookup(names) uses the names to lookup the label colors. ''' if pimms.is_int(ii): return self.by_id[ii].color if ii in self.by_id else None elif pimms.is_str(ii): return self.by_name[ii].color if ii in self.by_name else None else: return np.asarray([tbl[ii].color if ii in tbl else None for ii in ii for tbl in [self.by_name if pimms.is_str(ii) else self.by_id]])
def __getitem__(self, sub): if pimms.is_int(sub): sub = str(sub) check_path = self.options['check_path'] if self.bids: if sub.startswith('sub-'): (sub, name) = (sub, name[4:]) else: (sub, name) = ('sub-' + sub, sub) else: name = sub sd = os.path.join(self.path, sub) if os.path.isdir(sd) and (not check_path or is_hcp_subject_path(sd)): return self._get_subject(sd, name) if not bids: return False # try without the 'sub-' (e.g. for fsaverage) sub = name sd = os.path.join(self.path, sub) if os.path.isdir(sd) and (not check_path or is_hcp_subject_path(sd)): return self._get_subject(sd, name) raise KeyError(sub)
def _parse_field_function_argument(argdat, args, faces, edges, coords): # first, see if this is an easy one... if argdat == 'F': return faces elif argdat == 'X': return coords elif argdat == 'E': return edges elif pimms.is_int(argdat): return to_java_array(args[argdat]) # okay, none of those; must be a list with a default arg argname = argdat[0] argdflt = argdat[1] # see if we can find such an arg... for i in range(len(args)): if pimms.is_str(args[i]) and args[i].lower() == argname.lower(): return (args[i + 1] if pimms.is_number(args[i + 1]) else to_java_array(args[i + 1])) # did not find the arg; use the default: return argdflt
def label_colors(lbls, cmap=None): ''' label_colors(labels) yields a dict object whose keys are the unique values in labels and whose values are the (r,g,b,a) colors that should be assigned to each label. label_colors(n) is equivalent to label_colors(range(n)). Note that this function uses a heuristic and is not guaranteed to be optimal in any way for any value of n--but it generally works well enough for most common purposes. The following optional arguments may be given: * cmap (default: None) specifies a colormap to use as a base. If this is None, then a varianct of 'hsv' is used. ''' from neuropythy.graphics import label_cmap if pimms.is_int(lbls): lbls = np.arange(lbls) lbls0 = np.unique(lbls) lbls = np.arange(len(lbls0)) cm = label_cmap(lbls, cmap=cmap) mx = float(len(lbls) - 1) m = {k: cm(l / mx) for (k, l) in zip(lbls0, lbls)} return m
def _atlas_to_atlver(atl): atl0 = atl if not pimms.is_vector(atl): if ':' in atl: atl = atl.split(':') if len(atl) != 2: raise ValueError('Cannot parse atlas spec: %s' % atl0) else: atl = [atl, None] if len(atl) != 2: raise ValueError('Improperly specified atlas: %s' % atl0) if pimms.is_str(atl[1]): if len(atl[1]) == 0: atl = (atl[0], None) else: if atl[1][0] == 'v': atl[1] = atl[1][1:] try: atl = (atl[0], tuple([int(x) for x in re.split(r'[-_.]+', atl[1])])) except Exception: raise ValueError('Could not parse atlas version string: %s' % atl[1]) elif pimms.is_int(atl[1]): atl = (atl[0], (atl[1],)) elif pimms.is_real(atl[1]): atl = (atl[0], (int(atl[1]), int(10*(atl[1] - int(atl[1]))),)) elif pimms.is_vector(atl[1], int): atl = (atl[0], tuple(atl[1])) elif atl[1] is not None: raise ValueError('atlas version must be a string (like "v1_5_1") or a list of ints') else: atl = tuple(atl) return atl + (atl0,)
def load_subject(cache_directory, sid): ''' BensonWinawer2018Dataset.load_subject(dir, subjid) loads the given subject ID from the given Benson and Winawer (2018) cache directory. This directory must contain the relevant freesurfer_subjects/, retinotopy/, and analyses/, directories (they should be auto-downloaded if accessed via the databases interface). ''' if pimms.is_int(sid): sid = 'S12%02d' % sid sub = freesurfer_subject(os.path.join(cache_directory, 'freesurfer_subjects', sid)) # okay, we need functions that will lazily extract a hemisphere then load the retinotopy, # analyses, and atlas data onto it (also lazily) def update_hemi(subname, hemis, hname): # get the original hemisphere... hemi = hemis[hname] stup = {'sub':subname, 'hemi':hname} pdat = {} # okay, now we want to load a bunch of data; start with properties for (propname, filename) in six.iteritems(BensonWinawer2018Dataset.subject_properties): filename = os.path.join(cache_directory, filename.format(stup)) if not os.path.isfile(filename): continue pdat[propname] = curry(nyio.load, filename) # we can add this already... hemi = hemi.with_prop(pimms.lazy_map(pdat)) # next, we want to grab the registrations... rdat = {} for (rname, filename) in six.iteritems(BensonWinawer2018Dataset.subject_registrations): filename = os.path.join(cache_directory, filename.format(stup)) if not os.path.isfile(filename): continue rdat[rname] = curry(nyio.load, filename, 'freesurfer_geometry') hemi = hemi.copy(_registrations=pimms.merge(hemi.registrations, pimms.lazy_map(rdat))) # that's all return hemi # okay, update the hemi's map with a curried version of the above and return... hemis = reduce(lambda h,hname: h.set(hname, curry(update_hemi, sub.name, sub.hemis, hname)), ['lh','rh'], sub.hemis) return sub.copy(hemis=hemis)
def mesh_register(mesh, field, max_steps=2000, max_step_size=0.05, max_pe_change=1, method='random', return_report=False, initial_coordinates=None): ''' mesh_register(mesh, field) yields the mesh that results from registering the given mesh by minimizing the given potential field description over the position of the vertices in the mesh. The mesh argument must be a Mesh object (see neuropythy.geometry) such as can be read from FreeSurfer using the neuropythy.freesurfer_subject function. The field argument must be a list of field names and arguments; with the exception of 'mesh' (or 'standard'), the arguments must be a list, the first element of which is the field type name, the second element of which is the field shape name, and the final element of which is a dictionary of arguments accepted by the field shape. The following are valid field type names: * 'mesh' : the standard mesh potential, which includes an edge potential, an angle potential, and a perimeter potential. Accepts no arguments, and must be passed as a single string instead of a list. * 'edge': an edge potential field in which the potential is a function of the change in the edge length, summed over each edge in the mesh. * 'angle': an angle potential field in which the potential is a function of the change in the angle measure, summed over all angles in the mesh. * 'perimeter': a potential that depends on the vertices on the perimeter of a 2D mesh remaining in place; the potential changes as a function of the distance of each perimeter vertex from its reference position. * 'anchor': a potential that depends on the distance of a set of vertices from fixed points in space. After the shape name second argument, an anchor must be followed by a list of vertex ids then a list of fixed points to which the vertex ids are anchored: ['anchor', shape_name, vertex_ids, fixed_points, args...]. The following are valid shape names: * 'harmonic': a harmonic function with the form (c/q) * abs(x - x0)^q. Parameters: * 'scale', the scale parameter c; default: 1. * 'order', the order parameter q; default: 2. * 'Lennard-Jones': a Lennard-Jones function with the form c (1 + (r0/r)^q - 2(r0/r)^(q/2)); Parameters: * 'scale': the scale parameter c; default: 1. * 'order': the order parameter q; default: 2. * 'Gaussian': A Gaussian function with the form c (1 - exp(-0.5 abs((x - x0)/s)^q)) Parameters: * 'scale': the scale parameter c; default: 1. * 'order': the order parameter q; default: 2. * 'sigma': the standard deviation parameter s; default: 1. * 'infinite-well': an infinite well function with the form c ( (((x0 - m)/(x - m))^q - 1)^2 + (((M - x0)/(M - x))^q - 1)^2 ) Parameters: * 'scale': the scale parameter c; default: 1. * 'order': the order parameter q; default: 0.5. * 'min': the minimum value m; default: 0. * 'max': the maximum value M; default: pi. Options: The following optional arguments are accepted. * max_steps (default: 2000) the maximum number of steps to minimize for. * max_step_size (default: 0.1) the maximum distance to allow a vertex to move in a single minimization step. * max_pe_change: the maximum fraction of the initial potential value that the minimizer should minimize away before returning; i.e., 0 indicates that no minimization should be allowed while 0.9 would indicate that the minimizer should minimize until the potential is 10% or less of the initial potential. * return_report (default: False) indicates that instead of returning the registered data, mesh_register should instead return the Java Minimizer.Report object (for debugging). * method (default: 'random') specifies the search algorithm used; available options are 'random', 'nimble', and 'pure'. Generally all options will converge on a similar solution, but usually 'random' is fastest. The 'pure' option uses the nben library's step function, which performs straight-forward gradient descent. The 'nimble' option performs a gradient descent in which subsets of vertices in the mesh that have the highest gradients during the registration are updated more often than those vertices with small gradients; this can sometimes but not always increase the speed of the minimization. Note that instead of 'nimble', one may alternately provide ('nimble', k) where k is the number of partitions that the vertices should be sorted into (by partition). 'nimble' by itself is equivalent to ('nimble', 4). Note also that a single step of nimble minimization is equivalent to 2**k steps of 'pure' minimization. Finally, the 'random' option uses the nben library's randomStep function, which is a gradient descent algorithm that moves each vertex in the direction of its negative gradient during each step but which randomizes the length of the gradient at each individual vertex by drawing from an exponential distribution centered at the vertex's actual gradient length. In effect, this can prevent vertices with very large gradients from dominating the minimization and often results in the best results. * initial_coordinates (default: None) specifies the start coordinates of the registration; if None, uses those in the given mesh, which is generally desired. Examples: registered_mesh = mesh_register( mesh, [['edge', 'harmonic', 'scale', 0.5], # slightly weak edge potential ['angle', 'infinite-well'], # default arguments for an infinite-well angle potential ['anchor', 'Gaussian', [1, 10, 50], [[0.0, 0.0], [1.1, 1.1], [2.2, 2.2]]]], max_step_size=0.05, max_steps=10000) ''' # Sanity checking: # First, make sure that the arguments are all okay: if not isinstance(mesh, geo.Mesh): raise RuntimeError( 'mesh argument must be an instance of neuropythy.geometry.Mesh') if not pimms.is_vector(max_steps): max_steps = [max_steps] for ms in max_steps: if not pimms.is_int(ms) or ms < 0: raise RuntimeError('max_steps argument must be a positive integer') if not pimms.is_vector(max_step_size): max_step_size = [max_step_size] for mss in max_step_size: if not pimms.is_number(mss) or mss <= 0: raise RuntimeError('max_step_size must be a positive number') if not pimms.is_number( max_pe_change) or max_pe_change <= 0 or max_pe_change > 1: raise RuntimeError( 'max_pe_change must be a number x such that 0 < x <= 1') if pimms.is_vector(method): if method[0].lower( ) == 'nimble' and len(method) > 1 and not pimms.is_str(method[1]): method = [method] else: method = [method] if initial_coordinates is None: init_coords = mesh.coordinates else: init_coords = np.asarray(initial_coordinates) if init_coords.shape[0] != mesh.coordinates.shape[0]: init_coords = init_coords.T # If steps is 0, we can skip most of this... if np.sum(max_steps) == 0: if return_report: return None else: return init_coords # Otherwise, we run at least some minimization max_pe_change = float(max_pe_change) nrounds = len(max_steps) if nrounds > 1: if len(max_step_size) == 1: max_step_size = [max_step_size[0] for _ in max_steps] if len(method) == 1: method = [method[0] for _ in max_steps] # Parse the field argument. faces = to_java_ints(mesh.tess.indexed_faces) edges = to_java_ints(mesh.tess.indexed_edges) coords = to_java_doubles(mesh.coordinates) init_coords = coords if init_coords is mesh.coordinates else to_java_doubles( init_coords) potential = _parse_field_arguments(field, faces, edges, coords) # Okay, that's basically all we need to do the minimization... rep = [] for (method, max_step_size, max_steps) in zip(method, max_step_size, max_steps): minimizer = java_link().jvm.nben.mesh.registration.Minimizer( potential, init_coords) max_step_size = float(max_step_size) max_steps = int(max_steps) if pimms.is_str(method): method = method.lower() if method == 'nimble': k = 4 else: k = 0 else: k = method[1] method = method[0].lower() if method == 'pure': r = minimizer.step(max_pe_change, max_steps, max_step_size) elif method == 'random': # if k is -1, we do the inverse version where we draw from the 1/mean distribution r = minimizer.randomStep(max_pe_change, max_steps, max_step_size, k == -1) elif method == 'nimble': r = minimizer.nimbleStep(max_pe_change, max_steps, max_step_size, int(k)) else: raise ValueError('Unrecognized method: %s' % method) rep.append(r) init_coords = minimizer.getX() # Return the report if requested if return_report: return rep else: result = init_coords return np.asarray([[x for x in row] for row in result])
def subject(path, name=Ellipsis, meta_data=None, check_path=True, filter=None, default_alignment=Ellipsis): ''' subject(name) yields an HCP-based Subject object for the subject with the given name or path. Subjects are cached and not reloaded, so multiple calls to subject(name) will yield the same immutable subject object. The name argument is allowed to take a variety of forms: * a local (absolute or relative) path to a valid HCP subject directory * a url or pseudo-path to a valid HCP subject * an integer, in which case the neuropythy.data['hcp'] dataset is used (i.e., the subject data are auto-downloaded from the HCP Amazon-S3 bucket as required) If you request a subject by path, the HCP module has no way of knowing for sure if that subject should be auto-downloaded, so is not; if you want subjects to be auto-downloaded from the HCP database, you should represent the subjects by their integer ids. Note that subects returned by hcp_subject() are always persistent Immutable objects; this means that you must create a transient version of the subject to modify it via the member function sub.transient(). Better, you can make copies of the objects with desired modifications using the copy method--see the pimms library documentation regarding immutable classes and objects. If you wish to modify all subjects loaded by this function, you may set its attribute 'filter' to a function or list of functions that take a single argument (a subject object) and returns a single argument (a potentially-modified subject object). The argument name may alternately be a pseudo_path object or a path that can be converted into a pseudo_path object. The following options are accepted: * name (default: Ellipsis) may optionally specify the subject's name; if Ellipsis, then attempts to deduce the name from the initial argument (which may be a name or a path). * meta_data (default: None) may optionally be a map that contains meta-data to be passed along to the subject object (note that this meta-data will not be cached). * check_path (default: True) may optionally be set to False to ignore the requirement that a directory contain at least the mri/, label/, and surf/ directories to be considered a valid HCP subject directory. Subject objects returned when this argument is not True are not cached. Additionally, check_path may be set to None instead of False, indicating that no sanity checks or search should be performed whatsoever: the string name should be trusted to be an exact relative or absolute path to a valid HCP subejct. * filter (default: None) may optionally specify a filter that should be applied to the subject before returning. This must be a function that accepts as an argument the subject object and returns a (potentially) modified subject object. Filtered subjects are cached by using the id of the filters as part of the cache key. * default_alignment (default: Ellipsis) specifies the default alignment to use with HCP subjects; this may be either 'MSMAll' or 'MSMSulc'; the deafult (Ellipsis) indicates that the 'hcp_default_alignment' configuration value should be used (by default this is 'MSMAll'). ''' from neuropythy import data if pimms.is_str(default_alignment): default_alignment = to_default_alignment_value(default_alignment) elif default_alignment in (Ellipsis, None): default_alignment = config['hcp_default_alignment'] # first thing: if the sid is an integer, we try to get the subject from the hcp dataset; # in this case, because the hcp dataset actually calls down through here (with a pseudo-path), # we don't need to run any of the filters that are run below (they have already been run) if pimms.is_int(path): try: return data['hcp'].subjects[path] except Exception: pass # convert the path to a pseudo-dir; this may fail if the user is requesting a subject by name... try: pdir = to_pseudo_path(path) except Exception: pdir = None if pdir is None: # search for a subject with this name tmp = find_subject_path(path, check_path=check_path) if tmp is not None: pdir = to_pseudo_path(tmp) path = tmp if pdir is None: # It's possible that we need to check the hcp dataset try: return data['hcp'].subjects[int(path)] except: pass raise ValueError('could not find HCP subject: %s' % (path, )) path = pdir.actual_source_path # okay, before we continue, lets check the cache... tup = (path, default_alignment) if tup in subject._cache: sub = subject._cache[tup] else: # extract the name if need-be if name is Ellipsis: import re (pth, name) = (path, '.') while name == '.': (pth, name) = pdir._path_data['pathmod'].split(pth) name = name.split(':')[-1] name = pdir._path_data['pathmod'].split(name)[1] if '.tar' in name: name = name.split('.tar')[0] # make the filemap fmap = subject_file_map(pdir, name=name) # and make the subject! sub = subject_from_filemap(fmap, name=name, check_path=check_path, meta_data=meta_data, default_alignment=default_alignment) if mri.is_subject(sub): sub = sub.persist() sub = sub.with_meta(file_map=fmap) subject._cache[(path, default_alignment)] = sub # okay, we have the initial subject; let's organize the filters if pimms.is_list(subject.filter) or pimms.is_tuple(subject.filter): filts = list(subject.filter) else: filts = [] if pimms.is_list(filter) or pimms.is_tuple(filter): filter = list(filter) else: filter = [] filts = filts + filter if len(filts) == 0: return sub fids = tuple([id(f) for f in filts]) tup = fids + (path, default_alignment) if tup in subject._cache: return subject._cache[tup] for f in filts: sub = f(sub) if mri.is_subject(sub): subject._cache[tup] = sub return sub.persist()
def calc_oriented_contrast_images(image_array, pixels_per_degree, background, gabor_spatial_frequencies, gabor_orientations=_default_gabor_orientations, max_image_size=250, min_pixels_per_degree=0.5, max_pixels_per_filter=27, ideal_filter_size=17, use_spatial_gabors=True, multiprocess=True): ''' calc_oriented_contrast_images is a calculator that takes as input an image array along with its resolution (pixels_per_degree) and a set of gabor orientations and spatial frequencies, and computes the result of filtering the images in the image array with the various filters. The results of this calculation are stored in oriented_contrast_images, which is an ordered tuple of contrast image arrays; the elements of the tuple correspond to spatial frequencies, which are given by the afferent value gabor_spatial_frequencies, and the image arrays are o x n x n where o is the number of orientations, given by the afferent value gabor_orientations, and n is the size of the height/width of the image at that particular spatial frequency. Note that the scaled_pixels_per_degree gives the pixels per degree of all of the elements of the efferent value oriented_contrast_energy_images. Required afferent values: * image_array * pixels_per_degree * background @ use_spatial_gabors Must be either True (use spatial gabor filters instead of the steerable pyramid) or False (use the steerable pyramid); by default this is True. @ gabor_orientations Must be a list of orientation angles for the Gabor filters or an integer number of evenly-spaced gabor filters to use. By default this is equivalent to 8. @ gabor_spatial_frequencies Must be a list of spatial frequencies (in cycles per degree) to use when filtering the images. @ min_pixels_per_degree Must be the minimum number of pixels per degree that will be used to represent downsampled images during contrast energy filtering. If this number is too low, the resulting filters may lose precision, while if this number is too high, the filtering may require a long time and a lot of memory. The default value is 0.5. @ max_pixels_per_filter Must be the maximum size of a filter before down-sampling the image during contrast energy filtering. If this value is None, then no rescaling is done during filtering (this may require large amounts of time and memory). By default this is 27. Note that min_pixels_per_degree has a higher precedence than this value in determining if an image is to be resized. @ ideal_filter_size May specify the ideal size of a filter when rescaling. By default this is 17. @ max_image_size Specifies that if the image array has a dimension with more pixels thatn the given value, the images should be down-sampled. Efferent output values: @ oriented_contrast_images Will be a tuple of n image arrays, each of which is of size q x m x m, where n is the number of spatial frequencies, q is the number of orientations, and m is the size of the scaled image in pixels. The scaled image pixels-per-degree values are given by scaled_pixels_per_degree. The values in the arrays represent the complex-valued results of filtering the image_array with the specified filters. @ scaled_image_arrays Will be a tuple of scaled image arrays; these are scaled to different sizes for efficiency in filtering and are stored here for debugging purposes. @ scaled_pixels_per_degree Will be a tuple of values in pixels per degree that specify the resolutions of the oriented_contrast_energy_images. @ gabor_filters Will be a tuple of gabor filters used on each of the scaled_image_arrays. ''' # process some arguments if pimms.is_number(gabor_orientations): gabor_orientations = np.pi * np.arange(0, gabor_orientations) / gabor_orientations gabor_orientations = np.asarray([pimms.mag(th, 'rad') for th in gabor_orientations]) nth = len(gabor_orientations) if min_pixels_per_degree is None: min_pixels_per_degree = 0 min_pixels_per_degree = pimms.mag(min_pixels_per_degree, 'pixels / degree') if max_pixels_per_filter is None: max_pixels_per_filter = image_array.shape[-1] max_pixels_per_filter = pimms.mag(max_pixels_per_filter, 'pixels') # if the ideal filter size is given as None, make one up if ideal_filter_size is None: ideal_filter_size = (max_pixels_per_filter + 1) / 2 + 1 ideal_filter_size = pimms.mag(ideal_filter_size, 'pixels') if ideal_filter_size > max_pixels_per_filter: ideal_filter_size = max_pixels_per_filter pool = None if multiprocess is True or pimms.is_int(multiprocess): try: import multiprocessing as mp pool = mp.Pool(mp.cpu_count() if multiprocess is True else multiprocess) except: pool = None # These will be updated as we go through the spatial frequencies: d2p0 = pimms.mag(pixels_per_degree, 'pixels/degree') imar = image_array d2p = d2p0 # how wide are the images in degrees imsz_deg = float(image_array.shape[-1]) / d2p0 # If we're over the max image size, we need to downsample now if image_array.shape[1] > max_image_size: ideal_zoom = image_array.shape[-1] / max_image_size imar = sktr.pyramid_reduce(imar.T, ideal_zoom, mode='constant', cval=background, multichannel=True).T d2p = float(imar.shape[1]) / imsz_deg # We build up these solution oces = {} # oriented contrast energy images d2ps = {} # scaled pixels per degree sims = {} # scaled image arrays flts = {} # gabor filters # walk through spatial frequencies first for cpd in reversed(sorted(gabor_spatial_frequencies)): cpd = pimms.mag(cpd, 'cycles/degree') cpp = cpd / d2p # start by making the filter... filt = gabor_kernel(cpp, theta=0) # okay, if the filt is smaller than max_pixels_per_filter, we're fine if filt.shape[0] > max_pixels_per_filter and d2p > min_pixels_per_degree: # we need to potentially resize the image, but only if it's still # higher resolution than min pixels per degree ideal_zoom = float(filt.shape[0]) / float(ideal_filter_size) # resize an image and check it out... im = sktr.pyramid_reduce(imar[0], ideal_zoom, mode='constant', cval=background, multichannel=False) new_d2p = float(im.shape[0]) / imsz_deg if new_d2p < min_pixels_per_degree: # this zoom is too much; we will try to zoom to the min d2p instead ideal_zoom = d2p / min_pixels_per_degree im = sktr.pyramid_reduce(imar[0], ideal_zoom, mode='constant', cval=background, multichannel=False) new_d2p = float(im.shape[0]) / imsz_deg # if this still didn't work, we aren't resizing, just using what we have if new_d2p < min_pixels_per_degree: new_d2p = d2p ideal_zoom = 1 # okay, at this point, we've only failed to find a d2p if ideal_zoom is 1 if ideal_zoom != 1 and new_d2p != d2p: # resize and update values imar = sktr.pyramid_reduce(imar.T, ideal_zoom, mode='constant', cval=background, multichannel=True).T d2p = new_d2p cpp = cpd / d2p # Okay! We have resized if needed, now we do all the filters for this spatial frequency if use_spatial_gabors: # Using convolution with Gabors filters = np.asarray([gabor_kernel(cpp, theta=th) for th in gabor_orientations]) if pool is None: freal = np.asarray( [[ndi.convolve(im, k.real, mode='constant', cval=background) for im in imar] for k in filters]) fimag = np.asarray( [[ndi.convolve(im, k.imag, mode='constant', cval=background) for im in imar] for k in filters]) filt_ims = freal + 1j*fimag else: iis = np.asarray(np.round(np.linspace(0, imar.shape[0], len(pool._pool))), dtype=np.int) i0s = iis[:-1] iis = iis[1:] filt_ims = np.asarray( [np.concatenate( [x for x in pool.map( _convolve_from_arg, [(imar[i0:ii],k,background) for (i0,ii) in zip(i0s,iis)]) if len(x) > 0], axis=0) for k in filters]) else: # Using the steerable pyramid filters = None if pool is None: filt_ims = np.asarray([spyr_filter(imar, th, cpp, 1, len(gabor_orientations)) for th in gabor_orientations]) else: iis = np.asarray(np.round(np.linspace(0, imar.shape[0], len(pool._pool))), dtype=np.int) i0s = iis[:-1] iis = iis[1:] filt_ims = np.asarray( [np.concatenate( [x for x in pool.map( _spyr_from_arg, [(imar[i0:ii],th,cpp,nth) for (i0,ii) in zip(i0s,iis)]) if len(x) > 0], axis=0) for th in gabor_orientations]) # add the results to the lists of results filt_ims.setflags(write=False) imar.setflags(write=False) if filters is not None: filters.setflags(write=False) oces[cpd] = filt_ims d2ps[cpd] = d2p sims[cpd] = imar flts[cpd] = filters if pool is not None: pool.close() # okay, we've finished; just mark things as read-only and make lists into tuples... cpds = [pimms.mag(cpd, 'cycles/degree') for cpd in gabor_spatial_frequencies] (oces, d2ps, sims, flts) = [tuple([m[cpd] for cpd in cpds]) for m in (oces, d2ps, sims, flts)] # and return! return {'oriented_contrast_images': oces, 'scaled_pixels_per_degree': d2ps, 'scaled_image_arrays': sims, 'gabor_filters': flts}
def id(i): 'le.id is the id of the given label entry object le.' if not pimms.is_int(i): raise ValueError('label-entry id must be an int') return int(i)
def input_len(m): if m is None: return m assert(pimms.is_int(m) and m > 0) return int(m)