Ejemplo n.º 1
0
def CARtoSPH(x, y, z, dx=None, dy=None, dz=None):
    """
   convert cartesian coordinates (x,y,z) to spherical (phi,theta,rho)
     - if 3 inputs, convert position vectors
     - if 6 inputs, convert directional vectors
   """

    if dx == None or dy == None or dz == None:

        if dx == None and dy == None and dz == None:

            ## transform position vectors

            iSPHtoCAR = -1

            ## vectorize geopack_08 call using NumPy's vectorize() method:
            ## Note:  vectorize()'s doc string states that this is not very speed
            ##        optimized...Geopack should be ported to Python, or re-written
            ##        to accept array inputs, if speed is an issue. -EJR 10/2013
            ##
            #output = ghostpy.transform.geopack_08.sphcar_08(0.,0.,0.,x,y,z, iSPHtoCAR)
            v_gp08 = p.vectorize(ghostpy.transform.geopack_08.sphcar_08)
            output = v_gp08(0., 0., 0., x, y, z, iSPHtoCAR)

            ## if scalar inputs, generate scalar outputs -EJR 10/2013
            return (tuple(p.asscalar(otmp) for otmp in output[2::-1]) if
                    (p.isscalar(x) and p.isscalar(y) and p.isscalar(z)) else
                    (output[2], output[1], output[0]))
        else:
            raise Exception, 'SPHtoCAR requires exactly 3 or 6 inputs'

    else:

        iSPHtoCAR = -1

        ## transform local directional vectors

        ## vectorize geopack_08 call using NumPy's vectorize() method:
        ## Note:  vectorize()'s doc string states that this is not very speed
        ##        optimized...Geopack should be ported to Python, or re-written
        ##        to accept array inputs, if speed is an issue. -EJR 10/2013
        ##

        #output = ghostpy.transform.geopack_08.sphcar_08(0.,0.,0.,x,y,z, iSPHtoCAR)
        v_gp08a = p.vectorize(ghostpy.transform.geopack_08.sphcar_08)
        rho, theta, phi, _, _, _, _ = v_gp08a(0., 0., 0., x, y, z, iSPHtoCAR)

        #dx,dy,dz = ghostpy.transform.geopack_08.bspcar_08(theta,phi,drho,dtheta,dphi)
        v_gp08b = p.vectorize(ghostpy.transform.geopack_08.bcarsp_08)
        drho, dtheta, dphi = v_gp08b(x, y, z, dx, dy, dz)

        ## if scalar inputs, generate scalar outputs -EJR 10/2013
        return (tuple(
            p.asscalar(otmp)
            for otmp in (phi, theta, rho, dphi, dtheta, drho)) if
                (p.isscalar(x) and p.isscalar(y) and p.isscalar(z)
                 and p.isscalar(dx) and p.isscalar(dy) and p.isscalar(dz)) else
                (phi, theta, rho, dphi, dtheta, drho))
