[yt-svn] commit/yt: 39 new changesets

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Mon Mar 7 15:25:49 PST 2016


39 new commits in yt:

https://bitbucket.org/yt_analysis/yt/commits/a3d5f9badd7f/
Changeset:   a3d5f9badd7f
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:20:15+00:00
Summary:     Create a new unit object when creating an array with a registry

This prevents mutating unit objects underneath an already-created unit, causing
havoc that is nonlocal to this code path.
Affected #:  1 file

diff -r 9321ff69146d0129738ece9ffc181212fb2c648f -r a3d5f9badd7f4200786a7f6411e183c556fc72e1 yt/units/yt_array.py
--- a/yt/units/yt_array.py
+++ b/yt/units/yt_array.py
@@ -346,7 +346,8 @@
                 if registry is None:
                     pass
                 else:
-                    input_array.units.registry = registry
+                    units = Unit(str(input_array.units), registry=registry)
+                    input_array.units = units
             elif isinstance(input_units, Unit):
                 input_array.units = input_units
             else:


https://bitbucket.org/yt_analysis/yt/commits/5098d04198f5/
Changeset:   5098d04198f5
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:24:30+00:00
Summary:     Make the camera initializer take a scene object
Affected #:  4 files

diff -r a3d5f9badd7f4200786a7f6411e183c556fc72e1 -r 5098d04198f5e8ebc807fc1202a5c2fc57f98f87 doc/source/cookbook/various_lens.py
--- a/doc/source/cookbook/various_lens.py
+++ b/doc/source/cookbook/various_lens.py
@@ -1,5 +1,5 @@
 import yt
-from yt.visualization.volume_rendering.api import Scene, Camera, VolumeSource
+from yt.visualization.volume_rendering.api import Scene, VolumeSource
 import numpy as np
 
 field = ("gas", "density")
@@ -19,7 +19,7 @@
 tf.grey_opacity = True
 
 # Plane-parallel lens
-cam = Camera(ds, lens_type='plane-parallel')
+cam = sc.add_camera(ds, lens_type='plane-parallel')
 # Set the resolution of tbe final projection.
 cam.resolution = [250, 250]
 # Set the location of the camera to be (x=0.2, y=0.5, z=0.5)
@@ -32,13 +32,12 @@
 # Set the width of the camera, where width[0] and width[1] specify the length and
 # height of final projection, while width[2] in plane-parallel lens is not used.
 cam.set_width(ds.domain_width * 0.5)
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_plane-parallel.png', sigma_clip=6.0)
 
 # Perspective lens
-cam = Camera(ds, lens_type='perspective')
+cam = sc.add_camera(ds, lens_type='perspective')
 cam.resolution = [250, 250]
 # Standing at (x=0.2, y=0.5, z=0.5), we look at the area of x>0.2 (with some open angle
 # specified by camera width) along the positive x direction.
@@ -49,13 +48,12 @@
 # height of the final projection, while width[2] specifies the distance between the
 # camera and the final image.
 cam.set_width(ds.domain_width * 0.5)
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_perspective.png', sigma_clip=6.0)
 
 # Stereo-perspective lens
-cam = Camera(ds, lens_type='stereo-perspective')
+cam = sc.add_camera(ds, lens_type='stereo-perspective')
 # Set the size ratio of the final projection to be 2:1, since stereo-perspective lens
 # will generate the final image with both left-eye and right-eye ones jointed together.
 cam.resolution = [500, 250]
@@ -72,7 +70,7 @@
 
 # Fisheye lens
 dd = ds.sphere(ds.domain_center, ds.domain_width[0] / 10)
-cam = Camera(dd, lens_type='fisheye')
+cam = sc.add_camera(dd, lens_type='fisheye')
 cam.resolution = [250, 250]
 v, c = ds.find_max(field)
 cam.set_position(c - 0.0005 * ds.domain_width)
@@ -86,7 +84,7 @@
 sc.save('lens_fisheye.png', sigma_clip=6.0)
 
 # Spherical lens
-cam = Camera(ds, lens_type='spherical')
+cam = sc.add_camera(ds, lens_type='spherical')
 # Set the size ratio of the final projection to be 2:1, since spherical lens
 # will generate the final image with length of 2*pi and height of pi.
 cam.resolution = [500, 250]
@@ -103,7 +101,7 @@
 sc.save('lens_spherical.png', sigma_clip=6.0)
 
 # Stereo-spherical lens
-cam = Camera(ds, lens_type='stereo-spherical')
+cam = sc.add_camera(ds, lens_type='stereo-spherical')
 # Set the size ratio of the final projection to be 4:1, since spherical-perspective lens
 # will generate the final image with both left-eye and right-eye ones jointed together.
 cam.resolution = [1000, 250]

diff -r a3d5f9badd7f4200786a7f6411e183c556fc72e1 -r 5098d04198f5e8ebc807fc1202a5c2fc57f98f87 doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
--- a/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
+++ b/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
@@ -18,7 +18,7 @@
     "import yt\n",
     "import numpy as np\n",
     "from yt.visualization.volume_rendering.transfer_function_helper import TransferFunctionHelper\n",
-    "from yt.visualization.volume_rendering.api import Scene, Camera, VolumeSource\n",
+    "from yt.visualization.volume_rendering.api import Scene, VolumeSource\n",
     "\n",
     "ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n",
     "sc = yt.create_scene(ds)"
@@ -199,7 +199,7 @@
    },
    "outputs": [],
    "source": [
-    "cam = Camera(ds, lens_type='perspective')\n",
+    "cam = sc.add_camera(ds, lens_type='perspective')\n",
     "\n",
     "# Standing at (x=0.05, y=0.5, z=0.5), we look at the area of x>0.05 (with some open angle\n",
     "# specified by camera width) along the positive x direction.\n",
@@ -213,7 +213,6 @@
     "# The width determines the opening angle\n",
     "cam.set_width(ds.domain_width * 0.5)\n",
     "\n",
-    "sc.camera = cam\n",
     "print (sc.camera)"
    ]
   },

diff -r a3d5f9badd7f4200786a7f6411e183c556fc72e1 -r 5098d04198f5e8ebc807fc1202a5c2fc57f98f87 doc/source/visualizing/unstructured_mesh_rendering.rst
--- a/doc/source/visualizing/unstructured_mesh_rendering.rst
+++ b/doc/source/visualizing/unstructured_mesh_rendering.rst
@@ -304,15 +304,12 @@
     ms.cmap = 'Eos A'
    
     # Create a perspective Camera
-    cam = Camera(ds, lens_type='perspective')
+    cam = sc.add_camera(ds, lens_type='perspective')
     cam.focus = ds.arr([0.0, 0.0, 0.0], 'code_length')
     cam_pos = ds.arr([-4.5, 4.5, -4.5], 'code_length')
     north_vector = ds.arr([0.0, -1.0, -1.0], 'dimensionless')
     cam.set_position(cam_pos, north_vector)
    
-    # tell our scene to use it
-    sc.camera = cam
-   
     # increase the default resolution
     cam.resolution = (800, 800)
    
@@ -337,16 +334,13 @@
     sc = Scene()
 
     # set up our Camera
-    cam = Camera(ds)
+    sc.add_camera(ds)
     cam.focus = ds.arr([0.0, 0.0, 0.0], 'code_length')
     cam.set_position(ds.arr([-3.0, 3.0, -3.0], 'code_length'),
                      ds.arr([0.0, -1.0, 0.0], 'dimensionless'))
     cam.set_width = ds.arr([8.0, 8.0, 8.0], 'code_length')
     cam.resolution = (800, 800)
 
-    # tell the scene to use it
-    sc.camera = cam
-
     # create two distinct MeshSources from 'connect1' and 'connect2'
     ms1 = MeshSource(ds, ('connect1', 'diffused'))
     ms2 = MeshSource(ds, ('connect2', 'diffused'))
@@ -407,7 +401,7 @@
 .. code-block:: python
 
     import yt
-    from yt.visualization.volume_rendering.api import MeshSource, Camera
+    from yt.visualization.volume_rendering.api import MeshSource
     import pylab as plt
 
     NUM_STEPS = 127
@@ -432,7 +426,7 @@
 	# set up the camera here. these values were arrived by
 	# calling pitch, yaw, and roll in the notebook until I
 	# got the angle I wanted.
-	cam = Camera(ds)
+	sc.add_camera(ds)
 	camera_position = ds.arr([0.1, 0.0, 0.1], 'code_length')
 	cam.focus = ds.domain_center
 	north_vector = ds.arr([-0.3032476, -0.71782557, 0.62671153], 'dimensionless')

diff -r a3d5f9badd7f4200786a7f6411e183c556fc72e1 -r 5098d04198f5e8ebc807fc1202a5c2fc57f98f87 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -23,7 +23,6 @@
 
 
 class Camera(Orientation):
-
     r"""A representation of a point of view into a Scene.
 
     It is defined by a position (the location of the camera
@@ -35,6 +34,8 @@
 
     Parameters
     ----------
+    scene: A :class:`yt.visualization.volume_rendering.scene.Scene` object
+        A scene object that the camera will be attached to.
     data_source: :class:`AMR3DData` or :class:`Dataset`, optional
         This is the source to be rendered, which can be any arbitrary yt
         data object or dataset.
@@ -50,7 +51,7 @@
 
     Examples
     --------
-    
+
     In this example, the camera is set using defaults that are chosen
     to be reasonable for the argument Dataset.
 
@@ -83,24 +84,26 @@
     _position = None
     _resolution = None
 
-    def __init__(self, data_source=None, lens_type='plane-parallel',
+    def __init__(self, scene, data_source=None, lens_type='plane-parallel',
                  auto=False):
-        """Initialize a Camera Instance"""
+        self.scene = scene
         self.lens = None
         self.north_vector = None
         self.normal_vector = None
         self.light = None
+        self.data_source = data_source_or_all(data_source)
         self._resolution = (512, 512)
-        self._width = np.array([1.0, 1.0, 1.0])
-        self._focus = np.array([0.0]*3)
-        self._position = np.array([1.0]*3)
-        if data_source is not None:
-            data_source = data_source_or_all(data_source)
-            self._focus = data_source.ds.domain_center
-            self._position = data_source.ds.domain_right_edge
-            self._width = 1.5*data_source.ds.domain_width
-            self._domain_center = data_source.ds.domain_center
-            self._domain_width = data_source.ds.domain_width
+        if self.data_source is not None:
+            self._focus = self.data_source.ds.domain_center
+            self._position = self.data_source.ds.domain_right_edge
+            self._width = 1.5*self.data_source.ds.domain_width
+            self._domain_center = self.data_source.ds.domain_center
+            self._domain_width = self.data_source.ds.domain_width
+        else:
+            self._focus = scene.arr([0.0, 0.0, 0.0], 'unitary')
+            self._width = scene.arr([1.0, 1.0, 1.0], 'unitary')
+            self._position = scene.arr([1.0, 1.0, 1.0], 'unitary')
+
         if auto:
             self.set_defaults_from_data_source(data_source)
 
@@ -214,10 +217,14 @@
             'stereo-spherical'
 
         """
-        if lens_type not in lenses:
-            mylog.error("Lens type not available")
-            raise RuntimeError()
-        self.lens = lenses[lens_type]()
+        if isinstance(lens_type, Lens):
+            self.lens = lens_type
+        elif lens_type not in lenses:
+            raise RuntimeError(
+                "Lens type %s not in available list of available lens "
+                "types (%s)" % (lens_type, list(lenses.keys())))
+        else:
+            self.lens = lenses[lens_type]()
         self.lens.set_camera(self)
 
     def set_defaults_from_data_source(self, data_source):
