```python

```

## Histograms of Oriented Gradients (HOG)

As we saw with the ORB algorithm, we can use keypoints in images to do keypoint-based matching to detect objects in images. These type of algorithms work great when you want to detect objects that have a lot of consistent internal features that are not affected by the background. For example, these algorithms work well for facial detection because faces have a lot of consistent internal features that don’t get affected by the image background, such as the eyes, nose, and mouth. However, these type of algorithms don’t work so well when attempting to do more general object recognition, say for example, pedestrian detection in images. The reason is that people don’t have consistent internal features, like faces do, because the body shape and style of every person is different (see Fig. 1). This means that every person is going to have a different set of internal features, and so we need something that can more generally describe a person.

Fig. 1. - Pedestrians.

One option is to try to detect pedestrians by their contours instead.  Detecting objects in images by their contours (boundaries) is very challenging because we have to deal with the difficulties brought about by the contrast between the background and the foreground. For example, suppose you wanted to detect a pedestrian in an image that is walking in front of a white building and she is wearing a white coat and black pants (see Fig. 2). We can see in Fig. 2, that since the background of the image is mostly white, the black pants are going to have a very high contrast, but the coat, since it is white as well, is going to have very low contrast. In this case, detecting the edges of pants is going to be easy but detecting the edges of the coat is going to be very difficult. This is where **HOG** comes in. HOG stands for **Histograms of Oriented Gradients** and it was first introduced by Navneet Dalal and Bill Triggs in 2005.

Fig. 2. - High and Low Contrast.

The HOG algorithm works by creating histograms of the distribution of gradient orientations in an image and then normalizing them in a very special way. This special normalization is what makes HOG so effective at detecting the edges of objects even in cases where the contrast is very low. These normalized histograms are put together into a feature vector, known as the HOG descriptor, that can be used to train a machine learning algorithm, such as a Support Vector Machine (SVM), to detect objects in images based on their boundaries (edges). Due to its great success and reliability, HOG has become one of the most widely used algorithms in computer vison for object detection.

In this notebook, you will learn:

* How the HOG algorithm works
* How to use OpenCV to create a HOG descriptor
* How to visualize the HOG descriptor.

# The HOG Algorithm

As its name suggests, the HOG algorithm, is based on creating histograms from the orientation of image gradients. The HOG algorithm is implemented in a series of steps:

1. Given the image of particular object, set a detection window (region of interest) that covers the entire object in the image (see Fig. 3).

2. Calculate the magnitude and direction of the gradient for each individual pixel in the detection window.

3. Divide the detection window into connected *cells* of pixels, with all cells being of the same size (see Fig. 3). The size of the cells is a free parameter and it is usually chosen so as to match the scale of the features that want to be detected. For example, in a 64 x 128 pixel detection window, square cells 6 to 8 pixels wide are suitable for detecting human limbs.

4. Create a Histogram for each cell, by first grouping the gradient directions of all pixels in each cell into a particular number of orientation (angular) bins; and then adding up the gradient magnitudes of the gradients in each angular bin (see Fig. 3). The number of bins in the histogram is a free parameter and it is usually set to 9 angular bins.

5. Group adjacent cells into *blocks* (see Fig. 3). The number of cells in each block is a free parameter and all blocks must be of the same size. The distance between each block (known as the stride) is a free parameter but it is usually set to half the block size, in which case you will get overlapping blocks (*see video below*).  The HOG algorithm has been shown empirically to work better with overlapping blocks.

6. Use the cells contained within each block to normalize the cell histograms in that block (see Fig. 3). If you have overlapping blocks this means that most cells will be normalized with respect to different blocks (*see video below*). Therefore, the same cell may have several different normalizations.

7. Collect all the normalized histograms from all the blocks into a single feature vector called the HOG descriptor.

8. Use the resulting HOG descriptors from many images of the same type of object to train a machine learning algorithm, such as an SVM, to detect those type of objects in images. For example, you could use the HOG descriptors from many images of pedestrians to train an SVM to detect pedestrians in images. The training is done with both positive a negative examples of the object you want detect in the image.

9. Once the SVM has been trained, a sliding window approach is used to try to detect and locate objects in images.  Detecting an object in the image entails finding the part of the image that looks similar to the HOG pattern learned by the SVM.

Fig. 3. - HOG Diagram.

Vid. 1. - HOG Animation.

# Why The HOG Algorithm Works

As we learned above, HOG creates histograms by adding the magnitude of the gradients in particular orientations in localized portions of the image called *cells*.  By doing this we guarantee that stronger gradients will contribute more to the magnitude of their respective angular bin, while the effects of weak and randomly oriented gradients resulting from noise are minimized. In this manner the histograms tell us the dominant gradient orientation of each cell.

### Dealing with contrast

Now, the magnitude of the dominant orientation can vary widely due to variations in local illumination and the contrast between the background and the foreground.

To account for the background-foreground contrast differences, the HOG algorithm tries to detect edges locally.  In order to do this, it defines groups of cells, called **blocks**, and normalizes the histograms using this local group of cells. By normalizing locally, the HOG algorithm can detect the edges in each block very reliably; this is called **block normalization**.

In addition to using block normalization, the HOG algorithm also uses overlapping blocks to increase its performance. By using overlapping blocks, each cell contributes several independent components to the final HOG descriptor, where each component corresponds to a cell being normalized with respect to a different block. This may seem redundant but, it has been shown empirically that by normalizing each cell several times with respect to different local blocks, the performance of the HOG algorithm increases dramatically.

### Loading Images and Importing Resources

The first step in building our HOG descriptor is to load the required packages into Python and to load our image.

We start by using OpenCV to load an image of a triangle tile. Since, the `cv2.imread()` function loads images as BGR we will convert our image to RGB so we can display it with the correct colors. As usual we will convert our BGR image to Gray Scale for analysis.

```python
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# Set the default figure size
plt.rcParams['figure.figsize'] = [17.0, 7.0]

# Load the image
image = cv2.imread('./images/triangle_tile.jpeg')

# Convert the original image to RGB
original_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# Convert the original image to gray scale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Print the shape of the original and gray scale images
print('The original image has shape: ', original_image.shape)
print('The gray scale image has shape: ', gray_image.shape)

# Display the images
plt.subplot(121)
plt.imshow(original_image)
plt.title('Original Image')
plt.subplot(122)
plt.imshow(gray_image, cmap='gray')
plt.title('Gray Scale Image')
plt.show()
```

# Creating The HOG Descriptor

We will be using OpenCV’s `HOGDescriptor` class to create the HOG descriptor. The parameters of the HOG descriptor are setup using the `HOGDescriptor()` function. The parameters of the `HOGDescriptor()` function and their default values are given below:

`cv2.HOGDescriptor(win_size = (64, 128),
                  block_size = (16, 16),
                  block_stride = (8, 8),
                  cell_size = (8, 8),
                  nbins = 9,
                  win_sigma = DEFAULT_WIN_SIGMA,
                  threshold_L2hys = 0.2,
                  gamma_correction = true,
                  nlevels = DEFAULT_NLEVELS)`

Parameters:

* **win_size** – *Size*
Size of detection window in pixels (*width, height*). Defines the region of interest. Must be an integer multiple of cell size.

* **block_size** – *Size*
Block size in pixels (*width, height*). Defines how many cells are in each block. Must be an integer multiple of cell size and it must be smaller than the detection window. The smaller the block the finer detail you will get.

* **block_stride** – *Size*
Block stride in pixels (*horizontal, vertical*). It must be an integer multiple of cell size. The `block_stride` defines the distance between adjecent blocks, for example, 8 pixels horizontally and 8 pixels vertically. Longer `block_strides` makes the algorithm run faster (because less blocks are evaluated) but the algorithm may not perform as well.

* **cell_size** – *Size*
Cell size in pixels (*width, height*). Determines the size fo your cell. The smaller the cell the finer detail you will get.

* **nbins** – *int*
Number of bins for the histograms. Determines the number of angular bins used to make the histograms. With more bins you capture more gradient directions. HOG uses unsigned gradients, so the angular bins will have values between 0 and 180 degrees.

* **win_sigma** – *double*
Gaussian smoothing window parameter. The performance of the HOG algorithm can be improved by smoothing the pixels near the edges of the blocks by applying a Gaussian spatial window to each pixel before computing the histograms.

* **threshold_L2hys** – *double*
L2-Hys (Lowe-style clipped L2 norm) normalization method shrinkage. The L2-Hys method is used to normalize the blocks and it consists of an L2-norm followed by clipping and a renormalization. The clipping limits the maximum value of the descriptor vector for each block to have the value of the given threshold (0.2 by default). After the clipping the descriptor vector is renormalized as described in *IJCV*, 60(2):91-110, 2004.

* **gamma_correction** – *bool*
Flag to specify whether the gamma correction preprocessing is required or not. Performing gamma correction slightly increases the performance of the HOG algorithm.

* **nlevels** – *int*
Maximum number of detection window increases.

As we can see, the  `cv2.HOGDescriptor()`function supports a wide range of parameters. The first few arguments (`block_size, block_stride, cell_size`, and `nbins`) are probably the ones you are most likely to change. The other parameters can be safely left at their default values and you will get good results.

In the code below, we will use the `cv2.HOGDescriptor()`function to set the cell size, block size, block stride, and the number of bins for the histograms of the HOG descriptor. We will then use `.compute(image)`method to compute the HOG descriptor (feature vector) for the given `image`.

```python
# Specify the parameters for our HOG descriptor

# Cell Size in pixels (width, height). Must be smaller than the size of the detection window
# and must be chosen so that the resulting Block Size is smaller than the detection window.
cell_size = (6, 6)

# Number of cells per block in each direction (x, y). Must be chosen so that the resulting
# Block Size is smaller than the detection window
num_cells_per_block = (2, 2)

# Block Size in pixels (width, height). Must be an integer multiple of Cell Size.
# The Block Size must be smaller than the detection window
block_size = (num_cells_per_block[0] * cell_size[0],
              num_cells_per_block[1] * cell_size[1])

# Calculate the number of cells that fit in our image in the x and y directions
x_cells = gray_image.shape[1] // cell_size[0]
y_cells = gray_image.shape[0] // cell_size[1]

# Horizontal distance between blocks in units of Cell Size. Must be an integer and it must
# be set such that (x_cells - num_cells_per_block[0]) / h_stride = integer.
h_stride = 1

# Vertical distance between blocks in units of Cell Size. Must be an integer and it must
# be set such that (y_cells - num_cells_per_block[1]) / v_stride = integer.
v_stride = 1

# Block Stride in pixels (horizantal, vertical). Must be an integer multiple of Cell Size
block_stride = (cell_size[0] * h_stride, cell_size[1] * v_stride)

# Number of gradient orientation bins
num_bins = 9

# Specify the size of the detection window (Region of Interest) in pixels (width, height).
# It must be an integer multiple of Cell Size and it must cover the entire image. Because
# the detection window must be an integer multiple of cell size, depending on the size of
# your cells, the resulting detection window might be slightly smaller than the image.
# This is perfectly ok.
win_size = (x_cells * cell_size[0] , y_cells * cell_size[1])

# Print the shape of the gray scale image for reference
print('\nThe gray scale image has shape: ', gray_image.shape)
print()

# Print the parameters of our HOG descriptor
print('HOG Descriptor Parameters:\n')
print('Window Size:', win_size)
print('Cell Size:', cell_size)
print('Block Size:', block_size)
print('Block Stride:', block_stride)
print('Number of Bins:', num_bins)
print()

# Set the parameters of the HOG descriptor using the variables defined above
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, num_bins)

# Compute the HOG Descriptor for the gray scale image
hog_descriptor = hog.compute(gray_image)
```

    The gray scale image has shape:  (250, 250)

    HOG Descriptor Parameters:

    Window Size: (246, 246)
    Cell Size: (6, 6)
    Block Size: (12, 12)
    Block Stride: (6, 6)
    Number of Bins: 9

# Number of Elements In The HOG Descriptor

The resulting HOG Descriptor (feature vector), contains the normalized histograms from all cells from all blocks in the detection window concatenated in one long vector. Therefore, the size of the HOG feature vector will be given by the total number of blocks in the detection window, multiplied by the number of cells per block, times the number of orientation bins:

\begin{equation}
\mbox{total_elements} = (\mbox{total_number_of_blocks})\mbox{ } \times \mbox{ } (\mbox{number_cells_per_block})\mbox{ } \times \mbox{ } (\mbox{number_of_bins})
\end{equation}

If we don’t have overlapping blocks (*i.e.* the `block_stride`equals the `block_size`), the total number of blocks can be easily calculated by dividing the size of the detection window by the block size. However, in the general case we have to take into account the fact that we have overlapping blocks. To find the total number of blocks in the general case (*i.e.* for any `block_stride` and `block_size`), we can use the formula given below:

\begin{equation}
\mbox{Total}_i = \left( \frac{\mbox{block_size}_i}{\mbox{block_stride}_i} \right)\left( \frac{\mbox{window_size}_i}{\mbox{block_size}_i} \right) - \left [\left( \frac{\mbox{block_size}_i}{\mbox{block_stride}_i} \right) - 1 \right]; \mbox{  for  } i = x,y
\end{equation}

Where Total$_x$, is the total number of blocks along the width of the detection window, and Total$_y$, is the total number of blocks along the height of the detection window. This formula for Total$_x$ and Total$_y$, takes into account the extra blocks that result from overlapping. After calculating Total$_x$ and Total$_y$, we can get the total number of blocks in the detection window by multiplying Total$_x$  $\times$ Total$_y$. The above formula can be simplified considerably because the `block_size`, `block_stride`, and `window_size`are all defined in terms of the `cell_size`. By making all the appropriate substitutions and cancelations the above formula reduces to:

\begin{equation}
\mbox{Total}_i = \left(\frac{\mbox{cells}_i - \mbox{num_cells_per_block}_i}{N_i}\right) + 1\mbox{  }; \mbox{  for  } i = x,y
\end{equation}

Where cells$_x$ is the total number of cells along the width of the detection window, and cells$_y$, is the total number of cells along the height of the detection window. And $N_x$ is the horizontal block stride in units of `cell_size` and $N_y$ is the vertical block stride in units of `cell_size`.

Let's calculate what the number of elements for the HOG feature vector should be and check that it matches the shape of the HOG Descriptor calculated above.

```python
# Calculate the total number of blocks along the width of the detection window
tot_bx = np.uint32(((x_cells - num_cells_per_block[0]) / h_stride) + 1)

# Calculate the total number of blocks along the height of the detection window
tot_by = np.uint32(((y_cells - num_cells_per_block[1]) / v_stride) + 1)

# Calculate the total number of elements in the feature vector
tot_els = (tot_bx) * (tot_by) * num_cells_per_block[0] * num_cells_per_block[1] * num_bins

# Print the total number of elements the HOG feature vector should have
print('\nThe total number of elements in the HOG Feature Vector should be: ',
      tot_bx, 'x',
      tot_by, 'x',
      num_cells_per_block[0], 'x',
      num_cells_per_block[1], 'x',
      num_bins, '=',
      tot_els)

# Print the shape of the HOG Descriptor to see that it matches the above
print('\nThe HOG Descriptor has shape:', hog_descriptor.shape)
print()
```

    The total number of elements in the HOG Feature Vector should be:  40 x 40 x 2 x 2 x 9 = 57600

    The HOG Descriptor has shape: (57600, 1)

# Visualizing The HOG Descriptor

We can visualize the HOG Descriptor by plotting the histogram associated with each cell as a collection of vectors. To do this, we will plot each bin in the histogram as a single vector whose magnitude is given by the height of the bin and its orientation is given by the angular bin that its associated with. Since any given cell might have multiple histograms associated with it, due to the overlapping blocks, we will choose to average all the histograms for each cell to produce a single histogram for each cell.

OpenCV has no easy way to visualize the HOG Descriptor, so we have to do some manipulation first in order to visualize it. We will start by reshaping the HOG Descriptor in order to make our calculations easier.  We will then compute the average histogram of each cell and finally we will convert the histogram bins into vectors. Once we have the vectors, we plot the corresponding vectors for each cell in an image.

The code below produces an interactive plot so that you can interact with the figure. The figure contains:
* the grayscale image,
* the HOG Descriptor (feature vector),
* a zoomed-in portion of the HOG Descriptor, and
* the histogram of the selected cell.

**You can click anywhere on the gray scale image or the HOG Descriptor image to select a particular cell**. Once you click on either image a *magenta* rectangle will appear showing the cell you selected. The Zoom Window will show you a zoomed in version of the HOG descriptor around the selected cell; and the histogram plot will show you the corresponding histogram for the selected cell. The interactive window also has buttons at the bottom that allow for other functionality, such as panning, and giving you the option to save the figure if desired. The home button returns the figure to its default value.

**NOTE**: If you are running this notebook in the Udacity workspace, there is around a 2 second lag in the interactive plot. This means that if you click in the image to zoom in, it will take about 2 seconds for the plot to refresh.

```python
%matplotlib notebook

import copy
import matplotlib.patches as patches

# Set the default figure size
plt.rcParams['figure.figsize'] = [9.8, 9]

# Reshape the feature vector to [blocks_y, blocks_x, num_cells_per_block_x, num_cells_per_block_y, num_bins].
# The blocks_x and blocks_y will be transposed so that the first index (blocks_y) referes to the row number
# and the second index to the column number. This will be useful later when we plot the feature vector, so
# that the feature vector indexing matches the image indexing.
hog_descriptor_reshaped = hog_descriptor.reshape(tot_bx,
                                                 tot_by,
                                                 num_cells_per_block[0],
                                                 num_cells_per_block[1],
                                                 num_bins).transpose((1, 0, 2, 3, 4))

# Print the shape of the feature vector for reference
print('The feature vector has shape:', hog_descriptor.shape)

# Print the reshaped feature vector
print('The reshaped feature vector has shape:', hog_descriptor_reshaped.shape)

# Create an array that will hold the average gradients for each cell
ave_grad = np.zeros((y_cells, x_cells, num_bins))

# Print the shape of the ave_grad array for reference
print('The average gradient array has shape: ', ave_grad.shape)

# Create an array that will count the number of histograms per cell
hist_counter = np.zeros((y_cells, x_cells, 1))

# Add up all the histograms for each cell and count the number of histograms per cell
for i in range (num_cells_per_block[0]):
    for j in range(num_cells_per_block[1]):
        ave_grad[i:tot_by + i,
                 j:tot_bx + j] += hog_descriptor_reshaped[:, :, i, j, :]

        hist_counter[i:tot_by + i,
                     j:tot_bx + j] += 1

# Calculate the average gradient for each cell
ave_grad /= hist_counter

# Calculate the total number of vectors we have in all the cells.
len_vecs = ave_grad.shape[0] * ave_grad.shape[1] * ave_grad.shape[2]

# Create an array that has num_bins equally spaced between 0 and 180 degress in radians.
deg = np.linspace(0, np.pi, num_bins, endpoint = False)

# Each cell will have a histogram with num_bins. For each cell, plot each bin as a vector (with its magnitude
# equal to the height of the bin in the histogram, and its angle corresponding to the bin in the histogram).
# To do this, create rank 1 arrays that will hold the (x,y)-coordinate of all the vectors in all the cells in the
# image. Also, create the rank 1 arrays that will hold all the (U,V)-components of all the vectors in all the
# cells in the image. Create the arrays that will hold all the vector positons and components.
U = np.zeros((len_vecs))
V = np.zeros((len_vecs))
X = np.zeros((len_vecs))
Y = np.zeros((len_vecs))

# Set the counter to zero
counter = 0

# Use the cosine and sine functions to calculate the vector components (U,V) from their maginitudes. Remember the
# cosine and sine functions take angles in radians. Calculate the vector positions and magnitudes from the
# average gradient array
for i in range(ave_grad.shape[0]):
    for j in range(ave_grad.shape[1]):
        for k in range(ave_grad.shape[2]):
            U[counter] = ave_grad[i,j,k] * np.cos(deg[k])
            V[counter] = ave_grad[i,j,k] * np.sin(deg[k])

            X[counter] = (cell_size[0] / 2) + (cell_size[0] * i)
            Y[counter] = (cell_size[1] / 2) + (cell_size[1] * j)

            counter = counter + 1

# Create the bins in degress to plot our histogram.
angle_axis = np.linspace(0, 180, num_bins, endpoint = False)
angle_axis += ((angle_axis[1] - angle_axis[0]) / 2)

# Create a figure with 4 subplots arranged in 2 x 2
fig, ((a,b),(c,d)) = plt.subplots(2,2)

# Set the title of each subplot
a.set(title = 'Gray Scale Image\n(Click to Zoom)')
b.set(title = 'HOG Descriptor\n(Click to Zoom)')
c.set(title = 'Zoom Window', xlim = (0, 18), ylim = (0, 18), autoscale_on = False)
d.set(title = 'Histogram of Gradients')

# Plot the gray scale image
a.imshow(gray_image, cmap = 'gray')
a.set_aspect(aspect = 1)

# Plot the feature vector (HOG Descriptor)
b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
b.invert_yaxis()
b.set_aspect(aspect = 1)
b.set_facecolor('black')

# Define function for interactive zoom
def onpress(event):

    #Unless the left mouse button is pressed do nothing
    if event.button != 1:
        return

    # Only accept clicks for subplots a and b
    if event.inaxes in [a, b]:

        # Get mouse click coordinates
        x, y = event.xdata, event.ydata

        # Select the cell closest to the mouse click coordinates
        cell_num_x = np.uint32(x / cell_size[0])
        cell_num_y = np.uint32(y / cell_size[1])

        # Set the edge coordinates of the rectangle patch
        edgex = x - (x % cell_size[0])
        edgey = y - (y % cell_size[1])

        # Create a rectangle patch that matches the the cell selected above
        rect = patches.Rectangle((edgex, edgey),
                                  cell_size[0], cell_size[1],
                                  linewidth = 1,
                                  edgecolor = 'magenta',
                                  facecolor='none')

        # A single patch can only be used in a single plot. Create copies
        # of the patch to use in the other subplots
        rect2 = copy.copy(rect)
        rect3 = copy.copy(rect)

        # Update all subplots
        a.clear()
        a.set(title = 'Gray Scale Image\n(Click to Zoom)')
        a.imshow(gray_image, cmap = 'gray')
        a.set_aspect(aspect = 1)
        a.add_patch(rect)

        b.clear()
        b.set(title = 'HOG Descriptor\n(Click to Zoom)')
        b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
        b.invert_yaxis()
        b.set_aspect(aspect = 1)
        b.set_facecolor('black')
        b.add_patch(rect2)

        c.clear()
        c.set(title = 'Zoom Window')
        c.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 1)
        c.set_xlim(edgex - cell_size[0], edgex + (2 * cell_size[0]))
        c.set_ylim(edgey - cell_size[1], edgey + (2 * cell_size[1]))
        c.invert_yaxis()
        c.set_aspect(aspect = 1)
        c.set_facecolor('black')
        c.add_patch(rect3)

        d.clear()
        d.set(title = 'Histogram of Gradients')
        d.grid()
        d.set_xlim(0, 180)
        d.set_xticks(angle_axis)
        d.set_xlabel('Angle')
        d.bar(angle_axis,
              ave_grad[cell_num_y, cell_num_x, :],
              180 // num_bins,
              align = 'center',
              alpha = 0.5,
              linewidth = 1.2,
              edgecolor = 'k')

        fig.canvas.draw()

# Create a connection between the figure and the mouse click
fig.canvas.mpl_connect('button_press_event', onpress)
plt.show()
```

    The feature vector has shape: (57600, 1)
    The reshaped feature vector has shape: (40, 40, 2, 2, 9)
    The average gradient array has shape:  (41, 41, 9)

    <IPython.core.display.Javascript object>

# Understanding The Histograms

Let's take a look at a couple of snapshots of the above figure to see if the histograms for the selected cell make sense. Let's start looking at a cell that is inside a triangle and not near an edge:

Fig. 4. - Histograms Inside a Triangle.

In this case, since the triangle is nearly all of the same color there shouldn't be any dominant gradient in the selected cell. As we can clearly see in the Zoom Window and the histogram, this is indeed the case. We have many gradients but none of them clearly dominates over the other.

Now let’s take a look at a cell that is near a horizontal edge:

Fig. 5. - Histograms Near a Horizontal Edge.

Remember that edges are areas of an image where the intensity changes abruptly. In these cases, we will have a high intensity gradient in some particular direction. This is exactly what we see in the corresponding histogram and Zoom Window for the selected cell.  In the Zoom Window, we can see that the dominant gradient is pointing up, almost at 90 degrees, since that’s the direction in which there is a sharp change in intensity. Therefore, we should expect to see the 90-degree bin in the histogram to dominate strongly over the others. This is in fact what we see.

Now let’s take a look at a cell that is near a vertical edge:

Fig. 6. - Histograms Near a Vertical Edge.