Ejemplo n.º 2
0
def SPHtoCAR(phi, theta, rho, dphi=None, dtheta=None, drho=None):
    """
   convert spherical coordinates (phi,theta,rho) to cartesian (x,y,z)
     - if 3 inputs, convert position vectors
     - if 6 inputs, convert directional vectors
   """

    if dphi == None or dtheta == None or drho == None:

        if dphi == None and dtheta == None and drho == None:

            ## transform position vectors

            iSPHtoCAR = 1

            ## vectorize geopack_08 call using NumPy's vectorize() method:
            ## Note:  vectorize()'s doc string states that this is not very speed
            ##        optimized...Geopack should be ported to Python, or re-written
            ##        to accept array inputs, if speed is an issue. -EJR 10/2013
            ##
            #output = ghostpy.transform.geopack_08.sphcar_08(rho,theta,phi,0.,0.,0., iSPHtoCAR)
            v_gp08 = p.vectorize(ghostpy.transform.geopack_08.sphcar_08)
            output = v_gp08(rho, theta, phi, 0., 0., 0., iSPHtoCAR)

            ## if scalar inputs, generate scalar outputs -EJR 10/2013
            return (tuple(p.asscalar(otmp) for otmp in output[3:6]) if
                    (p.isscalar(phi) and p.isscalar(theta) and p.isscalar(rho))
                    else (output[3], output[4], output[5]))
        else:
            raise Exception, 'SPHtoCAR requires exactly 3 or 6 inputs'

    else:

        iSPHtoCAR = 1

        ## transform local directional vectors

        ## vectorize geopack_08 call using NumPy's vectorize() method:
        ## Note:  vectorize()'s doc string states that this is not very speed
        ##        optimized...Geopack should be ported to Python, or re-written
        ##        to accept array inputs, if speed is an issue. -EJR 10/2013
        ##

        #output = ghostpy.transform.geopack_08.sphcar_08(rho,theta,phi,0.,0.,0., iSPHtoCAR)
        v_gp08a = p.vectorize(ghostpy.transform.geopack_08.sphcar_08)
        _, _, _, x, y, z, _ = v_gp08a(rho, theta, phi, 0., 0., 0., iSPHtoCAR)

        #dx,dy,dz = ghostpy.transform.geopack_08.bspcar_08(theta,phi,drho,dtheta,dphi)
        v_gp08b = p.vectorize(ghostpy.transform.geopack_08.bspcar_08)
        dx, dy, dz = v_gp08b(theta, phi, drho, dtheta, dphi)

        ## if all scalar inputs, return scalar outputs, otherwise ndarrays -EJR 10/2013
        return (tuple(p.asscalar(otmp) for otmp in (x, y, z, dx, dy, dz)) if
                (p.isscalar(phi) and p.isscalar(theta) and p.isscalar(rho)
                 and p.isscalar(dphi) and p.isscalar(dtheta)
                 and p.isscalar(drho)) else (x, y, z, dx, dy, dz))
Ejemplo n.º 3
0
def bin_confint(pc, nsamp, ci=.05, bootstraps=2000):
    """Shortcut to computing confidence intervals on bernoulli trials (like
  percent correct).

  Inputs:
    pc - array (get back several cis) or single value (get back one ci) of percent corrects
    nsamp - number of trials used to obtain each pc
    ci - confidence level (e.g. 0.01, 0.05)
    bootstraps - number of bootstraps to use

  Output:
    3xN array - first row is median (should be approximately same as pc)
                last two rows are lower and upper ci as expected by pylab.errorbar


  """
    def one_ci(pc, nsamp, ci, bootstraps):
        booted_p = boot_p(pc, nsamp, bootstraps)
        booted_p.sort()

        p = pylab.median(booted_p)
        idx_lo = int(bootstraps * ci / 2.0)
        idx_hi = int(bootstraps * (1.0 - ci / 2))

        return p, p - booted_p[idx_lo], booted_p[idx_hi] - p

    #Need to make it user friendly here to handle array/single numbers intelligently
    if pylab.isscalar(pc):
        pc = pylab.array([pc])
        nsamp = pylab.array([nsamp])
    return pylab.array(
        [one_ci(p, ns, ci, bootstraps) for p, ns in zip(pc, nsamp)]).T
Ejemplo n.º 4
0
def bin_confint_lookup(pc, nsamp, ci=.05):
    """Return the confidence interval from the lookup table.
  Inputs:
    pc - array (get back several cis) or single value (get back one ci) of percent corrects
    nsamp - number of trials used to obtain each pc
    ci - confidence level (e.g. 0.01, 0.05)
    bootstraps - number of bootstraps to use
    use_table - if true then use a precomputed table instead of doing the bootstraps

  Output:
    3xN array - first row is pc
                last two rows are lower and upper ci as expected by pylab.errorbar
  """
    points = ci_table['points']
    values_lo = ci_table['values_lo']
    values_high = ci_table['values_high']

    from scipy.interpolate import griddata
    if pylab.isscalar(pc):
        pc = pylab.array([pc])
        nsamp = pylab.array([nsamp])
    ci_a = pylab.ones(pc.size) * ci
    xi = pylab.array((pc, nsamp, ci_a)).T

    low_ci = griddata(points, values_lo, xi, method='linear')
    high_ci = griddata(points, values_high, xi, method='linear')

    return pylab.array((pc, low_ci, high_ci))
