コード例 #1
0
def gaussian_2d(sz, sig):
    '''
    gaussian_2d(size, sigma) yields a matrix with the given size representing a 2D gaussian
      image with the given sigma as its standard deviation.
    '''
    sz = int(np.round(sz))
    mid = (sz - 1) / 2
    if not pimms.is_number(mid): mid = np.mean(mid)
    x = np.arange(-mid, sz - mid, 1)
    r2 = np.sum([u**2 for u in np.meshgrid(x, x)], axis=0)
    res = np.exp(-0.5 * r2 / sig**2)
    return res / np.sum(res)
コード例 #2
0
def make_disk(imsz, radius, mid=None, edge_width=0):
    '''
    make_disk(imsize, radius, centerpx, edge_width) yields a disk matrix whose internal values
      are 1 and whose external values are 0; the image size, radius of the disk, and the disk's
      center are given in pixels as the first three arguments. The final argument, edge_width
      is the number of pixels over which to smoothly blend the edge of the disk.
      
    If the parameter centerpx is omitted, the center of the image is used automatically.
    '''
    if pimms.is_number(imsz): imsz = (int(np.round(imsz)), int(np.round(imsz)))
    if mid is None: mid = ((imsz[0] - 1) / 2, (imsz[1] - 1) / 2)
    elif pimms.is_number(mid): mid = (mid, mid)
    if edge_width is None: edge_width = 0
    (x, y) = [np.arange(-u, sz - u, 1) for (u, sz) in zip(mid, imsz)]
    r = np.sqrt(np.sum([u**2 for u in np.meshgrid(x, y)], axis=0))
    im = (r < radius).astype('float')
    if not np.isclose(edge_width, 0) and edge_width > 0:
        (r0, r1) = (radius - edge_width / 2, radius + edge_width / 2)
        ii = (r > r0) & (r < r1)
        im[ii] = 0.5 * (1 - np.sin((r[ii] - radius) / edge_width * np.pi))
    return im
コード例 #3
0
ファイル: models.py プロジェクト: yashd94/neuropythy
 def parameters(params):
     '''
     mdl.parameters is a persistent map of the parameters for the given SchiraModel object mdl.
     '''
     if not pimms.is_pmap(params): params = pyr.pmap(params)
     # do the translations that we need...
     scale = params['scale']
     if pimms.is_number(scale):
         params = params.set('scale', (scale, scale))
     elif not is_tuple(scale):
         params = params.set('scale', tuple(scale))
     shear = params['shear']
     if pimms.is_number(shear) and np.isclose(shear, 0):
         params = params.set('shear', ((1, 0), (0, 1)))
     elif shear[0][0] != 1 or shear[1][1] != 1:
         raise RuntimeError('shear matrix diagonal elements must be 1!')
     elif not is_tuple(shear) or not all(is_tuple(s) for s in shear):
         params.set('shear', tuple([tuple(s) for s in shear]))
     center = params['center']
     if pimms.is_number(center) and np.isclose(center, 0):
         params = params.set('center', (0.0, 0.0))
     return pimms.persist(params, depth=None)
コード例 #4
0
 def to_name(nm):
     '''
     Dataset.to_name(name) yields a valid dataset name equivalent to the given name or raises an
       error if name is not valid. In order to be valid, a name must be either strings or a tuple
       of number and strings that start with a string.
     '''
     if pimms.is_str(nm): return nm
     if not pimms.is_vector(nm):
         raise ValueError('name must be a string or tuple')
     if len(nm) < 1:
         raise ValueError(
             'names that are tuples must have at least one element')
     if not pimms.is_str(nm):
         raise ValueError('names that are tuples must begin with a string')
     if not all(pimms.is_str(x) or pimms.is_number(x) for x in nm):
         raise ValueError(
             'dataset names that are tuples must contain only strings and numbers'
         )
     return tuple(nm)