In this case we expect the dominant gradient in the cell to be horizontal, close to 180 degrees, since that’s the direction in which there is a sharp change in intensity. Therefore, we should expect to see the 170-degree bin in the histogram to dominate strongly over the others. This is what we see in the histogram but we also see that there is another dominant gradient in the cell, namely the one in the 10-degree bin. The reason for this, is because the HOG algorithm is using unsigned gradients, which means 0 degrees and 180 degrees are considered the same. Therefore, when the histograms are being created, angles between 160 and 180 degrees, contribute proportionally to both the 10-degree bin and the 170-degree bin. This results in there being two dominant gradients in the cell near the vertical edge instead of just one.

To conclude let’s take a look at a cell that is near a diagonal edge.

Fig. 7. - Histograms Near a Diagonal Edge.

To understand what we are seeing, let’s first remember that gradients have an *x*-component, and a *y*-component, just like vectors. Therefore, the resulting orientation of a gradient is going to be given by the vector sum of its components. For this reason, on vertical edges the gradients are horizontal, because they only have an x-component, as we saw in Figure 4. While on horizontal edges the gradients are vertical, because they only have a y-component, as we saw in Figure 3. Consequently, on diagonal edges, the gradients are also going to be diagonal because both the *x* and *y* components are non-zero.  Since the diagonal edges in the image are close to 45 degrees, we should expect to see a dominant gradient orientation in the 50-degree bin. This is in fact what we see in the histogram but, just like in Figure 4., we see there are two dominant gradients instead of just one. The reason for this is that when the histograms are being created, angles that are near the boundaries of bins, contribute proportionally to the adjacent bins.  For example, a gradient with an angle of 40 degrees, is right in the middle of the 30-degree and 50-degree bin. Therefore, the magnitude of the gradient is split evenly into the 30-degree and 50-degree bin. This results in there being two dominant gradients in the cell near the diagonal edge instead of just one.

Now that you know how HOG is implemented, in the workspace you will find a notebook named *Examples*. In there, you will be able set your own paramters for the HOG descriptor for various images. Have fun!


# Optimización media-varianza