Ejemplo n.º 5
0
def get_subwindow(im, pos, sz, cos_window):
    """
    使用 replication padding 从图像中获得子窗口。子窗口以 [y, x] 为坐标中心,大小为 [height, width].
    如果子窗口超过图像边界,则复制图像的边界像素值。获得的子窗口将使用余弦窗口标准化到 [-0.5, 0.5]
    :param im: 输入图像
    :param pos: 子窗口中心点坐标 [y, x]
    :param sz: 子窗口大小 [height, width]
    :param cos_window: 余弦子窗口矩阵
    :return: 返回经过余弦子窗口截取的图像矩形框部分
    """
    # 如果不是高、宽组成的数组,而是一个一维数值,则转化为一个数组
    # 目标是子窗矩形化
    if pylab.isscalar(sz):  # square sub-window
        sz = [sz, sz]
    # 以 pos 为中心,以 sz 为窗口大小建立子窗
    ys = pylab.floor(pos[0]) + pylab.arange(sz[0], dtype=int) - pylab.floor(
        sz[0] / 2)
    xs = pylab.floor(pos[1]) + pylab.arange(sz[1], dtype=int) - pylab.floor(
        sz[1] / 2)
    ys = ys.astype(int)
    xs = xs.astype(int)
    # 如果子窗超过坐标,则设置为边界值
    ys[ys < 0] = 0
    ys[ys >= im.shape[0]] = im.shape[0] - 1
    xs[xs < 0] = 0
    xs[xs >= im.shape[1]] = im.shape[1] - 1
    # 提取子窗剪切的图像块
    out = im[pylab.ix_(ys, xs)]
    # 将图像像素值从 [0, 1] 平移到 [-0.5, 0.5]
    out = out.astype(pylab.float64) - 0.5
    # 余弦窗口化,论文公式 (18)

    return pylab.multiply(cos_window, out)
Ejemplo n.º 6
0
def bin_confint(pc, nsamp, ci = .05, bootstraps=2000):
  """Shortcut to computing confidence intervals on bernoulli trials (like
  percent correct).

  Inputs:
    pc - array (get back several cis) or single value (get back one ci) of percent corrects
    nsamp - number of trials used to obtain each pc
    ci - confidence level (e.g. 0.01, 0.05)
    bootstraps - number of bootstraps to use

  Output:
    3xN array - first row is median (should be approximately same as pc)
                last two rows are lower and upper ci as expected by pylab.errorbar


  """
  def one_ci(pc, nsamp, ci, bootstraps):
    booted_p = boot_p(pc, nsamp, bootstraps)
    booted_p.sort()

    p = pylab.median(booted_p)
    idx_lo = int(bootstraps * ci/2.0)
    idx_hi = int(bootstraps * (1.0-ci/2))

    return p, p-booted_p[idx_lo], booted_p[idx_hi]-p

  #Need to make it user friendly here to handle array/single numbers intelligently
  if pylab.isscalar(pc):
    pc = pylab.array([pc])
    nsamp = pylab.array([nsamp])
  return pylab.array([one_ci(p, ns, ci, bootstraps) for p,ns in zip(pc, nsamp)]).T
Ejemplo n.º 7
0
def bin_confint_lookup(pc, nsamp, ci = .05):
  """Return the confidence interval from the lookup table.
  Inputs:
    pc - array (get back several cis) or single value (get back one ci) of percent corrects
    nsamp - number of trials used to obtain each pc
    ci - confidence level (e.g. 0.01, 0.05)
    bootstraps - number of bootstraps to use
    use_table - if true then use a precomputed table instead of doing the bootstraps

  Output:
    3xN array - first row is pc
                last two rows are lower and upper ci as expected by pylab.errorbar
  """
  points = ci_table['points']
  values_lo = ci_table['values_lo']
  values_high = ci_table['values_high']

  from scipy.interpolate import griddata
  if pylab.isscalar(pc):
    pc = pylab.array([pc])
    nsamp = pylab.array([nsamp])
  ci_a = pylab.ones(pc.size)*ci
  xi = pylab.array((pc,nsamp,ci_a)).T

  low_ci = griddata(points, values_lo, xi, method='linear')
  high_ci = griddata(points, values_high, xi, method='linear')

  return pylab.array((pc,low_ci,high_ci))
