Repository: scikit-image/scikit-image

Issue:
plot_hog.py results in strangely-looking figure in the gallery
Compare http://scikit-image.org/docs/dev/_images/plot_hog_1.png and http://scikit-image.org/docs/0.11.x/_images/plot_hog_1.png

I don't think the example changed, so it must be inside the hog functions or in one of the functions called by this function.



Discussion:
@ivoflipse you have been working on HOG recently, so you may have an idea of what is going wrong for the example. If you find a little time to take a look at it, that'd be awesome!

Sorry @emmanuelle I had missed your mention, but I'm looking into it right now

Thanks for doing that, @ivoflipse 

As a simple proxy for what could be wrong it seems that there's a large difference in the maximum values of both hog images. The old method gives an image with a max of **0.26** whereas the new method gives a max of **67.78**.

Since the code is pretty much the same up to the call to _hoghistogram and the hog_image code gives the same results, the problem seems isolated to the loop we replaced.

After reading through the code some more, I think the difference is in the fact that the old approach masked the magnitude image, then applied a uniform filter and then subsampled the filtered magnitudes.

The new approach simply sums all magnitude values for coordinates that have the correct orientations in a small window (which I guess acts as a filter or subsampling strategy). However, it didn't correct the total. So if you divide total by (cell_rows \* cell_columns), you get the following image:

![image](https://cloud.githubusercontent.com/assets/485186/11117153/adc03352-8937-11e5-8ebf-e9cb544cfd5e.png)

When I subtract the old result from this one, we get the following difference:

![image](https://cloud.githubusercontent.com/assets/485186/11117136/8f5524d6-8937-11e5-9416-38946d75b96e.png)

Which might still not be entirely what we want, but at least much closer to what we had before.

On a side-note I noticed that inside cell_hog we calculate 

```
   range_rows_stop = cell_rows/2
    range_rows_start = -range_rows_stop
    range_columns_stop = cell_columns/2
    range_columns_start = -range_columns_stop
```

Which are constant for all iterations, so we could move that calculation outside all the loops and just pass it along.

I assume this correction is sufficient to resolve?

What is the status of this issue?  If possible, we should resolve this
before releasing 0.12.

@stefanv there are still small differences between the two implementations and I don't know how to adapt the Cython implementation to get rid of them.

I can make a PR with the changes that I made though and discuss it further there.


Resolving patch:
diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py
--- a/skimage/feature/_hog.py
+++ b/skimage/feature/_hog.py
@@ -5,7 +5,8 @@
 
 
 def hog(image, orientations=9, pixels_per_cell=(8, 8),
-        cells_per_block=(3, 3), visualise=False, normalise=False):
+        cells_per_block=(3, 3), visualise=False, normalise=False,
+        feature_vector=True):
     """Extract Histogram of Oriented Gradients (HOG) for a given image.
 
     Compute a Histogram of Oriented Gradients (HOG) by
@@ -31,6 +32,9 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
     normalise : bool, optional
         Apply power law compression to normalise the image before
         processing.
+    feature_vector : bool, optional
+        Return the data as a feature vector by calling .ravel() on the result
+        just before returning.
 
     Returns
     -------
@@ -127,13 +131,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
         orientations_arr = np.arange(orientations)
         dx_arr = radius * np.cos(orientations_arr / orientations * np.pi)
         dy_arr = radius * np.sin(orientations_arr / orientations * np.pi)
-        cr2 = cy + cy
-        cc2 = cx + cx
         hog_image = np.zeros((sy, sx), dtype=float)
         for x in range(n_cellsx):
             for y in range(n_cellsy):
                 for o, dx, dy in zip(orientations_arr, dx_arr, dy_arr):
-                    centre = tuple([y * cr2 // 2, x * cc2 // 2])
+                    centre = tuple([y * cy + cy // 2, x * cx + cx // 2])
                     rr, cc = draw.line(int(centre[0] - dx),
                                        int(centre[1] + dy),
                                        int(centre[0] + dx),
@@ -171,8 +173,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8),
     overlapping grid of blocks covering the detection window into a combined
     feature vector for use in the window classifier.
     """
+    
+    if feature_vector:
+        normalised_blocks = normalised_blocks.ravel()
 
     if visualise:
-        return normalised_blocks.ravel(), hog_image
+        return normalised_blocks, hog_image
     else:
-        return normalised_blocks.ravel()
+        return normalised_blocks


Test patch:
diff --git a/skimage/feature/tests/test_hog.py b/skimage/feature/tests/test_hog.py
--- a/skimage/feature/tests/test_hog.py
+++ b/skimage/feature/tests/test_hog.py
@@ -1,5 +1,7 @@
+import os
 import numpy as np
 from scipy import ndimage as ndi
+import skimage as si
 from skimage import data
 from skimage import feature
 from skimage import img_as_float
@@ -9,7 +11,7 @@
                            )
 
 
-def test_histogram_of_oriented_gradients():
+def test_histogram_of_oriented_gradients_output_size():
     img = img_as_float(data.astronaut()[:256, :].mean(axis=2))
 
     fd = feature.hog(img, orientations=9, pixels_per_cell=(8, 8),
@@ -18,6 +20,17 @@ def test_histogram_of_oriented_gradients():
     assert len(fd) == 9 * (256 // 8) * (512 // 8)
 
 
+def test_histogram_of_oriented_gradients_output_correctness():
+    img = np.load(os.path.join(si.data_dir, 'lena_GRAY_U8.npy'))
+    correct_output = np.load(os.path.join(si.data_dir, 'lena_GRAY_U8_hog.npy'))
+    
+    output = feature.hog(img, orientations=9, pixels_per_cell=(8, 8), 
+                         cells_per_block=(3, 3), feature_vector=True,
+                         normalise=False, visualise=False)
+    
+    assert_almost_equal(output, correct_output)
+
+
 def test_hog_image_size_cell_size_mismatch():
     image = data.camera()[:150, :200]
     fd = feature.hog(image, orientations=9, pixels_per_cell=(8, 8),