La **teoría de portafolios** es uno de los avances más importantes en las finanzas modernas e inversiones.
- Apareció por primera vez en un [artículo corto](https://www.math.ust.hk/~maykwok/courses/ma362/07F/markowitz_JF.pdf) llamado "Portfolio Selection" en la edición de Marzo de 1952 de "the Journal of Finance".
- Escrito por un desconocido estudiante de la Universidad de Chicago, llamado Harry Markowitz.
- Escrito corto (sólo 14 páginas), poco texto, fácil de entender, muchas gráficas y unas cuantas referencias.
- No se le prestó mucha atención hasta los 60s.

Finalmente, este trabajo se convirtió en una de las más grandes ideas en finanzas, y le dió a Markowitz el Premio Nobel casi 40 años después.
- Markowitz estaba incidentalmente interesado en los mercados de acciones e inversiones.
- Estaba más bien interesado en entender cómo las personas tomaban sus mejores decisiones cuando se enfrentaban con "trade-offs".
- Principio de conservación de la miseria. O, dirían los instructores de gimnasio: "no pain, no gain".
- Si queremos más de algo, tenemos que perder en algún otro lado.
- El estudio de este fenómeno era el que le atraía a Markowitz.

De manera que nadie se hace rico poniendo todo su dinero en la cuenta de ahorros. La única manera de esperar altos rendimientos es si se toma bastante riesgo. Sin embargo, riesgo significa también la posibilidad de perder, tanto como ganar.

Pero, ¿qué tanto riesgo es necesario?, y ¿hay alguna manera de minimizar el riesgo mientras se maximizan las ganancias?
- Markowitz básicamente cambió la manera en que los inversionistas pensamos acerca de esas preguntas.
- Alteró completamente la práctica de la administración de inversiones.
- Incluso el título de su artículo era innovador. Portafolio: una colección de activos en lugar de tener activos individuales.
- En ese tiempo, un portafolio se refería a una carpeta de piel.
- En el resto de este módulo, nos ocuparemos de la parte analítica de la teoría de portafolios, la cual puede ser resumida en dos frases:
 - No pain, no gain.
 - No ponga todo el blanquillo en una sola bolsa.

**Objetivos:**
- ¿Qué es la línea de asignación de capital?
- ¿Qué es el radio de Sharpe?
- ¿Cómo deberíamos asignar nuestro capital entre un activo riesgoso y un activo libre de riesgo?

*Referencia:*
- Notas del curso "Portfolio Selection and Risk Management", Rice University, disponible en Coursera.
___

## 1. Línea de asignación de capital

### 1.1. Motivación

El proceso de construcción de un portafolio tiene entonces los siguientes dos pasos:
1. Escoger un portafolio de activos riesgosos.
2. Decidir qué tanto de tu riqueza invertirás en el portafolio y qué tanto invertirás en activos libres de riesgo.

Al paso 2 lo llamamos **decisión de asignación de activos**.

Preguntas importantes:
1. ¿Qué es el portafolio óptimo de activos riesgosos?
 - ¿Cuál es el mejor portafolio de activos riesgosos?
 - Es un portafolio eficiente en media-varianza.
2. ¿Qué es la distribución óptima de activos?
 - ¿Cómo deberíamos distribuir nuestra riqueza entre el portafolo riesgoso óptimo y el activo libre de riesgo?
 - Concepto de **línea de asignación de capital**.
 - Concepto de **radio de Sharpe**.

Dos suposiciones importantes:
- Funciones de utilidad media-varianza.
- Inversionista averso al riesgo.

La idea sorprendente que saldrá de este análisis, es que cualquiera que sea la actitud del inversionista de cara al riesgo, el mejor portafolio de activos riesgosos es idéntico para todos los inversionistas.

Lo que nos importará a cada uno de nosotros en particular, es simplemente la desición óptima de asignación de activos.
___

### 1.2. Línea de asignación de capital

Sean:
- $r_s$ el rendimiento del activo riesgoso,
- $r_f$ el rendimiento libre de riesgo, y
- $w$ la fracción invertida en el activo riesgoso.

 Realizar deducción de la línea de asignación de capital en el tablero.

**Tres doritos después...**

#### Línea de asignación de capital (LAC):
$E[r_p]$ se relaciona con $\sigma_p$ de manera afín. Es decir, mediante la ecuación de una recta:

$$E[r_p]=r_f+\frac{E[r_s-r_f]}{\sigma_s}\sigma_p.$$

- La pendiente de la LAC es el radio de Sharpe $\frac{E[r_s-r_f]}{\sigma_s}=\frac{E[r_s]-r_f}{\sigma_s}$,
- el cual nos dice qué tanto rendimiento obtenemos por unidad de riesgo asumido en la tenencia del activo (portafolio) riesgoso.

Ahora, la pregunta es, ¿dónde sobre esta línea queremos estar?
___

### 1.3. Resolviendo para la asignación óptima de capital

Recapitulando de la clase pasada, tenemos las curvas de indiferencia: **queremos estar en la curva de indiferencia más alta posible, que sea tangente a la LAC**.

 Ver en el tablero.

Analíticamente, el problema es

$$\max_{w} \quad E[U(r_p)]\equiv\max_{w} \quad E[r_p]-\frac{1}{2}\gamma\sigma_p^2,$$

donde los puntos $(\sigma_p,E[r_p])$ se restringen a estar en la LAC, esto es $E[r_p]=r_f+\frac{E[r_s-r_f]}{\sigma_s}\sigma_p$ y $\sigma_p=w\sigma_s$. Entonces el problema anterior se puede escribir de la siguiente manera:

$$\max_{w} \quad r_f+wE[r_s-r_f]-\frac{1}{2}\gamma w^2\sigma_s^2.$$

 Encontrar la $w$ que maximiza la anterior expresión en el tablero.

**Tres doritos después...**

La solución es entonces:

$$w^\ast=\frac{E[r_s]-r_f}{\gamma\sigma_s^2}.$$

De manera intuitiva:
- $w^\ast\propto E[r_s-r_f]$: a más exceso de rendimiento que se obtenga del activo riesgoso, más querremos invertir en él.
- $w^\ast\propto \frac{1}{\gamma}$: mientras más averso al riesgo seas, menos querrás invertir en el activo riesgoso.
- $w^\ast\propto \frac{1}{\sigma_s^2}$: mientras más riesgoso sea el activo, menos querrás invertir en él.
___

## 2. Ejemplo de asignación óptima de capital: acciones y billetes de EU

Pongamos algunos números con algunos datos, para ilustrar la derivación que acabamos de hacer.

En este caso, consideraremos:
- **Portafolio riesgoso**: mercado de acciones de EU (representados en algún índice de mercado como el S&P500).
- **Activo libre de riesgo**: billetes del departamento de tesorería de EU (T-bills).

Tenemos los siguientes datos:

$$E[r_{US}]=11.9\%,\quad \sigma_{US}=19.15\%, \quad r_f=1\%.$$

Recordamos que podemos escribir la expresión de la LAC como:

\begin{align}
E[r_p]&=r_f+\left[\frac{E[r_{US}-r_f]}{\sigma_{US}}\right]\sigma_p\\
      &=0.01+\text{S.R.}\sigma_p,
\end{align}

donde $\text{S.R}=\frac{0.119-0.01}{0.1915}\approx0.569$ es el radio de Sharpe (¿qué es lo que es esto?).

Grafiquemos la LAC con estos datos reales:

```python
# Importamos librerías que vamos a utilizar
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
```

```python
# Datos
Ers = 0.119
ss = 0.1915
rf = 0.01
# Radio de Sharpe para este activo
RS = (Ers - rf) / ss
# Vector de volatilidades del portafolio (sugerido: 0% a 50%)
sp = np.linspace(0, 0.5)
# LAC
LAC = rf + RS * sp
```

```python
# Gráfica
plt.figure(figsize=(6, 4))
plt.plot(sp, LAC, lw=2, label='LAC')
plt.plot(0, rf, 'o', label='Activo libre de riesgo')
plt.plot(ss, Ers, 'o', label='Activo/portafolio riesgoso')
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento esperado E[r]')
plt.legend(loc='upper left', bbox_to_anchor=(1.05, 1))
```

Bueno, y ¿en qué punto de esta línea querríamos estar?
- Pues ya vimos que depende de tus preferencias.
- En particular, de tu actitud de cara al riesgo, medido por tu coeficiente de aversión al riesgo.

Solución al problema de asignación óptima de capital:

$$\max_{w} \quad E[U(r_p)]$$

$$w^\ast=\frac{E[r_s-r_f]}{\gamma\sigma_s^2}$$

Dado que ya tenemos datos, podemos intentar para varios coeficientes de aversión al riesgo:

```python
# importar pandas
import pandas as pd
```

```python
# Crear un DataFrame con los pesos, rendimiento
# esperado y volatilidad del portafolio óptimo
# entre los activos riesgoso y libre de riesgo
# cuyo índice sean los coeficientes de aversión
# al riesgo del 1 al 10 (enteros)
g = np.arange(1, 11)
w_opt = pd.DataFrame(data={'Coef. Av. Riesgo': g,
                           'w Opt.': (Ers - rf) / (g * ss**2)})
```

```python
w_opt
```

      Coef. Av. Riesgo
      w Opt.

      0
      1
      2.972275

      1
      2
      1.486137

      2
      3
      0.990758

      3
      4
      0.743069

      4
      5
      0.594455

      5
      6
      0.495379

      6
      7
      0.424611

      7
      8
      0.371534

      8
      9
      0.330253

      9
      10
      0.297227

¿Cómo se interpreta $w^\ast>1$?
- Cuando $0<w^\ast<1$, entonces $0<1-w^\ast<1$. Lo cual implica posiciones largas en el mercado de activos y en el activo libre de riesgo.
- Por el contrario, cuando $w^\ast>1$, tenemos $1-w^\ast<0$. Lo anterior implica una posición corta en el activo libre de riesgo (suponiendo que se puede) y una posición larga (de más del 100%) en el mercado de activos: apalancamiento.

# Anuncios parroquiales.

## 1. Quiz la siguiente clase.

## 2. Pueden consultar sus calificaciones en el siguiente [enlace](https://docs.google.com/spreadsheets/d/1boMnlLhcmcwxh_l1qkj6aR35YbwDP-QCaB2dUQqB01s/edit?usp=sharing).

Created with Jupyter by Esteban Jiménez Rodríguez.


```
from dictionary import *
from report_results_and_logging import *
import numpy as np
from sympy import * # used for differentiation
import math # used for natural log
#from random import random # for random gen
from decimal import Decimal # Used for results reporting calculations
from brian2 import *

epochs = 100
spikeMiliseconds = 100
spikeInterval = spikeMiliseconds * ms
spikeIntervalUnformatted = spikeMiliseconds * .001
dictionaryLongitude = 4
spikesPerChar=3
totalTime = epochs * spikesPerChar * dictionaryLongitude * spikeInterval
epochsToPrint = [0,1,2,25,50,75]
presentResults = False
logger = False
displayAllNeuronMemPotentials = False

taum = 20 * ms
taue = 1 * ms
taui = 10 * ms
Vt = 5 * mV
Vr = -0.001 * mV

Rm = 80
## added tauS constant below ##
tauS = 2
tauMax = 30
tauMin = 2
#tauM = 30 * ms # appears different than taum above due to the article specifying this as 30
tauM = .03 # removed ms for compatibility

SimulationDuration = 10000
epochMsDuration = (SimulationDuration / epochs) * 10 # Times 10 is to adjust to Ms scale

numberOfPixels = 15
#neuronFiringThreshold = 10 * mV
neuronFiringThreshold = 10 # removed mV for compatibility
W = np.random.uniform(0.5,1.0,[4,15]) # Initial weights
# none to 1 below
numberOfNeurons = 4
R = [[1] * numberOfPixels]*dictionaryLongitude # Initial Resistance Values
testR = [[1] * numberOfPixels]*dictionaryLongitude # Initial Resistance Values
## Added 1 init below but I don't really know what the right init value is
#Id = [[-.001] * numberOfPixels]*dictionaryLongitude # Initial Dendritic Post Synaptic Current
Id = [[0] * numberOfPixels]*dictionaryLongitude # Initial Dendritic Post Synaptic Current
testId = [[-.001] * numberOfPixels]*dictionaryLongitude # Initial Dendritic Post Synaptic Current
## Added 1 init below but I don't really know what the right init value is
tauD = [[1] * numberOfPixels]*dictionaryLongitude # Initial tauD
testTauD = [[1] * numberOfPixels]*dictionaryLongitude # Initial tauD
tF = [[None] * numberOfPixels]*dictionaryLongitude # Initial pre-synaptic spike times

Is = [-.001] * dictionaryLongitude
Um = [-.001] * dictionaryLongitude
testUm = [-.001] * dictionaryLongitude

Ureset = -.001
#Ureset = 0

APlus = 0.1
AMinus = -0.105
TauPlus = 1
TauMinus = 1
LearningRate = 0.1
ActionPotentialThreshold = .01
```

    C:\Python27\lib\site-packages\brian\utils\sparse_patch\__init__.py:39: UserWarning: Couldn't find matching sparse matrix patch for scipy version 0.14.0, but in most cases this shouldn't be a problem.
      warnings.warn("Couldn't find matching sparse matrix patch for scipy version %s, but in most cases this shouldn't be a problem." % scipy.__version__)

```
import io
from IPython.nbformat import current

def execute_notebook(nbfile):

    with io.open(nbfile) as f:
        nb = current.read(f, 'json')

    ip = get_ipython()

    for cell in nb.worksheets[0].cells:
        if cell.cell_type != 'code':
            continue
        ip.run_cell(cell.input)

execute_notebook("commonFurtherFormulas.ipynb")
```

    Mon Feb 09 04:12:00 2015

```
from architecture_further_formulas import *

class gupta_paper:
	neuralnet = Network()
	dictionary = dictionary()
	spiketimes = dictionary.spikeTimes(dictionaryLongitude, spikeInterval, spikesPerChar, epochs)
	trainingSpikeTimes = dictionary.spikeTimes(dictionaryLongitude, spikeInterval, 1, 300)
	LIK = SpikeGeneratorGroup(N=15, indices=spiketimes[:,0], times=spiketimes[:,1]*ms)
	absRefracTime = [0]*dictionaryLongitude
	relRefracTime = [0]*dictionaryLongitude
	timeStepInterval = 0.001
	time = 0.0000 - timeStepInterval
	neuronIndex = 0
	lastSpikeInterval = -0.001#0.0
	SummedDendriteGroup = 0;
	SynapseToSoma = 0;
	SigmaWDSyn = .0375 * mV
	SigmaWDSynMod = .5
	RmEq = Rm #Rm * mV
	DendR = .0375 * mV
	DendW = 1
	DendDirac = 1
	# tauD and R lines below are to avoid an odd 'reference before assignment' error
	tauD = tauD
	testTauD = testTauD
	W = W
	R = R
	testR = testR
	tauM = tauM
	currentEpoch = 0
	refractoryPointCounter = 0.0
	testRefractoryPointCounter = 0.0
	refractoryPointSwitch = False
	testRefractoryPointSwitch = False
	neuronIndexCounter = 0.0
	neuronIndexSwitch = False
	truePositiveSpikeResults = 0
	falsePositiveSpikeResults = 0
	trueNegativeSpikeResults = 0
	falseNegativeSpikeResults = 0
	totalSpikeIntervals = 12
	testSpikesFiredInInterval = np.array([[False]*(totalSpikeIntervals+1)]*dictionaryLongitude)
	IdSpikeFired = np.array([[False]*numberOfPixels]*dictionaryLongitude);
	testIdSpikeFired = np.array([[False]*numberOfPixels]*dictionaryLongitude);
	IsSpikeFired = np.array([False]*dictionaryLongitude);
	testIsSpikeFired = np.array([False]*dictionaryLongitude);
	IdRefractoryPeriod = np.array([[False]*numberOfPixels]*dictionaryLongitude);
	testIdRefractoryPeriod = np.array([[False]*numberOfPixels]*dictionaryLongitude);
	IsRefractoryPeriod = np.array([False]*dictionaryLongitude);
	testIsRefractoryPeriod = np.array([False]*dictionaryLongitude);
	UmRefractoryPeriod = np.array([False]*dictionaryLongitude);
	testUmRefractoryPeriod = np.array([False]*dictionaryLongitude);
	refractoryGraphingSwitch = False

	correctSpikes = np.array([[1]*(totalSpikeIntervals+1)]*dictionaryLongitude);#[[1] * totalSpikeIntervals]*dictionaryLongitude
	correctOrderOfNeurons = [2,0,1,3]
	correctSpikes[correctOrderOfNeurons[0]][0] = 0
	correctSpikes[correctOrderOfNeurons[0]][4:13] = 0
	correctSpikes[correctOrderOfNeurons[1]][0:4] = 0
	correctSpikes[correctOrderOfNeurons[1]][7:13] = 0
	correctSpikes[correctOrderOfNeurons[2]][0:7] = 0
	correctSpikes[correctOrderOfNeurons[2]][10:13] = 0
	correctSpikes[correctOrderOfNeurons[3]][0:10] = 0

	M = None
	testM = None
	UmM3 = None
	testUmM3 = None
	spikeIntervalCounter = 0.0
	relRefracTimeActivated = [False]*dictionaryLongitude
	absRefracTimeActivated = [False]*dictionaryLongitude
	# Parameters for tuning
	relRefracTimeDuration = 0.03#.1#.01
	absRefracTimeDuration = 0.02#.02#.002
	#dendCalcScaling = 0.16#.32#1.0
	dendCalcScaling = 1.0
	#somaDirectScaling = .0005#.0005#.001#.0005#.001#0.002285#0.002857
	somaDirectScaling = 1.0
	diracScaling = 1.0#1.25#0.9#1.0#0.1#0.5#1.0#0.1#1.0#0.0007#0.0007#0.0825#0.0325#0.0825
	weightScaling = 1.0#1.25
	#diracScaling = 1.0
	#uMScaling = 1.0#.32
	lateralInhibitScaling = 0.2#0.28#0.4#0.28
	lateralInhibitScaling2 = 0.7#0.8#1.0#1.5#1.7#2.0#1.8#1.3#0.6#1.6#1.1#1.6#1.3#0.7#2.5#0.5#0.8#1.5
	generalClockDt = 1.0*ms#0.1*ms
	voltageForInhibition = 0.0*mV
	inhibitionReduction = 1.0
	positiveWeightReinforcement = 1.0#2.0#2.0#7.0#1.0#7.0#20.0#5.0#10.0#1.0#2.0
	negativeWeightReinforcement = 2.0#5.0#3.5#7.5#30.0#30#2.0#30#7.5#15.0#30.0#8.0#6.0#1.0#50.0#10.0#50.0#10.0#3.0#0.5
	LearningRate = 0.2#0.1#1.0#0.7
	evaluateClassifier = True
	accelerateTraining = False
	runTime = 155*ms#1000*ms#300*ms#400*ms#155*ms#42*ms#135*ms#72*ms#135*ms#42*ms#100*ms#500*ms#5000*ms#100*ms#22*ms#15*ms#135*ms#15*ms#130*ms

	print 'initial Weights\n',W

	def run_model(self):
		neuralnet = self.neuralnet
		dictionary = self.dictionary
		#dv/dt = (-v+((Rm/mV)*(SynI+DendI)))/(tauM) : volt (unless refractory)
		eqs = Equations('''
			dv/dt = v/(1*second): volt
			dprelimV/dt = (-prelimV+((Rm/mV)*(SynI+DendI*1.0)))/(tauM) : volt (unless refractory)
			Rm = 80*mV : volt
			tauM = 30*ms : second
	        V : volt
	        DendI : volt
	        SynI : volt
	        v2 : volt
			UmSpikeFired : volt
			lateralInhibActive : boolean
			beginRefrac : volt
			uMScaling : volt
			preLatInhibV : volt
			inhibitionVoltage : volt
		    ''')

		# Units are removed in dv equ below and it is converted to mV.  .001 is conversion factor
		dendriteEqs = Equations('''
			dv/dt = (((-v/mV)+((r/mV)*(w/volt)*(dirac/volt)))/(tau))*mV : volt
			V : volt
	        r : volt
	        w : volt
	        dirac : volt
	        tau : second
	        v2: volt
	        vTest : volt
	        vTest2 : volt
	        vTest3 : volt
			''')

		directToSomaEqs = Equations('''
			dv/dt = (((-v/mV)+(summedWandDirac/volt))/(tauS))*mV : volt
			tauS = 2*ms : second
			V : volt
			summedWandDirac : volt
			v2 : volt
			spikeFired : boolean
			''')

		class ADDSNeuronModel(NeuronGroup, gupta_paper):
			neuronIndex = self.neuronIndex
			refractoryPointCounter = self.refractoryPointCounter
			calcDirac2 = None
			generalClockDt = self.generalClockDt
			def __init__(self, params):
				NeuronGroup.__init__(self, N=4, model=eqs,threshold='v>10*mV', reset='v=-0.002 * mV; dv=0; v2=10*mV;UmSpikeFired=1*mV;beginRefrac=1*mV;inhibitionVoltage=prelimV',refractory=8*ms,clock=Clock(dt=self.generalClockDt))
				@network_operation(dt=self.generalClockDt)
				def additionToNetwork():
					neuronIndex = self.neuronIndex
					spikeIntervalCounter = (floor(self.time/spikeIntervalUnformatted) * spikeIntervalUnformatted)*10
					def timePeriodAndRefractoryCalcs():
						self.time = self.time + self.timeStepInterval
						self.refractoryPointCounter = self.refractoryPointCounter + self.timeStepInterval
						tT = (Decimal(format(self.time, '.1f')))
						sIU = (Decimal(format(spikeIntervalUnformatted, '.1f')))
						self.spikeIntervalCounter = int(tT/sIU)

						# if statement below corrects for offset of spiketimes starting at .1 sec.
						if self.time >= .1:
							self.neuronIndexCounter = self.neuronIndexCounter + self.timeStepInterval

						self.refractoryPointSwitch = False
						if self.refractoryPointCounter >= spikeIntervalUnformatted:
							self.refractoryPointCounter = 0.000
							self.refractoryPointSwitch = True
							self.lastSpikeInterval = self.time-self.timeStepInterval

						#print '__+++===','time',self.testTime,'refractoryPointCounter',self.testRefractoryPointCounter

					def WeightChangeCalculation(neuronIndex, spiketimes):
						for inputNeuronIndex in range(numberOfPixels):
							## General STDP learning rule implementation ##
							# Find SpikePreSyn if it exists
							# Note: could replace this with use of dictionary data structure for lookups if convenient
							# for processing time later

							# SpikePreSyn not found than make it max distance from SpikePostSyn
							# TODO SpikePreSyn = 0.1 # (Max spike time interval distance)
							# round up to nearest 100ms interval
							SpikePreSyn = self.time*second#(math.ceil(self.time*10)*.1)*second
							# SpikePostSyn not found than make it max distance from SpikePreSyn
							# TODO SpikePostSyn = 0.0
							#SpikePostSyn = 0*ms

							# .003 used below as estimation of value to use when no post syn spike is found and weights should
							# decrease.  Based on the formulas the weights would not decrease, as they are intended to, unless
							# a close post syn spike is used in a prior to the pre syn spike position
							SpikePostSyn = SpikePreSyn-(.001*second)#(math.floor(self.time*10)*.1)*second

							preSynSpikeFound = False
							postSynSpikeFound = False

							spikeCollection = spiketimes
							NumberOfSpikes = shape(spikeCollection)[0]
							for i in range(NumberOfSpikes):
								CurrentSpikeNueron = spikeCollection[i][0]
								CurrentSpikeTime = spikeCollection[i][1]*second

								# exit loop once values below current time elapsed have all been checked
								if CurrentSpikeTime > (self.time*second):
									#print 'eeww1:\tspiketimes\t',spiketimes,'\tqq\t',CurrentSpikeTime
									break

								# (self.time*second-.1*second) is to check if in relevant time window below.
								# Note: that may not be a good cut off and I should check it
								# added < self.time just for testing
								if CurrentSpikeNueron == inputNeuronIndex and CurrentSpikeTime > (self.time*second-.1*second):# and CurrentSpikeTime < (self.time*second):
									SpikePreSyn = CurrentSpikeTime
									preSynSpikeFound = True

								#print 'compare~~\t',CurrentSpikeNueron,inputNeuronIndex,CurrentSpikeTime,(self.time*second-.1*second),preSynSpikeFound

							# Find SpikePostSyn if exists
							#spikeCollection = M.spikes
							spikeCollection = M.it
							#print 'spikeCollection',spikeCollection
							#print 'spikeCollection', spikeCollection
							NumberOfSpikes = len(spikeCollection[0])
							for i in range(NumberOfSpikes):
								#CurrentSpikeNueron = spikeCollection[i][0]
								#CurrentSpikeTime = spikeCollection[i][1]*1000
								CurrentSpikeNueron = spikeCollection[0][i]
								#CurrentSpikeTime = spikeCollection[1][i]*1000
								CurrentSpikeTime = spikeCollection[1][i]#*10

								#print 'CurrentSpikeNueron',CurrentSpikeNueron,'neuronIndex',neuronIndex,'CurrentSpikeTime',CurrentSpikeTime,'self.time*second',self.time*second

								# exit loop once values below current time elapsed have all been checked
								# Disabled due to spikeCollection not being sorted and causing break too early
								#if CurrentSpikeTime > (self.time*second):
								#	break

								# (self.time*second-.1*second) is to check if in relevant time window below.
								# Note: that may not be a good cut off and I should check it
								# * Important difference: CurrentSpikeNueron is compared to self.neuronIndex and not inputNeuronIndex here
								# added (.1+.008)
								if CurrentSpikeNueron == neuronIndex and CurrentSpikeTime >= (self.time*second-(.1+.008)*second) and CurrentSpikeTime <= (self.time*second):
									SpikePostSyn = CurrentSpikeTime
									postSynSpikeFound = True

							if preSynSpikeFound == False or postSynSpikeFound == False:
								SpikePostSyn = SpikePreSyn-(.001*second)
								'''# Todo: watch more for spikes in a relevant time frame but are actually from different synapses and would
								# give different results?  could cause unwanted results? brain would know synapse difference?

								## todo: currently included is logic for processing if SpikePostSyn or SpikePreSyn were not found?

								if preSynSpikeFound == False and postSynSpikeFound == True:
									SpikePreSyn = SpikePostSyn + .1*second
									#print 'F T'
								elif preSynSpikeFound == True and postSynSpikeFound == False:
									# Note: does postSynSpikeFound need to be true for the weight to be allowed to increase?
									SpikePostSyn = SpikePreSyn - .1*second
									#print 'T F'
								#else:
									#print 'F F'''

							#SpikePreSyn = 500*ms
							#SpikePostSyn = 700*ms

							#print 'neuronIndex', neuronIndex, 'inputNeuronIndex',inputNeuronIndex,'self.time', self.time, 'ADDS.v2',ADDS.v2, 'preSynSpikeFound',preSynSpikeFound,'postSynSpikeFound',postSynSpikeFound,'SpikePostSyn', SpikePostSyn, 'SpikePreSyn', SpikePreSyn

							# Find DeltaW
							DeltaW, DeltaSpikeTime = returnDeltaW(SpikePreSyn, SpikePostSyn)

							# Find new weight
							#WOld = W[neuronIndex][inputNeuronIndex];
							WOld = dend[neuronIndex].w[inputNeuronIndex]

							# if statement below skips W change until initial spikes can be formed
							if self.time>.1:
								NewW = returnNewW(WOld, DeltaW, DeltaSpikeTime)
								if NewW < 0: NewW = 0
								elif NewW > 1: NewW = 1
								#W[neuronIndex][inputNeuronIndex] = NewW
								dend[neuronIndex].w[inputNeuronIndex] = NewW*volt
								#print 'time',self.time,'NewW > 0',NewW,(NewW > 0),dend[neuronIndex].w[inputNeuronIndex]
								#print 'changes::','neuronIndex',neuronIndex,'inputNeuronIndex',inputNeuronIndex,'WOld',WOld,'dend[neuronIndex].w[inputNeuronIndex]',dend[neuronIndex].w[inputNeuronIndex],'DeltaW',DeltaW,'DeltaSpikeTime',DeltaSpikeTime

								#dend[neuronIndex].w[inputNeuronIndex] = W[neuronIndex][inputNeuronIndex]*volt
							else:
								dend[neuronIndex].w[inputNeuronIndex] = WOld*volt
							#print 'DeltaW',DeltaW,'WOld',WOld,'WNew',W[neuronIndex][inputNeuronIndex]
							# Reuse existing values below instead of recalculating
							#somaDirect[neuronIndex].w[inputNeuronIndex] = dend[neuronIndex].w[inputNeuronIndex]

					def returnDeltaW(SpikePreSyn, SpikePostSyn):
						DeltaSpikeTime = SpikePreSyn - SpikePostSyn
						DeltaSpikeTime = DeltaSpikeTime/second # remove second units
						# In thesis article it explains if DeltaT = 0 then DeltaW = 0
						DeltaW = 0
						# changed below line from that content in the article for a workaround/temp fix
						#if DeltaSpikeTime < 0:
						if DeltaSpikeTime < 0:
							DeltaW = APlus * (e ** (DeltaSpikeTime / (TauPlus)))
							# for testing
							#DeltaW = 1
						elif DeltaSpikeTime > 0:
							DeltaW = AMinus * (e ** (-DeltaSpikeTime / (TauMinus)))
							# for testing
							#DeltaW = -1

						return DeltaW, DeltaSpikeTime

					def returnNewW(WOld, DeltaW, DeltaSpikeTime):
						WOld = WOld/volt # remove volt unit
						WMin = 0;WMax = 1;
						# Check for inhibition occurence.  DeltaSpikeTime > 0 is used to represent inhibition synapse presence
						if DeltaSpikeTime > 0:
							WMin = -1;WMax = 0;
						#print 'DeltaW', DeltaW
						## Relaxation rule implementation ##
						if DeltaW < 0:
							WNew = WOld + ((LearningRate * (DeltaW * (WOld - WMin)))*self.negativeWeightReinforcement)

						elif DeltaW >= 0:
							WNew = WOld + ((LearningRate * (DeltaW * (WMax - WOld)))*self.positiveWeightReinforcement)

						return WNew;
						##

					def tauDCalc(neuronIndex, dendObject, W):
						# Weight loop
						for WIndex in range(len(W[0][:])):
							if abs(W[neuronIndex][WIndex]) <= 1:
								dendObject[neuronIndex].tau[WIndex] = (tauMax - abs(W[neuronIndex][WIndex])*(tauMax-tauMin)) * ms# .001 is scaling factor found in dirac method

						#return dendObject[neuronIndex].tau

					def resistanceCalc(neuronIndex, dendObject, R):
						tauM = self.tauM
						#Resistance loop
						for RIndex in range(len(R[0][:])):
							if (dendObject[neuronIndex].tau[RIndex]*.001 == tauM*second):
								# avoid a division by 0 issue
								tauM = tauM - .000001

							dendObject[neuronIndex].r[RIndex] = (((((dendObject[neuronIndex].tau[RIndex]*.001)/ms)*neuronFiringThreshold) / Rm) * ((tauM / ((dendObject[neuronIndex].tau[RIndex]/ms)*.001) ) ** (tauM / (tauM - ((dendObject[neuronIndex].tau[RIndex]/ms)*.001) ))))*volt

						#return dendObject[neuronIndex].r

					def dendCalcs(neuronIndex, ADDSObj, dendObj, spiketimes, evaluationActive):
						# Below sequentially Dirac, Tau, then Resistance are calculated every end of a spike-time interval.
						# The resulting Dend I is added to the Um calc for the ADDS soma.

						# Dirac
						dendObj[neuronIndex].dirac = diracCalc(dendObj, neuronIndex, spiketimes)

						# Initialize weights
						if self.time == 0.000:
							dend[neuronIndex].w = W[neuronIndex]*volt
							#for inputNeuronIndex in range(numberOfPixels):
							#	dend[neuronIndex].w[inputNeuronIndex] = W[neuronIndex][inputNeuronIndex]*volt
							#print 'W::',W

						if (evaluationActive==False and self.refractoryPointSwitch==True):
							# Only change weights of neuron fired
							if ADDSObj.UmSpikeFired[neuronIndex] == 1*mV:
								# Weights
								WeightChangeCalculation(neuronIndex, spiketimes)
								#print 'w',' time',self.time,'\t',dendObj[0].w,'\t',dendObj[1].w,'\t',dendObj[2].w,'\t',dendObj[3].w
							# Tau
							tauDCalc(neuronIndex, dendObj, self.W)
							# Resistance
							resistanceCalc(neuronIndex, dendObj, self.R)

						# TODO: check do I need additional loop below?
						for indexOfDend in range(dictionaryLongitude):
							ADDSObj.DendI[indexOfDend] = sum(dendObj[indexOfDend].v[:])*self.dendCalcScaling

					def somaDirectCalcs(neuronIndex, ADDSObj, somaDirectObj, dendObj):
						# somaDirect calcs
						# Reuse existing dendtrite values below instead of recalculating
						dotProductWandDirac =  sum(w*d for w,d in zip(dendObj[neuronIndex].w[:], dendObj[neuronIndex].dirac[:]))
						somaDirectObj.summedWandDirac[neuronIndex] = ((dotProductWandDirac*volt)/(volt**2))*self.somaDirectScaling
						#print 'test::_::somaDirectObject.summedWandDirac',somaDirectObject.summedWandDirac
						#print 'test::_::somaDirectObject.v',somaDirectObject.v
						#print 'test::_::testSomaDirect.v',testSomaDirect.v

						for neuronNumber in range(dictionaryLongitude):
							ADDSObj.SynI[neuronNumber] = somaDirectObj.v[neuronNumber]

					def diracCalc(IDend, neuronIndex, spiketimes):
						time = self.time
						e = math.e
						lastSpikeInterval = self.lastSpikeInterval
						dend = IDend
						# This method only calculates dirac
						# dirac function helps allow or disallow signal to be sent from input neurons through dendrite nodes or not
						# converted Dt to units which seem to make more sense for dirac function used, not sure if right to do that
						# though

						#print ':::range(len(IDend[neuronIndex][:])\t','self.time\t',self.time,'\tneuronIndex\t',neuronIndex,'\t',IDend,'\t',IDend[neuronIndex]

						dendGroup = [None]*len(dend[neuronIndex][:])
						for IdIndex in range(len(dend[neuronIndex][:])):
							# set tpresyn initially too far to trigger dirac
							tPreSyn = time + 1
							for presynInd in range(shape(spiketimes)[0]):
								comparedSpikeTime = spiketimes[presynInd][1]

								if comparedSpikeTime > (lastSpikeInterval + spikeIntervalUnformatted):
									break

								#print ':::',comparedSpikeTime,self.lastSpikeInterval,spikeIntervalUnformatted
								##print 'comparedSpikeTime:::\t',comparedSpikeTime,'\t::self.lastSpikeInterval\t',self.lastSpikeInterval,'spikeIntervalUnformatted',spikeIntervalUnformatted,'spiketimes[presynInd][0] == IdIndex',(spiketimes[presynInd][0] == IdIndex),'IdIndex',IdIndex,'comparedSpikeTime > self.lastSpikeInterval',(comparedSpikeTime > self.lastSpikeInterval),'comparedSpikeTime <= (self.lastSpikeInterval + spikeIntervalUnformatted)',(comparedSpikeTime <= (self.lastSpikeInterval + spikeIntervalUnformatted))
								'''# spikeIntervalUnformatted included below to have offset where skiping input starting at .1 becomes 0.0, representing input spikes right from
								# 0.0 to 300 ms, as the input is intended'''
								# checking prior interval for a spike.  This looks for a spike in the prior spike time interval
								#if spiketimes[presynInd][0] == IdIndex and comparedSpikeTime > lastSpikeInterval and comparedSpikeTime <= (lastSpikeInterval + spikeIntervalUnformatted):
								#print 'IdIndex',IdIndex,'time',time,'comparedSpikeTime',comparedSpikeTime,'time == comparedSpikeTime',(time == comparedSpikeTime),'(Decimal(format(time, .8f)) == Decimal(format(comparedSpikeTime, .8f)))',(Decimal(format(time, '.8f')) == Decimal(format(comparedSpikeTime, '.8f'))),Decimal(format(time, '.8f')),Decimal(format(comparedSpikeTime, '.8f'))
								#if comparedSpikeTime > .89:
								#	print 'compare',(math.floor(1000*Decimal(format(time, '.3f')))*.001 == math.floor(1000*Decimal(format(comparedSpikeTime, '.3f')))*.001),math.floor(1000*Decimal(format(time, '.3f')))*.001,math.floor(1000*Decimal(format(comparedSpikeTime, '.3f')))*.001
								if spiketimes[presynInd][0] == IdIndex and (math.floor(1000*Decimal(format(time, '.3f')))*.001 == math.floor(1000*Decimal(format(comparedSpikeTime, '.3f')))*.001):
									## adding below if statement to avoid constant input stimulus in evaluation, that seem more accurate to how the article did it's evaulation.
									# normalize t to count just time within spike interval.  Formula from article just uses t in a way
									# relative to the spike interval it seems and therefore that approach is applied to the formula here.
									#tNorm = time - (floor((time/.001)*.01) * .1)
									#print 'tNorm',tNorm
									#if tNorm <= .01:# or evaluationActive == False:
									#if True:
										#tPreSyn = comparedSpikeTime
									tPreSyn = comparedSpikeTime
									#print '++++++++++',time,'+++++++++++','spiketimesnumber',spiketimes[presynInd][0],'neuronIndex',neuronIndex,'spiketimestime',(math.floor(1000*Decimal(format(comparedSpikeTime, '.3f')))*.001),'comparedSpikeTime',comparedSpikeTime

							Dt = Decimal(format(time, '.8f')) - Decimal(format(tPreSyn, '.8f'))
							#print '++++++++Dt++++++++',Dt,'Decimal(format(time, .8f))',Decimal(format(time, '.8f')),'Decimal(format(tPreSyn, .8f))',Decimal(format(tPreSyn, '.8f'))
							# self.time == lines below are a workaround for initialization values
							if self.time == 0.121 or self.time == 0.421 or self.time == 0.721 or self.time == 1.021:
								Dt = 1.0
							##print 'neuronIndex',neuronIndex,'tPreSyn000:',tPreSyn,'Dt',Dt

							# simplify dirac until later.  TODO: try more comple dirac
							#if (t > -(Dt/2) and t < (Dt/2)):
							# Seems to me what is looked for here is that the post synapse (output neurons) is after the pre synapse (input neurons)
							if Dt >= 0.0:
								if self.time == 0.121 or self.time == 0.421 or self.time == 0.721 or self.time == 1.021:
									DiracFun = 1.00
								if Dt != 0:
									# A closer post to pre synapse creates a larger dirac signal for greater weight I'd suppose than
									# a further distance below in DiracFun
									#Dt = Dt*1000
									DiracFun = 1/Dt
								else:
									DiracFun = 1#1000
								DiracFun = 1
								#dendGroup[IdIndex] = float(DiracFun)*volt*.001*self.diracScaling #add .001 as scaling factor.
								# Scaling factor of .001 could account for 1ms being present instead of .001 as it is computed, difference of
								# 1/1 compared to 1/.001
								dendGroup[IdIndex] = float(DiracFun)*volt*self.diracScaling
							else:
								DiracFun = 0
								dendGroup[IdIndex] = float(DiracFun)*volt*.001*self.diracScaling

						return dendGroup

					def checkForResets(neuronIndex, dendObj, somaDirectObj):
						if self.v2[neuronIndex] == 10*mV:
							self.v2[neuronIndex] = 0*mV
							'''dendObj[neuronIndex].dirac = [0*volt]*len(dendObj[neuronIndex][:])
							dendObj[neuronIndex].v = [0*volt]*len(dendObj[neuronIndex][:])	'''
							# Activate lateral inhibition upon a spike to inhibit other neurons from spiking.  As brian's
							# Inhibition inhibits before dend input can increase the signal to balance things out.
							#ADDS.lateralInhibition()
						elif self.v[neuronIndex] != -0.002 * mV:
							self.v2[neuronIndex] = self.v[neuronIndex]

						if somaDirectObj.v2[neuronIndex] == 10*mV:
							somaDirectObj.v2[neuronIndex] = 0*mV

						if (self.beginRefrac[neuronIndex] == 1*mV) and (self.relRefracTimeActivated[neuronIndex] == False):
							self.beginRefrac[neuronIndex] = 0*mV
							self.absRefracTime[neuronIndex] = self.time
							self.relRefracTime[neuronIndex] = self.time
							self.absRefracTimeActivated[neuronIndex] = True
							self.relRefracTimeActivated[neuronIndex] = True
						# Absolute refrac
						if self.absRefracTimeActivated[neuronIndex] == True:
							'''testADDS.v[neuronIndex] = -0.002*mV
							testADDS.DendI[neuronIndex] = -0.002*mV
							testADDS.SynI[neuronIndex] = -0.002*mV
							testDend[neuronIndex].dirac = [0*volt]*len(dend[neuronIndex][:])'''
							if self.time >= (self.absRefracTime[neuronIndex] + self.absRefracTimeDuration):
								self.absRefracTimeActivated[neuronIndex] = False
						#print 'neuronIndex', neuronIndex, 'abs',self.testAbsRefracTimeActivated[neuronIndex],'self.testTime',self.testTime,'self.beginRefrac[neuronIndex]',self.beginRefrac[neuronIndex],'self.relRefracTimeDuration/ms',(self.absRefracTimeDuration/ms),'self.testAbsRefracTime[neuronIndex] + self.absRefracTimeDuration',(self.testAbsRefracTime[neuronIndex] + self.absRefracTimeDuration)
						# Relative refrac
						if self.relRefracTimeActivated[neuronIndex] == True:
							'''if testADDS.v[neuronIndex] > -0.002 * mV:
								testADDS.v[neuronIndex] = -0.002*mV
								testADDS.DendI[neuronIndex] = -0.002*mV
								testADDS.SynI[neuronIndex] = -0.002*mV
								testDend[neuronIndex].dirac = [0*volt]*len(dend[neuronIndex][:])'''
							if self.time >= (self.relRefracTime[neuronIndex] + self.relRefracTimeDuration):
								self.relRefracTimeActivated[neuronIndex] = False
						#print 'neuronIndex', neuronIndex, 'rel',self.testRelRefracTimeActivated[neuronIndex],'self.testTime',self.testTime,'self.testRelRefracTime[neuronIndex]',self.testRelRefracTime[neuronIndex],'self.relRefracTimeDuration/ms',(self.relRefracTimeDuration/ms),'self.testRelRefracTime[neuronIndex] + self.relRefracTimeDuration',(self.testRelRefracTime[neuronIndex] + self.relRefracTimeDuration)

					def printOutputForTesting():
						print 'neuronIndex',self.neuronIndex,'time',self.time
						print 'dend.dirac',dend[0].dirac,dend[1].dirac,dend[2].dirac,dend[3].dirac
						print 'dend.v',dend[0].v,dend[1].v,dend[2].v,dend[3].v
						print 'sum(dend[neuronIndex].v[:])',sum(dend[neuronIndex].v[:])
						print 'Prelim Weights\n0',dend[0].w[:]
						print '1',dend[1].w[:]
						print '2',dend[2].w[:]
						print '3',dend[3].w[:]
						print 'Prelim Tau\n0',dend[0].tau[:]
						print '1',dend[1].tau[:]
						print '2',dend[2].tau[:]
						print '3',dend[3].tau[:]
						print 'Prelim Res\n0',dend[0].r[:]
						print '1',dend[1].r[:]
						print '2',dend[2].r[:]
						print '3',dend[3].r[:]

					def sortVersionOutForTesting(ADDSObj, dendObj):
						print 'ADDSObj.v','time',self.time,ADDSObj.v[0],ADDSObj.v[1],ADDSObj.v[2],ADDSObj.v[3]
						print 'sum(ADDSObj.DendI)',(ADDSObj.DendI[0]),(ADDSObj.DendI[1]),(ADDSObj.DendI[2]),(ADDSObj.DendI[3])
						print 'sum(ADDSObj.SynI)',sum(ADDSObj.SynI[0]),sum(ADDSObj.SynI[1]),sum(ADDSObj.SynI[2]),sum(ADDSObj.SynI[3])

					def printEvalOutputForTesting(ADDSObj, dendObj):
						'''print 'dendObj.dirac','time',self.time,'\t',dendObj[0].dirac,dendObj[1].dirac,dendObj[2].dirac,dendObj[3].dirac
						print 'dendObj.v',dendObj[0].v,dendObj[1].v,dendObj[2].v,dendObj[3].v
						print 'sum(dendObj[neuronIndex])',sum(dendObj[0].v[:]),sum(dendObj[1].v[:]),sum(dendObj[2].v[:]),sum(dendObj[3].v[:])
						print 'testSomaDirect.v',testSomaDirect.v[0],testSomaDirect.v[1],testSomaDirect.v[2],testSomaDirect.v[3]
						print 'sum(testSomaDirect[neuronIndex])',sum(testSomaDirect.v[0]),sum(testSomaDirect.v[1]),sum(testSomaDirect.v[2]),sum(testSomaDirect.v[3])
						print 'testSomaDirect.summedWandDirac',testSomaDirect.summedWandDirac[0],testSomaDirect.summedWandDirac[1],testSomaDirect.summedWandDirac[2],testSomaDirect.summedWandDirac[3]					'''
						print 'ADDSObj.v',ADDSObj.v[0],ADDSObj.v[1],ADDSObj.v[2],ADDSObj.v[3]
						print 'dendObj.dirac','time',self.time,'\t',dendObj[0].dirac,dendObj[1].dirac,dendObj[2].dirac,dendObj[3].dirac
						print 'dendObj.v',dendObj[0].v,dendObj[1].v,dendObj[2].v,dendObj[3].v
						print 'v',dendObj[0].v,'\t',dendObj[1].v
						print 'r',dendObj[0].r,'\t',dendObj[1].r
						print 'w',' time',self.time,'\t',dendObj[0].w,'\t',dendObj[1].w,'\t',dendObj[2].w,'\t',dendObj[3].w
						print 'dirac',dendObj[0].dirac,'t',dendObj[1].dirac
						print 'tau',dendObj[0].tau,'t',dendObj[1].tau
						print 'units',(-dendObj[1].v[0]/mV),((dendObj[1].r[0])/volt),((dendObj[1].w[0])/volt),(dendObj[1].dirac[0]/volt),(dendObj[1].tau[0]/second)
						dendObj[1].vTest[0] = (((-dendObj[1].v[0]/mV)+(((dendObj[1].r[0])/volt)*((dendObj[1].w[0])/volt)*(dendObj[1].dirac[0]/volt)))/(dendObj[1].tau[0]/second))*volt#((-dendObj[1].v[0]/volt)+((dendObj[1].r[0])/volt))/(dendObj[1].tau[0]/ms)*volt
						dendObj[1].vTest2[0] = -dendObj[1].v[0]
						dendObj[1].vTest3[0] = ((dendObj[1].r[0]/volt)*(dendObj[1].w[0]/volt)*(dendObj[1].dirac[0]))/(dendObj[1].tau[0]/second)
						print 'vTest',dendObj[1].vTest[0]
						print 'vTest2',dendObj[2].vTest2[0],dendObj[1].v[0],dendObj[1].v[1],dendObj[1].v[2],dendObj[1].v[3],dendObj[1].v[4]
						print 'vTest3',dendObj[3].vTest3[0]
						#print 'rs equ',(-dendObj[0].v+((dendObj[0].r)*(dendObj[0].w)*dendObj[0].dirac)/(dendObj[0].tau)),'\t',(-dendObj[1].v+((dendObj[1].r)*(dendObj[1].w)*dendObj[1].dirac)/(dendObj[1].tau))
						print 'sum(ADDSObj.DendI)',(ADDSObj.DendI[0]),(ADDSObj.DendI[1]),(ADDSObj.DendI[2]),(ADDSObj.DendI[3])
						print 'sum(ADDSObj.SynI)',sum(ADDSObj.SynI[0]),sum(ADDSObj.SynI[1]),sum(ADDSObj.SynI[2]),sum(ADDSObj.SynI[3])
						print 'ADDSObj.UmSpikeFired',ADDSObj.UmSpikeFired[0],ADDSObj.UmSpikeFired[1],ADDSObj.UmSpikeFired[2],ADDSObj.UmSpikeFired[3]
						#print 'Prelim Weights\n0',dendObj[0].w[:]
						#print '1',dendObj[1].w[:]
						#print '2',dendObj[2].w[:]
						#print '3',dendObj[3].w[:]
						#print 'Prelim Tau\n0',dendObj[0].tau[:]
						#print '1',dendObj[1].tau[:]
						#print '2',dendObj[2].tau[:]
						#print '3',dendObj[3].tau[:]
						#print 'Prelim Res\n0',dendObj[0].r[:]
						#print '1',dendObj[1].r[:]
						#print '2',dendObj[2].r[:]
						#print '3',dendObj[3].r[:]
						print '____________________'

					def evaluateClassifier(ADDSObj):
						# Negative results below are only set to be measured after a full spike interval has passed and had the opportunity to have created a spike
						# (self.testSpikeIntervalCounter-1) is to correct for refractoryPointSwitch occuring after spikeInterval it addresses.
						#print 'refractoryPointSwitch = ', self.refractoryPointSwitch
						#print 'refractoryPointSwitch =', self.testRefractoryPointSwitch
						#if self.refractoryPointSwitch == true and (self.testSpikeIntervalCounter > 0):
						if self.refractoryPointSwitch == True:# and (self.testSpikeIntervalCounter > 0):
							'''print 'self.testTime',self.testTime,'refrac on'
							tT = (Decimal(format(self.testTime, '.1f')))
							sIU = (Decimal(format(spikeIntervalUnformatted, '.1f')))
							timeDiff = tT/sIU
							print 'recalc testT',(floor(self.testTime/spikeIntervalUnformatted) * spikeIntervalUnformatted)*10,'floor(self.testTime/spikeIntervalUnformatted)',floor(self.testTime/spikeIntervalUnformatted),'floor with decimal',int(timeDiff)'''
						#if self.testSpikesFiredInInterval[neuronIndex][self.testSpikeIntervalCounter-1] == False:
							# Only evaluate results for enough epochs to test each char in input (3 spike interv per char * 4 char = 12 spike intervals total)
							# the +1 in (self.timeotalSpikeIntervals+1) is to allow a last refractoryPointSwitch triggered negative spike evaluation to occur.
							# * A change was made to the scoring to not count the 0.0-0.1 second period because the input spike generator does not start until .1
							# seconds and the first occurences of output spikes should be monitored looking at .2 seconds to see if any occured in seconds .1-.2 .
							if (self.spikeIntervalCounter >= 2) and (self.spikeIntervalCounter <= (self.totalSpikeIntervals+1)):
								for neuronIndex in range(dictionaryLongitude):
									#print 'eval','self.testSpikeIntervalCounter\t',self.testSpikeIntervalCounter
									if (ADDSObj.UmSpikeFired[neuronIndex] == 1*mV):# and (self.testSpikeIntervalCounter < self.totalSpikeIntervals):
										if (self.correctSpikes[neuronIndex][(self.spikeIntervalCounter-1)] == 1):
											self.truePositiveSpikeResults = self.truePositiveSpikeResults + 1
											print 'TP found\t','self.testSpikeIntervalCounter-1\t',self.spikeIntervalCounter-1,'neuronIndex\t',neuronIndex
										else:
											self.falsePositiveSpikeResults = self.falsePositiveSpikeResults + 1
										#self.testSpikesFiredInInterval[neuronIndex][self.testSpikeIntervalCounter] = True
									elif (ADDSObj.UmSpikeFired[neuronIndex] == 0*mV):# and (self.testSpikeIntervalCounter < self.totalSpikeIntervals):
										if (self.correctSpikes[neuronIndex][(self.spikeIntervalCounter-1)] == 1):
											self.falseNegativeSpikeResults = self.falseNegativeSpikeResults + 1
										else:
											self.trueNegativeSpikeResults = self.trueNegativeSpikeResults + 1
							for neuronIndex in range(dictionaryLongitude):
								ADDSObj.UmSpikeFired[neuronIndex] = 0*mV
									#print 'results',self.truePositiveSpikeResults,self.falsePositiveSpikeResults,self.trueNegativeSpikeResults,self.falseNegativeSpikeResults

					def intitializeTrainedModelParameters(dendObj):
						# Below values that have been created through prior training are used for w, tau, and r
						testW = [[0.90433194, 0.6139531, 0.50387484, 0.55220372, 0.51213536, 0.85443374, 0.99955922, 0.5039825, 0.73091913, 0.9780236, 0.5241028, 0.71571812, 0.93782861, 0.51210244, 0.73074697],
						[0., 0., 0.03412608, 0., 0.90455366, 0.78683668, 0., 0.95912629, 0.7282637, 0., 0.78548583, 0.78935491, 0.03193823, 0.00609877, 0.17287094],
						[0.4474444, 0., 0.98135641, 0., 0.96315942, 0., 0., 0., 0.15930208, 0., 0.77299245, 0., 0., 0.71739497, 0.02804206],
						[0., 0., 0.99815102, 0., 0.9239562, 0., 0., 0.32862838, 0.29682383, 0., 0.85108903, 0., 0., 0., 0.6687179]]

						testTauD = [[4.67870579, 12.80931328, 15.89150457, 14.53829579, 15.66020997, 6.07585538, 2.0123418, 15.88849011, 9.53426444, 2.6153392, 15.32512158, 9.95989264, 3.74079895, 15.66113156, 9.53908472],
						[9.38715598, 12.1194624, 9.34555739, 6.3921125, 5.61075905, 4.06988445, 9.88122329, 3.54626289, 6.41301412, 7.44992802, 10.1151254, 3.96914614, 3.41772316, 4.7844492, 2.40562813],
						[14.7112847, 14.11913186, 2.72683686, 10.43654572, 3.43626265, 14.20735373, 9.15843206, 4.26396028, 3.39998086, 7.5902114, 10.85008977, 7.25459199, 9.86718901, 3.20308826, 10.16608643],
						[15.97156807, 4.01171718, 2.07503494, 9.24935852, 5.08599601, 13.72643044, 13.71622469, 7.36132416, 5.76486557, 7.2621342, 8.04307852, 11.68604314, 5.89535223, 10.07822174, 15.44403097]]

						testR = [[5.28618924, 7.07014396, 7.67118426, 7.41054119, 7.62696131, 5.62546396, 4.55408732, 7.67060873, 6.39667963, 4.73398033, 7.56265998, 6.48674751, 5.04468982, 7.62713777, 6.39770456],
						[6.36534465, 6.93170042, 6.35646422, 5.69956225, 5.51481581, 5.13093667, 6.47016506, 4.99283791, 5.70442879, 5.94150634, 6.51938451, 5.10472189, 4.95819508, 5.31264187, 4.67271447],
						[7.44412582, 7.32882233, 4.76605631, 6.58661617, 4.96321112, 7.34606257, 6.31640753, 5.1810014, 4.95338848, 5.97297472, 6.67245869, 5.8974608, 6.46720377, 4.89962516, 6.53007485],
						[7.68646215, 5.11581952, 4.57335711, 6.33589406, 5.38732016, 7.25180984, 7.2498024, 5.92156072, 5.55171007, 5.89916649, 6.07367171, 6.84386832, 5.58276752, 6.51163569, 7.58550996]]
						#print 't:',self.time,'neuronIndex',neuronIndex,'main calc resistanceCalc()', testR

						for indexOfDend in range(dictionaryLongitude):
							# TODO see if some of these do not need to be recomputed every time
							dendObj[indexOfDend].w = testW[indexOfDend]*volt*self.weightScaling
							dendObj[indexOfDend].tau = testTauD[indexOfDend]*ms # unclear if dividing by tau in ms is e.x. /.02 or /20 but it is assumed to be 20, therefore no ms conversion here
							dendObj[indexOfDend].r = testR[indexOfDend]*mV	# volt unit is cancelled out in the equation anyhow, doesn't matter if it is volt or mV due to being cancelled.  Having mv could cause *.001 that is now wanted

					def lateralInhibition(ADDSObj):
						'''spikeOccured = False;inhibitionVoltage = None
						for neuronIndex3 in range(dictionaryLongitude):
							if ADDSObj.UmSpikeFired[neuronIndex3] == 1*mV:
								spikeOccured = True
								inhibitionVoltage = ADDSObj.inhibitionVoltage[neuronIndex3]#*self.lateralInhibitScaling'''

						tNorm = self.time - (floor((self.time/.001)*.01) * .1)
						preliminaryV = [None]*dictionaryLongitude
						greatestMembraneVoltage = 0.0
						greatestNeuronNumber = None
						neuronNumberFired = None
						neuronNumberAboutToFire = None

						for neuronIndex in range(dictionaryLongitude):
							preliminaryV[neuronIndex] = ADDSObj.prelimV[neuronIndex]
						for neuronIndex in range(dictionaryLongitude):
							for neuronIndex2 in range(len(preliminaryV)):
								if (neuronIndex != neuronIndex2) and (preliminaryV[neuronIndex2] >= greatestMembraneVoltage):
									#print 'comparison','neuronIndex',neuronIndex,' ',preliminaryV[neuronIndex],'neuronIndex2',neuronIndex2,' ',preliminaryV[neuronIndex2],'greatestMembraneVoltage',greatestMembraneVoltage,preliminaryV[neuronIndex2] >= greatestMembraneVoltage
									greatestMembraneVoltage = preliminaryV[neuronIndex2]
									greatestNeuronNumber = neuronIndex
								if ADDSObj.UmSpikeFired[neuronIndex2] == 1*mV: neuronNumberFired = neuronIndex2

							if (greatestMembraneVoltage >= (10*mV)) and (neuronNumberFired == None):
								self.voltageForInhibition = greatestMembraneVoltage - (10*mV)
								neuronNumberAboutToFire = greatestNeuronNumber
								self.inhibitionReduction = 1.0
							elif (greatestMembraneVoltage < (10*mV)) and (neuronNumberFired == None):
								self.voltageForInhibition = greatestMembraneVoltage
							else:
								self.voltageForInhibition = 70*mV#(70*mV/self.inhibitionReduction)
								if self.inhibitionReduction < 150: self.inhibitionReduction += 0.01
							#print 'time',self.time,'neuronIndex',neuronIndex,'neuronIndex2',neuronIndex2,'self.voltageForInhibition',self.voltageForInhibition,(70*mV/self.inhibitionReduction),'self.inhibitionReduction',self.inhibitionReduction

						for neuronIndex in range(dictionaryLongitude):
							if neuronIndex != neuronNumberAboutToFire and neuronIndex != neuronNumberFired:
								#if self.voltageForInhibition > 0: preliminaryV[neuronIndex] -= self.voltageForInhibition
								#else: preliminaryV[neuronIndex] += self.voltageForInhibition
								preliminaryV[neuronIndex] -= abs(self.voltageForInhibition)
								ADDSObj.v[neuronIndex] = preliminaryV[neuronIndex]

							'''for neuronIndex2 in range(dictionaryLongitude):
								#inhibitionVoltage = ADDSObj.prelimV[neuronIndex2]*self.lateralInhibitScaling
								#if ADDSObj.UmSpikeFired[neuronIndex2] == 1*mV:
								#	inhibitionVoltage = ADDSObj.inhibitionVoltage[neuronIndex2]#*self.lateralInhibitScaling
								if neuronIndex != neuronIndex2 and self.voltageForInhibition > 0:
									preliminaryV[neuronIndex] -= inhibitionVoltage'''
							#print 'self.time',self.time,'neuronIndex',neuronIndex,'self.voltageForInhibition',self.voltageForInhibition,'ADDSObj.prelimV[neuronIndex]',ADDSObj.prelimV[neuronIndex],'preliminaryV[neuronIndex]',preliminaryV[neuronIndex],'ADDSObj.v[neuronIndex]',ADDSObj.v[neuronIndex],'ADDSObj.UmSpikeFired[neuronIndex]',ADDSObj.UmSpikeFired[neuronIndex]
							#if ADDSObj.UmSpikeFired[neuronIndex] != 1*mV: ADDSObj.v[neuronIndex] = preliminaryV[neuronIndex]

					def mainSimulationCalcs(ADDSObj, dendObj, somaDirectObj, spiketimes, evaluationActive):
						# dend then somaDirect calcs are done which are then used to set lat inhib.
						# Soma Um calcs are done automatically using equations entered for brian
						# once dend and somaDirect are updated
						tNorm = self.time - (floor((self.time/.001)*.01) * .1)
						#print 'tNorm',tNorm

						timePeriodAndRefractoryCalcs()

						#print 'time',self.time,'brian time',self.t

						if (evaluationActive==True):
							intitializeTrainedModelParameters(dendObj)

						# Option to accelerate computations for training
						if self.accelerateTraining == False or (evaluationActive == False and tNorm <= .005 or tNorm >= .096):
							if self.accelerateTraining == True and (tNorm >= .096 and tNorm < .099):
								for i in range(dictionaryLongitude):
									ADDSObj.DendI[i]=0*mV
									ADDSObj.SynI[i]=0*mV
									ADDSObj.prelimV[i]=0*mV
									ADDSObj.v[i]=0*mV#-65*mV#0*mV
									for i2 in range(len(dend[0].v)):
										dendObj[i].v[i2] = 0*mV
									somaDirectObj.v[i] = 0*mV

							#print 'running'
							for neuronIndex in range(dictionaryLongitude):
								dendCalcs(neuronIndex, ADDSObj, dendObj, spiketimes, evaluationActive)

								somaDirectCalcs(neuronIndex, ADDSObj, somaDirectObj, dendObj)
								# Lat inhib.
								#S.w[:]=(-1*(ADDSObj.Rm/mV*(ADDSObj.DendI[neuronIndex]+ADDSObj.SynI[neuronIndex])))*self.lateralInhibitScaling2

								#checkForResets(neuronIndex, dendObj, somaDirectObj)

							lateralInhibition(ADDSObj)

							for neuronIndex in range(dictionaryLongitude): checkForResets(neuronIndex, dendObj, somaDirectObj)

							evaluateClassifier(ADDSObj)

							'''if tNorm <= 0.003 or tNorm >= .099:
								printEvalOutputForTesting(ADDS, dend)
							else:
								sortVersionOutForTesting(ADDS, dend)'''

					if self.evaluateClassifier == False:
						mainSimulationCalcs(ADDS, dend, somaDirect, self.trainingSpikeTimes, False)
					else:
						mainSimulationCalcs(ADDS, dend, somaDirect, self.spiketimes, True)

				self.contained_objects.append(additionToNetwork)

			def OutputEvaluationResults(self, dendObj):
				print 'Final Weights\n',dendObj[0].w[:]/volt
				print dendObj[1].w[:]/volt
				print dendObj[2].w[:]/volt
				print dendObj[3].w[:]/volt
				print 'Final Tau\n',dendObj[0].tau[:]/ms
				print dendObj[1].tau[:]/ms
				print dendObj[2].tau[:]/ms
				print dendObj[3].tau[:]/ms
				print 'Final Res\n',dendObj[0].r[:]/mV
				print dendObj[1].r[:]/mV
				print dendObj[2].r[:]/mV
				print dendObj[3].r[:]/mV
				print '\n'
				print '+++ Results +++'
				print 'Spike results: TP:\t',self.truePositiveSpikeResults,'\tFP:\t',self.falsePositiveSpikeResults,'\tTN:\t',self.trueNegativeSpikeResults,'\tFN:\t',self.falseNegativeSpikeResults
				print 'totalSpikeIntervalsTested:\t',self.totalSpikeIntervals,'\ttotalCharsPresented:\t',dictionaryLongitude
				print 'True positives correct percentage (TP/totalSpikeIntervalsTested):\t',Decimal(format(self.truePositiveSpikeResults, '.1f'))/Decimal(format(self.totalSpikeIntervals, '.1f')),'\t(this is the percentage of all true positves that were found)'
				print 'Total correct percentage (TP+TN/(totalSpikeIntervals*totalCharsPresented)):\t',(Decimal(format(self.truePositiveSpikeResults, '.1f'))+Decimal(format(self.trueNegativeSpikeResults, '.1f')))/(Decimal(format(self.totalSpikeIntervals, '.1f'))*Decimal(format(dictionaryLongitude, '.1f')))
				print '+++++++++++++++'

		class DendriteNeuronModel(NeuronGroup):
			generalClockDt = self.generalClockDt
			def __init__(self, params):
				NeuronGroup.__init__(self, N=15, model=dendriteEqs,clock=Clock(dt=self.generalClockDt))
				@network_operation(dt=self.generalClockDt)
				def additionToNetwork():
					placeHolderForLaterContent = True
				self.contained_objects.append(additionToNetwork)

		class SomaDirectNeuronModel(NeuronGroup):
			generalClockDt = self.generalClockDt
			def __init__(self, params):
				NeuronGroup.__init__(self, N=4, model=directToSomaEqs,clock=Clock(dt=self.generalClockDt))
				@network_operation(dt=self.generalClockDt)
				def additionToNetwork():
					placeHolderForLaterContent = True
						#print 'DtSoma','testTime',self.t,'\tneuronIndex',neuronIndex,'\tself.v[neuronIndex]',self.v[neuronIndex],'\tsummedWandDirac',self.summedWandDirac
				self.contained_objects.append(additionToNetwork)

		dend = [None]*dictionaryLongitude
		testDend = [None]*dictionaryLongitude
		for firstLayerIndex in range(dictionaryLongitude):
			dend[firstLayerIndex] = DendriteNeuronModel(15)
			testDend[firstLayerIndex] = DendriteNeuronModel(15)
		somaDirect = SomaDirectNeuronModel(4)
		testSomaDirect = SomaDirectNeuronModel(4)

		'''if self.evaluateClassifier == False:
			ADDS = ADDSNeuronModel(self)
		else:
			testADDS = ADDSNeuronModel(self)'''

		ADDS = ADDSNeuronModel(self)

		# Synapses for lateral inhibition
		#S=Synapses(testADDS,testADDS,model='w:volt',pre='v+=w')
		'''S=Synapses(ADDS,ADDS,model='w:volt',pre='v+=w',clock=Clock(dt=self.generalClockDt))
		S.connect('i != j')
		S.w[:]=0.0*volt	'''

		M = SpikeMonitor(ADDS)
		self.M = M # for ipython compatibility
		#testM = SpikeMonitor(testADDS)
		#self.testM = testM
		Mv = StateMonitor(ADDS, 'V', record=True)
		#testMv = StateMonitor(testADDS, 'v', record=True)
		MDendI = StateMonitor(ADDS, 'DendI', record=True)
		MSynI = StateMonitor(ADDS, 'SynI', record=True)
		UmM3 = StateMonitor(ADDS, 'v2', record=True)
		self.UmM3 = UmM3 # for ipython compatibility
		#testUmM3 = StateMonitor(testADDS, 'v2', record=True)
		#self.testUmM3 = testUmM3
		somaDirectM = SpikeMonitor(somaDirect)
		somaDirectMv = StateMonitor(somaDirect, 'V', record=True)
		#synM = StateMonitor(S,'w',record=S['i!=j'])  # all synapses excluding autapses
		dendSpikeM = [None]*dictionaryLongitude
		dendVoltageM = [None]*dictionaryLongitude
		'''testDendSpikeM = [None]*dictionaryLongitude
		testDendVoltageM = [None]*dictionaryLongitude'''
		for firstLayerIndex in range(dictionaryLongitude):
			dendSpikeM[firstLayerIndex] = SpikeMonitor(dend[firstLayerIndex])
			dendVoltageM[firstLayerIndex] = StateMonitor(dend[firstLayerIndex], 'v', record=True)
			'''testDendSpikeM[firstLayerIndex] = SpikeMonitor(dend[firstLayerIndex])
			testDendVoltageM[firstLayerIndex] = StateMonitor(dend[firstLayerIndex], 'v', record=True)'''

			neuralnet.add(dend[firstLayerIndex])
			neuralnet.add(dendSpikeM[firstLayerIndex])
			neuralnet.add(dendVoltageM[firstLayerIndex])
			'''neuralnet.add(testDend[firstLayerIndex])
			neuralnet.add(testDendSpikeM[firstLayerIndex])
			neuralnet.add(testDendVoltageM[firstLayerIndex])	'''

		neuralnet.add(ADDS)
		#neuralnet.add(testADDS)
		neuralnet.add(M)
		#neuralnet.add(testM)
		#neuralnet.add(testMv)
		neuralnet.add(UmM3)
		#neuralnet.add(testUmM3)
		neuralnet.add(somaDirect)
		#neuralnet.add(testSomaDirect)
		neuralnet.add(somaDirectM)
		neuralnet.add(somaDirectMv)
		#neuralnet.add(S)

		#neuralnet.add(synM)
		#neuralnet.run(70*ms,report='text')
		self.runTime *= 10 # scaling factor
		neuralnet.run(self.runTime,report='text')

		ADDS.OutputEvaluationResults(dend)

		neuronToPlot = 1
		colors = ['r']*1+['g']*1+['b']*1+['y']*1
		colors = ['blue', 'green', 'magenta', 'cyan']
		subplot(221)
		plot(M.t/ms, M.i, '.')
		#plot(M.t/ms, M.i, '.')
		#plot(dendSpikeM[0].t/ms, dendSpikeM[0].i, '.')
		subplot(222)
		#plot(testM.t/ms, testM.i, '.')
		#plot(testM.t/ms, testM.i, '.')
		#plot(dendSpikeM[0].t/ms, dendSpikeM[0].i, '.')
		subplot(223)
		#plot(UmM3.t, UmM3.v2.T/mV)
		#plot(UmM3.t, UmM3.v2.T/mV)
		#plot(testMv.t, testMv.v.T/mV)
		plot(Mv.t, Mv.V.T/mV)
		legend(['A','B','C','D'], loc='upper left')
		xlabel('Time (ms)')
		ylabel('Membrane Potential (mV)')
		subplot(224)
		#plot(testUmM3.t, testUmM3.v2.T/mV)
		plot(UmM3.t, UmM3.v2.T/mV)
		#plot(testUmM3.t, testUmM3.v2Test.T/mV)
		#plot(testMv.t, testMv.vTest.T/mV)
		#plot(dendVoltageM[0].t, dendVoltageM[0].v.T/mV)
		xlabel('Time (ms)')
		ylabel('Membrane Potential (mV)')
		'''subplot(225)
		plot(somaDirectM.t/ms, somaDirectM.i, '.')
		xlabel('Time (ms)')
		ylabel('Membrane Potential (mV)')'''
		'''subplot(226)
		plot(somaDirectMv.t, somaDirectMv.v2.T/mV)
		xlabel('Time (ms)')
		ylabel('Membrane Potential (mV)')'''
		'''subplot(225)
		plot(synM.t / ms, synM[0, :].w / nS)
		#plot(testUmM3.t, testUmM3.v2Test.T/mV)
		#plot(testMv.t, testMv.vTest.T/mV)
		#plot(dendVoltageM[0].t, dendVoltageM[0].v.T/mV)
		xlabel('Time (ms)')
		ylabel('Membrane Potential (mV)')'''
		show()

	def __init__(self):
		self.run_model()

def main():
	run_gupta_paper = gupta_paper()

if  __name__ =='__main__':main()

```

We will now save all the important variables : neuron voltages, neuron spikes, and weights to a file called simulation.hdf5
while saving the data, we will generate downsampled arrays, so when we plot we don't use that much RAM.
if you are on ubuntu you can use:

    hdfview simulation.hdf5
to explore the dataset.
we haven't implemented a method for downsampling sparse arrays yet, so spiketime isn't save , but it shouldn't be to hard to do that

```
bp = brianPlotter('simulation.hdf5')

gupta_paper = gupta_paper()

# spikeVoltage and testSpikeVoltage used for reformatting to avoid formatting issue with plotting
spikeVoltage = []
for timeIndex in range(len(gupta_paper.M.i[:])):
	spikeVoltage.append(gupta_paper.M.i[timeIndex].tolist())

'''testSpikeVoltage = []
for timeIndex in range(len(gupta_paper.testM.i[:])):
	testSpikeVoltage.append(gupta_paper.testM.i[timeIndex].tolist())'''

spikes = zip(spikeVoltage, gupta_paper.M.t)
#testSpikes = zip(testSpikeVoltage, gupta_paper.testM.t)

bp.saveData('spikes', np.asarray(spikes), downsample=False);
#bp.saveData('testSpikes', np.asarray(testSpikes), downsample=False);

voltage =  np.asarray(gupta_paper.UmM3.v2.T/mV)
#testVoltage =  np.asarray(gupta_paper.testUmM3.v2.T/mV)
#print 'voltage\t',voltage
bp.saveData('voltage',voltage)
#bp.saveData('testVoltage',testVoltage)

del bp #you have to delete the object so the file is closed
```

If you want to see the analysis of this simulation click here.

Remember to shutdown this notebook to release memory

```
#Just to know when this finishes
import time
print time.ctime()
```

    Mon Feb 09 05:56:08 2015