Ejemplo n.º 8
0
    def get_subwindow(self, im, pos, sz):
        """
        Obtain sub-window from image, with replication-padding.
        Returns sub-window of image IM centered at POS ([y, x] coordinates),
        with size SZ ([height, width]). If any pixels are outside of the image,
        they will replicate the values at the borders.

        The subwindow is also normalized to range -0.5 .. 0.5, and the given
        cosine window COS_WINDOW is applied
        (though this part could be omitted to make the function more general).
        """

        if pylab.isscalar(sz):  # square sub-window
            sz = [sz, sz]

        ys = pylab.floor(pos[0]) \
            + pylab.arange(sz[0], dtype=int) - pylab.floor(sz[0]/2)
        xs = pylab.floor(pos[1]) \
            + pylab.arange(sz[1], dtype=int) - pylab.floor(sz[1]/2)

        ys = ys.astype(int)
        xs = xs.astype(int)

        # check for out-of-bounds coordinates,
        # and set them to the values at the borders
        ys[ys < 0] = 0
        ys[ys >= im.shape[0]] = im.shape[0] - 1

        xs[xs < 0] = 0
        xs[xs >= im.shape[1]] = im.shape[1] - 1
        #zs = range(im.shape[2])

        # extract image
        #out = im[pylab.ix_(ys, xs, zs)]
        out = im[pylab.ix_(ys, xs)]

        out = cv2.resize(out, dsize = (int(self.window_sz[1]), int(self.window_sz[0])), fx=0, fy=0)
        #cos_window = pylab.outer(pylab.hanning(sz[0]), pylab.hanning(sz[1]))

        if debug:
            print("Out max/min value==", out.max(), "/", out.min())
            pylab.figure()
            pylab.imshow(out, cmap=pylab.cm.gray)
            pylab.title("cropped subwindow")

        #pre-process window --
        # normalize to range -0.5 .. 0.5
        # pixels are already in range 0 to 1
        out = out.astype(pylab.float64) - 0.5

        # apply cosine window
        #out = pylab.multiply(cos_window, out)
        out = pylab.multiply(self.cos_window, out)

        return out
def get_subwindow(im, pos, sz, cos_window):
    """
    Obtain sub-window from image, with replication-padding.
    Returns sub-window of image IM centered at POS ([y, x] coordinates),
    with size SZ ([height, width]). If any pixels are outside of the image,
    they will replicate the values at the borders.

    The subwindow is also normalized to range -0.5 .. 0.5, and the given
    cosine window COS_WINDOW is applied
    (though this part could be omitted to make the function more general).
    """

    if pylab.isscalar(sz):  # square sub-window
        sz = [sz, sz]

    ys = pylab.floor(pos[0]) \
        + pylab.arange(sz[0], dtype=int) - pylab.floor(sz[0]/2)
    xs = pylab.floor(pos[1]) \
        + pylab.arange(sz[1], dtype=int) - pylab.floor(sz[1]/2)

    ys = ys.astype(int)
    xs = xs.astype(int)

    # check for out-of-bounds coordinates,
    # and set them to the values at the borders
    ys[ys < 0] = 0
    ys[ys >= im.shape[0]] = im.shape[0] - 1

    xs[xs < 0] = 0
    xs[xs >= im.shape[1]] = im.shape[1] - 1
    #zs = range(im.shape[2])

    # extract image
    #out = im[pylab.ix_(ys, xs, zs)]
    out = im[pylab.ix_(ys, xs)]

    if debug:
        print("Out max/min value==", out.max(), "/", out.min())
        pylab.figure()
        pylab.imshow(out, cmap=pylab.cm.gray)
        pylab.title("cropped subwindow")

    #pre-process window --
    # normalize to range -0.5 .. 0.5
    # pixels are already in range 0 to 1
    out = out.astype(pylab.float64) / 255 - 0.5

    # apply cosine window
    out = pylab.multiply(cos_window, out)

    return out
