Repository: scikit-image/scikit-image

Issue:
HOG visualization should use midpoints of bins
## Description
The HOG visualization draws lines that are perpendicular to the edges of orientation bins.  It would be clearer if the lines were perpendicular to the _midpoints_ of bins, instead of the edges.

For example, if `hog` is applied with two orientation bins (`orientations=2`) to the image on the left below (reproducible by the code below), I would have expected the visualization to produce something looking like "concentric diamonds."  But it instead looks like the image to its right.

![comparison](https://cloud.githubusercontent.com/assets/5924638/21959910/293b0192-da8b-11e6-8ba9-c72c5085a368.png)

If others agree that this would be a worthwhile change, I'd be happy to work on a pull request for it.

## Way to reproduce
Here is the code to reproduce the images above:
```python
import numpy as np
from matplotlib import pyplot as plt
from skimage.feature import hog

X,Y = np.mgrid[-256:256,-256:256]
image = np.sqrt(X**2 + Y**2)
_, hog_image = hog(
    image,
    orientations=2,
    pixels_per_cell=(32, 32),
    cells_per_block=(1, 1),
    visualise=True
)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
ax1.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax2.imshow(hog_image, cmap=plt.cm.gray, interpolation='nearest')
```
(I'm using Python 3.5.2, scikit-image 0.12.3)





Discussion:
@vnavkal could you provide some image (from Google, for example) of your proposed version, please?
Sure, I propose for the visualization to generate an image like the one below, when `orientations=2`.
![comparison](https://cloud.githubusercontent.com/assets/5924638/21994811/6a615d1e-dbd6-11e6-8a31-4214d0297a3e.png)

When it is run with `orientations=2`, as above, the HOG function groups gradients into two bins:
* one bin contains gradient vectors lying in the (+x, +y) and (-x, -y) quadrants
* the other bin contains gradient vectors lying in the (-x, +y) and (+x, -y) quadrants

The current visualization scheme associates a horizontal line segment with the first bin and a vertical line segment with the second.

I propose that instead the first bin should be associated with a line segment of slope -1, and the second bin should be associated with a line segment of slope 1.  Each line segment would then better represent the range of gradient vectors that could fall into its bin.
@vnavkal But would you then just bias the gradient directions in the diagonal directions?  I think what you want is `orientations=4`.
> @vnavkal But would you then just bias the gradient directions in the diagonal directions?

I'm not sure I understand what this means, maybe you could elaborate a little?  If I understand correctly, the visualization is meant to display lines that are roughly orthogonal to the gradient directions, and my proposed visualization comes closer to doing that than the current implementation.

> I think what you want is `orientations=4`.

This wouldn't give me the visualization I generated; instead, it would produce the following.
![hog_4_orientations](https://cloud.githubusercontent.com/assets/5924638/23052098/807e97ae-f483-11e6-85fc-6533029b9433.png)

@stefanv I have a feeling I'm missing something in your comment; please let me know if this doesn't clarify things.
@vnavkal That's convincing!  Want to make a PR to address this?
Sure, I'll create a PR to change it, thanks!

Resolving patch:
diff --git a/doc/examples/features_detection/plot_hog.py b/doc/examples/features_detection/plot_hog.py
--- a/doc/examples/features_detection/plot_hog.py
+++ b/doc/examples/features_detection/plot_hog.py
@@ -87,7 +87,7 @@
 
 image = color.rgb2gray(data.astronaut())
 
-fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
+fd, hog_image = hog(image, orientations=8, pixels_per_cell=(32, 32),
                     cells_per_block=(1, 1), visualize=True)
 
 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True)
diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py
--- a/skimage/feature/_hog.py
+++ b/skimage/feature/_hog.py
@@ -61,7 +61,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(3, 3),
            For details, see [3]_, [4]_.
 
     visualize : bool, optional
-        Also return an image of the HOG.
+        Also return an image of the HOG.  For each cell and orientation bin,
+        the image contains a line segment that is centered at the cell center,
+        is perpendicular to the midpoint of the range of angles spanned by the
+        orientation bin, and has intensity proportional to the corresponding
+        histogram value.
     transform_sqrt : bool, optional
         Apply power law compression to normalize the image before
         processing. DO NOT use this if the image contains negative
@@ -199,8 +203,11 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(3, 3),
 
         radius = min(cx, cy) // 2 - 1
         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)
+        # set dx_arr, dy_arr to correspond to midpoints of orientation bins
+        orientation_bin_midpoints = (
+            np.pi * (orientations_arr + .5) / orientations)
+        dx_arr = radius * np.cos(orientation_bin_midpoints)
+        dy_arr = radius * np.sin(orientation_bin_midpoints)
         hog_image = np.zeros((sy, sx), dtype=float)
         for x in range(n_cellsx):
             for y in range(n_cellsy):


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
@@ -201,6 +201,35 @@ def test_hog_orientations_circle():
         assert_almost_equal(actual, desired, decimal=1)
 
 
+def test_hog_visualization_orientation():
+    """Test that the visualization produces a line with correct orientation
+
+    The hog visualization is expected to draw line segments perpendicular to
+    the midpoints of orientation bins.  This example verifies that when
+    orientations=3 and the gradient is entirely in the middle bin (bisected
+    by the y-axis), the line segment drawn by the visualization is horizontal.
+    """
+
+    width = height = 11
+
+    image = np.zeros((height, width), dtype='float')
+    image[height // 2:] = 1
+
+    _, hog_image = feature.hog(
+        image,
+        orientations=3,
+        pixels_per_cell=(width, height),
+        cells_per_block=(1, 1),
+        visualize=True
+    )
+
+    middle_index = height // 2
+    indices_excluding_middle = [x for x in range(height) if x != middle_index]
+
+    assert (hog_image[indices_excluding_middle, :] == 0).all()
+    assert (hog_image[middle_index, 1:-1] > 0).all()
+
+
 def test_hog_block_normalization_incorrect_error():
     img = np.eye(4)
     with testing.raises(ValueError):