@@ -269,15 +276,6 @@
             depth directions. If a scalar, assumes that the width is the same in
             all three directions.
         """
-        try:
-            width = width.in_units('code_length')
-        except (AttributeError, UnitParseError):
-            raise ValueError(
-                'Volume rendering width must be a YTArray that can be '
-                'converted to code units')
-
-        if not iterable(width):
-            width = YTArray([width.d]*3, width.units)  # Can't get code units.
         self.width = width
         self.switch_orientation()
 


https://bitbucket.org/yt_analysis/yt/commits/b0af2d20807d/
Changeset:   b0af2d20807d
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:26:20+00:00
Summary:     Give scene objects an add_camera method
Affected #:  1 file

diff -r 5098d04198f5e8ebc807fc1202a5c2fc57f98f87 -r b0af2d20807d64eaf3c3a6c92ab460a4b176bd45 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -289,6 +289,66 @@
 
         return im
 
+    def add_camera(self, data_source=None, lens_type='plane-parallel',
+                   auto=False):
+        r"""Add a camera to the Scene.
+
+        It is defined by a position (the location of the camera
+        in the simulation domain,), a focus (the point at which the
+        camera is pointed), a width (the width of the snapshot that will
+        be taken, a resolution (the number of pixels in the image), and
+        a north_vector (the "up" direction in the resulting image). A
+        camera can use a variety of different Lens objects.
+
+        Parameters
+        ----------
+        data_source: :class:`AMR3DData` or :class:`Dataset`, optional
+            This is the source to be rendered, which can be any arbitrary yt
+            data object or dataset.
+        lens_type: string, optional
+            This specifies the type of lens to use for rendering. Current
+            options are 'plane-parallel', 'perspective', and 'fisheye'. See
+            :class:`yt.visualization.volume_rendering.lens.Lens` for details.
+            Default: 'plane-parallel'
+        auto: boolean
+            If True, build smart defaults using the data source extent. This
+            can be time-consuming to iterate over the entire dataset to find
+            the positional bounds. Default: False
+
+        Examples
+        --------
+
+        In this example, the camera is set using defaults that are chosen
+        to be reasonable for the argument Dataset.
+
+        >>> import yt
+        >>> from yt.visualization.volume_rendering.api import Scene, Camera
+        >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
+        >>> sc = Scene()
+        >>> sc.add_camera()
+
+        Here, we set the camera properties manually:
+
+        >>> import yt
+        >>> from yt.visualization.volume_rendering.api import Scene, Camera
+        >>> sc = Scene()
+        >>> cam = sc.add_camera()
+        >>> cam.position = np.array([0.5, 0.5, -1.0])
+        >>> cam.focus = np.array([0.5, 0.5, 0.0])
+        >>> cam.north_vector = np.array([1.0, 0.0, 0.0])
+
+        Finally, we create a camera with a non-default lens:
+
+        >>> import yt
+        >>> from yt.visualization.volume_rendering.api import Camera
+        >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
+        >>> sc = Scene()
+        >>> sc.add_camera(ds, lens_type='perspective')
+
+        """
+        self._camera = Camera(self, data_source, lens_type, auto)
+        return self.camera
+
     def camera():
         doc = r"""The camera property.
 
@@ -299,13 +359,16 @@
         def fget(self):
             cam = self._camera
             if cam is None:
-                cam = Camera()
+                cam = Camera(self)
             self._camera = cam
             return self._camera
 
         def fset(self, value):
-            # Should add better validation here
-            self._camera = value
+            cam = Camera(self, value.data_source, value.lens)
+            cam.width = value.width
+            cam.focus = value.focus
+            cam.position = value.position
+            self._camera = cam
 
         def fdel(self):
             del self._camera


https://bitbucket.org/yt_analysis/yt/commits/65a35d3ae0ad/
Changeset:   65a35d3ae0ad
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:26:44+00:00
Summary:     Give scene objects a unit registry that inherits unit symbols from rendered datasets.
Affected #:  1 file

diff -r b0af2d20807d64eaf3c3a6c92ab460a4b176bd45 -r 65a35d3ae0ad49f2ad67dae8cb84af48ea61c26a yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -12,13 +12,27 @@
 # -----------------------------------------------------------------------------
 
 
+import functools
 import numpy as np
 from collections import OrderedDict
 from yt.funcs import mylog, get_image_suffix
 from yt.extern.six import iteritems, itervalues, string_types
+from yt.units.dimensions import \
+    length
+from yt.units.unit_registry import \
+    UnitRegistry
+from yt.units.yt_array import \
+    YTQuantity, \
+    YTArray
 from .camera import Camera
-from .render_source import OpaqueSource, BoxSource, CoordinateVectorSource, \
-    GridSource, RenderSource, MeshSource
+from .render_source import \
+    OpaqueSource, \
+    BoxSource, \
+    CoordinateVectorSource, \
+    GridSource, \
+    RenderSource, \
+    MeshSource, \
+    VolumeSource
 from .zbuffer_array import ZBuffer
 from yt.extern.six.moves import builtins
 from yt.utilities.exceptions import YTNotInsideNotebook
@@ -75,9 +89,10 @@
         r"""Create a new Scene instance"""
         super(Scene, self).__init__()
         self.sources = OrderedDict()
-        self.camera = None
-        # An image array containing the last rendered image of the scene
         self.last_render = None
+        self.unit_registry = UnitRegistry()
+        # This will be updated when we add a volume source
+        self.unit_registry.add("unitary", 1.0, length)
         # A non-public attribute used to get around the fact that we can't
         # pass kwargs into _repr_png_()
         self._sigma_clip = None
@@ -124,6 +139,26 @@
 
         self.sources[keyname] = render_source
 
+        if isinstance(render_source, (VolumeSource, MeshSource, GridSource)):
+            if hasattr(self, 'unit_registry'):
+                current_scaling = self.unit_registry['unitary'][0]
+                source_reg = render_source.data_source.ds.unit_registry
+                if current_scaling != source_reg['unitary'][0]:
+                    for source in self.sources.items():
+                        data_source = getattr(source, 'data_source', None)
+                        if data_source is None:
+                            continue
+                        scaling = data_source.ds.unit_registry['unitary'][0]
+                        if scaling != source_reg['unitary'][0]:
+                            raise NotImplementedError(
+                                "Scenes including VolumeSource instances based "
+                                "on datasets with different unit scalings is "
+                                "not yet supported."
+                            )
+            self.unit_registry = UnitRegistry(
+                add_default_symbols=False,
+                lut=render_source.data_source.ds.unit_registry.lut)
+
         return self
 
     def render(self, camera=None):
@@ -542,6 +577,92 @@
         else:
             raise YTNotInsideNotebook
 
+    _arr = None
+    @property
+    def arr(self):
+        """Converts an array into a :class:`yt.units.yt_array.YTArray`
+
+        The returned YTArray will be dimensionless by default, but can be
+        cast to arbitrary units using the ``input_units`` keyword argument.
+
+        Parameters
+        ----------
+
+        input_array : iterable
+            A tuple, list, or array to attach units to
+        input_units : String unit specification, unit symbol object, or astropy
+                      units object
+            The units of the array. Powers must be specified using python syntax
+            (cm**3, not cm^3).
+        dtype : string or NumPy dtype object
+            The dtype of the returned array data
+
+        Examples
+        --------
+
+        >>> a = sc.arr([1, 2, 3], 'cm')
+        >>> b = sc.arr([4, 5, 6], 'm')
+        >>> a + b
+        YTArray([ 401.,  502.,  603.]) cm
+        >>> b + a
+        YTArray([ 4.01,  5.02,  6.03]) m
+
+        Arrays returned by this function know about the scene's unit system
+
+        >>> a = sc.arr(np.ones(5), 'unitary')
+        >>> a.in_units('Mpc')
+        YTArray([ 1.00010449,  1.00010449,  1.00010449,  1.00010449,
+                 1.00010449]) Mpc
+
+        """
+        if self._arr is not None:
+            return self._arr
+        self._arr = functools.partial(YTArray, registry=self.unit_registry)
+        return self._arr
+
+    _quan = None
+    @property
+    def quan(self):
+        """Converts an scalar into a :class:`yt.units.yt_array.YTQuantity`
+
+        The returned YTQuantity will be dimensionless by default, but can be
+        cast to arbitrary units using the ``input_units`` keyword argument.
+
+        Parameters
+        ----------
+
+        input_scalar : an integer or floating point scalar
+            The scalar to attach units to
+        input_units : String unit specification, unit symbol object, or astropy
+                      units
+            The units of the quantity. Powers must be specified using python
+            syntax (cm**3, not cm^3).
+        dtype : string or NumPy dtype object
+            The dtype of the array data.
+
+        Examples
+        --------
+
+        >>> a = sc.quan(1, 'cm')
+        >>> b = sc.quan(2, 'm')
+        >>> a + b
+        201.0 cm
+        >>> b + a
+        2.01 m
+
+        Quantities created this way automatically know about the unit system
+        of the scene
+
+        >>> a = ds.quan(5, 'unitary')
+        >>> a.in_cgs()
+        1.543e+25 cm
+
+        """
+        if self._quan is not None:
+            return self._quan
+        self._quan = functools.partial(YTQuantity, registry=self.unit_registry)
+        return self._quan
+
     def _repr_png_(self):
         if self.last_render is None:
             self.render()


https://bitbucket.org/yt_analysis/yt/commits/4037d6004866/
Changeset:   4037d6004866
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:27:45+00:00
Summary:     Do a better job of sanitizing units for camera properties
Affected #:  1 file

diff -r 65a35d3ae0ad49f2ad67dae8cb84af48ea61c26a -r 4037d600486649b45012be3e38ba5c355c288242 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -13,7 +13,9 @@
 
 from yt.funcs import iterable, mylog, ensure_numpy_array
 from yt.utilities.orientation import Orientation
-from yt.units.yt_array import YTArray
+from yt.units.yt_array import \
+    YTArray, \
+    YTQuantity
 from yt.units.unit_registry import UnitParseError
 from yt.utilities.math_utils import get_rotation_matrix
 from yt.extern.six import string_types
@@ -22,6 +24,21 @@
 import numpy as np
 
 
+def _sanitize_camera_property_units(value, scene):
+    if iterable(value):
+        if isinstance(value, YTArray):
+            san_value = scene.arr(value.in_units('unitary'))
+        else:
+            san_value = scene.arr(value, 'unitary')
+    else:
+        if isinstance(value, YTQuantity):
+            san_value = scene.arr([value.d]*3, value.units)
+            san_value.convert_to_units('unitary')
+        else:
+            san_value = scene.arr([value]*3, 'unitary')
+    return san_value
+
+
 class Camera(Orientation):
     r"""A representation of a point of view into a Scene.
 
@@ -124,9 +141,8 @@
             return self._position
 
         def fset(self, value):
-            if isinstance(value, YTArray):
-                value = value.in_units("code_length")
-            self._position = value
+            position = _sanitize_camera_property_units(value, self.scene)
+            self._position = position
             self.switch_orientation()
 
         def fdel(self):
@@ -144,9 +160,8 @@
             return self._width
 
         def fset(self, value):
-            if isinstance(value, YTArray):
-                value = value.in_units("code_length")
-            self._width = value
+            width = _sanitize_camera_property_units(value, self.scene)
+            self._width = width
             self.switch_orientation()
 
         def fdel(self):
@@ -166,9 +181,8 @@
             return self._focus
 
         def fset(self, value):
-            if isinstance(value, YTArray):
-                value = value.in_units("code_length")
-            self._focus = value
+            focus = _sanitize_camera_property_units(value, self.scene)
+            self._focus = focus
             self.switch_orientation()
 
         def fdel(self):


https://bitbucket.org/yt_analysis/yt/commits/5da7e8ef46ca/
Changeset:   5da7e8ef46ca
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:29:02+00:00
Summary:     Update tests and docs to match new Camera API
Affected #:  3 files

diff -r 4037d600486649b45012be3e38ba5c355c288242 -r 5da7e8ef46ca79c9850ee0d79991a71712b99435 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -20,7 +20,9 @@
 from yt.utilities.math_utils import get_rotation_matrix
 from yt.extern.six import string_types
 from .utils import data_source_or_all
-from .lens import lenses
+from .lens import \
+    lenses, \
+    Lens
 import numpy as np
 
 

diff -r 4037d600486649b45012be3e38ba5c355c288242 -r 5da7e8ef46ca79c9850ee0d79991a71712b99435 yt/visualization/volume_rendering/tests/test_lenses.py
--- a/yt/visualization/volume_rendering/tests/test_lenses.py
+++ b/yt/visualization/volume_rendering/tests/test_lenses.py
@@ -14,7 +14,7 @@
 import tempfile
 import shutil
 from yt.testing import fake_random_ds
-from yt.visualization.volume_rendering.api import Scene, Camera, VolumeSource
+from yt.visualization.volume_rendering.api import Scene, VolumeSource
 import numpy as np
 from unittest import TestCase
 
@@ -39,6 +39,7 @@
 
         self.field = ("gas", "density")
         self.ds = fake_random_ds(32, fields=self.field)
+        self.ds.index
 
     def tearDown(self):
         if self.use_tmpdir:
@@ -47,7 +48,7 @@
 
     def test_perspective_lens(self):
         sc = Scene()
-        cam = Camera(self.ds, lens_type='perspective')
+        cam = sc.add_camera(self.ds, lens_type='perspective')
         cam.position = self.ds.arr(np.array([1.0, 1.0, 1.0]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
@@ -59,7 +60,7 @@
 
     def test_stereoperspective_lens(self):
         sc = Scene()
-        cam = Camera(self.ds, lens_type='stereo-perspective')
+        cam = sc.add_camera(self.ds, lens_type='stereo-perspective')
         cam.resolution = [1024, 512]
         cam.position = self.ds.arr(np.array([0.7, 0.7, 0.7]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
@@ -74,7 +75,7 @@
         dd = self.ds.sphere(self.ds.domain_center,
                             self.ds.domain_width[0] / 10)
         sc = Scene()
-        cam = Camera(dd, lens_type='fisheye')
+        cam = sc.add_camera(dd, lens_type='fisheye')
         cam.lens.fov = 360.0
         cam.set_width(self.ds.domain_width)
         v, c = self.ds.find_max('density')
@@ -91,7 +92,7 @@
         dd = self.ds.sphere(self.ds.domain_center,
                             self.ds.domain_width[0] / 10)
         sc = Scene()
-        cam = Camera(dd, lens_type='plane-parallel')
+        cam = sc.add_camera(dd, lens_type='plane-parallel')
         cam.set_width(self.ds.domain_width*1e-2)
         v, c = self.ds.find_max('density')
         vol = VolumeSource(dd, field=self.field)
@@ -104,7 +105,7 @@
 
     def test_spherical_lens(self):
         sc = Scene()
-        cam = Camera(self.ds, lens_type='spherical')
+        cam = sc.add_camera(self.ds, lens_type='spherical')
         cam.resolution = [512, 256]
         cam.position = self.ds.arr(np.array([0.6, 0.5, 0.5]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
@@ -119,7 +120,7 @@
         w = (self.ds.domain_width).in_units('code_length')
         w = self.ds.arr(w, 'code_length')
         sc = Scene()
-        cam = Camera(self.ds, lens_type='stereo-spherical')
+        cam = sc.add_camera(self.ds, lens_type='stereo-spherical')
         cam.resolution = [1024, 256]
         cam.position = self.ds.arr(np.array([0.6, 0.5, 0.5]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)

diff -r 4037d600486649b45012be3e38ba5c355c288242 -r 5da7e8ef46ca79c9850ee0d79991a71712b99435 yt/visualization/volume_rendering/volume_rendering.py
--- a/yt/visualization/volume_rendering/volume_rendering.py
+++ b/yt/visualization/volume_rendering/volume_rendering.py
@@ -77,7 +77,7 @@
         source = VolumeSource(data_source, field=field)
 
     sc.add_source(source)
-    sc.camera = Camera(data_source=data_source, lens_type=lens_type)
+    sc.add_camera(data_source=data_source, lens_type=lens_type)
     return sc
 
 


https://bitbucket.org/yt_analysis/yt/commits/a580bcd37857/
Changeset:   a580bcd37857
Branch:      yt
User:        ngoldbaum
Date:        2016-03-03 23:37:19+00:00
Summary:     Making the unit_registry attribute of the scene object a property

This allows us to update camera properties when the registry changes
Affected #:  1 file

diff -r 5da7e8ef46ca79c9850ee0d79991a71712b99435 -r a580bcd378575c00bb2f8dee6b6533e8ebc8e474 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -84,15 +84,13 @@
 
     _current = None
     _camera = None
+    _unit_registry = None
 
     def __init__(self):
         r"""Create a new Scene instance"""
         super(Scene, self).__init__()
         self.sources = OrderedDict()
         self.last_render = None
-        self.unit_registry = UnitRegistry()
-        # This will be updated when we add a volume source
-        self.unit_registry.add("unitary", 1.0, length)
         # A non-public attribute used to get around the fact that we can't
         # pass kwargs into _repr_png_()
         self._sigma_clip = None
@@ -411,6 +409,31 @@
         return locals()
     camera = property(**camera())
 
+    def unit_registry():
+        def fget(self):
+            ur = self._unit_registry
+            if ur is None:
+                ur = UnitRegistry()
+                # This will be updated when we add a volume source
+                ur.add("unitary", 1.0, length)
+            self._unit_registry = ur
+            return self._unit_registry
+
+        def fset(self, value):
+            self._unit_registry = value
+            self.camera.width = \
+                YTArray(self.camera.width.in_units('unitary'), registry=value)
+            self.camera.focus = \
+                YTArray(self.camera.focus.in_units('unitary'), registry=value)
+            self.camera.position = \
+                YTArray(self.camera.position.in_units('unitary'), registry=value)
+
+        def fdel(self):
+            del self._unit_registry
+            self._unit_registry = None
+        return locals()
+    unit_registry = property(**unit_registry())
+
     def set_camera(self, camera):
         r"""
 


https://bitbucket.org/yt_analysis/yt/commits/971ac881b80a/
Changeset:   971ac881b80a
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 00:12:02+00:00
Summary:     Sanitize units for arctan2 call
Affected #:  1 file

diff -r a580bcd378575c00bb2f8dee6b6533e8ebc8e474 -r 971ac881b80a1860d4d157445ced31528b0ce63b yt/visualization/volume_rendering/lens.py
--- a/yt/visualization/volume_rendering/lens.py
+++ b/yt/visualization/volume_rendering/lens.py
@@ -319,7 +319,8 @@
         north_vec = camera.unit_vectors[1]
         normal_vec = camera.unit_vectors[2]
 
-        angle_disparity = - np.arctan2(disparity, camera.width[2])
+        angle_disparity = - np.arctan2(disparity.in_units(camera.width.units),
+                                       camera.width[2])
         R = get_rotation_matrix(angle_disparity, north_vec)
 
         east_vec_rot = np.dot(R, east_vec)


https://bitbucket.org/yt_analysis/yt/commits/19a6351697ee/
Changeset:   19a6351697ee
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 02:38:12+00:00
Summary:     Refactor new unit registry setting code so camera can use it
Affected #:  2 files

diff -r 971ac881b80a1860d4d157445ced31528b0ce63b -r 19a6351697ee5f236e268c958f7e040bac0b8a6b yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -113,6 +113,7 @@
         self.data_source = data_source_or_all(data_source)
         self._resolution = (512, 512)
         if self.data_source is not None:
+            self.scene.set_new_unit_registry(data_source.ds.unit_registry)
             self._focus = self.data_source.ds.domain_center
             self._position = self.data_source.ds.domain_right_edge
             self._width = 1.5*self.data_source.ds.domain_width

diff -r 971ac881b80a1860d4d157445ced31528b0ce63b -r 19a6351697ee5f236e268c958f7e040bac0b8a6b yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -135,29 +135,33 @@
         if keyname is None:
             keyname = 'source_%02i' % len(self.sources)
 
+        if isinstance(render_source, (VolumeSource, MeshSource, GridSource)):
+            self.set_new_unit_registry(
+                render_source.data_source.ds.unit_registry)
+
         self.sources[keyname] = render_source
 
-        if isinstance(render_source, (VolumeSource, MeshSource, GridSource)):
-            if hasattr(self, 'unit_registry'):
-                current_scaling = self.unit_registry['unitary'][0]
-                source_reg = render_source.data_source.ds.unit_registry
-                if current_scaling != source_reg['unitary'][0]:
-                    for source in self.sources.items():
-                        data_source = getattr(source, 'data_source', None)
-                        if data_source is None:
-                            continue
-                        scaling = data_source.ds.unit_registry['unitary'][0]
-                        if scaling != source_reg['unitary'][0]:
-                            raise NotImplementedError(
-                                "Scenes including VolumeSource instances based "
-                                "on datasets with different unit scalings is "
-                                "not yet supported."
-                            )
-            self.unit_registry = UnitRegistry(
-                add_default_symbols=False,
-                lut=render_source.data_source.ds.unit_registry.lut)
+        return self
 
-        return self
+    def set_new_unit_registry(self, input_registry):
+        self.unit_registry = UnitRegistry(
+            add_default_symbols=False,
+            lut=input_registry.lut)
+
+        # Validate that the new unit registry makes sense
+        current_scaling = self.unit_registry['unitary'][0]
+        if current_scaling != input_registry['unitary'][0]:
+            for source in self.sources.items():
+                data_source = getattr(source, 'data_source', None)
+                if data_source is None:
+                    continue
+                scaling = data_source.ds.unit_registry['unitary'][0]
+                if scaling != current_scaling:
+                    raise NotImplementedError(
+                        "Simultaneously rendering data from datasets with "
+                        "different units is not supported"
+                    )
+
 
     def render(self, camera=None):
         r"""Render all sources in the Scene.


https://bitbucket.org/yt_analysis/yt/commits/411e34b39402/
Changeset:   411e34b39402
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 02:38:39+00:00
Summary:     Update docstrings, tests, and style issues
Affected #:  12 files

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
--- a/yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
+++ b/yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
@@ -2,7 +2,7 @@
 import numpy as np
 from yt.utilities.lib.bounding_volume_hierarchy import BVH, \
     test_ray_trace
-from yt.visualization.volume_rendering.api import Camera
+from yt.visualization.volume_rendering.api import Camera, Scene
 from yt.testing import requires_file
 
 
@@ -36,7 +36,7 @@
 
     bvh = BVH(vertices, indices, field_data)
 
-    cam = Camera()
+    cam = Camera(Scene())
     cam.set_position(np.array([8.0, 8.0, 8.0]))
     cam.focus = np.array([0.0, 0.0, 0.0])
     origins, direction = get_rays(cam)

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -11,12 +11,11 @@
 # The full license is in the file COPYING.txt, distributed with this software.
 # ----------------------------------------------------------------------------
 
-from yt.funcs import iterable, mylog, ensure_numpy_array
+from yt.funcs import iterable, ensure_numpy_array
 from yt.utilities.orientation import Orientation
 from yt.units.yt_array import \
     YTArray, \
     YTQuantity
-from yt.units.unit_registry import UnitParseError
 from yt.utilities.math_utils import get_rotation_matrix
 from yt.extern.six import string_types
 from .utils import data_source_or_all
@@ -75,15 +74,17 @@
     to be reasonable for the argument Dataset.
 
     >>> import yt
-    >>> from yt.visualization.volume_rendering.api import Camera
+    >>> from yt.visualization.volume_rendering.api import Scene
     >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
-    >>> cam = Camera(ds)
+    >>> sc = Scene()
+    >>> cam = sc.add_camera(ds)
 
     Here, we set the camera properties manually:
 
     >>> import yt
-    >>> from yt.visualization.volume_rendering.api import Camera
-    >>> cam = Camera()
+    >>> from yt.visualization.volume_rendering.api import Scene
+    >>> sc = Scene()
+    >>> cam = sc.add_camera()
     >>> cam.position = np.array([0.5, 0.5, -1.0])
     >>> cam.focus = np.array([0.5, 0.5, 0.0])
     >>> cam.north_vector = np.array([1.0, 0.0, 0.0])
@@ -91,9 +92,10 @@
     Finally, we create a camera with a non-default lens:
 
     >>> import yt
-    >>> from yt.visualization.volume_rendering.api import Camera
+    >>> from yt.visualization.volume_rendering.api import Scene
     >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
-    >>> cam = Camera(ds, lens_type='perspective')
+    >>> sc = Scene()
+    >>> cam = sc.add_camera(ds, lens_type='perspective')
 
     """
 
@@ -113,7 +115,7 @@
         self.data_source = data_source_or_all(data_source)
         self._resolution = (512, 512)
         if self.data_source is not None:
-            self.scene.set_new_unit_registry(data_source.ds.unit_registry)
+            self.scene.set_new_unit_registry(self.data_source.ds.unit_registry)
             self._focus = self.data_source.ds.domain_center
             self._position = self.data_source.ds.domain_right_edge
             self._width = 1.5*self.data_source.ds.domain_width
@@ -381,8 +383,9 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> cam = sc.add_camera()
         >>> # rotate the camera by pi / 4 radians:
         >>> cam.rotate(np.pi/4.0)  
         >>> # rotate the camera about the y-axis instead of cam.north_vector:
@@ -434,10 +437,11 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> sc.add_camera()
         >>> # pitch the camera by pi / 4 radians:
-        >>> cam.pitch(np.pi/4.0)  
+        >>> cam.pitch(np.pi/4.0)
         >>> # pitch the camera about the origin instead of its own position:
         >>> cam.pitch(np.pi/4.0, rot_center=np.array([0.0, 0.0, 0.0]))
 
@@ -461,10 +465,11 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> cam = sc.add_camera()
         >>> # yaw the camera by pi / 4 radians:
-        >>> cam.yaw(np.pi/4.0)  
+        >>> cam.yaw(np.pi/4.0)
         >>> # yaw the camera about the origin instead of its own position:
         >>> cam.yaw(np.pi/4.0, rot_center=np.array([0.0, 0.0, 0.0]))
 
@@ -488,8 +493,9 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> cam = sc.add_camera(ds)
         >>> # roll the camera by pi / 4 radians:
         >>> cam.roll(np.pi/4.0)  
         >>> # roll the camera about the origin instead of its own position:
@@ -599,9 +605,10 @@
         --------
 
         >>> import yt
-        >>> from yt.visualization.volume_rendering.api import Camera
+        >>> from yt.visualization.volume_rendering.api import Scene
         >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
-        >>> cam = Camera(ds)
+        >>> sc = Scene()
+        >>> cam = sc.add_camera(ds)
         >>> cam.zoom(1.1)
 
         """
@@ -626,7 +633,6 @@
         --------
 
         >>> import yt
-        >>> from yt.visualization.volume_rendering.api import Camera
         >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
         >>> im, sc = yt.volume_render(ds)
         >>> cam = sc.camera

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e 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
@@ -13,7 +13,6 @@
 
 
 from .scene import Scene
-from .camera import Camera
 from .render_source import VolumeSource
 from .transfer_functions import ProjectionTransferFunction
 from .utils import data_source_or_all
@@ -149,7 +148,7 @@
         data_source.ds.field_dependencies.update(deps)
         fields = [weightfield, weight]
         vol.set_fields(fields)
-    camera = Camera(data_source)
+    camera = sc.add_camera(data_source)
     camera.set_width(width)
     if not iterable(resolution):
         resolution = [resolution]*2
@@ -172,7 +171,6 @@
     camera.switch_orientation(normal_vector,
                               north_vector)
 
-    sc.camera = camera
     sc.add_source(vol)
 
     vol.set_sampler(camera)

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -114,8 +114,7 @@
     >>> sc = Scene()
     >>> source = VolumeSource(ds.all_data(), 'density')
     >>> sc.add_source(source)
-    >>> cam = Camera(ds)
-    >>> sc.camera = cam
+    >>> sc.add_camera()
     >>> im = sc.render()
 
     """

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -66,8 +66,7 @@
     >>> sc = Scene()
     >>> source = VolumeSource(ds.all_data(), 'density')
     >>> sc.add_source(source)
-    >>> cam = Camera(ds)
-    >>> sc.camera = cam
+    >>> cam = sc.add_camera()
     >>> im = sc.render()
 
     Alternatively, you can use the create_scene function to set up defaults 

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/tests/test_composite.py
--- a/yt/visualization/volume_rendering/tests/test_composite.py
+++ b/yt/visualization/volume_rendering/tests/test_composite.py
@@ -14,8 +14,11 @@
 import tempfile
 import shutil
 from yt.testing import fake_random_ds
-from yt.visualization.volume_rendering.api import Scene, Camera, \
-    VolumeSource, LineSource, BoxSource
+from yt.visualization.volume_rendering.api import \
+    Scene, \
+    VolumeSource, \
+    LineSource, \
+    BoxSource
 from yt.data_objects.api import ImageArray
 import numpy as np
 from unittest import TestCase
@@ -52,9 +55,8 @@
         ds.field_info[ds.field_list[0]].take_log=False
 
         sc = Scene()
-        cam = Camera(ds)
+        cam = sc.add_camera(ds)
         cam.resolution = (512, 512)
-        sc.camera = cam
         vr = VolumeSource(dd, field=ds.field_list[0])
         vr.transfer_function.clear()
         vr.transfer_function.grey_opacity=True

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/tests/test_lenses.py
--- a/yt/visualization/volume_rendering/tests/test_lenses.py
+++ b/yt/visualization/volume_rendering/tests/test_lenses.py
@@ -53,7 +53,6 @@
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_perspective_%s.png' % self.field[1], sigma_clip=6.0)
@@ -66,7 +65,6 @@
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_stereoperspective_%s.png' % self.field[1], sigma_clip=6.0)
@@ -83,7 +81,6 @@
         vol = VolumeSource(dd, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_fisheye_%s.png' % self.field[1], sigma_clip=6.0)
@@ -98,7 +95,6 @@
         vol = VolumeSource(dd, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_plane_%s.png' % self.field[1], sigma_clip=6.0)
@@ -111,7 +107,6 @@
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_spherical_%s.png' % self.field[1], sigma_clip=6.0)
@@ -126,7 +121,6 @@
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_stereospherical_%s.png' % self.field[1], sigma_clip=6.0)

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/tests/test_mesh_render.py
--- a/yt/visualization/volume_rendering/tests/test_mesh_render.py
+++ b/yt/visualization/volume_rendering/tests/test_mesh_render.py
@@ -15,7 +15,7 @@
 from yt.testing import fake_hexahedral_ds
 from yt.testing import requires_module
 from yt.visualization.volume_rendering.render_source import MeshSource
-from yt.visualization.volume_rendering.camera import Camera
+from yt.visualization.volume_rendering.scene import Scene
 
 
 @requires_module("pyembree")
@@ -24,17 +24,18 @@
     images = []
 
     ds = fake_tetrahedral_ds()
+    sc = Scene()
     for field in ds.field_list:
-        ms = MeshSource(ds, field)
-        cam = Camera(ds)
-        im = ms.render(cam)
+        sc.add_source(MeshSource(ds, field))
+        sc.add_camera()
+        im = sc.render()
         images.append(im)
 
     ds = fake_hexahedral_ds()
     for field in ds.field_list:
-        ms = MeshSource(ds, field)
-        cam = Camera(ds)
-        im = ms.render(cam)
+        sc.add_source(MeshSource(ds, field))
+        sc.add_camera()
+        im = sc.render()
         images.append(im)
 
     return images

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/tests/test_points.py
--- a/yt/visualization/volume_rendering/tests/test_points.py
+++ b/yt/visualization/volume_rendering/tests/test_points.py
@@ -14,8 +14,10 @@
 import tempfile
 import shutil
 from yt.testing import fake_random_ds
-from yt.visualization.volume_rendering.api import Scene, Camera, \
-    VolumeSource, PointSource
+from yt.visualization.volume_rendering.api import \
+    Scene, \
+    VolumeSource, \
+    PointSource
 import numpy as np
 from unittest import TestCase
 
@@ -51,9 +53,8 @@
         ds.field_info[ds.field_list[0]].take_log=False
 
         sc = Scene()
-        cam = Camera(ds)
+        cam = sc.add_camera(ds)
         cam.resolution = (512,512)
-        sc.camera = cam
         vr = VolumeSource(dd, field=ds.field_list[0])
         vr.transfer_function.clear()
         vr.transfer_function.grey_opacity=False

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/tests/test_vr_orientation.py
--- a/yt/visualization/volume_rendering/tests/test_vr_orientation.py
+++ b/yt/visualization/volume_rendering/tests/test_vr_orientation.py
@@ -20,7 +20,6 @@
     GenericImageTest
 from yt.visualization.volume_rendering.api import \
     Scene, \
-    Camera, \
     VolumeSource, \
     ColorTransferFunction, \
     off_axis_projection
@@ -114,14 +113,13 @@
     for lens_type in ['plane-parallel', 'perspective']:
         frame = 0
 
-        cam = Camera(ds, lens_type='plane-parallel')
+        cam = sc.add_camera(ds, lens_type='plane-parallel')
         cam.resolution = (1000, 1000)
         cam.position = ds.arr(np.array([-4., 0., 0.]), 'code_length')
         cam.switch_orientation(normal_vector=[1., 0., 0.],
                                north_vector=[0., 0., 1.])
         cam.set_width(ds.domain_width*2.)
 
-        sc.camera = cam
         sc.add_source(vol)
         yield VRImageComparisonTest(
             sc, ds, '%s_%04d' % (lens_type, frame), decimals)
@@ -130,7 +128,6 @@
             frame += 1
             center = ds.arr([0, 0, 0], 'code_length')
             cam.yaw(theta, rot_center=center)
-            sc.camera = cam
             yield VRImageComparisonTest(
                 sc, ds, 'yaw_%s_%04d' % (lens_type, frame), decimals)
 
@@ -139,7 +136,6 @@
             theta = np.pi / n_frames
             center = ds.arr([0, 0, 0], 'code_length')
             cam.pitch(theta, rot_center=center)
-            sc.camera = cam
             yield VRImageComparisonTest(
                 sc, ds, 'pitch_%s_%04d' % (lens_type, frame), decimals)
 
@@ -148,7 +144,6 @@
             theta = np.pi / n_frames
             center = ds.arr([0, 0, 0], 'code_length')
             cam.roll(theta, rot_center=center)
-            sc.camera = cam
             yield VRImageComparisonTest(
                 sc, ds, 'roll_%s_%04d' % (lens_type, frame), decimals)
 

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/tests/test_zbuff.py
--- a/yt/visualization/volume_rendering/tests/test_zbuff.py
+++ b/yt/visualization/volume_rendering/tests/test_zbuff.py
@@ -15,8 +15,10 @@
 import shutil
 from yt.testing import fake_random_ds
 from yt.visualization.volume_rendering.api import \
-    Scene, Camera, ZBuffer, \
-    VolumeSource, OpaqueSource
+    Scene, \
+    ZBuffer, \
+    VolumeSource, \
+    OpaqueSource
 from yt.testing import assert_almost_equal
 import numpy as np
 from unittest import TestCase
@@ -54,9 +56,8 @@
         ds.field_info[ds.field_list[0]].take_log=False
 
         sc = Scene()
-        cam = Camera(ds)
+        cam = sc.add_camera(ds)
         cam.resolution = (512,512)
-        sc.camera = cam
         vr = VolumeSource(dd, field=ds.field_list[0])
         vr.transfer_function.clear()
         vr.transfer_function.grey_opacity=True

diff -r 19a6351697ee5f236e268c958f7e040bac0b8a6b -r 411e34b394024e7be98d6764864915458d59b47e yt/visualization/volume_rendering/volume_rendering.py
--- a/yt/visualization/volume_rendering/volume_rendering.py
+++ b/yt/visualization/volume_rendering/volume_rendering.py
@@ -13,7 +13,6 @@
 
 
 from .scene import Scene
-from .camera import Camera
 from .render_source import VolumeSource, \
     MeshSource
 from .utils import data_source_or_all


https://bitbucket.org/yt_analysis/yt/commits/c5d62fc55f23/
Changeset:   c5d62fc55f23
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 03:01:40+00:00
Summary:     Add extra detail to docstring for add_camera
Affected #:  1 file

diff -r 411e34b394024e7be98d6764864915458d59b47e -r c5d62fc55f239a3131555f1dbca42ded9bd54635 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -327,15 +327,18 @@
 
     def add_camera(self, data_source=None, lens_type='plane-parallel',
                    auto=False):
-        r"""Add a camera to the Scene.
+        r"""Add a new camera to the Scene.
 
-        It is defined by a position (the location of the camera
+        The camera is defined by a position (the location of the camera
         in the simulation domain,), a focus (the point at which the
         camera is pointed), a width (the width of the snapshot that will
         be taken, a resolution (the number of pixels in the image), and
         a north_vector (the "up" direction in the resulting image). A
         camera can use a variety of different Lens objects.
 
+        If the scene already has a camera associated with it, this function
+        will create a new camera and discard the old one.
+
         Parameters
         ----------
         data_source: :class:`AMR3DData` or :class:`Dataset`, optional


https://bitbucket.org/yt_analysis/yt/commits/bbbdfb96ca1e/
Changeset:   bbbdfb96ca1e
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 03:43:40+00:00
Summary:     Give GridSource a data_source attribute
Affected #:  1 file

diff -r c5d62fc55f239a3131555f1dbca42ded9bd54635 -r bbbdfb96ca1ef708979eed1ce272f125e810916b yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -945,10 +945,10 @@
 
     def __init__(self, data_source, alpha=0.3, cmap='algae',
                  min_level=None, max_level=None):
-        data_source = data_source_or_all(data_source)
+        self.data_source = data_source_or_all(data_source)
         corners = []
         levels = []
-        for block, mask in data_source.blocks:
+        for block, mask in self.data_source.blocks:
             block_corners = np.array([
                 [block.LeftEdge[0], block.LeftEdge[1], block.LeftEdge[2]],
                 [block.RightEdge[0], block.LeftEdge[1], block.LeftEdge[2]],
@@ -975,7 +975,7 @@
 
         colors = apply_colormap(
             levels*1.0,
-            color_bounds=[0, data_source.ds.index.max_level],
+            color_bounds=[0, self.data_source.ds.index.max_level],
             cmap_name=cmap)[0, :, :]*alpha/255.
         colors[:, 3] = alpha
 


https://bitbucket.org/yt_analysis/yt/commits/bf83734326f3/
Changeset:   bf83734326f3
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 16:09:51+00:00
Summary:     Fix BVH test
Affected #:  1 file

diff -r bbbdfb96ca1ef708979eed1ce272f125e810916b -r bf83734326f3dc5431c92534fed0b0f0c255359f yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
--- a/yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
+++ b/yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
@@ -12,13 +12,13 @@
     W = np.array([8.0, 8.0])
     N = np.array([800, 800])
     dx = W / N
-    
+
     x_points = np.linspace((-N[0]/2 + 0.5)*dx[0], (N[0]/2 - 0.5)*dx[0], N[0])
     y_points = np.linspace((-N[1]/2 + 0.5)*dx[1], (N[1]/2 - 0.5)*dx[0], N[1])
-    
+
     X, Y = np.meshgrid(x_points, y_points)
-    result = np.dot(camera.unit_vectors[0:2].T, [X.ravel(), Y.ravel()]) 
-    vec_origins = result.T + camera.position
+    result = np.dot(camera.unit_vectors[0:2].T, [X.ravel(), Y.ravel()])
+    vec_origins = camera.scene.arr(result.T, 'unitary') + camera.position
     return np.array(vec_origins), np.array(normal_vector)
 
 


https://bitbucket.org/yt_analysis/yt/commits/2c0e44fb7d02/
Changeset:   2c0e44fb7d02
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 21:48:59+00:00
Summary:     Fix two cases where the proper registry wasn't being attached to new arrays
Affected #:  1 file

diff -r bf83734326f3dc5431c92534fed0b0f0c255359f -r 2c0e44fb7d025bf9abccf10f8c58e7bf20f0fa8f yt/units/yt_array.py
--- a/yt/units/yt_array.py
+++ b/yt/units/yt_array.py
@@ -358,7 +358,7 @@
         elif iterable(input_array) and input_array:
             if isinstance(input_array[0], YTArray):
                 return YTArray(np.array(input_array, dtype=dtype),
-                               input_array[0].units)
+                               input_array[0].units, registry=registry)
 
         # Input array is an already formed ndarray instance
         # We first cast to be our class type
@@ -370,7 +370,10 @@
             # Nothing provided. Make dimensionless...
             units = Unit()
         elif isinstance(input_units, Unit):
-            units = input_units
+            if registry and registry is not input_units.registry:
+                units = Unit(str(input_units), registry=registry)
+            else:
+                units = input_units
         else:
             # units kwarg set, but it's not a Unit object.
             # don't handle all the cases here, let the Unit class handle if


https://bitbucket.org/yt_analysis/yt/commits/2871daa99b4b/
Changeset:   2871daa99b4b
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 21:49:55+00:00
Summary:     Add new wrappers around numpy functions that drop units
Affected #:  1 file

diff -r 2c0e44fb7d025bf9abccf10f8c58e7bf20f0fa8f -r 2871daa99b4b19c91ac7e4fda75b6c4b955278e6 yt/units/yt_array.py
--- a/yt/units/yt_array.py
+++ b/yt/units/yt_array.py
@@ -1412,6 +1412,31 @@
     v = validate_numpy_wrapper_units(v, [arr1, arr2])
     return v
 
+def unorm(data):
+    """Matrix or vector norm that preserves units
+
+    This is a wrapper around np.linalg.norm that preserves units.
+    """
+    return YTArray(np.linalg.norm(data), data.units)
+
+def uvstack(arrs):
+    """Stack arrays in sequence vertically (row wise) while preserving units
+
+    This is a wrapper around np.vstack that preserves units.
+    """
+    v = np.vstack(arrs)
+    v = validate_numpy_wrapper_units(v, arrs)
+    return v
+
+def uhstack(arrs):
+    """Stack arrays in sequence horizontally (column wise) while preserving units
+
+    This is a wrapper around np.hstack that preserves units.
+    """
+    v = np.vstack(arrs)
+    v = validate_numpy_wrapper_units(v, arrs)
+    return v
+
 def array_like_field(data, x, field):
     field = data._determine_fields(field)[0]
     if isinstance(field, tuple):


https://bitbucket.org/yt_analysis/yt/commits/246d7181e546/
Changeset:   246d7181e546
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 21:50:34+00:00
Summary:     Don't create a camera if one hasn't been created yet
Affected #:  1 file

diff -r 2871daa99b4b19c91ac7e4fda75b6c4b955278e6 -r 246d7181e546d825f7120dfce26326a26f6847d5 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -396,10 +396,6 @@
         """
 
         def fget(self):
-            cam = self._camera
-            if cam is None:
-                cam = Camera(self)
-            self._camera = cam
             return self._camera
 
         def fset(self, value):
@@ -427,12 +423,13 @@
 
         def fset(self, value):
             self._unit_registry = value
-            self.camera.width = \
-                YTArray(self.camera.width.in_units('unitary'), registry=value)
-            self.camera.focus = \
-                YTArray(self.camera.focus.in_units('unitary'), registry=value)
-            self.camera.position = \
-                YTArray(self.camera.position.in_units('unitary'), registry=value)
+            if self.camera is not None:
+                self.camera.width = YTArray(
+                    self.camera.width.in_units('unitary'), registry=value)
+                self.camera.focus = YTArray(
+                    self.camera.focus.in_units('unitary'), registry=value)
+                self.camera.position = YTArray(
+                    self.camera.position.in_units('unitary'), registry=value)
 
         def fdel(self):
             del self._unit_registry


https://bitbucket.org/yt_analysis/yt/commits/e9ec498104f6/
Changeset:   e9ec498104f6
Branch:      yt
User:        ngoldbaum
Date:        2016-03-04 21:50:53+00:00
Summary:     Fix a number of unit issues in the lens code
Affected #:  2 files

diff -r 246d7181e546d825f7120dfce26326a26f6847d5 -r e9ec498104f6bc11abe3ab7dbba192401990c102 yt/visualization/volume_rendering/lens.py
--- a/yt/visualization/volume_rendering/lens.py
+++ b/yt/visualization/volume_rendering/lens.py
@@ -16,8 +16,8 @@
 from yt.funcs import mylog
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     ParallelAnalysisInterface
-from yt.units.yt_array import YTArray
 from yt.data_objects.image_array import ImageArray
+from yt.units.yt_array import unorm, uvstack
 from yt.utilities.math_utils import get_rotation_matrix
 import numpy as np
 
@@ -59,13 +59,14 @@
         unit_vectors = camera.unit_vectors
         width = camera.width
         center = camera.focus
-        self.box_vectors = YTArray([unit_vectors[0] * width[0],
-                                    unit_vectors[1] * width[1],
-                                    unit_vectors[2] * width[2]])
-        self.origin = center - 0.5 * width.dot(YTArray(unit_vectors, ""))
+
+        self.box_vectors = camera.scene.arr(
+            [unit_vectors[0] * width[0],
+             unit_vectors[1] * width[1],
+             unit_vectors[2] * width[2]])
+        self.origin = center - 0.5 * width.dot(unit_vectors)
         self.back_center = center - 0.5 * width[2] * unit_vectors[2]
         self.front_center = center + 0.5 * width[2] * unit_vectors[2]
-
         self.set_viewpoint(camera)
 
     def set_viewpoint(self, camera):
@@ -181,8 +182,9 @@
             camera.resolution[0], camera.resolution[1], 3)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width))
+
         # Rescale the ray to be long enough to cover the entire domain
         vectors = (sample_x + sample_y + normal_vecs * camera.width[2]) * \
             (max_length / camera.width[2])
@@ -200,7 +202,7 @@
             dict(vp_pos=positions,
                  vp_dir=vectors,
                  center=self.back_center.d,
-                 bounds=(0.0, 1.0, 0.0, 1.0),
+                 bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
                  x_vec=uv,
                  y_vec=uv,
                  width=np.zeros(3, dtype='float64'),
@@ -291,8 +293,8 @@
         uv = np.ones(3, dtype='float64')
 
         image = self.new_image(camera)
-        vectors_comb = np.vstack([vectors_left, vectors_right])
-        positions_comb = np.vstack([positions_left, positions_right])
+        vectors_comb = uvstack([vectors_left, vectors_right])
+        positions_comb = uvstack([positions_left, positions_right])
 
         image.shape = (camera.resolution[0], camera.resolution[1], 4)
         vectors_comb.shape = (camera.resolution[0], camera.resolution[1], 3)
@@ -302,7 +304,7 @@
             dict(vp_pos=positions_comb,
                  vp_dir=vectors_comb,
                  center=self.back_center.d,
-                 bounds=(0.0, 1.0, 0.0, 1.0),
+                 bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
                  x_vec=uv,
                  y_vec=uv,
                  width=np.zeros(3, dtype='float64'),
@@ -352,8 +354,9 @@
             single_resolution_x, camera.resolution[1], 3)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width) + np.abs(self.disparity.d)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width)
+                      + np.abs(self.disparity))
         # Rescale the ray to be long enough to cover the entire domain
         vectors = (sample_x + sample_y + normal_vecs * camera.width[2]) * \
             (max_length / camera.width[2])
@@ -383,9 +386,9 @@
         px_right, py_right, dz_right = self._get_px_py_dz(
             camera, pos, res, self.disparity)
 
-        px = np.vstack([px_left, px_right])
-        py = np.vstack([py_left, py_right])
-        dz = np.vstack([dz_left, dz_right])
+        px = uvstack([px_left, px_right])
+        py = uvstack([py_left, py_right])
+        dz = uvstack([dz_left, dz_right])
 
         return px, py, dz
 
@@ -500,7 +503,7 @@
             dict(vp_pos=positions,
                  vp_dir=vp,
                  center=self.center,
-                 bounds=(0.0, 1.0, 0.0, 1.0),
+                 bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
                  x_vec=uv,
                  y_vec=uv,
                  width=np.zeros(3, dtype='float64'),
@@ -583,8 +586,8 @@
         vectors[:, :, 2] = np.sin(py)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width))
         # Rescale the ray to be long enough to cover the entire domain
         vectors = vectors * max_length
 
@@ -615,7 +618,7 @@
             vp_pos=positions,
             vp_dir=vectors,
             center=self.back_center.d,
-            bounds=(0.0, 1.0, 0.0, 1.0),
+            bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
             x_vec=dummy,
             y_vec=dummy,
             width=np.zeros(3, dtype="float64"),
@@ -695,8 +698,9 @@
         vectors[:, :, 2] = np.sin(py)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width) + np.abs(self.disparity.d)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width)
+                      + np.abs(self.disparity))
         # Rescale the ray to be long enough to cover the entire domain
         vectors = vectors * max_length
 
@@ -730,8 +734,8 @@
 
         dummy = np.ones(3, dtype='float64')
 
-        vectors_comb = np.vstack([vectors, vectors])
-        positions_comb = np.vstack([positions_left, positions_right])
+        vectors_comb = uvstack([vectors, vectors])
+        positions_comb = uvstack([positions_left, positions_right])
 
         image.shape = (camera.resolution[0], camera.resolution[1], 4)
         vectors_comb.shape = (camera.resolution[0], camera.resolution[1], 3)
@@ -741,7 +745,7 @@
             vp_pos=positions_comb,
             vp_dir=vectors_comb,
             center=self.back_center.d,
-            bounds=(0.0, 1.0, 0.0, 1.0),
+            bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
             x_vec=dummy,
             y_vec=dummy,
             width=np.zeros(3, dtype="float64"),

diff -r 246d7181e546d825f7120dfce26326a26f6847d5 -r e9ec498104f6bc11abe3ab7dbba192401990c102 yt/visualization/volume_rendering/utils.py
--- a/yt/visualization/volume_rendering/utils.py
+++ b/yt/visualization/volume_rendering/utils.py
@@ -15,7 +15,7 @@
 
 
 def new_mesh_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     args = (
         np.atleast_3d(params['vp_pos']),
         np.atleast_3d(params['vp_dir']),
@@ -32,7 +32,7 @@
 
 
 def new_volume_render_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     params.update(transfer_function=render_source.transfer_function)
     params.update(transfer_function=render_source.transfer_function)
     params.update(num_samples=render_source.num_samples)
@@ -60,7 +60,7 @@
 
 
 def new_interpolated_projection_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     params.update(transfer_function=render_source.transfer_function)
     params.update(num_samples=render_source.num_samples)
     args = (
@@ -84,7 +84,7 @@
 
 
 def new_projection_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     params.update(transfer_function=render_source.transfer_function)
     params.update(num_samples=render_source.num_samples)
     args = (
@@ -106,6 +106,14 @@
     sampler = ProjectionSampler(*args, **kwargs)
     return sampler
 
+def ensure_code_unit_params(params):
+    for param_name in ['center', 'vp_dir', 'width']:
+        param = params[param_name]
+        if hasattr(param, 'in_units'):
+            params[param_name] = param.in_units('code_length')
+    params['bounds'] = tuple(b.in_units('code_length') for b in params['bounds'])
+
+    return params
 
 def get_corners(le, re):
     return np.array([


https://bitbucket.org/yt_analysis/yt/commits/50f40b6d350c/
Changeset:   50f40b6d350c
Branch:      yt
User:        ngoldbaum
Date:        2016-03-06 21:59:45+00:00
Summary:     Fix image comparison answer test failures
Affected #:  1 file

diff -r e9ec498104f6bc11abe3ab7dbba192401990c102 -r 50f40b6d350c092476f686547862ec51bf6b3058 yt/visualization/volume_rendering/lens.py
--- a/yt/visualization/volume_rendering/lens.py
+++ b/yt/visualization/volume_rendering/lens.py
@@ -96,8 +96,9 @@
             image = self.new_image(camera)
 
         sampler_params =\
-            dict(vp_pos=np.concatenate([camera.inv_mat.ravel('F'),
-                                        self.back_center.ravel()]),
+            dict(vp_pos=np.concatenate(
+                [camera.inv_mat.ravel('F'),
+                 self.back_center.ravel().in_units('code_length')]),
                  vp_dir=self.box_vectors[2],  # All the same
                  center=self.back_center,
                  bounds=(-camera.width[0] / 2.0, camera.width[0] / 2.0,
@@ -744,7 +745,7 @@
         sampler_params = dict(
             vp_pos=positions_comb,
             vp_dir=vectors_comb,
-            center=self.back_center.d,
+            center=self.back_center,
             bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
             x_vec=dummy,
             y_vec=dummy,


https://bitbucket.org/yt_analysis/yt/commits/16cc36c9218f/
Changeset:   16cc36c9218f
Branch:      yt
User:        ngoldbaum
Date:        2016-03-06 22:33:30+00:00
Summary:     Fix issues in enzo simulation time series revealed by change to unit registry handling
Affected #:  1 file

diff -r 50f40b6d350c092476f686547862ec51bf6b3058 -r 16cc36c9218f550dd1f9d23aa51fa3a5b6d3ea24 yt/frontends/enzo/simulation_handling.py
--- a/yt/frontends/enzo/simulation_handling.py
+++ b/yt/frontends/enzo/simulation_handling.py
@@ -82,6 +82,7 @@
     def _set_units(self):
         self.unit_registry = UnitRegistry()
         self.unit_registry.add("code_time", 1.0, dimensions.time)
+        self.unit_registry.add("code_length", 1.0, dimensions.length)
         if self.cosmological_simulation:
             # Instantiate EnzoCosmology object for units and time conversions.
             self.cosmology = \
@@ -107,6 +108,7 @@
         else:
             self.time_unit = self.quan(self.parameters["TimeUnits"], "s")
         self.unit_registry.modify("code_time", self.time_unit)
+        self.unit_registry.modify("code_length", self.length_unit)
 
     def get_time_series(self, time_data=True, redshift_data=True,
                         initial_time=None, final_time=None,


https://bitbucket.org/yt_analysis/yt/commits/c4144a866100/
Changeset:   c4144a866100
Branch:      yt
User:        ngoldbaum
Date:        2016-03-06 23:08:30+00:00
Summary:     Make the photon simulator tests less sensitive to tiny changes in answers
Affected #:  2 files

diff -r 16cc36c9218f550dd1f9d23aa51fa3a5b6d3ea24 -r c4144a8661008242c30688d3f27b5525986dca32 yt/analysis_modules/photon_simulator/tests/test_sloshing.py
--- a/yt/analysis_modules/photon_simulator/tests/test_sloshing.py
+++ b/yt/analysis_modules/photon_simulator/tests/test_sloshing.py
@@ -15,10 +15,11 @@
     ThermalPhotonModel, PhotonList, EventList, \
     convert_old_file, merge_files
 from yt.config import ytcfg
-from yt.testing import requires_file
+from yt.testing import \
+    requires_file, \
+    assert_almost_equal
 from yt.utilities.answer_testing.framework import requires_ds, \
     GenericArrayTest, data_dir_load
-from numpy.testing import assert_array_equal
 from numpy.random import RandomState
 from yt.units.yt_array import uconcatenate
 import os
@@ -117,11 +118,11 @@
             arr1 = photons1[k]
             arr2 = photons2[k]
             arr3 = photons3[k]
-        yield assert_array_equal, arr1, arr2
-        yield assert_array_equal, arr1, arr3
+        assert_almost_equal(arr1, arr2)
+        assert_almost_equal(arr1, arr3)
     for k in events1.keys():
-        yield assert_array_equal, events1[k], events2[k]
-        yield assert_array_equal, events1[k], events3[k]
+        assert_almost_equal(events1[k], events2[k])
+        assert_almost_equal(events1[k], events3[k])
 
     nevents = 0
 

diff -r 16cc36c9218f550dd1f9d23aa51fa3a5b6d3ea24 -r c4144a8661008242c30688d3f27b5525986dca32 yt/utilities/answer_testing/framework.py
--- a/yt/utilities/answer_testing/framework.py
+++ b/yt/utilities/answer_testing/framework.py
@@ -803,13 +803,15 @@
 class GenericArrayTest(AnswerTestingTest):
     _type_name = "GenericArray"
     _attrs = ('array_func_name','args','kwargs')
-    def __init__(self, ds_fn, array_func, args=None, kwargs=None, decimals=None):
+    def __init__(self, ds_fn, array_func, args=None, kwargs=None, decimals=None,
+                 close=None):
         super(GenericArrayTest, self).__init__(ds_fn)
         self.array_func = array_func
         self.array_func_name = array_func.__name__
         self.args = args
         self.kwargs = kwargs
         self.decimals = decimals
+        self.close = close
     def run(self):
         if self.args is None:
             args = []
@@ -826,7 +828,7 @@
                                           verbose=True)
         for k in new_result:
             if self.decimals is None:
-                assert_equal(new_result[k], old_result[k])
+                assert_almost_equal(new_result[k], old_result[k])
             else:
                 assert_allclose_units(new_result[k], old_result[k],
                                       10**(-self.decimals))


https://bitbucket.org/yt_analysis/yt/commits/39d6c4f1e5f5/
Changeset:   39d6c4f1e5f5
Branch:      yt
User:        ngoldbaum
Date:        2016-03-06 23:09:20+00:00
Summary:     Remove unused argument
Affected #:  1 file

diff -r c4144a8661008242c30688d3f27b5525986dca32 -r 39d6c4f1e5f51eadd4e1674d51b892eb7795ee23 yt/utilities/answer_testing/framework.py
--- a/yt/utilities/answer_testing/framework.py
+++ b/yt/utilities/answer_testing/framework.py
@@ -803,15 +803,13 @@
 class GenericArrayTest(AnswerTestingTest):
     _type_name = "GenericArray"
     _attrs = ('array_func_name','args','kwargs')
-    def __init__(self, ds_fn, array_func, args=None, kwargs=None, decimals=None,
-                 close=None):
+    def __init__(self, ds_fn, array_func, args=None, kwargs=None, decimals=None):
         super(GenericArrayTest, self).__init__(ds_fn)
         self.array_func = array_func
         self.array_func_name = array_func.__name__
         self.args = args
         self.kwargs = kwargs
         self.decimals = decimals
-        self.close = close
     def run(self):
         if self.args is None:
             args = []


https://bitbucket.org/yt_analysis/yt/commits/bc88f391d8fc/
Changeset:   bc88f391d8fc
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 01:55:03+00:00
Summary:     Fix unstructured mesh rendering example
Affected #:  1 file

diff -r 39d6c4f1e5f51eadd4e1674d51b892eb7795ee23 -r bc88f391d8fc0295ad065f31d30b8d03c3a93747 doc/source/visualizing/unstructured_mesh_rendering.rst
--- a/doc/source/visualizing/unstructured_mesh_rendering.rst
+++ b/doc/source/visualizing/unstructured_mesh_rendering.rst
@@ -334,7 +334,7 @@
     sc = Scene()
 
     # set up our Camera
-    sc.add_camera(ds)
+    cam = sc.add_camera(ds)
     cam.focus = ds.arr([0.0, 0.0, 0.0], 'code_length')
     cam.set_position(ds.arr([-3.0, 3.0, -3.0], 'code_length'),
                      ds.arr([0.0, -1.0, 0.0], 'dimensionless'))


https://bitbucket.org/yt_analysis/yt/commits/34570d12fde1/
Changeset:   34570d12fde1
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 02:30:57+00:00
Summary:     Add vp_pos to parameters that need to be converted to code units
Affected #:  1 file

diff -r bc88f391d8fc0295ad065f31d30b8d03c3a93747 -r 34570d12fde17be941ece863b8c4a9708fe11993 yt/visualization/volume_rendering/utils.py
--- a/yt/visualization/volume_rendering/utils.py
+++ b/yt/visualization/volume_rendering/utils.py
@@ -107,7 +107,7 @@
     return sampler
 
 def ensure_code_unit_params(params):
-    for param_name in ['center', 'vp_dir', 'width']:
+    for param_name in ['center', 'vp_pos', 'vp_dir', 'width']:
         param = params[param_name]
         if hasattr(param, 'in_units'):
             params[param_name] = param.in_units('code_length')


https://bitbucket.org/yt_analysis/yt/commits/9c60f90ee17f/
Changeset:   9c60f90ee17f
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 02:44:24+00:00
Summary:     Fix pernicious issue with vp_pos for perspective camera
Affected #:  1 file

diff -r 34570d12fde17be941ece863b8c4a9708fe11993 -r 9c60f90ee17fa7abf1e970833891fb5c5e44b9ec yt/visualization/volume_rendering/lens.py
--- a/yt/visualization/volume_rendering/lens.py
+++ b/yt/visualization/volume_rendering/lens.py
@@ -95,10 +95,12 @@
         else:
             image = self.new_image(camera)
 
+        vp_pos = np.concatenate(
+            [camera.inv_mat.ravel('F').d,
+             self.back_center.ravel().in_units('code_length').d])
+
         sampler_params =\
-            dict(vp_pos=np.concatenate(
-                [camera.inv_mat.ravel('F'),
-                 self.back_center.ravel().in_units('code_length')]),
+            dict(vp_pos=vp_pos,
                  vp_dir=self.box_vectors[2],  # All the same
                  center=self.back_center,
                  bounds=(-camera.width[0] / 2.0, camera.width[0] / 2.0,


https://bitbucket.org/yt_analysis/yt/commits/438754229d49/
Changeset:   438754229d49
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 02:46:19+00:00
Summary:     Remove unncessary Camera imports in the docs
Affected #:  2 files

diff -r 9c60f90ee17fa7abf1e970833891fb5c5e44b9ec -r 438754229d49e7d995752908b60e2f8a6635fbf0 doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb
--- a/doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb
+++ b/doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb
@@ -22,7 +22,6 @@
     "from IPython.core.display import Image\n",
     "from yt.visualization.volume_rendering.transfer_function_helper import TransferFunctionHelper\n",
     "from yt.visualization.volume_rendering.render_source import VolumeSource\n",
-    "from yt.visualization.volume_rendering.camera import Camera\n",
     "\n",
     "def showme(im):\n",
     "    # screen out NaNs\n",

diff -r 9c60f90ee17fa7abf1e970833891fb5c5e44b9ec -r 438754229d49e7d995752908b60e2f8a6635fbf0 doc/source/visualizing/unstructured_mesh_rendering.rst
--- a/doc/source/visualizing/unstructured_mesh_rendering.rst
+++ b/doc/source/visualizing/unstructured_mesh_rendering.rst
@@ -292,7 +292,6 @@
 .. python-script::
 
     import yt
-    from yt.visualization.volume_rendering.api import Camera
 
     ds = yt.load("MOOSE_sample_data/out.e-s010")
 
@@ -326,7 +325,7 @@
 .. python-script::
 
     import yt
-    from yt.visualization.volume_rendering.api import MeshSource, Camera, Scene
+    from yt.visualization.volume_rendering.api import MeshSource, Scene
 
     ds = yt.load("MOOSE_sample_data/out.e-s010")
 


https://bitbucket.org/yt_analysis/yt/commits/eddf4c926cb8/
Changeset:   eddf4c926cb8
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 03:53:37+00:00
Summary:     Revert changes to bounds
Affected #:  2 files

diff -r 438754229d49e7d995752908b60e2f8a6635fbf0 -r eddf4c926cb8fa5da566445d94fe7a77ee15b0c0 yt/visualization/volume_rendering/lens.py
--- a/yt/visualization/volume_rendering/lens.py
+++ b/yt/visualization/volume_rendering/lens.py
@@ -204,8 +204,8 @@
         sampler_params =\
             dict(vp_pos=positions,
                  vp_dir=vectors,
-                 center=self.back_center.d,
-                 bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
+                 center=self.back_center,
+                 bounds=(0.0, 1.0, 0.0, 1.0),
                  x_vec=uv,
                  y_vec=uv,
                  width=np.zeros(3, dtype='float64'),
@@ -306,8 +306,8 @@
         sampler_params =\
             dict(vp_pos=positions_comb,
                  vp_dir=vectors_comb,
-                 center=self.back_center.d,
-                 bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
+                 center=self.back_center,
+                 bounds=(0.0, 1.0, 0.0, 1.0),
                  x_vec=uv,
                  y_vec=uv,
                  width=np.zeros(3, dtype='float64'),
@@ -506,7 +506,7 @@
             dict(vp_pos=positions,
                  vp_dir=vp,
                  center=self.center,
-                 bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
+                 bounds=(0.0, 1.0, 0.0, 1.0),
                  x_vec=uv,
                  y_vec=uv,
                  width=np.zeros(3, dtype='float64'),
@@ -620,8 +620,8 @@
         sampler_params = dict(
             vp_pos=positions,
             vp_dir=vectors,
-            center=self.back_center.d,
-            bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
+            center=self.back_center,
+            bounds=(0.0, 1.0, 0.0, 1.0),
             x_vec=dummy,
             y_vec=dummy,
             width=np.zeros(3, dtype="float64"),
@@ -748,7 +748,7 @@
             vp_pos=positions_comb,
             vp_dir=vectors_comb,
             center=self.back_center,
-            bounds=camera.scene.arr((0.0, 1.0, 0.0, 1.0), 'unitary'),
+            bounds=(0.0, 1.0, 0.0, 1.0),
             x_vec=dummy,
             y_vec=dummy,
             width=np.zeros(3, dtype="float64"),

diff -r 438754229d49e7d995752908b60e2f8a6635fbf0 -r eddf4c926cb8fa5da566445d94fe7a77ee15b0c0 yt/visualization/volume_rendering/utils.py
--- a/yt/visualization/volume_rendering/utils.py
+++ b/yt/visualization/volume_rendering/utils.py
@@ -111,7 +111,9 @@
         param = params[param_name]
         if hasattr(param, 'in_units'):
             params[param_name] = param.in_units('code_length')
-    params['bounds'] = tuple(b.in_units('code_length') for b in params['bounds'])
+    bounds = params['bounds']
+    if hasattr(bounds[0], 'units'):
+        params['bounds'] = tuple(b.in_units('code_length').d for b in bounds)
 
     return params
 


https://bitbucket.org/yt_analysis/yt/commits/28e2dac5dc6f/
Changeset:   28e2dac5dc6f
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 04:12:45+00:00
Summary:     Implement the scene camera property setter in a more robust way
Affected #:  1 file

diff -r eddf4c926cb8fa5da566445d94fe7a77ee15b0c0 -r 28e2dac5dc6fc13df31aa2da7fc9a530223a5189 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -399,11 +399,10 @@
             return self._camera
 
         def fset(self, value):
-            cam = Camera(self, value.data_source, value.lens)
-            cam.width = value.width
-            cam.focus = value.focus
-            cam.position = value.position
-            self._camera = cam
+            value.width = self.arr(value.width)
+            value.focus = self.arr(value.focus)
+            value.position = self.arr(value.position)
+            self._camera = value
 
         def fdel(self):
             del self._camera


https://bitbucket.org/yt_analysis/yt/commits/47e1be4fae37/
Changeset:   47e1be4fae37
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 04:13:40+00:00
Summary:     Remove unnecessary scene camera setter calls
Affected #:  1 file

diff -r 28e2dac5dc6fc13df31aa2da7fc9a530223a5189 -r 47e1be4fae37c38c14ef7244d2c2e23e4206f4cc doc/source/cookbook/various_lens.py
--- a/doc/source/cookbook/various_lens.py
+++ b/doc/source/cookbook/various_lens.py
@@ -63,7 +63,6 @@
 cam.set_width(ds.domain_width*0.5)
 # Set the distance between left-eye and right-eye.
 cam.lens.disparity = ds.domain_width[0] * 1.e-3
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_stereo-perspective.png', sigma_clip=6.0)
@@ -78,7 +77,6 @@
                        north_vector=north_vector)
 cam.set_width(ds.domain_width)
 cam.lens.fov = 360.0
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_fisheye.png', sigma_clip=6.0)
@@ -95,7 +93,6 @@
                        north_vector=north_vector)
 # In (stereo)spherical camera, camera width is not used since the entire volume
 # will be rendered
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_spherical.png', sigma_clip=6.0)
@@ -112,7 +109,6 @@
 # will be rendered
 # Set the distance between left-eye and right-eye.
 cam.lens.disparity = ds.domain_width[0] * 1.e-3
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_stereo-spherical.png', sigma_clip=6.0)


https://bitbucket.org/yt_analysis/yt/commits/23c55889be10/
Changeset:   23c55889be10
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:13:55+00:00
Summary:     Add a bbox argument for fake_random_ds.

Simply implemented by passing down to load_uniform_grid
Affected #:  1 file

diff -r 47e1be4fae37c38c14ef7244d2c2e23e4206f4cc -r 23c55889be10d448969b6d797596263425a3ae9d yt/testing.py
--- a/yt/testing.py
+++ b/yt/testing.py
@@ -171,7 +171,7 @@
         units = ('g/cm**3', 'cm/s', 'cm/s', 'cm/s'),
         particle_fields=None, particle_field_units=None,
         negative = False, nprocs = 1, particles = 0, length_unit=1.0,
-        unit_system="cgs"):
+        unit_system="cgs", bbox=None):
     from yt.frontends.stream.api import load_uniform_grid
     if not iterable(ndims):
         ndims = [ndims, ndims, ndims]
@@ -208,7 +208,7 @@
             data['io', 'particle_mass'] = (np.random.random(particles), 'g')
         data['number_of_particles'] = particles
     ug = load_uniform_grid(data, ndims, length_unit=length_unit, nprocs=nprocs,
-                           unit_system=unit_system)
+                           unit_system=unit_system, bbox=bbox)
     return ug
 
 _geom_transforms = {


https://bitbucket.org/yt_analysis/yt/commits/195376322a5a/
Changeset:   195376322a5a
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:15:09+00:00
Summary:     Add more input sanitization and improved documentation for camera properties.
Affected #:  1 file

diff -r 23c55889be10d448969b6d797596263425a3ae9d -r 195376322a5ad2552340a85d674857f7b387bbc1 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -23,20 +23,37 @@
     lenses, \
     Lens
 import numpy as np
-
+from numbers import Number as numeric_type
 
 def _sanitize_camera_property_units(value, scene):
     if iterable(value):
-        if isinstance(value, YTArray):
-            san_value = scene.arr(value.in_units('unitary'))
+        if len(value) == 1:
+            return _sanitize_camera_property_units(value[0], scene)
+        elif isinstance(value, YTArray) and len(value) == 3:
+            return scene.arr(value).in_units('unitary')
+        elif isinstance(value[0], numeric_type) and isinstance(value[1], string_types):
+            return scene.arr([scene.arr(value[0], value[1]).in_units('unitary')]*3)
+        if len(value) == 3:
+            if all([iterable(v) for v in value]):
+                if all([isinstance(v[0], numeric_type) and
+                        isinstance(v[1], string_types) for v in value]):
+                    return scene.arr(
+                        [scene.arr(v[0], v[1]) for v in value])
+                else:
+                    raise RuntimeError(
+                        "Cannot set camera width to invalid value '%s'" % (value, ))
+            return scene.arr(value, 'unitary')
         else:
-            san_value = scene.arr(value, 'unitary')
+            raise RuntimeError(
+                "Cannot set camera width to invalid value '%s'" % (value, ))
     else:
         if isinstance(value, YTQuantity):
             san_value = scene.arr([value.d]*3, value.units)
             san_value.convert_to_units('unitary')
+        elif isinstance(value, numeric_type):
+            san_value = scene.arr([value]*3, 'unitary')
         else:
-            san_value = scene.arr([value]*3, 'unitary')
+            raise RuntimeError("Temp error message2")
     return san_value
 
 
@@ -135,12 +152,17 @@
         self.set_lens(lens_type)
 
     def position():
-        doc = '''The position is the location of the camera in
-               the coordinate system of the simulation. This needs
-               to be either a YTArray or a numpy array. If it is a 
-               numpy array, it is assumed to be in code units. If it
-               is a YTArray, it will be converted to code units 
-               automatically. '''
+        doc = '''
+        The location of the camera. 
+
+        Parameters
+        ----------
+
+        position : number, YTQuantity, iterable, or 3 element YTArray
+            If a scalar, assumes that the position is the same in all three
+            coordinates. If an interable, must contain only scalars or
+            (length, unit) tuples.
+        '''
 
         def fget(self):
             return self._position
@@ -156,10 +178,17 @@
     position = property(**position())
 
     def width():
-        doc = '''The width of the region that will be seen in the image. 
-               This needs to be either a YTArray or a numpy array. If it 
-               is a numpy array, it is assumed to be in code units. If it
-               is a YTArray, it will be converted to code units automatically. '''
+        doc = '''The width of the region that will be seen in the image.
+
+        Parameters
+        ----------
+
+        width : number, YTQuantity, iterable, or 3 element YTArray
+            The width of the volume rendering in the horizontal, vertical, and
+            depth directions. If a scalar, assumes that the width is the same in
+            all three directions. If an interable, must contain only scalars or
+            (length, unit) tuples.
+        '''
 
         def fget(self):
             return self._width
@@ -176,11 +205,18 @@
     width = property(**width())
 
     def focus():
-        doc = '''The focus defines the point the Camera is pointed at. This needs
-               to be either a YTArray or a numpy array. If it is a 
-               numpy array, it is assumed to be in code units. If it
-               is a YTArray, it will be converted to code units 
-               automatically. '''
+        doc = '''
+        The focus defines the point the Camera is pointed at.
+
+        Parameters
+        ----------
+
+        focus : number, YTQuantity, iterable, or 3 element YTArray
+            The width of the volume rendering in the horizontal, vertical, and
+            depth directions. If a scalar, assumes that the width is the same in
+            all three directions. If an interable, must contain only scalars or
+            (length, unit) tuples.
+        '''
 
         def fget(self):
             return self._focus
@@ -197,14 +233,15 @@
 
     def resolution():
         doc = '''The resolution is the number of pixels in the image that
-               will be produced. '''
+               will be produced. Must be a 2-tuple of integers or an integer.'''
 
         def fget(self):
             return self._resolution
 
         def fset(self, value):
             if iterable(value):
-                assert (len(value) == 2)
+                if len(value) != 2:
+                    raise RuntimeError
             else:
                 value = (value, value)
             self._resolution = value
@@ -215,6 +252,19 @@
         return locals()
     resolution = property(**resolution())
 
+    def set_resolution(self, resolution):
+        """
+        The resolution is the number of pixels in the image that
+        will be produced. Must be a 2-tuple of integers or an integer.
+        """
+        self.resolution = resolution
+
+    def get_resolution(self):
+        """
+        Returns the resolution of the volume rendering
+        """
+        return self.resolution
+
     def _get_sampler_params(self, render_source):
         lens_params = self.lens._get_sampler_params(self, render_source)
         lens_params.update(width=self.width)
@@ -290,31 +340,61 @@
         Parameters
         ----------
 
-        width : YTQuantity or 3 element YTArray
+        width : number, YTQuantity, iterable, or 3 element YTArray
             The width of the volume rendering in the horizontal, vertical, and
             depth directions. If a scalar, assumes that the width is the same in
-            all three directions.
+            all three directions. If an interable, must contain only scalars or
+            (length, unit) tuples.
         """
         self.width = width
         self.switch_orientation()
 
+    def get_width(self):
+        """Return the current camera width"""
+        return self.width
+
     def set_position(self, position, north_vector=None):
         r"""Set the position of the camera.
 
         Parameters
         ----------
 
-        position : array_like
-            The new position
+        width : number, YTQuantity, iterable, or 3 element YTArray
+            If a scalar, assumes that the position is the same in all three
+            coordinates. If an interable, must contain only scalars or
+            (length, unit) tuples.
+
         north_vector : array_like, optional
             The 'up' direction for the plane of rays.  If not specific,
             calculated automatically.
 
         """
+        self.position = position
+        if north_vector is not None:
+            self.switch_orientation(normal_vector=self.focus - self.position,
+                                    north_vector=north_vector)
 
-        self.position = position
-        self.switch_orientation(normal_vector=self.focus - self.position,
-                                north_vector=north_vector)
+    def get_position(self):
+        """Return the current camera position"""
+        return self.position
+
+    def set_focus(self, new_focus):
+        """Sets the point the Camera is pointed at.
+
+        Parameters
+        ----------
+
+        focus : number, YTQuantity, iterable, or 3 element YTArray
+            If a scalar, assumes that the focus is the same is all three
+            coordinates. If an interable, must contain only scalars or
+            (length, unit) tuples.
+
+        """
+        self.focus = new_focus
+
+    def get_focus(self):
+        """Returns the current camera focus"""
+        return self.focus
 
     def switch_orientation(self, normal_vector=None, north_vector=None):
         r"""Change the view direction based on any of the orientation parameters.


https://bitbucket.org/yt_analysis/yt/commits/e55256417757/
Changeset:   e55256417757
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:15:29+00:00
Summary:     Add tests for camera attributes and properties
Affected #:  1 file

diff -r 195376322a5ad2552340a85d674857f7b387bbc1 -r e552564177577d79a50051aa186d7dad0582e98d yt/visualization/volume_rendering/tests/test_camera_attributes.py
--- /dev/null
+++ b/yt/visualization/volume_rendering/tests/test_camera_attributes.py
@@ -0,0 +1,101 @@
+"""
+Tests for setting camera and scene attributes
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2014, yt Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+import numpy as np
+import yt.units as u
+
+from yt.testing import \
+    assert_equal, \
+    fake_random_ds
+from yt.visualization.volume_rendering.api import \
+    Scene
+
+
+valid_lens_types = [
+    'plane-parallel',
+    'perspective',
+    'stereo-perspective',
+    'fisheye',
+    'spherical',
+    'stereo-spherical'
+]
+
+
+def test_scene_and_camera_attributes():
+    ds = fake_random_ds(64, length_unit=2, bbox=np.array([[-1, 1], [-1, 1], [-1, 1]]))
+    sc = Scene()
+    cam = sc.add_camera(ds)
+
+    # test that initial values are correct in code units
+    assert_equal(cam.width, ds.arr([3, 3, 3], 'code_length'))
+    assert_equal(cam.position, ds.arr([1, 1, 1], 'code_length'))
+    assert_equal(cam.focus, ds.arr([0, 0, 0], 'code_length'))
+
+    # test setting the attributes in various ways
+
+    attribute_values = [
+        (1, ds.arr([2, 2, 2], 'code_length'), ),
+        ([1], ds.arr([2, 2, 2], 'code_length'), ),
+        ([1, 2], RuntimeError, ),
+        ([1, 1, 1], ds.arr([2, 2, 2], 'code_length'), ),
+        ((1, 'code_length'), ds.arr([1, 1, 1], 'code_length'), ),
+        (((1, 'code_length'), (1, 'code_length')), RuntimeError, ),
+        (((1, 'cm'), (2, 'cm'), (3, 'cm')),
+         ds.arr([0.5, 1, 1.5], 'code_length'), ),
+        (2*u.cm, ds.arr([1, 1, 1], 'code_length'), ),
+        ([2*u.cm], ds.arr([1, 1, 1], 'code_length'), ),
+        ([1, 2, 3]*u.cm, ds.arr([0.5, 1, 1.5], 'code_length'), ),
+        ([1, 2]*u.cm, RuntimeError, ),
+        ([u.cm*w for w in [1, 2, 3]], ds.arr([0.5, 1, 1.5], 'code_length'), ),
+    ]
+
+    for attribute in ['focus', 'width', 'position']:
+        for attribute_value, expected_result in attribute_values:
+            try:
+                # test properties
+                setattr(cam, attribute, attribute_value)
+                assert_equal(getattr(cam, attribute), expected_result)
+            except RuntimeError:
+                assert expected_result is RuntimeError
+
+            try:
+                # test setters/getters
+                getattr(cam, 'set_%s' % attribute)(attribute_value)
+                assert_equal(getattr(cam, 'get_%s' % attribute)(),
+                             expected_result)
+            except RuntimeError:
+                assert expected_result is RuntimeError
+
+    resolution_values = (
+        (512, (512, 512), ),
+        ((512, 512), (512, 512), ),
+        ((256, 512), (256, 512), ),
+        ((256, 256, 256), RuntimeError),
+    )
+
+    for resolution_value, expected_result in resolution_values:
+        try:
+            # test properties
+            cam.resolution = resolution_value
+            assert_equal(cam.resolution, expected_result)
+        except RuntimeError:
+            assert expected_result is RuntimeError
+
+        try:
+            # test setters/getters
+            cam.set_resolution(resolution_value)
+            assert_equal(cam.get_resolution(), expected_result)
+        except RuntimeError:
+            assert expected_result is RuntimeError
+
+    for lens_type in valid_lens_types:
+        cam.set_lens(lens_type)


https://bitbucket.org/yt_analysis/yt/commits/2e6433fcc8b5/
Changeset:   2e6433fcc8b5
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:30:36+00:00
Summary:     Remove temp error message
Affected #:  1 file

diff -r e552564177577d79a50051aa186d7dad0582e98d -r 2e6433fcc8b589c214fd2a7f816fbf9f586f1b0b yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -53,7 +53,8 @@
         elif isinstance(value, numeric_type):
             san_value = scene.arr([value]*3, 'unitary')
         else:
-            raise RuntimeError("Temp error message2")
+            raise RuntimeError(
+                "Cannot set camera width to invalid value '%s'" % (value, ))
     return san_value
 
 


https://bitbucket.org/yt_analysis/yt/commits/a5fe589ea703/
Changeset:   a5fe589ea703
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:54:47+00:00
Summary:     Fix corner case in camera attribute input sanitization machinery
Affected #:  2 files

diff -r 2e6433fcc8b589c214fd2a7f816fbf9f586f1b0b -r a5fe589ea703586a99fa5e4f792162ffd565f74a yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -47,15 +47,12 @@
             raise RuntimeError(
                 "Cannot set camera width to invalid value '%s'" % (value, ))
     else:
-        if isinstance(value, YTQuantity):
-            san_value = scene.arr([value.d]*3, value.units)
-            san_value.convert_to_units('unitary')
+        if isinstance(value, (YTQuantity, YTArray)):
+            return scene.arr([value.d]*3, value.units).in_units('unitary')
         elif isinstance(value, numeric_type):
-            san_value = scene.arr([value]*3, 'unitary')
-        else:
-            raise RuntimeError(
-                "Cannot set camera width to invalid value '%s'" % (value, ))
-    return san_value
+            return scene.arr([value]*3, 'unitary')
+    raise RuntimeError(
+        "Cannot set camera width to invalid value '%s'" % (value, ))
 
 
 class Camera(Orientation):

diff -r 2e6433fcc8b589c214fd2a7f816fbf9f586f1b0b -r a5fe589ea703586a99fa5e4f792162ffd565f74a yt/visualization/volume_rendering/tests/test_camera_attributes.py
--- a/yt/visualization/volume_rendering/tests/test_camera_attributes.py
+++ b/yt/visualization/volume_rendering/tests/test_camera_attributes.py
@@ -52,6 +52,7 @@
         (((1, 'cm'), (2, 'cm'), (3, 'cm')),
          ds.arr([0.5, 1, 1.5], 'code_length'), ),
         (2*u.cm, ds.arr([1, 1, 1], 'code_length'), ),
+        (ds.arr(2, 'cm'), ds.arr([1, 1, 1], 'code_length'), ),
         ([2*u.cm], ds.arr([1, 1, 1], 'code_length'), ),
         ([1, 2, 3]*u.cm, ds.arr([0.5, 1, 1.5], 'code_length'), ),
         ([1, 2]*u.cm, RuntimeError, ),


https://bitbucket.org/yt_analysis/yt/commits/bd3190fbf498/
Changeset:   bd3190fbf498
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:56:14+00:00
Summary:     Simplify error hanging in camera attribute sanitization
Affected #:  1 file

diff -r a5fe589ea703586a99fa5e4f792162ffd565f74a -r bd3190fbf49844d45e1a29cce37d82122e54c634 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -43,9 +43,6 @@
                     raise RuntimeError(
                         "Cannot set camera width to invalid value '%s'" % (value, ))
             return scene.arr(value, 'unitary')
-        else:
-            raise RuntimeError(
-                "Cannot set camera width to invalid value '%s'" % (value, ))
     else:
         if isinstance(value, (YTQuantity, YTArray)):
             return scene.arr([value.d]*3, value.units).in_units('unitary')


https://bitbucket.org/yt_analysis/yt/commits/8bc6b6fb691c/
Changeset:   8bc6b6fb691c
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 06:57:19+00:00
Summary:     Add extra condition for single length, unit tuple handling
Affected #:  1 file

diff -r bd3190fbf49844d45e1a29cce37d82122e54c634 -r 8bc6b6fb691c8b8eb98459f971a032c2ef5b6e04 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -31,7 +31,8 @@
             return _sanitize_camera_property_units(value[0], scene)
         elif isinstance(value, YTArray) and len(value) == 3:
             return scene.arr(value).in_units('unitary')
-        elif isinstance(value[0], numeric_type) and isinstance(value[1], string_types):
+        elif (len(value) == 2 and isinstance(value[0], numeric_type)
+              and isinstance(value[1], string_types)):
             return scene.arr([scene.arr(value[0], value[1]).in_units('unitary')]*3)
         if len(value) == 3:
             if all([iterable(v) for v in value]):


https://bitbucket.org/yt_analysis/yt/commits/afd6e1cc3cf4/
Changeset:   afd6e1cc3cf4
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 17:31:41+00:00
Summary:     Don't set camera focus and position to the same value in camera attribute tests
Affected #:  1 file

diff -r 8bc6b6fb691c8b8eb98459f971a032c2ef5b6e04 -r afd6e1cc3cf41ac98a85c077b806ea454dc57e52 yt/visualization/volume_rendering/tests/test_camera_attributes.py
--- a/yt/visualization/volume_rendering/tests/test_camera_attributes.py
+++ b/yt/visualization/volume_rendering/tests/test_camera_attributes.py
@@ -59,7 +59,17 @@
         ([u.cm*w for w in [1, 2, 3]], ds.arr([0.5, 1, 1.5], 'code_length'), ),
     ]
 
-    for attribute in ['focus', 'width', 'position']:
+    # define default values to avoid accidentally setting focus = position
+    default_values = {
+        'focus': [0, 0, 0],
+        'position': [4, 4, 4],
+        'width': [1, 1, 1],
+    }
+    attribute_list = list(default_values.keys())
+
+    for attribute in attribute_list:
+        for other_attribute in [a for a in attribute_list if a != attribute]:
+            setattr(cam, other_attribute, default_values[other_attribute])
         for attribute_value, expected_result in attribute_values:
             try:
                 # test properties


https://bitbucket.org/yt_analysis/yt/commits/f8e2e80cd0d3/
Changeset:   f8e2e80cd0d3
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 17:32:14+00:00
Summary:     Revert change to camera.set_position() that broke VR orientation tests
Affected #:  1 file

diff -r afd6e1cc3cf41ac98a85c077b806ea454dc57e52 -r f8e2e80cd0d311ee18a589ff3f100fa71c146016 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -366,9 +366,8 @@
 
         """
         self.position = position
-        if north_vector is not None:
-            self.switch_orientation(normal_vector=self.focus - self.position,
-                                    north_vector=north_vector)
+        self.switch_orientation(normal_vector=self.focus - self.position,
+                                north_vector=north_vector)
 
     def get_position(self):
         """Return the current camera position"""


https://bitbucket.org/yt_analysis/yt/commits/f5e332e8c411/
Changeset:   f5e332e8c411
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 17:32:32+00:00
Summary:     Fail early when camera focus and position are set to the same value
Affected #:  1 file

diff -r f8e2e80cd0d311ee18a589ff3f100fa71c146016 -r f5e332e8c41181fd5b1b771911e780bbf13cd527 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -165,6 +165,9 @@
 
         def fset(self, value):
             position = _sanitize_camera_property_units(value, self.scene)
+            if np.array_equal(position, self.focus):
+                raise RuntimeError(
+                    'Cannot set the camera focus and position to the same value')
             self._position = position
             self.switch_orientation()
 
@@ -219,6 +222,9 @@
 
         def fset(self, value):
             focus = _sanitize_camera_property_units(value, self.scene)
+            if np.array_equal(focus, self.position):
+                raise RuntimeError(
+                    'Cannot set the camera focus and position to the same value')
             self._focus = focus
             self.switch_orientation()
 


https://bitbucket.org/yt_analysis/yt/commits/bdb4a8a9139e/
Changeset:   bdb4a8a9139e
Branch:      yt
User:        ngoldbaum
Date:        2016-03-07 23:25:36+00:00
Summary:     Merged in ngoldbaum/yt (pull request #2016)

VR scene/camera unit overhaul. Closes #1132
Affected #:  24 files

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 doc/source/cookbook/various_lens.py
--- a/doc/source/cookbook/various_lens.py
+++ b/doc/source/cookbook/various_lens.py
@@ -1,5 +1,5 @@
 import yt
-from yt.visualization.volume_rendering.api import Scene, Camera, VolumeSource
+from yt.visualization.volume_rendering.api import Scene, VolumeSource
 import numpy as np
 
 field = ("gas", "density")
@@ -19,7 +19,7 @@
 tf.grey_opacity = True
 
 # Plane-parallel lens
-cam = Camera(ds, lens_type='plane-parallel')
+cam = sc.add_camera(ds, lens_type='plane-parallel')
 # Set the resolution of tbe final projection.
 cam.resolution = [250, 250]
 # Set the location of the camera to be (x=0.2, y=0.5, z=0.5)
@@ -32,13 +32,12 @@
 # Set the width of the camera, where width[0] and width[1] specify the length and
 # height of final projection, while width[2] in plane-parallel lens is not used.
 cam.set_width(ds.domain_width * 0.5)
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_plane-parallel.png', sigma_clip=6.0)
 
 # Perspective lens
-cam = Camera(ds, lens_type='perspective')
+cam = sc.add_camera(ds, lens_type='perspective')
 cam.resolution = [250, 250]
 # Standing at (x=0.2, y=0.5, z=0.5), we look at the area of x>0.2 (with some open angle
 # specified by camera width) along the positive x direction.
@@ -49,13 +48,12 @@
 # height of the final projection, while width[2] specifies the distance between the
 # camera and the final image.
 cam.set_width(ds.domain_width * 0.5)
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_perspective.png', sigma_clip=6.0)
 
 # Stereo-perspective lens
-cam = Camera(ds, lens_type='stereo-perspective')
+cam = sc.add_camera(ds, lens_type='stereo-perspective')
 # Set the size ratio of the final projection to be 2:1, since stereo-perspective lens
 # will generate the final image with both left-eye and right-eye ones jointed together.
 cam.resolution = [500, 250]
@@ -65,14 +63,13 @@
 cam.set_width(ds.domain_width*0.5)
 # Set the distance between left-eye and right-eye.
 cam.lens.disparity = ds.domain_width[0] * 1.e-3
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_stereo-perspective.png', sigma_clip=6.0)
 
 # Fisheye lens
 dd = ds.sphere(ds.domain_center, ds.domain_width[0] / 10)
-cam = Camera(dd, lens_type='fisheye')
+cam = sc.add_camera(dd, lens_type='fisheye')
 cam.resolution = [250, 250]
 v, c = ds.find_max(field)
 cam.set_position(c - 0.0005 * ds.domain_width)
@@ -80,13 +77,12 @@
                        north_vector=north_vector)
 cam.set_width(ds.domain_width)
 cam.lens.fov = 360.0
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_fisheye.png', sigma_clip=6.0)
 
 # Spherical lens
-cam = Camera(ds, lens_type='spherical')
+cam = sc.add_camera(ds, lens_type='spherical')
 # Set the size ratio of the final projection to be 2:1, since spherical lens
 # will generate the final image with length of 2*pi and height of pi.
 cam.resolution = [500, 250]
@@ -97,13 +93,12 @@
                        north_vector=north_vector)
 # In (stereo)spherical camera, camera width is not used since the entire volume
 # will be rendered
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_spherical.png', sigma_clip=6.0)
 
 # Stereo-spherical lens
-cam = Camera(ds, lens_type='stereo-spherical')
+cam = sc.add_camera(ds, lens_type='stereo-spherical')
 # Set the size ratio of the final projection to be 4:1, since spherical-perspective lens
 # will generate the final image with both left-eye and right-eye ones jointed together.
 cam.resolution = [1000, 250]
@@ -114,7 +109,6 @@
 # will be rendered
 # Set the distance between left-eye and right-eye.
 cam.lens.disparity = ds.domain_width[0] * 1.e-3
-sc.camera = cam
 sc.add_source(vol)
 sc.render()
 sc.save('lens_stereo-spherical.png', sigma_clip=6.0)

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb
--- a/doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb
+++ b/doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb
@@ -22,7 +22,6 @@
     "from IPython.core.display import Image\n",
     "from yt.visualization.volume_rendering.transfer_function_helper import TransferFunctionHelper\n",
     "from yt.visualization.volume_rendering.render_source import VolumeSource\n",
-    "from yt.visualization.volume_rendering.camera import Camera\n",
     "\n",
     "def showme(im):\n",
     "    # screen out NaNs\n",

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
--- a/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
+++ b/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
@@ -18,7 +18,7 @@
     "import yt\n",
     "import numpy as np\n",
     "from yt.visualization.volume_rendering.transfer_function_helper import TransferFunctionHelper\n",
-    "from yt.visualization.volume_rendering.api import Scene, Camera, VolumeSource\n",
+    "from yt.visualization.volume_rendering.api import Scene, VolumeSource\n",
     "\n",
     "ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n",
     "sc = yt.create_scene(ds)"
@@ -199,7 +199,7 @@
    },
    "outputs": [],
    "source": [
-    "cam = Camera(ds, lens_type='perspective')\n",
+    "cam = sc.add_camera(ds, lens_type='perspective')\n",
     "\n",
     "# Standing at (x=0.05, y=0.5, z=0.5), we look at the area of x>0.05 (with some open angle\n",
     "# specified by camera width) along the positive x direction.\n",
@@ -213,7 +213,6 @@
     "# The width determines the opening angle\n",
     "cam.set_width(ds.domain_width * 0.5)\n",
     "\n",
-    "sc.camera = cam\n",
     "print (sc.camera)"
    ]
   },

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 doc/source/visualizing/unstructured_mesh_rendering.rst
--- a/doc/source/visualizing/unstructured_mesh_rendering.rst
+++ b/doc/source/visualizing/unstructured_mesh_rendering.rst
@@ -292,7 +292,6 @@
 .. python-script::
 
     import yt
-    from yt.visualization.volume_rendering.api import Camera
 
     ds = yt.load("MOOSE_sample_data/out.e-s010")
 
@@ -304,15 +303,12 @@
     ms.cmap = 'Eos A'
    
     # Create a perspective Camera
-    cam = Camera(ds, lens_type='perspective')
+    cam = sc.add_camera(ds, lens_type='perspective')
     cam.focus = ds.arr([0.0, 0.0, 0.0], 'code_length')
     cam_pos = ds.arr([-4.5, 4.5, -4.5], 'code_length')
     north_vector = ds.arr([0.0, -1.0, -1.0], 'dimensionless')
     cam.set_position(cam_pos, north_vector)
    
-    # tell our scene to use it
-    sc.camera = cam
-   
     # increase the default resolution
     cam.resolution = (800, 800)
    
@@ -329,7 +325,7 @@
 .. python-script::
 
     import yt
-    from yt.visualization.volume_rendering.api import MeshSource, Camera, Scene
+    from yt.visualization.volume_rendering.api import MeshSource, Scene
 
     ds = yt.load("MOOSE_sample_data/out.e-s010")
 
@@ -337,16 +333,13 @@
     sc = Scene()
 
     # set up our Camera
-    cam = Camera(ds)
+    cam = sc.add_camera(ds)
     cam.focus = ds.arr([0.0, 0.0, 0.0], 'code_length')
     cam.set_position(ds.arr([-3.0, 3.0, -3.0], 'code_length'),
                      ds.arr([0.0, -1.0, 0.0], 'dimensionless'))
     cam.set_width = ds.arr([8.0, 8.0, 8.0], 'code_length')
     cam.resolution = (800, 800)
 
-    # tell the scene to use it
-    sc.camera = cam
-
     # create two distinct MeshSources from 'connect1' and 'connect2'
     ms1 = MeshSource(ds, ('connect1', 'diffused'))
     ms2 = MeshSource(ds, ('connect2', 'diffused'))
@@ -407,7 +400,7 @@
 .. code-block:: python
 
     import yt
-    from yt.visualization.volume_rendering.api import MeshSource, Camera
+    from yt.visualization.volume_rendering.api import MeshSource
     import pylab as plt
 
     NUM_STEPS = 127
@@ -432,7 +425,7 @@
 	# set up the camera here. these values were arrived by
 	# calling pitch, yaw, and roll in the notebook until I
 	# got the angle I wanted.
-	cam = Camera(ds)
+	sc.add_camera(ds)
 	camera_position = ds.arr([0.1, 0.0, 0.1], 'code_length')
 	cam.focus = ds.domain_center
 	north_vector = ds.arr([-0.3032476, -0.71782557, 0.62671153], 'dimensionless')

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/analysis_modules/photon_simulator/tests/test_sloshing.py
--- a/yt/analysis_modules/photon_simulator/tests/test_sloshing.py
+++ b/yt/analysis_modules/photon_simulator/tests/test_sloshing.py
@@ -15,10 +15,11 @@
     ThermalPhotonModel, PhotonList, EventList, \
     convert_old_file, merge_files
 from yt.config import ytcfg
-from yt.testing import requires_file
+from yt.testing import \
+    requires_file, \
+    assert_almost_equal
 from yt.utilities.answer_testing.framework import requires_ds, \
     GenericArrayTest, data_dir_load
-from numpy.testing import assert_array_equal
 from numpy.random import RandomState
 from yt.units.yt_array import uconcatenate
 import os
@@ -117,11 +118,11 @@
             arr1 = photons1[k]
             arr2 = photons2[k]
             arr3 = photons3[k]
-        yield assert_array_equal, arr1, arr2
-        yield assert_array_equal, arr1, arr3
+        assert_almost_equal(arr1, arr2)
+        assert_almost_equal(arr1, arr3)
     for k in events1.keys():
-        yield assert_array_equal, events1[k], events2[k]
-        yield assert_array_equal, events1[k], events3[k]
+        assert_almost_equal(events1[k], events2[k])
+        assert_almost_equal(events1[k], events3[k])
 
     nevents = 0
 

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/frontends/enzo/simulation_handling.py
--- a/yt/frontends/enzo/simulation_handling.py
+++ b/yt/frontends/enzo/simulation_handling.py
@@ -82,6 +82,7 @@
     def _set_units(self):
         self.unit_registry = UnitRegistry()
         self.unit_registry.add("code_time", 1.0, dimensions.time)
+        self.unit_registry.add("code_length", 1.0, dimensions.length)
         if self.cosmological_simulation:
             # Instantiate EnzoCosmology object for units and time conversions.
             self.cosmology = \
@@ -107,6 +108,7 @@
         else:
             self.time_unit = self.quan(self.parameters["TimeUnits"], "s")
         self.unit_registry.modify("code_time", self.time_unit)
+        self.unit_registry.modify("code_length", self.length_unit)
 
     def get_time_series(self, time_data=True, redshift_data=True,
                         initial_time=None, final_time=None,

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/testing.py
--- a/yt/testing.py
+++ b/yt/testing.py
@@ -171,7 +171,7 @@
         units = ('g/cm**3', 'cm/s', 'cm/s', 'cm/s'),
         particle_fields=None, particle_field_units=None,
         negative = False, nprocs = 1, particles = 0, length_unit=1.0,
-        unit_system="cgs"):
+        unit_system="cgs", bbox=None):
     from yt.frontends.stream.api import load_uniform_grid
     if not iterable(ndims):
         ndims = [ndims, ndims, ndims]
@@ -208,7 +208,7 @@
             data['io', 'particle_mass'] = (np.random.random(particles), 'g')
         data['number_of_particles'] = particles
     ug = load_uniform_grid(data, ndims, length_unit=length_unit, nprocs=nprocs,
-                           unit_system=unit_system)
+                           unit_system=unit_system, bbox=bbox)
     return ug
 
 _geom_transforms = {

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/units/yt_array.py
--- a/yt/units/yt_array.py
+++ b/yt/units/yt_array.py
@@ -346,7 +346,8 @@
                 if registry is None:
                     pass
                 else:
-                    input_array.units.registry = registry
+                    units = Unit(str(input_array.units), registry=registry)
+                    input_array.units = units
             elif isinstance(input_units, Unit):
                 input_array.units = input_units
             else:
@@ -357,7 +358,7 @@
         elif iterable(input_array) and input_array:
             if isinstance(input_array[0], YTArray):
                 return YTArray(np.array(input_array, dtype=dtype),
-                               input_array[0].units)
+                               input_array[0].units, registry=registry)
 
         # Input array is an already formed ndarray instance
         # We first cast to be our class type
@@ -369,7 +370,10 @@
             # Nothing provided. Make dimensionless...
             units = Unit()
         elif isinstance(input_units, Unit):
-            units = input_units
+            if registry and registry is not input_units.registry:
+                units = Unit(str(input_units), registry=registry)
+            else:
+                units = input_units
         else:
             # units kwarg set, but it's not a Unit object.
             # don't handle all the cases here, let the Unit class handle if
@@ -1408,6 +1412,31 @@
     v = validate_numpy_wrapper_units(v, [arr1, arr2])
     return v
 
+def unorm(data):
+    """Matrix or vector norm that preserves units
+
+    This is a wrapper around np.linalg.norm that preserves units.
+    """
+    return YTArray(np.linalg.norm(data), data.units)
+
+def uvstack(arrs):
+    """Stack arrays in sequence vertically (row wise) while preserving units
+
+    This is a wrapper around np.vstack that preserves units.
+    """
+    v = np.vstack(arrs)
+    v = validate_numpy_wrapper_units(v, arrs)
+    return v
+
+def uhstack(arrs):
+    """Stack arrays in sequence horizontally (column wise) while preserving units
+
+    This is a wrapper around np.hstack that preserves units.
+    """
+    v = np.vstack(arrs)
+    v = validate_numpy_wrapper_units(v, arrs)
+    return v
+
 def array_like_field(data, x, field):
     field = data._determine_fields(field)[0]
     if isinstance(field, tuple):

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/utilities/answer_testing/framework.py
--- a/yt/utilities/answer_testing/framework.py
+++ b/yt/utilities/answer_testing/framework.py
@@ -826,7 +826,7 @@
                                           verbose=True)
         for k in new_result:
             if self.decimals is None:
-                assert_equal(new_result[k], old_result[k])
+                assert_almost_equal(new_result[k], old_result[k])
             else:
                 assert_allclose_units(new_result[k], old_result[k],
                                       10**(-self.decimals))

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
--- a/yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
+++ b/yt/utilities/lib/tests/test_bounding_volume_hierarchy.py
@@ -2,7 +2,7 @@
 import numpy as np
 from yt.utilities.lib.bounding_volume_hierarchy import BVH, \
     test_ray_trace
-from yt.visualization.volume_rendering.api import Camera
+from yt.visualization.volume_rendering.api import Camera, Scene
 from yt.testing import requires_file
 
 
@@ -12,13 +12,13 @@
     W = np.array([8.0, 8.0])
     N = np.array([800, 800])
     dx = W / N
-    
+
     x_points = np.linspace((-N[0]/2 + 0.5)*dx[0], (N[0]/2 - 0.5)*dx[0], N[0])
     y_points = np.linspace((-N[1]/2 + 0.5)*dx[1], (N[1]/2 - 0.5)*dx[0], N[1])
-    
+
     X, Y = np.meshgrid(x_points, y_points)
-    result = np.dot(camera.unit_vectors[0:2].T, [X.ravel(), Y.ravel()]) 
-    vec_origins = result.T + camera.position
+    result = np.dot(camera.unit_vectors[0:2].T, [X.ravel(), Y.ravel()])
+    vec_origins = camera.scene.arr(result.T, 'unitary') + camera.position
     return np.array(vec_origins), np.array(normal_vector)
 
 
@@ -36,7 +36,7 @@
 
     bvh = BVH(vertices, indices, field_data)
 
-    cam = Camera()
+    cam = Camera(Scene())
     cam.set_position(np.array([8.0, 8.0, 8.0]))
     cam.focus = np.array([0.0, 0.0, 0.0])
     origins, direction = get_rays(cam)

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -11,19 +11,49 @@
 # The full license is in the file COPYING.txt, distributed with this software.
 # ----------------------------------------------------------------------------
 
-from yt.funcs import iterable, mylog, ensure_numpy_array
+from yt.funcs import iterable, ensure_numpy_array
 from yt.utilities.orientation import Orientation
-from yt.units.yt_array import YTArray
-from yt.units.unit_registry import UnitParseError
+from yt.units.yt_array import \
+    YTArray, \
+    YTQuantity
 from yt.utilities.math_utils import get_rotation_matrix
 from yt.extern.six import string_types
 from .utils import data_source_or_all
-from .lens import lenses
+from .lens import \
+    lenses, \
+    Lens
 import numpy as np
+from numbers import Number as numeric_type
+
+def _sanitize_camera_property_units(value, scene):
+    if iterable(value):
+        if len(value) == 1:
+            return _sanitize_camera_property_units(value[0], scene)
+        elif isinstance(value, YTArray) and len(value) == 3:
+            return scene.arr(value).in_units('unitary')
+        elif (len(value) == 2 and isinstance(value[0], numeric_type)
+              and isinstance(value[1], string_types)):
+            return scene.arr([scene.arr(value[0], value[1]).in_units('unitary')]*3)
+        if len(value) == 3:
+            if all([iterable(v) for v in value]):
+                if all([isinstance(v[0], numeric_type) and
+                        isinstance(v[1], string_types) for v in value]):
+                    return scene.arr(
+                        [scene.arr(v[0], v[1]) for v in value])
+                else:
+                    raise RuntimeError(
+                        "Cannot set camera width to invalid value '%s'" % (value, ))
+            return scene.arr(value, 'unitary')
+    else:
+        if isinstance(value, (YTQuantity, YTArray)):
+            return scene.arr([value.d]*3, value.units).in_units('unitary')
+        elif isinstance(value, numeric_type):
+            return scene.arr([value]*3, 'unitary')
+    raise RuntimeError(
+        "Cannot set camera width to invalid value '%s'" % (value, ))
 
 
 class Camera(Orientation):
-
     r"""A representation of a point of view into a Scene.
 
     It is defined by a position (the location of the camera
@@ -35,6 +65,8 @@
 
     Parameters
     ----------
+    scene: A :class:`yt.visualization.volume_rendering.scene.Scene` object
+        A scene object that the camera will be attached to.
     data_source: :class:`AMR3DData` or :class:`Dataset`, optional
         This is the source to be rendered, which can be any arbitrary yt
         data object or dataset.
@@ -50,20 +82,22 @@
 
     Examples
     --------
-    
+
     In this example, the camera is set using defaults that are chosen
     to be reasonable for the argument Dataset.
 
     >>> import yt
-    >>> from yt.visualization.volume_rendering.api import Camera
+    >>> from yt.visualization.volume_rendering.api import Scene
     >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
-    >>> cam = Camera(ds)
+    >>> sc = Scene()
+    >>> cam = sc.add_camera(ds)
 
     Here, we set the camera properties manually:
 
     >>> import yt
-    >>> from yt.visualization.volume_rendering.api import Camera
-    >>> cam = Camera()
+    >>> from yt.visualization.volume_rendering.api import Scene
+    >>> sc = Scene()
+    >>> cam = sc.add_camera()
     >>> cam.position = np.array([0.5, 0.5, -1.0])
     >>> cam.focus = np.array([0.5, 0.5, 0.0])
     >>> cam.north_vector = np.array([1.0, 0.0, 0.0])
@@ -71,9 +105,10 @@
     Finally, we create a camera with a non-default lens:
 
     >>> import yt
-    >>> from yt.visualization.volume_rendering.api import Camera
+    >>> from yt.visualization.volume_rendering.api import Scene
     >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
-    >>> cam = Camera(ds, lens_type='perspective')
+    >>> sc = Scene()
+    >>> cam = sc.add_camera(ds, lens_type='perspective')
 
     """
 
@@ -83,24 +118,27 @@
     _position = None
     _resolution = None
 
-    def __init__(self, data_source=None, lens_type='plane-parallel',
+    def __init__(self, scene, data_source=None, lens_type='plane-parallel',
                  auto=False):
-        """Initialize a Camera Instance"""
+        self.scene = scene
         self.lens = None
         self.north_vector = None
         self.normal_vector = None
         self.light = None
+        self.data_source = data_source_or_all(data_source)
         self._resolution = (512, 512)
-        self._width = np.array([1.0, 1.0, 1.0])
-        self._focus = np.array([0.0]*3)
-        self._position = np.array([1.0]*3)
-        if data_source is not None:
-            data_source = data_source_or_all(data_source)
-            self._focus = data_source.ds.domain_center
-            self._position = data_source.ds.domain_right_edge
-            self._width = 1.5*data_source.ds.domain_width
-            self._domain_center = data_source.ds.domain_center
-            self._domain_width = data_source.ds.domain_width
+        if self.data_source is not None:
+            self.scene.set_new_unit_registry(self.data_source.ds.unit_registry)
+            self._focus = self.data_source.ds.domain_center
+            self._position = self.data_source.ds.domain_right_edge
+            self._width = 1.5*self.data_source.ds.domain_width
+            self._domain_center = self.data_source.ds.domain_center
+            self._domain_width = self.data_source.ds.domain_width
+        else:
+            self._focus = scene.arr([0.0, 0.0, 0.0], 'unitary')
+            self._width = scene.arr([1.0, 1.0, 1.0], 'unitary')
+            self._position = scene.arr([1.0, 1.0, 1.0], 'unitary')
+
         if auto:
             self.set_defaults_from_data_source(data_source)
 
@@ -110,20 +148,27 @@
         self.set_lens(lens_type)
 
     def position():
-        doc = '''The position is the location of the camera in
-               the coordinate system of the simulation. This needs
-               to be either a YTArray or a numpy array. If it is a 
-               numpy array, it is assumed to be in code units. If it
-               is a YTArray, it will be converted to code units 
-               automatically. '''
+        doc = '''
+        The location of the camera. 
+
+        Parameters
+        ----------
+
+        position : number, YTQuantity, iterable, or 3 element YTArray
+            If a scalar, assumes that the position is the same in all three
+            coordinates. If an interable, must contain only scalars or
+            (length, unit) tuples.
+        '''
 
         def fget(self):
             return self._position
 
         def fset(self, value):
-            if isinstance(value, YTArray):
-                value = value.in_units("code_length")
-            self._position = value
+            position = _sanitize_camera_property_units(value, self.scene)
+            if np.array_equal(position, self.focus):
+                raise RuntimeError(
+                    'Cannot set the camera focus and position to the same value')
+            self._position = position
             self.switch_orientation()
 
         def fdel(self):
@@ -132,18 +177,24 @@
     position = property(**position())
 
     def width():
-        doc = '''The width of the region that will be seen in the image. 
-               This needs to be either a YTArray or a numpy array. If it 
-               is a numpy array, it is assumed to be in code units. If it
-               is a YTArray, it will be converted to code units automatically. '''
+        doc = '''The width of the region that will be seen in the image.
+
+        Parameters
+        ----------
+
+        width : number, YTQuantity, iterable, or 3 element YTArray
+            The width of the volume rendering in the horizontal, vertical, and
+            depth directions. If a scalar, assumes that the width is the same in
+            all three directions. If an interable, must contain only scalars or
+            (length, unit) tuples.
+        '''
 
         def fget(self):
             return self._width
 
         def fset(self, value):
-            if isinstance(value, YTArray):
-                value = value.in_units("code_length")
-            self._width = value
+            width = _sanitize_camera_property_units(value, self.scene)
+            self._width = width
             self.switch_orientation()
 
         def fdel(self):
@@ -153,19 +204,28 @@
     width = property(**width())
 
     def focus():
-        doc = '''The focus defines the point the Camera is pointed at. This needs
-               to be either a YTArray or a numpy array. If it is a 
-               numpy array, it is assumed to be in code units. If it
-               is a YTArray, it will be converted to code units 
-               automatically. '''
+        doc = '''
+        The focus defines the point the Camera is pointed at.
+
+        Parameters
+        ----------
+
+        focus : number, YTQuantity, iterable, or 3 element YTArray
+            The width of the volume rendering in the horizontal, vertical, and
+            depth directions. If a scalar, assumes that the width is the same in
+            all three directions. If an interable, must contain only scalars or
+            (length, unit) tuples.
+        '''
 
         def fget(self):
             return self._focus
 
         def fset(self, value):
-            if isinstance(value, YTArray):
-                value = value.in_units("code_length")
-            self._focus = value
+            focus = _sanitize_camera_property_units(value, self.scene)
+            if np.array_equal(focus, self.position):
+                raise RuntimeError(
+                    'Cannot set the camera focus and position to the same value')
+            self._focus = focus
             self.switch_orientation()
 
         def fdel(self):
@@ -175,14 +235,15 @@
 
     def resolution():
         doc = '''The resolution is the number of pixels in the image that
-               will be produced. '''
+               will be produced. Must be a 2-tuple of integers or an integer.'''
 
         def fget(self):
             return self._resolution
 
         def fset(self, value):
             if iterable(value):
-                assert (len(value) == 2)
+                if len(value) != 2:
+                    raise RuntimeError
             else:
                 value = (value, value)
             self._resolution = value
@@ -193,6 +254,19 @@
         return locals()
     resolution = property(**resolution())
 
+    def set_resolution(self, resolution):
+        """
+        The resolution is the number of pixels in the image that
+        will be produced. Must be a 2-tuple of integers or an integer.
+        """
+        self.resolution = resolution
+
+    def get_resolution(self):
+        """
+        Returns the resolution of the volume rendering
+        """
+        return self.resolution
+
     def _get_sampler_params(self, render_source):
         lens_params = self.lens._get_sampler_params(self, render_source)
         lens_params.update(width=self.width)
@@ -214,10 +288,14 @@
             'stereo-spherical'
 
         """
-        if lens_type not in lenses:
-            mylog.error("Lens type not available")
-            raise RuntimeError()
-        self.lens = lenses[lens_type]()
+        if isinstance(lens_type, Lens):
+            self.lens = lens_type
+        elif lens_type not in lenses:
+            raise RuntimeError(
+                "Lens type %s not in available list of available lens "
+                "types (%s)" % (lens_type, list(lenses.keys())))
+        else:
+            self.lens = lenses[lens_type]()
         self.lens.set_camera(self)
 
     def set_defaults_from_data_source(self, data_source):
@@ -264,41 +342,61 @@
         Parameters
         ----------
 
-        width : YTQuantity or 3 element YTArray
+        width : number, YTQuantity, iterable, or 3 element YTArray
             The width of the volume rendering in the horizontal, vertical, and
             depth directions. If a scalar, assumes that the width is the same in
-            all three directions.
+            all three directions. If an interable, must contain only scalars or
+            (length, unit) tuples.
         """
-        try:
-            width = width.in_units('code_length')
-        except (AttributeError, UnitParseError):
-            raise ValueError(
-                'Volume rendering width must be a YTArray that can be '
-                'converted to code units')
-
-        if not iterable(width):
-            width = YTArray([width.d]*3, width.units)  # Can't get code units.
         self.width = width
         self.switch_orientation()
 
+    def get_width(self):
+        """Return the current camera width"""
+        return self.width
+
     def set_position(self, position, north_vector=None):
         r"""Set the position of the camera.
 
         Parameters
         ----------
 
-        position : array_like
-            The new position
+        width : number, YTQuantity, iterable, or 3 element YTArray
+            If a scalar, assumes that the position is the same in all three
+            coordinates. If an interable, must contain only scalars or
+            (length, unit) tuples.
+
         north_vector : array_like, optional
             The 'up' direction for the plane of rays.  If not specific,
             calculated automatically.
 
         """
-
         self.position = position
         self.switch_orientation(normal_vector=self.focus - self.position,
                                 north_vector=north_vector)
 
+    def get_position(self):
+        """Return the current camera position"""
+        return self.position
+
+    def set_focus(self, new_focus):
+        """Sets the point the Camera is pointed at.
+
+        Parameters
+        ----------
+
+        focus : number, YTQuantity, iterable, or 3 element YTArray
+            If a scalar, assumes that the focus is the same is all three
+            coordinates. If an interable, must contain only scalars or
+            (length, unit) tuples.
+
+        """
+        self.focus = new_focus
+
+    def get_focus(self):
+        """Returns the current camera focus"""
+        return self.focus
+
     def switch_orientation(self, normal_vector=None, north_vector=None):
         r"""Change the view direction based on any of the orientation parameters.
 
@@ -366,8 +464,9 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> cam = sc.add_camera()
         >>> # rotate the camera by pi / 4 radians:
         >>> cam.rotate(np.pi/4.0)  
         >>> # rotate the camera about the y-axis instead of cam.north_vector:
@@ -419,10 +518,11 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> sc.add_camera()
         >>> # pitch the camera by pi / 4 radians:
-        >>> cam.pitch(np.pi/4.0)  
+        >>> cam.pitch(np.pi/4.0)
         >>> # pitch the camera about the origin instead of its own position:
         >>> cam.pitch(np.pi/4.0, rot_center=np.array([0.0, 0.0, 0.0]))
 
@@ -446,10 +546,11 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> cam = sc.add_camera()
         >>> # yaw the camera by pi / 4 radians:
-        >>> cam.yaw(np.pi/4.0)  
+        >>> cam.yaw(np.pi/4.0)
         >>> # yaw the camera about the origin instead of its own position:
         >>> cam.yaw(np.pi/4.0, rot_center=np.array([0.0, 0.0, 0.0]))
 
@@ -473,8 +574,9 @@
 
         >>> import yt
         >>> import numpy as np
-        >>> from yt.visualization.volume_rendering.api import Camera
-        >>> cam = Camera()
+        >>> from yt.visualization.volume_rendering.api import Scene
+        >>> sc = Scene()
+        >>> cam = sc.add_camera(ds)
         >>> # roll the camera by pi / 4 radians:
         >>> cam.roll(np.pi/4.0)  
         >>> # roll the camera about the origin instead of its own position:
@@ -584,9 +686,10 @@
         --------
 
         >>> import yt
-        >>> from yt.visualization.volume_rendering.api import Camera
+        >>> from yt.visualization.volume_rendering.api import Scene
         >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
-        >>> cam = Camera(ds)
+        >>> sc = Scene()
+        >>> cam = sc.add_camera(ds)
         >>> cam.zoom(1.1)
 
         """
@@ -611,7 +714,6 @@
         --------
 
         >>> import yt
-        >>> from yt.visualization.volume_rendering.api import Camera
         >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
         >>> im, sc = yt.volume_render(ds)
         >>> cam = sc.camera

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/lens.py
--- a/yt/visualization/volume_rendering/lens.py
+++ b/yt/visualization/volume_rendering/lens.py
@@ -17,8 +17,8 @@
 from yt.funcs import mylog
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     ParallelAnalysisInterface
-from yt.units.yt_array import YTArray
 from yt.data_objects.image_array import ImageArray
+from yt.units.yt_array import unorm, uvstack
 from yt.utilities.math_utils import get_rotation_matrix
 import numpy as np
 
@@ -60,13 +60,14 @@
         unit_vectors = camera.unit_vectors
         width = camera.width
         center = camera.focus
-        self.box_vectors = YTArray([unit_vectors[0] * width[0],
-                                    unit_vectors[1] * width[1],
-                                    unit_vectors[2] * width[2]])
-        self.origin = center - 0.5 * width.dot(YTArray(unit_vectors, ""))
+
+        self.box_vectors = camera.scene.arr(
+            [unit_vectors[0] * width[0],
+             unit_vectors[1] * width[1],
+             unit_vectors[2] * width[2]])
+        self.origin = center - 0.5 * width.dot(unit_vectors)
         self.back_center = center - 0.5 * width[2] * unit_vectors[2]
         self.front_center = center + 0.5 * width[2] * unit_vectors[2]
-
         self.set_viewpoint(camera)
 
     def set_viewpoint(self, camera):
@@ -99,9 +100,12 @@
         else:
             image = self.new_image(camera)
 
+        vp_pos = np.concatenate(
+            [camera.inv_mat.ravel('F').d,
+             self.back_center.ravel().in_units('code_length').d])
+
         sampler_params =\
-            dict(vp_pos=np.concatenate([camera.inv_mat.ravel('F'),
-                                        self.back_center.ravel()]),
+            dict(vp_pos=vp_pos,
                  vp_dir=self.box_vectors[2],  # All the same
                  center=self.back_center,
                  bounds=(-camera.width[0] / 2.0, camera.width[0] / 2.0,
@@ -190,8 +194,9 @@
             camera.resolution[0], camera.resolution[1], 3)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width))
+
         # Rescale the ray to be long enough to cover the entire domain
         vectors = (sample_x + sample_y + normal_vecs * camera.width[2]) * \
             (max_length / camera.width[2])
@@ -208,7 +213,7 @@
         sampler_params =\
             dict(vp_pos=positions,
                  vp_dir=vectors,
-                 center=self.back_center.d,
+                 center=self.back_center,
                  bounds=(0.0, 1.0, 0.0, 1.0),
                  x_vec=uv,
                  y_vec=uv,
@@ -303,8 +308,8 @@
         uv = np.ones(3, dtype='float64')
 
         image = self.new_image(camera)
-        vectors_comb = np.vstack([vectors_left, vectors_right])
-        positions_comb = np.vstack([positions_left, positions_right])
+        vectors_comb = uvstack([vectors_left, vectors_right])
+        positions_comb = uvstack([positions_left, positions_right])
 
         image.shape = (camera.resolution[0], camera.resolution[1], 4)
         vectors_comb.shape = (camera.resolution[0], camera.resolution[1], 3)
@@ -313,7 +318,7 @@
         sampler_params =\
             dict(vp_pos=positions_comb,
                  vp_dir=vectors_comb,
-                 center=self.back_center.d,
+                 center=self.back_center,
                  bounds=(0.0, 1.0, 0.0, 1.0),
                  x_vec=uv,
                  y_vec=uv,
@@ -331,7 +336,8 @@
         north_vec = camera.unit_vectors[1]
         normal_vec = camera.unit_vectors[2]
 
-        angle_disparity = - np.arctan2(disparity, camera.width[2])
+        angle_disparity = - np.arctan2(disparity.in_units(camera.width.units),
+                                       camera.width[2])
         R = get_rotation_matrix(angle_disparity, north_vec)
 
         east_vec_rot = np.dot(R, east_vec)
@@ -363,8 +369,9 @@
             single_resolution_x, camera.resolution[1], 3)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width) + np.abs(self.disparity.d)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width)
+                      + np.abs(self.disparity))
         # Rescale the ray to be long enough to cover the entire domain
         vectors = (sample_x + sample_y + normal_vecs * camera.width[2]) * \
             (max_length / camera.width[2])
@@ -394,9 +401,9 @@
         px_right, py_right, dz_right = self._get_px_py_dz(
             camera, pos, res, self.disparity)
 
-        px = np.vstack([px_left, px_right])
-        py = np.vstack([py_left, py_right])
-        dz = np.vstack([dz_left, dz_right])
+        px = uvstack([px_left, px_right])
+        py = uvstack([py_left, py_right])
+        dz = uvstack([dz_left, dz_right])
 
         return px, py, dz
 
@@ -594,8 +601,8 @@
         vectors[:, :, 2] = np.sin(py)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width))
         # Rescale the ray to be long enough to cover the entire domain
         vectors = vectors * max_length
 
@@ -625,7 +632,7 @@
         sampler_params = dict(
             vp_pos=positions,
             vp_dir=vectors,
-            center=self.back_center.d,
+            center=self.back_center,
             bounds=(0.0, 1.0, 0.0, 1.0),
             x_vec=dummy,
             y_vec=dummy,
@@ -706,8 +713,9 @@
         vectors[:, :, 2] = np.sin(py)
 
         # The maximum possible length of ray
-        max_length = np.linalg.norm(camera.position - camera._domain_center) \
-            + 0.5 * np.linalg.norm(camera._domain_width) + np.abs(self.disparity.d)
+        max_length = (unorm(camera.position - camera._domain_center)
+                      + 0.5 * unorm(camera._domain_width)
+                      + np.abs(self.disparity))
         # Rescale the ray to be long enough to cover the entire domain
         vectors = vectors * max_length
 
@@ -741,8 +749,8 @@
 
         dummy = np.ones(3, dtype='float64')
 
-        vectors_comb = np.vstack([vectors, vectors])
-        positions_comb = np.vstack([positions_left, positions_right])
+        vectors_comb = uvstack([vectors, vectors])
+        positions_comb = uvstack([positions_left, positions_right])
 
         image.shape = (camera.resolution[0], camera.resolution[1], 4)
         vectors_comb.shape = (camera.resolution[0], camera.resolution[1], 3)
@@ -751,7 +759,7 @@
         sampler_params = dict(
             vp_pos=positions_comb,
             vp_dir=vectors_comb,
-            center=self.back_center.d,
+            center=self.back_center,
             bounds=(0.0, 1.0, 0.0, 1.0),
             x_vec=dummy,
             y_vec=dummy,

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 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
@@ -13,7 +13,6 @@
 
 
 from .scene import Scene
-from .camera import Camera
 from .render_source import VolumeSource
 from .transfer_functions import ProjectionTransferFunction
 from .utils import data_source_or_all
@@ -149,7 +148,7 @@
         data_source.ds.field_dependencies.update(deps)
         fields = [weightfield, weight]
         vol.set_fields(fields)
-    camera = Camera(data_source)
+    camera = sc.add_camera(data_source)
     camera.set_width(width)
     if not iterable(resolution):
         resolution = [resolution]*2
@@ -172,7 +171,6 @@
     camera.switch_orientation(normal_vector,
                               north_vector)
 
-    sc.camera = camera
     sc.add_source(vol)
 
     vol.set_sampler(camera)

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -114,8 +114,7 @@
     >>> sc = Scene()
     >>> source = VolumeSource(ds.all_data(), 'density')
     >>> sc.add_source(source)
-    >>> cam = Camera(ds)
-    >>> sc.camera = cam
+    >>> sc.add_camera()
     >>> im = sc.render()
 
     """
@@ -946,10 +945,10 @@
 
     def __init__(self, data_source, alpha=0.3, cmap='algae',
                  min_level=None, max_level=None):
-        data_source = data_source_or_all(data_source)
+        self.data_source = data_source_or_all(data_source)
         corners = []
         levels = []
-        for block, mask in data_source.blocks:
+        for block, mask in self.data_source.blocks:
             block_corners = np.array([
                 [block.LeftEdge[0], block.LeftEdge[1], block.LeftEdge[2]],
                 [block.RightEdge[0], block.LeftEdge[1], block.LeftEdge[2]],
@@ -976,7 +975,7 @@
 
         colors = apply_colormap(
             levels*1.0,
-            color_bounds=[0, data_source.ds.index.max_level],
+            color_bounds=[0, self.data_source.ds.index.max_level],
             cmap_name=cmap)[0, :, :]*alpha/255.
         colors[:, 3] = alpha
 

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -12,13 +12,27 @@
 # -----------------------------------------------------------------------------
 
 
+import functools
 import numpy as np
 from collections import OrderedDict
 from yt.funcs import mylog, get_image_suffix
 from yt.extern.six import iteritems, itervalues, string_types
+from yt.units.dimensions import \
+    length
+from yt.units.unit_registry import \
+    UnitRegistry
+from yt.units.yt_array import \
+    YTQuantity, \
+    YTArray
 from .camera import Camera
-from .render_source import OpaqueSource, BoxSource, CoordinateVectorSource, \
-    GridSource, RenderSource, MeshSource
+from .render_source import \
+    OpaqueSource, \
+    BoxSource, \
+    CoordinateVectorSource, \
+    GridSource, \
+    RenderSource, \
+    MeshSource, \
+    VolumeSource
 from .zbuffer_array import ZBuffer
 from yt.extern.six.moves import builtins
 from yt.utilities.exceptions import YTNotInsideNotebook
@@ -52,8 +66,7 @@
     >>> sc = Scene()
     >>> source = VolumeSource(ds.all_data(), 'density')
     >>> sc.add_source(source)
-    >>> cam = Camera(ds)
-    >>> sc.camera = cam
+    >>> cam = sc.add_camera()
     >>> im = sc.render()
 
     Alternatively, you can use the create_scene function to set up defaults 
@@ -70,13 +83,12 @@
 
     _current = None
     _camera = None
+    _unit_registry = None
 
     def __init__(self):
         r"""Create a new Scene instance"""
         super(Scene, self).__init__()
         self.sources = OrderedDict()
-        self.camera = None
-        # An image array containing the last rendered image of the scene
         self.last_render = None
         # A non-public attribute used to get around the fact that we can't
         # pass kwargs into _repr_png_()
@@ -122,10 +134,34 @@
         if keyname is None:
             keyname = 'source_%02i' % len(self.sources)
 
+        if isinstance(render_source, (VolumeSource, MeshSource, GridSource)):
+            self.set_new_unit_registry(
+                render_source.data_source.ds.unit_registry)
+
         self.sources[keyname] = render_source
 
         return self
 
+    def set_new_unit_registry(self, input_registry):
+        self.unit_registry = UnitRegistry(
+            add_default_symbols=False,
+            lut=input_registry.lut)
+
+        # Validate that the new unit registry makes sense
+        current_scaling = self.unit_registry['unitary'][0]
+        if current_scaling != input_registry['unitary'][0]:
+            for source in self.sources.items():
+                data_source = getattr(source, 'data_source', None)
+                if data_source is None:
+                    continue
+                scaling = data_source.ds.unit_registry['unitary'][0]
+                if scaling != current_scaling:
+                    raise NotImplementedError(
+                        "Simultaneously rendering data from datasets with "
+                        "different units is not supported"
+                    )
+
+
     def render(self, camera=None):
         r"""Render all sources in the Scene.
 
@@ -289,6 +325,69 @@
 
         return im
 
+    def add_camera(self, data_source=None, lens_type='plane-parallel',
+                   auto=False):
+        r"""Add a new camera to the Scene.
+
+        The camera is defined by a position (the location of the camera
+        in the simulation domain,), a focus (the point at which the
+        camera is pointed), a width (the width of the snapshot that will
+        be taken, a resolution (the number of pixels in the image), and
+        a north_vector (the "up" direction in the resulting image). A
+        camera can use a variety of different Lens objects.
+
+        If the scene already has a camera associated with it, this function
+        will create a new camera and discard the old one.
+
+        Parameters
+        ----------
+        data_source: :class:`AMR3DData` or :class:`Dataset`, optional
+            This is the source to be rendered, which can be any arbitrary yt
+            data object or dataset.
+        lens_type: string, optional
+            This specifies the type of lens to use for rendering. Current
+            options are 'plane-parallel', 'perspective', and 'fisheye'. See
+            :class:`yt.visualization.volume_rendering.lens.Lens` for details.
+            Default: 'plane-parallel'
+        auto: boolean
+            If True, build smart defaults using the data source extent. This
+            can be time-consuming to iterate over the entire dataset to find
+            the positional bounds. Default: False
+
+        Examples
+        --------
+
+        In this example, the camera is set using defaults that are chosen
+        to be reasonable for the argument Dataset.
+
+        >>> import yt
+        >>> from yt.visualization.volume_rendering.api import Scene, Camera
+        >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
+        >>> sc = Scene()
+        >>> sc.add_camera()
+
+        Here, we set the camera properties manually:
+
+        >>> import yt
+        >>> from yt.visualization.volume_rendering.api import Scene, Camera
+        >>> sc = Scene()
+        >>> cam = sc.add_camera()
+        >>> cam.position = np.array([0.5, 0.5, -1.0])
+        >>> cam.focus = np.array([0.5, 0.5, 0.0])
+        >>> cam.north_vector = np.array([1.0, 0.0, 0.0])
+
+        Finally, we create a camera with a non-default lens:
+
+        >>> import yt
+        >>> from yt.visualization.volume_rendering.api import Camera
+        >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')
+        >>> sc = Scene()
+        >>> sc.add_camera(ds, lens_type='perspective')
+
+        """
+        self._camera = Camera(self, data_source, lens_type, auto)
+        return self.camera
+
     def camera():
         doc = r"""The camera property.
 
@@ -297,14 +396,12 @@
         """
 
         def fget(self):
-            cam = self._camera
-            if cam is None:
-                cam = Camera()
-            self._camera = cam
             return self._camera
 
         def fset(self, value):
-            # Should add better validation here
+            value.width = self.arr(value.width)
+            value.focus = self.arr(value.focus)
+            value.position = self.arr(value.position)
             self._camera = value
 
         def fdel(self):
@@ -313,6 +410,32 @@
         return locals()
     camera = property(**camera())
 
+    def unit_registry():
+        def fget(self):
+            ur = self._unit_registry
+            if ur is None:
+                ur = UnitRegistry()
+                # This will be updated when we add a volume source
+                ur.add("unitary", 1.0, length)
+            self._unit_registry = ur
+            return self._unit_registry
+
+        def fset(self, value):
+            self._unit_registry = value
+            if self.camera is not None:
+                self.camera.width = YTArray(
+                    self.camera.width.in_units('unitary'), registry=value)
+                self.camera.focus = YTArray(
+                    self.camera.focus.in_units('unitary'), registry=value)
+                self.camera.position = YTArray(
+                    self.camera.position.in_units('unitary'), registry=value)
+
+        def fdel(self):
+            del self._unit_registry
+            self._unit_registry = None
+        return locals()
+    unit_registry = property(**unit_registry())
+
     def set_camera(self, camera):
         r"""
 
@@ -479,6 +602,92 @@
         else:
             raise YTNotInsideNotebook
 
+    _arr = None
+    @property
+    def arr(self):
+        """Converts an array into a :class:`yt.units.yt_array.YTArray`
+
+        The returned YTArray will be dimensionless by default, but can be
+        cast to arbitrary units using the ``input_units`` keyword argument.
+
+        Parameters
+        ----------
+
+        input_array : iterable
+            A tuple, list, or array to attach units to
+        input_units : String unit specification, unit symbol object, or astropy
+                      units object
+            The units of the array. Powers must be specified using python syntax
+            (cm**3, not cm^3).
+        dtype : string or NumPy dtype object
+            The dtype of the returned array data
+
+        Examples
+        --------
+
+        >>> a = sc.arr([1, 2, 3], 'cm')
+        >>> b = sc.arr([4, 5, 6], 'm')
+        >>> a + b
+        YTArray([ 401.,  502.,  603.]) cm
+        >>> b + a
+        YTArray([ 4.01,  5.02,  6.03]) m
+
+        Arrays returned by this function know about the scene's unit system
+
+        >>> a = sc.arr(np.ones(5), 'unitary')
+        >>> a.in_units('Mpc')
+        YTArray([ 1.00010449,  1.00010449,  1.00010449,  1.00010449,
+                 1.00010449]) Mpc
+
+        """
+        if self._arr is not None:
+            return self._arr
+        self._arr = functools.partial(YTArray, registry=self.unit_registry)
+        return self._arr
+
+    _quan = None
+    @property
+    def quan(self):
+        """Converts an scalar into a :class:`yt.units.yt_array.YTQuantity`
+
+        The returned YTQuantity will be dimensionless by default, but can be
+        cast to arbitrary units using the ``input_units`` keyword argument.
+
+        Parameters
+        ----------
+
+        input_scalar : an integer or floating point scalar
+            The scalar to attach units to
+        input_units : String unit specification, unit symbol object, or astropy
+                      units
+            The units of the quantity. Powers must be specified using python
+            syntax (cm**3, not cm^3).
+        dtype : string or NumPy dtype object
+            The dtype of the array data.
+
+        Examples
+        --------
+
+        >>> a = sc.quan(1, 'cm')
+        >>> b = sc.quan(2, 'm')
+        >>> a + b
+        201.0 cm
+        >>> b + a
+        2.01 m
+
+        Quantities created this way automatically know about the unit system
+        of the scene
+
+        >>> a = ds.quan(5, 'unitary')
+        >>> a.in_cgs()
+        1.543e+25 cm
+
+        """
+        if self._quan is not None:
+            return self._quan
+        self._quan = functools.partial(YTQuantity, registry=self.unit_registry)
+        return self._quan
+
     def _repr_png_(self):
         if self.last_render is None:
             self.render()

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_camera_attributes.py
--- /dev/null
+++ b/yt/visualization/volume_rendering/tests/test_camera_attributes.py
@@ -0,0 +1,112 @@
+"""
+Tests for setting camera and scene attributes
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2014, yt Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+import numpy as np
+import yt.units as u
+
+from yt.testing import \
+    assert_equal, \
+    fake_random_ds
+from yt.visualization.volume_rendering.api import \
+    Scene
+
+
+valid_lens_types = [
+    'plane-parallel',
+    'perspective',
+    'stereo-perspective',
+    'fisheye',
+    'spherical',
+    'stereo-spherical'
+]
+
+
+def test_scene_and_camera_attributes():
+    ds = fake_random_ds(64, length_unit=2, bbox=np.array([[-1, 1], [-1, 1], [-1, 1]]))
+    sc = Scene()
+    cam = sc.add_camera(ds)
+
+    # test that initial values are correct in code units
+    assert_equal(cam.width, ds.arr([3, 3, 3], 'code_length'))
+    assert_equal(cam.position, ds.arr([1, 1, 1], 'code_length'))
+    assert_equal(cam.focus, ds.arr([0, 0, 0], 'code_length'))
+
+    # test setting the attributes in various ways
+
+    attribute_values = [
+        (1, ds.arr([2, 2, 2], 'code_length'), ),
+        ([1], ds.arr([2, 2, 2], 'code_length'), ),
+        ([1, 2], RuntimeError, ),
+        ([1, 1, 1], ds.arr([2, 2, 2], 'code_length'), ),
+        ((1, 'code_length'), ds.arr([1, 1, 1], 'code_length'), ),
+        (((1, 'code_length'), (1, 'code_length')), RuntimeError, ),
+        (((1, 'cm'), (2, 'cm'), (3, 'cm')),
+         ds.arr([0.5, 1, 1.5], 'code_length'), ),
+        (2*u.cm, ds.arr([1, 1, 1], 'code_length'), ),
+        (ds.arr(2, 'cm'), ds.arr([1, 1, 1], 'code_length'), ),
+        ([2*u.cm], ds.arr([1, 1, 1], 'code_length'), ),
+        ([1, 2, 3]*u.cm, ds.arr([0.5, 1, 1.5], 'code_length'), ),
+        ([1, 2]*u.cm, RuntimeError, ),
+        ([u.cm*w for w in [1, 2, 3]], ds.arr([0.5, 1, 1.5], 'code_length'), ),
+    ]
+
+    # define default values to avoid accidentally setting focus = position
+    default_values = {
+        'focus': [0, 0, 0],
+        'position': [4, 4, 4],
+        'width': [1, 1, 1],
+    }
+    attribute_list = list(default_values.keys())
+
+    for attribute in attribute_list:
+        for other_attribute in [a for a in attribute_list if a != attribute]:
+            setattr(cam, other_attribute, default_values[other_attribute])
+        for attribute_value, expected_result in attribute_values:
+            try:
+                # test properties
+                setattr(cam, attribute, attribute_value)
+                assert_equal(getattr(cam, attribute), expected_result)
+            except RuntimeError:
+                assert expected_result is RuntimeError
+
+            try:
+                # test setters/getters
+                getattr(cam, 'set_%s' % attribute)(attribute_value)
+                assert_equal(getattr(cam, 'get_%s' % attribute)(),
+                             expected_result)
+            except RuntimeError:
+                assert expected_result is RuntimeError
+
+    resolution_values = (
+        (512, (512, 512), ),
+        ((512, 512), (512, 512), ),
+        ((256, 512), (256, 512), ),
+        ((256, 256, 256), RuntimeError),
+    )
+
+    for resolution_value, expected_result in resolution_values:
+        try:
+            # test properties
+            cam.resolution = resolution_value
+            assert_equal(cam.resolution, expected_result)
+        except RuntimeError:
+            assert expected_result is RuntimeError
+
+        try:
+            # test setters/getters
+            cam.set_resolution(resolution_value)
+            assert_equal(cam.get_resolution(), expected_result)
+        except RuntimeError:
+            assert expected_result is RuntimeError
+
+    for lens_type in valid_lens_types:
+        cam.set_lens(lens_type)

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_composite.py
--- a/yt/visualization/volume_rendering/tests/test_composite.py
+++ b/yt/visualization/volume_rendering/tests/test_composite.py
@@ -14,8 +14,11 @@
 import tempfile
 import shutil
 from yt.testing import fake_random_ds
-from yt.visualization.volume_rendering.api import Scene, Camera, \
-    VolumeSource, LineSource, BoxSource
+from yt.visualization.volume_rendering.api import \
+    Scene, \
+    VolumeSource, \
+    LineSource, \
+    BoxSource
 from yt.data_objects.api import ImageArray
 import numpy as np
 from unittest import TestCase
@@ -52,9 +55,8 @@
         ds.field_info[ds.field_list[0]].take_log=False
 
         sc = Scene()
-        cam = Camera(ds)
+        cam = sc.add_camera(ds)
         cam.resolution = (512, 512)
-        sc.camera = cam
         vr = VolumeSource(dd, field=ds.field_list[0])
         vr.transfer_function.clear()
         vr.transfer_function.grey_opacity=True

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_lenses.py
--- a/yt/visualization/volume_rendering/tests/test_lenses.py
+++ b/yt/visualization/volume_rendering/tests/test_lenses.py
@@ -14,7 +14,7 @@
 import tempfile
 import shutil
 from yt.testing import fake_random_ds
-from yt.visualization.volume_rendering.api import Scene, Camera, VolumeSource
+from yt.visualization.volume_rendering.api import Scene, VolumeSource
 import numpy as np
 from unittest import TestCase
 
@@ -39,6 +39,7 @@
 
         self.field = ("gas", "density")
         self.ds = fake_random_ds(32, fields=self.field)
+        self.ds.index
 
     def tearDown(self):
         if self.use_tmpdir:
@@ -47,25 +48,23 @@
 
     def test_perspective_lens(self):
         sc = Scene()
-        cam = Camera(self.ds, lens_type='perspective')
+        cam = sc.add_camera(self.ds, lens_type='perspective')
         cam.position = self.ds.arr(np.array([1.0, 1.0, 1.0]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_perspective_%s.png' % self.field[1], sigma_clip=6.0)
 
     def test_stereoperspective_lens(self):
         sc = Scene()
-        cam = Camera(self.ds, lens_type='stereo-perspective')
+        cam = sc.add_camera(self.ds, lens_type='stereo-perspective')
         cam.resolution = [1024, 512]
         cam.position = self.ds.arr(np.array([0.7, 0.7, 0.7]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_stereoperspective_%s.png' % self.field[1], sigma_clip=6.0)
@@ -74,7 +73,7 @@
         dd = self.ds.sphere(self.ds.domain_center,
                             self.ds.domain_width[0] / 10)
         sc = Scene()
-        cam = Camera(dd, lens_type='fisheye')
+        cam = sc.add_camera(dd, lens_type='fisheye')
         cam.lens.fov = 360.0
         cam.set_width(self.ds.domain_width)
         v, c = self.ds.find_max('density')
@@ -82,7 +81,6 @@
         vol = VolumeSource(dd, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_fisheye_%s.png' % self.field[1], sigma_clip=6.0)
@@ -91,26 +89,24 @@
         dd = self.ds.sphere(self.ds.domain_center,
                             self.ds.domain_width[0] / 10)
         sc = Scene()
-        cam = Camera(dd, lens_type='plane-parallel')
+        cam = sc.add_camera(dd, lens_type='plane-parallel')
         cam.set_width(self.ds.domain_width*1e-2)
         v, c = self.ds.find_max('density')
         vol = VolumeSource(dd, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_plane_%s.png' % self.field[1], sigma_clip=6.0)
 
     def test_spherical_lens(self):
         sc = Scene()
-        cam = Camera(self.ds, lens_type='spherical')
+        cam = sc.add_camera(self.ds, lens_type='spherical')
         cam.resolution = [512, 256]
         cam.position = self.ds.arr(np.array([0.6, 0.5, 0.5]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_spherical_%s.png' % self.field[1], sigma_clip=6.0)
@@ -119,13 +115,12 @@
         w = (self.ds.domain_width).in_units('code_length')
         w = self.ds.arr(w, 'code_length')
         sc = Scene()
-        cam = Camera(self.ds, lens_type='stereo-spherical')
+        cam = sc.add_camera(self.ds, lens_type='stereo-spherical')
         cam.resolution = [1024, 256]
         cam.position = self.ds.arr(np.array([0.6, 0.5, 0.5]), 'code_length')
         vol = VolumeSource(self.ds, field=self.field)
         tf = vol.transfer_function
         tf.grey_opacity = True
-        sc.camera = cam
         sc.add_source(vol)
         sc.render()
         sc.save('test_stereospherical_%s.png' % self.field[1], sigma_clip=6.0)

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_mesh_render.py
--- a/yt/visualization/volume_rendering/tests/test_mesh_render.py
+++ b/yt/visualization/volume_rendering/tests/test_mesh_render.py
@@ -15,7 +15,7 @@
 from yt.testing import fake_hexahedral_ds
 from yt.testing import requires_module
 from yt.visualization.volume_rendering.render_source import MeshSource
-from yt.visualization.volume_rendering.camera import Camera
+from yt.visualization.volume_rendering.scene import Scene
 
 
 @requires_module("pyembree")
@@ -24,17 +24,18 @@
     images = []
 
     ds = fake_tetrahedral_ds()
+    sc = Scene()
     for field in ds.field_list:
-        ms = MeshSource(ds, field)
-        cam = Camera(ds)
-        im = ms.render(cam)
+        sc.add_source(MeshSource(ds, field))
+        sc.add_camera()
+        im = sc.render()
         images.append(im)
 
     ds = fake_hexahedral_ds()
     for field in ds.field_list:
-        ms = MeshSource(ds, field)
-        cam = Camera(ds)
-        im = ms.render(cam)
+        sc.add_source(MeshSource(ds, field))
+        sc.add_camera()
+        im = sc.render()
         images.append(im)
 
     return images

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_points.py
--- a/yt/visualization/volume_rendering/tests/test_points.py
+++ b/yt/visualization/volume_rendering/tests/test_points.py
@@ -14,8 +14,10 @@
 import tempfile
 import shutil
 from yt.testing import fake_random_ds
-from yt.visualization.volume_rendering.api import Scene, Camera, \
-    VolumeSource, PointSource
+from yt.visualization.volume_rendering.api import \
+    Scene, \
+    VolumeSource, \
+    PointSource
 import numpy as np
 from unittest import TestCase
 
@@ -51,9 +53,8 @@
         ds.field_info[ds.field_list[0]].take_log=False
 
         sc = Scene()
-        cam = Camera(ds)
+        cam = sc.add_camera(ds)
         cam.resolution = (512,512)
-        sc.camera = cam
         vr = VolumeSource(dd, field=ds.field_list[0])
         vr.transfer_function.clear()
         vr.transfer_function.grey_opacity=False

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_vr_orientation.py
--- a/yt/visualization/volume_rendering/tests/test_vr_orientation.py
+++ b/yt/visualization/volume_rendering/tests/test_vr_orientation.py
@@ -20,7 +20,6 @@
     GenericImageTest
 from yt.visualization.volume_rendering.api import \
     Scene, \
-    Camera, \
     VolumeSource, \
     ColorTransferFunction, \
     off_axis_projection
@@ -114,14 +113,13 @@
     for lens_type in ['plane-parallel', 'perspective']:
         frame = 0
 
-        cam = Camera(ds, lens_type='plane-parallel')
+        cam = sc.add_camera(ds, lens_type='plane-parallel')
         cam.resolution = (1000, 1000)
         cam.position = ds.arr(np.array([-4., 0., 0.]), 'code_length')
         cam.switch_orientation(normal_vector=[1., 0., 0.],
                                north_vector=[0., 0., 1.])
         cam.set_width(ds.domain_width*2.)
 
-        sc.camera = cam
         sc.add_source(vol)
         yield VRImageComparisonTest(
             sc, ds, '%s_%04d' % (lens_type, frame), decimals)
@@ -130,7 +128,6 @@
             frame += 1
             center = ds.arr([0, 0, 0], 'code_length')
             cam.yaw(theta, rot_center=center)
-            sc.camera = cam
             yield VRImageComparisonTest(
                 sc, ds, 'yaw_%s_%04d' % (lens_type, frame), decimals)
 
@@ -139,7 +136,6 @@
             theta = np.pi / n_frames
             center = ds.arr([0, 0, 0], 'code_length')
             cam.pitch(theta, rot_center=center)
-            sc.camera = cam
             yield VRImageComparisonTest(
                 sc, ds, 'pitch_%s_%04d' % (lens_type, frame), decimals)
 
@@ -148,7 +144,6 @@
             theta = np.pi / n_frames
             center = ds.arr([0, 0, 0], 'code_length')
             cam.roll(theta, rot_center=center)
-            sc.camera = cam
             yield VRImageComparisonTest(
                 sc, ds, 'roll_%s_%04d' % (lens_type, frame), decimals)
 

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/tests/test_zbuff.py
--- a/yt/visualization/volume_rendering/tests/test_zbuff.py
+++ b/yt/visualization/volume_rendering/tests/test_zbuff.py
@@ -15,8 +15,10 @@
 import shutil
 from yt.testing import fake_random_ds
 from yt.visualization.volume_rendering.api import \
-    Scene, Camera, ZBuffer, \
-    VolumeSource, OpaqueSource
+    Scene, \
+    ZBuffer, \
+    VolumeSource, \
+    OpaqueSource
 from yt.testing import assert_almost_equal
 import numpy as np
 from unittest import TestCase
@@ -54,9 +56,8 @@
         ds.field_info[ds.field_list[0]].take_log=False
 
         sc = Scene()
-        cam = Camera(ds)
+        cam = sc.add_camera(ds)
         cam.resolution = (512,512)
-        sc.camera = cam
         vr = VolumeSource(dd, field=ds.field_list[0])
         vr.transfer_function.clear()
         vr.transfer_function.grey_opacity=True

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/utils.py
--- a/yt/visualization/volume_rendering/utils.py
+++ b/yt/visualization/volume_rendering/utils.py
@@ -15,7 +15,7 @@
 
 
 def new_mesh_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     args = (
         np.atleast_3d(params['vp_pos']),
         np.atleast_3d(params['vp_dir']),
@@ -32,7 +32,7 @@
 
 
 def new_volume_render_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     params.update(transfer_function=render_source.transfer_function)
     params.update(transfer_function=render_source.transfer_function)
     params.update(num_samples=render_source.num_samples)
@@ -61,7 +61,7 @@
 
 
 def new_interpolated_projection_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     params.update(transfer_function=render_source.transfer_function)
     params.update(num_samples=render_source.num_samples)
     args = (
@@ -85,7 +85,7 @@
 
 
 def new_projection_sampler(camera, render_source):
-    params = camera._get_sampler_params(render_source)
+    params = ensure_code_unit_params(camera._get_sampler_params(render_source))
     params.update(transfer_function=render_source.transfer_function)
     params.update(num_samples=render_source.num_samples)
     args = (
@@ -107,6 +107,16 @@
     sampler = ProjectionSampler(*args, **kwargs)
     return sampler
 
+def ensure_code_unit_params(params):
+    for param_name in ['center', 'vp_pos', 'vp_dir', 'width']:
+        param = params[param_name]
+        if hasattr(param, 'in_units'):
+            params[param_name] = param.in_units('code_length')
+    bounds = params['bounds']
+    if hasattr(bounds[0], 'units'):
+        params['bounds'] = tuple(b.in_units('code_length').d for b in bounds)
+
+    return params
 
 def get_corners(le, re):
     return np.array([

diff -r 169c6b14377e4e691657a18547322d3d0c87aae3 -r bdb4a8a9139ed14202d0e55c30eae8a9dc9c0977 yt/visualization/volume_rendering/volume_rendering.py
--- a/yt/visualization/volume_rendering/volume_rendering.py
+++ b/yt/visualization/volume_rendering/volume_rendering.py
@@ -13,7 +13,6 @@
 
 
 from .scene import Scene
-from .camera import Camera
 from .render_source import VolumeSource, \
     MeshSource
 from .utils import data_source_or_all
@@ -77,7 +76,7 @@
         source = VolumeSource(data_source, field=field)
 
     sc.add_source(source)
-    sc.camera = Camera(data_source=data_source, lens_type=lens_type)
+    sc.add_camera(data_source=data_source, lens_type=lens_type)
     return sc

Repository URL: https://bitbucket.org/yt_analysis/yt/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.


More information about the yt-svn mailing list