Ejemplo n.º 10
0
 def __init__(self, space, w, b=0., name=""):
     """
     ------
     This class defines a linear projection f(x) = <w,x>+b as an algebraic
     object to compute basis, nullspace and various operations/properties
     ------
     space: (Space instance) used to port the axes_names and the dimension
     w    : (list or array) must be of length space.dimension. Defines the 
             linear hyperplane coefficients
     b    : (real or vector) the bias of the hyperplane. If a vector it
             represent the direction in which to push the linear hyperplane\
                     of coefficients w. If a scalar then it is assumed that\
                     the bias vector is made of 0 and b for the last dimension
     ------"""
     self.ambiant_dimension = space.dimension + 1
     self.input_dimension = space.dimension
     self.dim = space.dimension
     self.name = name if name is not "" else "hyperplane"
     # Setting bias
     if pl.isscalar(b):
         self.bias = pl.zeros(self.ambiant_dimension)
         self.bias[-1] = b
     else:
         if len(b) < self.input_dimension:
             raise ValueError("Hyperplane class\
                 init: given len(b)<ambiant dimension\
                 ({}<{})".format(len(b), self.ambiant_dimension))
         self.biasias = b
     # Setting the slope
     if len(w) < self.input_dimension:
         raise ValueError("Hyperplane class init: given len(w)<\
             ambiant dimension ({}<{})".format(len(w),
                                               self.input_dimension))
     self.slope = w
     # Generate current basis
     # it is a sparse matrix of shape (ambiant_dim,dim)
     self.basis = self._generate_basis()
     print("Initialization of Hyperplane:\n\
             \tName : {}\n\
             \tAmbiant Dimension : {}\n\
             \tInput Dimension : {}\n\
             \tw : {}\n\
             \tb : {}"                         .format(self.name,self.ambiant_dimension,\
             self.input_dimension,self.slope,self.bias))
Ejemplo n.º 11
0
    def __doplot(self, dO, xa, ya, style, kind, ax, fnc_x, fnc_y, label):
        from pylab import isscalar
        if dO.data.has_key(xa) and dO.data.has_key(ya):
            x = (dO.getData(xa))
            y = (dO.getData(ya))
            try:
                if isscalar(label):
                    lab = '%s=%g' % (label, dO.getHeader(label))
                else:
                    lab = ''
                    for l in label:
                        lab += '%s=%g, ' % (l, dO.getHeader(l))

                    lab = lab[:-2]
            except:
                lab = None

            if x is not None and y is not None:
                args = [
                    fnc_x(x, *(self.__fnc_params(dO, fnc_x))),
                    fnc_y(y, *(self.__fnc_params(dO, fnc_y)))
                ]

                kwargs = dict(label=lab,
                              linestyle=style[0],
                              linewidth=style[2],
                              marker=style[1],
                              markersize=style[3],
                              markeredgecolor=None)

                if kind == 'semilogy':
                    ax.semilogy(*args, **kwargs)

                elif kind == 'loglog':
                    ax.loglog(*args, **kwargs)

                elif kind == 'semilogx':
                    ax.semilogx(*args, **kwargs)

                elif kind == 'plot':
                    ax.plot(*args, **kwargs)
Ejemplo n.º 12
0
def errorfill(x,
              y,
              yerr,
              lw=1,
              elinewidth=1,
              color=None,
              alpha_fill=0.2,
              ax=None):
    ax = ax if ax is not None else gca()
    if color is None:
        color = ax._get_lines.color_cycle.next()
    if isscalar(yerr) or len(yerr) == len(y):
        ymin = y - yerr
        ymax = y + yerr
    elif len(yerr) == 2:
        ymin, ymax = yerr
    ax.plot(x, y, color=color, lw=lw)
    ax.fill_between(x,
                    ymax,
                    ymin,
                    color=color,
                    lw=elinewidth,
                    alpha=alpha_fill)
