Repository: yt-project/yt

Issue:
North vector does not work in OffAxisProjections
### Bug report

**Bug summary**

The addition of the OffAxisProjectionPlot for use with particle-based datasets is great and a wonderful contribution to the code, but I've discovered a few small issues. One of which is that the `north_vector` doesn't ever get used in the code, so one just gets an arbitrary north_vector applied no matter what.

**Code for reproduction**

```python
import yt
import numpy as np
ds = yt.load('FIRE/FIRE_M12i_ref11/snapshot_600.hdf5')
x = np.array([1,0,0])
y = np.array([0,1,0])
z = np.array([0,0,1])
p = yt.OffAxisProjectionPlot(ds, x, ('gas', 'density'))
p.save('OAP.png')
p = yt.OffAxisProjectionPlot(ds, x, ('gas', 'density'), north_vector=y)
p.save('OAP_north_y.png')
p = yt.OffAxisProjectionPlot(ds, x, ('gas', 'density'), north_vector=z)
p.save('OAP_north_z.png')
```

**Actual outcome**
All three images are the same, and there is no rotation seen in the image, despite the change in `north_vector`:

![oap](https://user-images.githubusercontent.com/8250999/42977598-ef38d5ce-8b7c-11e8-95d7-15ab30b7a99d.png)


Discussion:

Resolving patch:
diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py
--- a/yt/visualization/volume_rendering/off_axis_projection.py
+++ b/yt/visualization/volume_rendering/off_axis_projection.py
@@ -22,7 +22,8 @@
 from yt.units.unit_object import Unit
 from yt.data_objects.api import ImageArray
 from yt.utilities.lib.pixelization_routines import \
-    off_axis_projection_SPH
+    off_axis_projection_SPH, \
+    normalization_2d_utility
 import numpy as np
 
 
@@ -117,6 +118,11 @@ def off_axis_projection(data_source, center, normal_vector,
 
     item = data_source._determine_fields([item])[0]
 
+    # Assure vectors are numpy arrays as expected by cython code
+    normal_vector = np.array(normal_vector, dtype='float64')
+    if north_vector is not None:
+        north_vector = np.array(north_vector, dtype='float64')
+
     # Sanitize units
     if not hasattr(center, "units"):
         center = data_source.ds.arr(center, 'code_length')
@@ -132,11 +138,14 @@ def off_axis_projection(data_source, center, normal_vector,
 
         raise_error = False
 
+        # Assure that the field we're trying to off-axis project 
+        # has a field type as the SPH particle type or if the field is an 
+        # alias to an SPH field or is a 'gas' field
         if fi.alias_field:
             if fi.alias_name[0] != ptype:
                 raise_error = True
         else:
-            if fi.name[0] != ptype:
+            if fi.name[0] != ptype and fi.name[0] != 'gas':
                 raise_error = True
 
         if raise_error:
@@ -190,7 +199,8 @@ def off_axis_projection(data_source, center, normal_vector,
                     width.to('code_length').d,
                     chunk[item].in_units(ounits),
                     buf,
-                    normal_vector)
+                    normal_vector,
+                    north)
 
             path_length_unit = data_source.ds._get_field_info('smoothing_length').units
             path_length_unit = Unit(path_length_unit, registry=data_source.ds.unit_registry)
@@ -218,6 +228,7 @@ def off_axis_projection(data_source, center, normal_vector,
                     chunk[item].in_units(ounits),
                     buf,
                     normal_vector,
+                    north,
                     weight_field=chunk[weight].in_units(wounits))
 
             for chunk in data_source.chunks([], 'io'):
@@ -233,9 +244,10 @@ def off_axis_projection(data_source, center, normal_vector,
                     width.to('code_length').d,
                     chunk[weight].to(wounits),
                     weight_buff,
-                    normal_vector)
+                    normal_vector,
+                    north)
 
-            buf /= weight_buff
+            normalization_2d_utility(buf, weight_buff)
             item_unit = data_source.ds._get_field_info(item).units
             item_unit = Unit(item_unit, registry=data_source.ds.unit_registry)
             funits = item_unit


Test patch:
diff --git a/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py b/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py
--- a/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py
+++ b/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py
@@ -71,6 +71,7 @@ def test_basic_rotation_1():
     """
     expected_maxima = ([0., 0., 0., 0., 1.], [0., -1., -2., -3., 0.])
     normal_vector = [0., 1., 0.]
+    north_vector = [0., 0., -1.]
     resolution = (64, 64)
     ds = fake_sph_orientation_ds()
     left_edge = ds.domain_left_edge
@@ -82,7 +83,8 @@ def test_basic_rotation_1():
                                      normal_vector,
                                      width,
                                      resolution,
-                                     ('gas', 'density')  
+                                     ('gas', 'density'),
+                                     north_vector=north_vector
                                      )
     find_compare_maxima(expected_maxima, buf1, resolution, width)
 
@@ -105,6 +107,7 @@ def test_basic_rotation_2():
     expected_maxima = ([-1., -2., -3., 0., 0., 0.], 
                        [0., 0., 0., 0., 1., 2.])
     normal_vector = [1., 0., 0.]
+    north_vector = [0., 1., 0.]
     resolution = (64, 64)
     ds = fake_sph_orientation_ds()
     left_edge = ds.domain_left_edge
@@ -116,7 +119,8 @@ def test_basic_rotation_2():
                                      normal_vector,
                                      width,
                                      resolution,
-                                     ('gas', 'density')  
+                                     ('gas', 'density'),
+                                     north_vector=north_vector
                                      )
     find_compare_maxima(expected_maxima, buf1, resolution, width)
 
@@ -155,6 +159,44 @@ def test_basic_rotation_3():
     find_compare_maxima(expected_maxima, buf1, resolution, width)
 
 
+@requires_module('scipy')
+def test_basic_rotation_4():
+    """ Rotation of x-axis to z-axis and original z-axis to y-axis with the use
+    of the north_vector. All fake particles on z-axis should now be on the
+    y-Axis.  All fake particles on the x-axis should now be on the z-axis, and
+    all fake particles on the y-axis should now be on the x-axis.
+
+    (0, 0, 1) -> (0, 1)
+    (0, 0, 2) -> (0, 2)
+    (0, 0, 3) -> (0, 3)
+    In addition, (0, 0, 0) should contribute to the local maxima at (0, 0):
+    (0, 0, 0) -> (0, 0)
+    x-axis particles should be rotated and contribute to the local maxima at (0, 0):
+    (1, 0, 0) -> (0, 0)
+    and the y-axis particles shift into the positive x direction:
+    (0, 1, 0) -> (1, 0)
+    (0, 2, 0) -> (2, 0)
+    """
+    expected_maxima = ([0., 0., 0., 0., 1., 2.], [1., 2., 3., 0., 0., 0.])
+    normal_vector = [1., 0., 0.]
+    north_vector = [0., 0., 1.]
+    resolution = (64, 64)
+    ds = fake_sph_orientation_ds()
+    left_edge = ds.domain_left_edge
+    right_edge = ds.domain_right_edge
+    center = (left_edge + right_edge)/2
+    width = (right_edge - left_edge)
+    buf1 = OffAP.off_axis_projection(ds,
+                                     center,
+                                     normal_vector,
+                                     width,
+                                     resolution,
+                                     ('gas', 'density'),
+                                     north_vector=north_vector
+                                     )
+    find_compare_maxima(expected_maxima, buf1, resolution, width)
+
+
 @requires_module('scipy')
 def test_center_1():
     """ Change the center to [0, 3, 0] 
@@ -177,7 +219,7 @@ def test_center_1():
     # center = [(left_edge[0] + right_edge[0])/2,
     #            left_edge[1],
     #           (left_edge[2] + right_edge[2])/2]
-    center = np.array([0., 3., 0.])
+    center = [0., 3., 0.]
     width = (right_edge - left_edge)
     buf1 = OffAP.off_axis_projection(ds,
                                      center,
@@ -208,7 +250,7 @@ def test_center_2():
     ds = fake_sph_orientation_ds()
     left_edge = ds.domain_left_edge
     right_edge = ds.domain_right_edge
-    center = np.array([0., -1., 0.])
+    center = [0., -1., 0.]
     width = (right_edge - left_edge)      
     buf1 = OffAP.off_axis_projection(ds,
                                      center,
@@ -232,7 +274,7 @@ def test_center_3():
     ds = fake_sph_orientation_ds()
     left_edge = ds.domain_left_edge
     right_edge = ds.domain_right_edge
-    center = np.array([0., -1., 0.])
+    center = [0., -1., 0.]
     width = [(right_edge[0] - left_edge[0]),
              left_edge[1],
              (right_edge[2] - left_edge[2])]