Пример #1
0
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
Пример #2
0
 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
Пример #3
0
 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]])
Пример #4
0
 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]])
Пример #5
0
 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
         ])
Пример #6
0
 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))
Пример #7
0
 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]])
Пример #8
0
 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)
Пример #9
0
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
Пример #10
0
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
Пример #11
0
 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,)
Пример #12
0
 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)
Пример #13
0
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])
Пример #14
0
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()
Пример #15
0
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}
Пример #16
0
 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)
Пример #17
0
 def input_len(m):
     if m is None: return m
     assert(pimms.is_int(m) and m > 0)
     return int(m)