Ejemplo n.º 13
0
def window_spike_train(timestamps,
                       start_time=0,
                       zero_times=0,
                       end_time=None,
                       window_len=1,
                       subwindow_len=None):
    """Break up a spike train into windows and subwindows marching outwards from zero_time.

  * = zero
  | = window boundary
  - = subwindow

  --|----|----|*|----|----|----|----|--


  Inputs:
    Note:
      1. All time units should be in the same units as the timestamp time units
      2. The zero_times should be in ascending order, otherwise the results will be wrong. The algorithm is made efficient by marching along the timestamps sequentially.

    timestamps - timestamps of the spikes
    start_time - time rel to zero_time we end our windows (needs to be <= 0).
                 If zero, means no pre windows.
                 If None, means prewindows stretch to begining of data
                 The start_time is extended to include an integer number of windows
    zero_times  - reference time. Can be a zx1 array, in which case will give us an array of windows. If scalar
                 will only give one set of windows.
    end_time   - time rel to zero_time we end our windows (needs to >= 0)
                 If zero, means no post-windows
                 If None, means post-windows stretch to end of data
                 The end_time is extended to include an integer number of windows
    window_len - Length of each window.
    subwindow_len - There can be multiple subwindows within a window
                    If subwindow_len is none, subwindow_len is made the same as window_len
  Output:
    window_edges - (w + 1 ) array of times, where w is the number of windows
    windows      - (z x w x 2) array of times.
                    z - number of epochs (size of zero_time array)
                    w - number of windows
                    0 - start idx of timestamps
                    1 - end idx of timestamps
                    If zero_time is a scalar, windows is 2D
    sub_windows  - (z x w x s x 2) array of times.
                    s - number of sub windows
                    If zero_time is a scalar, sub_windows is 3D
  """
    from math import ceil

    def march(timestamps, zero_time, start_idx, window_edges, subwindow_len,
              n_windows, n_sub_windows):
        """Repeatedly finding indexes from the same array that fit within the current window would be criminally
    inefficient. We make things a little less time consuming by keeping a running index.
    """
        window_edges_abs = window_edges + zero_time

        windows = pylab.ones((n_windows, 2)) * -1
        subwindows = pylab.ones((n_windows, n_sub_windows, 2)) * -1

        while timestamps[start_idx] <= window_edges_abs[0]:
            start_idx += 1
            if start_idx == timestamps.size:
                break

        end_idx = start_idx
        for widx in xrange(n_windows):
            if start_idx == timestamps.size:
                break
            window_start = window_edges_abs[widx]
            window_end = window_edges_abs[widx + 1]
            twst = start_idx
            for swidx in xrange(n_sub_windows):
                if end_idx == timestamps.size:
                    break
                subwindow_end = window_start + subwindow_len * (swidx + 1)
                subwindow_end = min(
                    window_end, subwindow_end
                )  #This is needed so we synchronise the end of subwindows and windows
                while timestamps[end_idx] <= subwindow_end:
                    end_idx += 1
                    if end_idx == timestamps.size:
                        break
                subwindows[widx, swidx, :] = [twst, end_idx]
                twst = end_idx

            windows[widx, :] = [start_idx, end_idx]
            start_idx = end_idx

        return window_edges, windows, subwindows, end_idx

    if subwindow_len is None:
        subwindow_len = window_len

    if pylab.isscalar(zero_times):
        zero_times = [zero_times]

    if start_time is None:
        start_time = timestamps[0] - max(zero_times)
    if end_time is None:
        end_time = timestamps[-1] - min(zero_times)

    n_pre_windows = int(ceil(-start_time / float(window_len)))
    n_post_windows = int(ceil(end_time / float(window_len)))
    n_windows = n_pre_windows + n_post_windows
    n_sub_windows = int(ceil(window_len / float(subwindow_len)))
    window_edges = (pylab.arange(n_windows + 1) - n_pre_windows) * window_len

    all_windows = []
    all_subwindows = []
    start_idx = 0
    for this_zero_time in zero_times:
        window_edges, windows, subwindows, start_idx = march(
            timestamps, this_zero_time, start_idx, window_edges, subwindow_len,
            n_windows, n_sub_windows)
        all_windows.append(windows)
        all_subwindows.append(subwindows)

    all_windows = pylab.array(all_windows)
    all_subwindows = pylab.array(all_subwindows)

    return window_edges, all_windows, all_subwindows