コード例 #5
0
ファイル: core.py プロジェクト: yashd94/neuropythy
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
コード例 #6
0
ファイル: core.py プロジェクト: yashd94/neuropythy
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])
コード例 #7
0
 def _generate_subject_DROI_boundary_data(sub,
                                          h,
                                          paradigm,
                                          angle_delta,
                                          min_variance_explained=0,
                                          method=None,
                                          eccentricity_range=(0, 7),
                                          surface_area='midgray'):
     import neuropythy as ny, numpy as np, copy, six
     CLS = VisualPerformanceFieldsDataset
     paradigm = paradigm.lower()
     erng = eccentricity_range
     minvexpl = min_variance_explained
     if paradigm == 'vertical':
         # This is the weird case: we handle it separately: just run the function
         # for both ventral and dorsal and concatenate the V1 parts
         (vnt, drs) = [
             CLS._generate_subject_DROI_boundary_data(
                 sub,
                 h,
                 para,
                 angle_delta,
                 min_variance_explained=minvexpl,
                 eccentricity_range=erng,
                 surface_area=surface_area)
             for para in ['ventral', 'dorsal']
         ]
         f = CLS._vertical_DROI_from_ventral_dorsal
         return f(vnt, drs)
     # Get the hemisphere:
     hem = sub.hemis[h]
     # Setup masks and handle eccentricity_range
     if erng in [None, Ellipsis]: erng = (0, 7)
     if pimms.is_number(erng): erng = (0, erng)
     masks = [('inf_eccentricity', erng[0], erng[1])]
     dmasks = [('prf_variance_explained', min_variance_explained, 1)]
     # Get the inferred angle (we'll need this)
     angle = np.abs(hem.prop('inf_polar_angle'))
     # We setup two different ROIs for V1/V2 or for D/V then we join them; this
     # is because we aren;t 100% confident that the boundaries are drawn in the
     # right place, but this should let the ROI grow appropriately on either side
     masks = (copy.copy(masks), masks)
     dmasks = (copy.copy(dmasks), dmasks)
     if paradigm == 'ventral':
         dprop = 'ventral_distance'
         xprop = 'dorsal_distance'
         ref_angle = 0
         for m in masks:
             m.append(('inf_polar_angle', -0.1, 90))
         masks[0].append(('inf_visual_area', 1))
         masks[1].append(('inf_visual_area', 2))
     elif paradigm == 'dorsal':
         dprop = 'dorsal_distance'
         xprop = 'ventral_distance'
         ref_angle = 180
         for m in masks:
             m.append(('inf_polar_angle', 90, 180.1))
         masks[0].append(('inf_visual_area', 1))
         masks[1].append(('inf_visual_area', 2))
     elif paradigm == 'horizontal':
         dprop = 'horizontal_distance'
         xprop = None
         ref_angle = 90
         for m in masks:
             m.append(('inf_visual_area', 1))
         masks[0].append(('inf_polar_angle', 90, 180.1))
         masks[1].append(('inf_polar_angle', -0.1, 90))
     elif paradigm == 'hventral':
         dprop = 'horizontal_distance'
         xprop = None
         ref_angle = 90
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks[0].append(('inf_visual_area', 1))
         masks[0].append(('inf_polar_angle', -0.1, 90))
     elif paradigm == 'hdorsal':
         dprop = 'horizontal_distance'
         xprop = None
         ref_angle = 90
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks[0].append(('inf_visual_area', 1))
         masks[0].append(('inf_polar_angle', 90, 180.1))
     elif paradigm == 'ventral_v1':
         dprop = 'ventral_distance'
         xprop = 'dorsal_distance'
         ref_angle = 0
         for m in masks:
             m.append(('inf_polar_angle', -0.1, 90))
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks[0].append(('inf_visual_area', 1))
     elif paradigm == 'dorsal_v1':
         dprop = 'dorsal_distance'
         xprop = 'ventral_distance'
         ref_angle = 180
         for m in masks:
             m.append(('inf_polar_angle', 90, 180.1))
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks[0].append(('inf_visual_area', 1))
     elif paradigm == 'ventral_v2':
         dprop = 'ventral_distance'
         xprop = 'dorsal_distance'
         ref_angle = 0
         for m in masks:
             m.append(('inf_polar_angle', -0.1, 90))
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks[0].append(('inf_visual_area', 2))
     elif paradigm == 'dorsal_v2':
         dprop = 'dorsal_distance'
         xprop = 'ventral_distance'
         ref_angle = 180
         for m in masks:
             m.append(('inf_polar_angle', 90, 180.1))
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks = (masks[0], )
         dmasks = (dmasks[0], )
         masks[0].append(('inf_visual_area', 2))
     else:
         raise ValueError('unrecognized paradigm: %s' % (paradigm, ))
     # Get the indices
     ii = []
     for (m, dm) in zip(masks, dmasks):
         kk = CLS._generate_DROIs(sub,
                                  h,
                                  dprop,
                                  'prf_polar_angle',
                                  ref_angle,
                                  angle_delta,
                                  masks=m,
                                  distance_masks=dm,
                                  inv_prop=xprop,
                                  method=method)
         ii = np.union1d(ii, kk)
         ii = np.array(ii, dtype='int')
     # Grab the other data:
     if pimms.is_str(
             surface_area) and not surface_area.endswith('_surface_area'):
         surface_area = surface_area + '_surface_area'
     surface_area = hem.property(surface_area)
     sa = surface_area[ii]
     th = hem.prop('thickness')[ii]
     vl = sa * th
     return {
         'surface_area_mm2': sa,
         'mean_thickness_mm': th,
         'volume_mm3': vl,
         'indices': ii,
         'visual_area': hem.prop('inf_visual_area')[ii]
     }
コード例 #8
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}