--- a
+++ b/4x/reference/batch.py
@@ -0,0 +1,270 @@
+# -*- coding: utf-8 -*-
+# <nbformat>3.0</nbformat>
+
+# <codecell>
+
+# Files
+import os
+import pickle
+import sys
+
+# Basic
+import numpy as np
+
+# Image Processing
+import skimage as ski
+from skimage import io, feature, morphology, filter, exposure, color, transform, measure
+import scipy.signal as sig
+from scipy.spatial import distance
+from scipy.ndimage import maximum_filter, minimum_filter, binary_fill_holes
+
+# Stats
+import scipy.stats as st
+
+import matplotlib
+%matplotlib inline
+import matplotlib.pyplot as plt
+matplotlib.rcParams["figure.figsize"] = (16, 10)
+
+# <codecell>
+
+# Read files
+files = []
+directory = "../data/"
+for i in os.listdir(directory) :
+    if i.endswith(".jpg") : # if it's a jpg
+        if not i.endswith("_processed.jpg") : # and isn't a processed image
+            files.append(directory + i) # then add it to the list to be processed
+            
+files = [files[50]]
+io.imshow(io.imread(files[0]))
+
+# <codecell>
+
+
+# Iterate over files
+for f in files :
+
+    # If it's not been processed before
+    if not os.path.exists(f + "_processed.jpg") :
+
+        ### PROCESSING
+        
+        # Read the image
+        A = io.imread(f)
+        
+        # Constrast enhancement
+        B = exposure.adjust_sigmoid(A, gain=12)
+        
+        # Extract luminosity
+        C = color.rgb2xyz(B)[:, :, 1]
+        
+        # Apply adaptive thresholding
+        D = filter.threshold_adaptive(C, 301)
+        
+        # Clean
+        E = morphology.remove_small_objects(~morphology.remove_small_objects(~D, 100), 100)
+
+        # Save to disk
+        io.imsave(f + "_processed.jpg", ski.img_as_float(E))
+        
+        # Downsample for Gabor filtering
+        Es = ski.img_as_float(transform.rescale(E, 0.25))
+
+
+    else :
+
+        # Otherwise, we've processed it before, so read it in for speed
+        A = io.imread(f)
+        E = ski.img_as_float(io.imread(f + "_processed.jpg"))
+        Es = ski.img_as_float(transform.rescale(E, 0.25))
+        
+
+# <codecell>
+
+A = io.imread(files[0])[:2000, 1000:-1000, :]
+A2 = filter.gaussian_filter(A, 5)
+B1 = exposure.adjust_sigmoid(A, gain=12)
+B2 = exposure.adjust_sigmoid(A2, gain=12)
+C1 = color.rgb2xyz(B1)[:, :, 1]
+C2 = color.rgb2xyz(B2)[:, :, 1]
+D1 = filter.threshold_adaptive(C1, 301)
+D2 = filter.threshold_adaptive(C2, 301)
+E1 = morphology.remove_small_objects(~morphology.remove_small_objects(~D1, 100), 100)
+E2 = morphology.remove_small_objects(~morphology.remove_small_objects(~D2, 100), 100)
+
+d1 = filter.threshold_adaptive(C1, 301, offset=-0.01)
+d2 = filter.threshold_adaptive(C2, 301, offset=-0.01)
+e1 = morphology.remove_small_objects(~morphology.remove_small_objects(~d1, 100), 100)
+e2 = morphology.remove_small_objects(~morphology.remove_small_objects(~d2, 100), 100)
+
+print np.abs(E1 - e1).sum()
+print np.abs(E2 - e2).sum()
+
+# <codecell>
+
+(A2.shape[0] * A2.shape[1])
+
+# <codecell>
+
+    pixelscales = np.arange(15, 55, 2)
+    gaborscales = 4. / pixelscales # 2 to 20 pixels
+
+    orientations = np.linspace(0, np.pi * 11./12., 12) # 0 to 180 degrees in 15 degree increments
+
+    # Results array
+    gabor = np.zeros((len(orientations), len(gaborscales)))
+
+    # Perform Gabor filtering
+    for i, iv in enumerate(orientations) :
+        for j, jv in enumerate(gaborscales) :
+            gaborReal, gaborImag = filter.gabor_filter(Es, jv, iv)
+            gabor[i, j] = np.sqrt(np.sum(np.abs(gaborReal) ** 2) + np.sum(np.abs(gaborImag) ** 2)) # Return energy
+        print "Thread %s. Gabor filtering. Completion : %f" % (sys.argv[1], (i / float(len(orientations))))
+
+    # Determine orientation-independent scale which fits best
+    optimalscale = np.argmax(np.sum(gabor, axis = 0))
+
+    # At this scale, calculate directionality coefficient
+    g = gabor[:, optimalscale]
+    directionality = (g.max() - g.min()) / g.max()
+
+# <codecell>
+
+    scaleent = []
+    scaleentstd = []
+    roylac = []
+
+
+    # Characteristic scale
+    s = pixelscales[optimalscale]
+
+    # Generate a disk at this scale
+    circle = morphology.disk(s)
+    circlesize = circle.sum()
+
+    # Convolve with image
+    Y = sig.fftconvolve(E, circle, "valid")
+
+    # Compute information entropy
+    px = Y.ravel() / circlesize
+    py = 1. - px
+    idx = np.logical_and(px > 1. / circlesize, px < 1.)
+    entropy = - ( np.mean(px[idx] * np.log(px[idx])) + np.mean(py[idx] * np.log(py[idx])) )
+    entropystd = np.std(px[idx] * np.log(px[idx]) + py[idx] * np.log(py[idx]))
+
+    # Compute normalised lacunarity
+    lx = np.var(Y) / (np.mean(Y) ** 2) + 1
+    ly = np.var(1. - Y) / (np.mean(1. - Y) ** 2) + 1
+
+    # Roy et al, J. Struct. Geol. 2010
+
+    # Results
+    roylac.append((lx - 1.) / (1./np.mean(E) - 1.))
+    scaleent.append(entropy)
+    scaleentstd.append(entropystd)
+
+# <codecell>
+
+    qstain = np.array([[.26451728, .5205347, .81183386], [.9199094, .29797825, .25489032], [.28947765, .80015373, .5253158]])
+
+    deconv = ski.img_as_float(color.separate_stains(transform.rescale(A, 0.25), np.linalg.inv(qstain)))
+
+
+
+    subveins1 = \
+    morphology.remove_small_objects(
+        filter.threshold_adaptive(
+            filter.gaussian_filter(
+                deconv[:, :, 2] / deconv[:, :, 0],
+                11),
+            250, offset = -0.13),
+        60)
+
+    subveins2 = \
+    morphology.remove_small_objects(
+        filter.threshold_adaptive(
+            filter.gaussian_filter(
+                    maximum_filter(
+                    deconv[:, :, 2] / deconv[:, :, 0],
+                    5),
+                11),
+            250, offset = -0.13),
+        60)
+
+    veins = \
+    maximum_filter(
+        morphology.remove_small_objects(
+            binary_fill_holes(
+                morphology.binary_closing(
+                    np.logical_or(subveins1, subveins2),
+                    morphology.disk(25)),
+                ),
+            250),
+        55)
+
+
+
+
+
+
+    rawinflammation = \
+    morphology.remove_small_objects(
+    filter.threshold_adaptive(
+        exposure.adjust_sigmoid(
+            filter.gaussian_filter(
+                exposure.equalize_adapthist(
+                    exposure.rescale_intensity(
+                        deconv[:, :, 1],
+                        out_range = (0, 1)),
+                    ntiles_y = 1),
+                    5),
+            cutoff = 0.6),
+            75, offset = -0.12),
+        250)
+
+    
+    inflammation = \
+    maximum_filter(
+        rawinflammation,
+        55)
+
+# <codecell>
+
+    total = veins + inflammation
+    coloured = np.zeros_like(deconv)
+    coloured[:, :, 1] = veins
+    coloured[:, :, 2] = inflammation
+
+
+    labelled, regions = measure.label(total, return_num = True)
+    
+
+    inflammationcount = 0
+    inflammationsize = []
+
+
+
+    for b in range(1, regions) :
+
+        if (inflammation * (labelled == b)).sum() / ((veins * (labelled == b)).sum() + 1) > 0.5 :
+            inflammationcount += 1
+            inflammationsize.append((rawinflammation * labelled == b).sum())
+
+# <codecell>
+
+regions
+
+# <codecell>
+
+io.imshow(A)
+
+# <codecell>
+
+io.imshow(inflammation)
+plt.scatter([qq[i].centroid[1] for i in range(regions-1)], [qq[i].centroid[0] for i in range(regions-1)])
+
+# <codecell>
+
+pairwise.min(axis=0).mean()
+