Ejemplo n.º 14
0
def window_spike_train(timestamps, start_time=0, zero_times=0, end_time=None, window_len=1, subwindow_len=None):
  """Break up a spike train into windows and subwindows marching outwards from zero_time.

  * = zero
  | = window boundary
  - = subwindow

  --|----|----|*|----|----|----|----|--


  Inputs:
    Note:
      1. All time units should be in the same units as the timestamp time units
      2. The zero_times should be in ascending order, otherwise the results will be wrong. The algorithm is made efficient by marching along the timestamps sequentially.

    timestamps - timestamps of the spikes
    start_time - time rel to zero_time we end our windows (needs to be <= 0).
                 If zero, means no pre windows.
                 If None, means prewindows stretch to begining of data
                 The start_time is extended to include an integer number of windows
    zero_times  - reference time. Can be a zx1 array, in which case will give us an array of windows. If scalar
                 will only give one set of windows.
    end_time   - time rel to zero_time we end our windows (needs to >= 0)
                 If zero, means no post-windows
                 If None, means post-windows stretch to end of data
                 The end_time is extended to include an integer number of windows
    window_len - Length of each window.
    subwindow_len - There can be multiple subwindows within a window
                    If subwindow_len is none, subwindow_len is made the same as window_len
  Output:
    window_edges - (w + 1 ) array of times, where w is the number of windows
    windows      - (z x w x 2) array of times.
                    z - number of epochs (size of zero_time array)
                    w - number of windows
                    0 - start idx of timestamps
                    1 - end idx of timestamps
                    If zero_time is a scalar, windows is 2D
    sub_windows  - (z x w x s x 2) array of times.
                    s - number of sub windows
                    If zero_time is a scalar, sub_windows is 3D
  """
  from math import ceil
  def march(timestamps, zero_time, start_idx, window_edges, subwindow_len, n_windows, n_sub_windows):
    """Repeatedly finding indexes from the same array that fit within the current window would be criminally
    inefficient. We make things a little less time consuming by keeping a running index.
    """
    window_edges_abs = window_edges + zero_time

    windows = pylab.ones((n_windows,2)) * -1
    subwindows = pylab.ones((n_windows,n_sub_windows,2)) * -1

    while timestamps[start_idx] <= window_edges_abs[0]:
      start_idx += 1
      if start_idx == timestamps.size:
        break

    end_idx = start_idx
    for widx in xrange(n_windows):
      if start_idx == timestamps.size:
        break
      window_start = window_edges_abs[widx]
      window_end = window_edges_abs[widx+1]
      twst = start_idx
      for swidx in xrange(n_sub_windows):
        if end_idx == timestamps.size:
          break
        subwindow_end = window_start + subwindow_len*(swidx+1)
        subwindow_end = min(window_end, subwindow_end) #This is needed so we synchronise the end of subwindows and windows
        while timestamps[end_idx] <= subwindow_end:
          end_idx += 1
          if end_idx == timestamps.size:
            break
        subwindows[widx, swidx, :] = [twst, end_idx]
        twst = end_idx

      windows[widx,:] = [start_idx,end_idx]
      start_idx = end_idx

    return window_edges, windows, subwindows, end_idx

  if subwindow_len is None:
    subwindow_len = window_len

  if pylab.isscalar(zero_times):
    zero_times = [zero_times]

  if start_time is None:
    start_time = timestamps[0] - max(zero_times)
  if end_time is None:
    end_time = timestamps[-1] - min(zero_times)

  n_pre_windows = int(ceil(-start_time / float(window_len)))
  n_post_windows = int(ceil(end_time / float(window_len)))
  n_windows = n_pre_windows + n_post_windows
  n_sub_windows = int(ceil(window_len / float(subwindow_len)))
  window_edges = (pylab.arange(n_windows+1) - n_pre_windows) * window_len

  all_windows = []
  all_subwindows = []
  start_idx = 0
  for this_zero_time in zero_times:
    window_edges, windows, subwindows, start_idx = march(timestamps, this_zero_time, start_idx, window_edges, subwindow_len, n_windows, n_sub_windows)
    all_windows.append(windows)
    all_subwindows.append(subwindows)

  all_windows = pylab.array(all_windows)
  all_subwindows = pylab.array(all_subwindows)

  return window_edges, all_windows, all_subwindows