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

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Thu Nov 6 07:33:43 PST 2014


65 new commits in yt:

https://bitbucket.org/yt_analysis/yt/commits/3b7b51950d92/
Changeset:   3b7b51950d92
Branch:      yt
User:        jzuhone
Date:        2014-10-06 18:13:09+00:00
Summary:     Allow for the spectral axis to be converted to units other than velocity.
Affected #:  1 file

diff -r 4cf5c5369df9b012759ab979330d8e356d1b0e00 -r 3b7b51950d924c7cb4ddd2aedf8af7c2822b9f7b yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -16,6 +16,8 @@
 from yt.utilities.fits_image import FITSImageBuffer
 from yt.visualization.volume_rendering.camera import off_axis_projection
 from yt.funcs import get_pbar
+from yt.utilities.physical_constants import clight
+import yt.units.dimensions as ytdims
 
 def create_vlos(z_hat):
     def _v_los(field, data):
@@ -25,9 +27,14 @@
         return -vz
     return _v_los
 
+fits_info = {"velocity":("m/s","VELOCITY"),
+             "frequency":("Hz","FREQUENCY"),
+             "energy":("eV","ENERGY"),
+             "wavelength":("angstrom","WAVELENG")}
+
 class PPVCube(object):
     def __init__(self, ds, normal, field, width=(1.0,"unitary"),
-                 dims=(100,100,100), velocity_bounds=None):
+                 dims=(100,100,100), velocity_bounds=None, rest_freq=None):
         r""" Initialize a PPVCube object.
 
         Parameters
@@ -47,6 +54,11 @@
             A 3-tuple of (vmin, vmax, units) for the velocity bounds to
             integrate over. If None, the largest velocity of the
             dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())
+        rest_freq : tuple, optional
+            A (value, unit) tuple indicating the "rest frequency" of the spectral
+            axis of the PPV cube. The spectral axis will be converted to the units and
+            displaced by the value. Can be in units of energy, wavelength, or frequency.
+            If not set the default is to leave the spectral axis in velocity units.
 
         Examples
         --------
@@ -58,6 +70,7 @@
         self.ds = ds
         self.field = field
         self.width = width
+        self.rest_freq = rest_freq
 
         self.nx = dims[0]
         self.ny = dims[1]
@@ -86,10 +99,10 @@
 
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-        self.dv = (self.v_bnd[1]-self.v_bnd[0])/self.nv
+        self.dv = self.v_bins[1]-self.v_bins[0]
 
         _vlos = create_vlos(orient.unit_vectors[2])
-        ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+        self.ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
         pbar = get_pbar("Generating cube.", self.nv)
@@ -104,6 +117,13 @@
 
         pbar.finish()
 
+        if self.rest_freq is not None:
+            # If we want units other than velocity, we re-calculate these quantities
+            self.rest_freq = self.ds.quan(rest_freq[0], rest_freq[1])
+            self.vbins = self.rest_freq*(1.-self.vbins.in_cgs()/clight)
+            self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+            self.dv = self.v_bins[1]-self.v_bins[0]
+
     def write_fits(self, filename, clobber=True, length_unit=(10.0, "kpc"),
                    sky_center=(30.,45.)):
         r""" Write the PPVCube to a FITS file.
@@ -132,11 +152,25 @@
             center = [0.0,0.0]
             types = ["LINEAR","LINEAR"]
 
-        v_center = 0.5*(self.v_bnd[0]+self.v_bnd[1]).in_units("m/s").value
+        dims = self.dv.units.dimensions
+
+        if dims == ytdims.rate:
+            axis_type = "frequency"
+        elif dims == ytdims.length:
+            axis_type = "wavelength"
+        elif dims == ytdims.energy:
+            axis_type = "energy"
+        elif dims == ytdims.velocity:
+            axis_type = "velocity"
+
+        vunit = fits_info[axis_type]
+        vtype = fits_info[axis_type]
+
+        v_center = 0.5*(self.v_bnd[0]+self.v_bnd[1]).in_units(vunit).value
 
         dx = length_unit[0]/self.nx
         dy = length_unit[0]/self.ny
-        dv = self.dv.in_units("m/s").value
+        dv = self.dv.in_units(vunit).value
 
         if length_unit[1] == "deg":
             dx *= -1.
@@ -145,8 +179,8 @@
         w.wcs.crpix = [0.5*(self.nx+1), 0.5*(self.ny+1), 0.5*(self.nv+1)]
         w.wcs.cdelt = [dx,dy,dv]
         w.wcs.crval = [center[0], center[1], v_center]
-        w.wcs.cunit = [length_unit[1],length_unit[1],"m/s"]
-        w.wcs.ctype = [types[0],types[1],"VELO-LSR"]
+        w.wcs.cunit = [length_unit[1],length_unit[1],vunit]
+        w.wcs.ctype = [types[0],types[1],vtype]
 
         fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
         fib[0].header["bunit"] = self.field_units


https://bitbucket.org/yt_analysis/yt/commits/24665f82c66e/
Changeset:   24665f82c66e
Branch:      yt
User:        jzuhone
Date:        2014-10-06 20:13:16+00:00
Summary:     Attempting to integrate thermal broadening. Also some bug fixes.
Affected #:  1 file

diff -r 3b7b51950d924c7cb4ddd2aedf8af7c2822b9f7b -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -16,7 +16,7 @@
 from yt.utilities.fits_image import FITSImageBuffer
 from yt.visualization.volume_rendering.camera import off_axis_projection
 from yt.funcs import get_pbar
-from yt.utilities.physical_constants import clight
+from yt.utilities.physical_constants import clight, mh, kboltz
 import yt.units.dimensions as ytdims
 
 def create_vlos(z_hat):
@@ -34,7 +34,8 @@
 
 class PPVCube(object):
     def __init__(self, ds, normal, field, width=(1.0,"unitary"),
-                 dims=(100,100,100), velocity_bounds=None, rest_freq=None):
+                 dims=(100,100,100), velocity_bounds=None, rest_value=None,
+                 ion_weight=None):
         r""" Initialize a PPVCube object.
 
         Parameters
@@ -54,11 +55,14 @@
             A 3-tuple of (vmin, vmax, units) for the velocity bounds to
             integrate over. If None, the largest velocity of the
             dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())
-        rest_freq : tuple, optional
-            A (value, unit) tuple indicating the "rest frequency" of the spectral
+        rest_value : tuple, optional
+            A (value, unit) tuple indicating the rest value of the spectral
             axis of the PPV cube. The spectral axis will be converted to the units and
             displaced by the value. Can be in units of energy, wavelength, or frequency.
             If not set the default is to leave the spectral axis in velocity units.
+        ion_weight : float, optional
+            Set this value to the atomic weight of the ion that is emitting the line
+            in order to include thermal broadening.
 
         Examples
         --------
@@ -70,7 +74,7 @@
         self.ds = ds
         self.field = field
         self.width = width
-        self.rest_freq = rest_freq
+        self.rest_value = rest_value
 
         self.nx = dims[0]
         self.ny = dims[1]
@@ -99,7 +103,14 @@
 
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-        self.dv = self.v_bins[1]-self.v_bins[0]
+        self.dv = self.vbins[1]-self.vbins[0]
+
+        if ion_weight is None:
+            self.ion_mass = mh
+            self.phi_th = lambda v, v_th: 1.0
+        else:
+            self.ion_mass = ion_weight*mh
+            self.phi_th = lambda v, v_th: self.dv/(np.sqrt(np.pi)*v_th)*np.exp(-(v/v_th)**2)
 
         _vlos = create_vlos(orient.unit_vectors[2])
         self.ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
@@ -117,10 +128,10 @@
 
         pbar.finish()
 
-        if self.rest_freq is not None:
+        if self.rest_value is not None:
             # If we want units other than velocity, we re-calculate these quantities
-            self.rest_freq = self.ds.quan(rest_freq[0], rest_freq[1])
-            self.vbins = self.rest_freq*(1.-self.vbins.in_cgs()/clight)
+            self.rest_value = self.ds.quan(rest_value[0], rest_value[1])
+            self.vbins = self.rest_value*(1.-self.vbins.in_cgs()/clight)
             self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
             self.dv = self.v_bins[1]-self.v_bins[0]
 
@@ -166,7 +177,7 @@
         vunit = fits_info[axis_type]
         vtype = fits_info[axis_type]
 
-        v_center = 0.5*(self.v_bnd[0]+self.v_bnd[1]).in_units(vunit).value
+        v_center = 0.5*(self.v_bins[0]+self.v_bins[-1]).in_units(vunit).value
 
         dx = length_unit[0]/self.nx
         dy = length_unit[0]/self.ny
@@ -191,8 +202,9 @@
     def _create_intensity(self, i):
         def _intensity(field, data):
             vlos = data["v_los"]
+            v_th = np.sqrt(2*kboltz*data["temperature"]/self.ion_mass)
             w = np.abs(vlos-self.vmid[i])/self.dv.in_units(vlos.units)
             w = 1.-w
             w[w < 0.0] = 0.0
-            return data[self.field]*w
+            return data[self.field]*self.phi_th(vlos, v_th)*w
         return _intensity


https://bitbucket.org/yt_analysis/yt/commits/7181ddfcb573/
Changeset:   7181ddfcb573
Branch:      yt
User:        jzuhone
Date:        2014-10-06 20:13:51+00:00
Summary:     Merge
Affected #:  29 files

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/install_script.sh
--- a/doc/install_script.sh
+++ b/doc/install_script.sh
@@ -986,8 +986,10 @@
 
 if !( ( ${DEST_DIR}/bin/python2.7 -c "import readline" 2>&1 )>> ${LOG_FILE})
 then
-    echo "Installing pure-python readline"
-    ( ${DEST_DIR}/bin/pip install readline 2>&1 ) 1>> ${LOG_FILE}
+    if !( ( ${DEST_DIR}/bin/python2.7 -c "import gnureadline" 2>&1 )>> ${LOG_FILE})
+        echo "Installing pure-python readline"
+        ( ${DEST_DIR}/bin/pip install gnureadline 2>&1 ) 1>> ${LOG_FILE}
+    fi
 fi
 
 if [ $INST_ENZO -eq 1 ]

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/analyzing/analysis_modules/PPVCube.ipynb
--- a/doc/source/analyzing/analysis_modules/PPVCube.ipynb
+++ b/doc/source/analyzing/analysis_modules/PPVCube.ipynb
@@ -291,7 +291,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "prj = yt.ProjectionPlot(ds, \"z\", [\"density\"], proj_style=\"sum\")\n",
+      "prj = yt.ProjectionPlot(ds, \"z\", [\"density\"], method=\"sum\")\n",
       "prj.set_log(\"density\", True)\n",
       "prj.set_zlim(\"density\", 1.0e-3, 0.2)\n",
       "prj.show()"
@@ -304,4 +304,4 @@
    "metadata": {}
   }
  ]
-}
\ No newline at end of file
+}

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/analyzing/objects.rst
--- a/doc/source/analyzing/objects.rst
+++ b/doc/source/analyzing/objects.rst
@@ -225,12 +225,12 @@
 
 **Projection** 
     | Class :class:`~yt.data_objects.construction_data_containers.YTQuadTreeProjBase`
-    | Usage: ``proj(field, axis, weight_field=None, center=None, ds=None, data_source=None, style="integrate", field_parameters=None)``
+    | Usage: ``proj(field, axis, weight_field=None, center=None, ds=None, data_source=None, method="integrate", field_parameters=None)``
     | A 2D projection of a 3D volume along one of the axis directions.  
       By default, this is a line integral through the entire simulation volume 
       (although it can be a subset of that volume specified by a data object
       with the ``data_source`` keyword).  Alternatively, one can specify 
-      a weight_field and different ``style`` values to change the nature
+      a weight_field and different ``method`` values to change the nature
       of the projection outcome.  See :ref:`projection-types` for more information.
 
 **Streamline** 

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/cookbook/fits_radio_cubes.ipynb
--- a/doc/source/cookbook/fits_radio_cubes.ipynb
+++ b/doc/source/cookbook/fits_radio_cubes.ipynb
@@ -183,14 +183,14 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "We can also make a projection of all the emission along the line of sight. Since we're not doing an integration along a path length, we needed to specify `proj_style = \"sum\"`:"
+      "We can also make a projection of all the emission along the line of sight. Since we're not doing an integration along a path length, we needed to specify `method = \"sum\"`:"
      ]
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "prj = yt.ProjectionPlot(ds, \"z\", [\"intensity\"], proj_style=\"sum\", origin=\"native\")\n",
+      "prj = yt.ProjectionPlot(ds, \"z\", [\"intensity\"], method=\"sum\", origin=\"native\")\n",
       "prj.show()"
      ],
      "language": "python",

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/cookbook/fits_xray_images.ipynb
--- a/doc/source/cookbook/fits_xray_images.ipynb
+++ b/doc/source/cookbook/fits_xray_images.ipynb
@@ -264,7 +264,7 @@
       "                   [\"flux\",\"projected_temperature\",\"pseudo_pressure\",\"pseudo_entropy\"], \n",
       "                   origin=\"native\", field_parameters={\"exposure_time\":exposure_time},\n",
       "                   data_source=circle_reg,\n",
-      "                   proj_style=\"sum\")\n",
+      "                   method=\"sum\")\n",
       "prj.set_log(\"flux\",True)\n",
       "prj.set_log(\"pseudo_pressure\",False)\n",
       "prj.set_log(\"pseudo_entropy\",False)\n",

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/cookbook/free_free_field.py
--- a/doc/source/cookbook/free_free_field.py
+++ b/doc/source/cookbook/free_free_field.py
@@ -6,6 +6,7 @@
 # Need to grab the proton mass from the constants database
 from yt.utilities.physical_constants import mp
 
+exit()
 # Define the emission field
 
 keVtoerg = 1.602e-9  # Convert energy in keV to energy in erg

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/cookbook/hse_field.py
--- a/doc/source/cookbook/hse_field.py
+++ b/doc/source/cookbook/hse_field.py
@@ -1,163 +1,35 @@
-### THIS RECIPE IS CURRENTLY BROKEN IN YT-3.0
-### DO NOT TRUST THIS RECIPE UNTIL THIS LINE IS REMOVED
-
 import numpy as np
 import yt
 
+from yt.fields.field_plugin_registry import \
+    register_field_plugin
+from yt.fields.fluid_fields import \
+    setup_gradient_fields
+
+
 # Define the components of the gravitational acceleration vector field by
 # taking the gradient of the gravitational potential
-
- at yt.derived_field(name='gravitational_acceleration_x',
-                  units='cm/s**2', take_log=False,
-                  validators=[yt.ValidateSpatial(1,["gravitational_potential"])])
-def gravitational_acceleration_x(field, data):
-
-    # We need to set up stencils
-
-    sl_left = slice(None, -2, None)
-    sl_right = slice(2, None, None)
-    div_fac = 2.0
-
-    dx = div_fac * data['dx'][0]
-
-    gx = data["gravitational_potential"][sl_right, 1:-1, 1:-1]/dx
-    gx -= data["gravitational_potential"][sl_left, 1:-1, 1:-1]/dx
-
-    new_field = np.zeros(data["gravitational_potential"].shape,
-                         dtype='float64')*gx.uq
-    new_field[1:-1, 1:-1, 1:-1] = -gx
-
-    return new_field
-
-
- at yt.derived_field(name='gravitational_acceleration_y',
-                  units='cm/s**2', take_log=False,
-                  validators=[yt.ValidateSpatial(1,["gravitational_potential"])])
-def gravitational_acceleration_y(field, data):
-
-    # We need to set up stencils
-
-    sl_left = slice(None, -2, None)
-    sl_right = slice(2, None, None)
-    div_fac = 2.0
-
-    dy = div_fac * data['dy'].flatten()[0]
-
-    gy = data["gravitational_potential"][1:-1, sl_right, 1:-1]/dy
-    gy -= data["gravitational_potential"][1:-1, sl_left, 1:-1]/dy
-
-    new_field = np.zeros(data["gravitational_potential"].shape,
-                         dtype='float64')*gy.uq
-
-    new_field[1:-1, 1:-1, 1:-1] = -gy
-
-    return new_field
-
-
- at yt.derived_field(name='gravitational_acceleration_z',
-                  units='cm/s**2', take_log=False,
-                  validators=[yt.ValidateSpatial(1,["gravitational_potential"])])
-def gravitational_acceleration_z(field, data):
-
-    # We need to set up stencils
-
-    sl_left = slice(None, -2, None)
-    sl_right = slice(2, None, None)
-    div_fac = 2.0
-
-    dz = div_fac * data['dz'].flatten()[0]
-
-    gz = data["gravitational_potential"][1:-1, 1:-1, sl_right]/dz
-    gz -= data["gravitational_potential"][1:-1, 1:-1, sl_left]/dz
-
-    new_field = np.zeros(data["gravitational_potential"].shape,
-                         dtype='float64')*gz.uq
-    new_field[1:-1, 1:-1, 1:-1] = -gz
-
-    return new_field
-
-
-# Define the components of the pressure gradient field
-
-
- at yt.derived_field(name='grad_pressure_x', units='g/(cm*s)**2', take_log=False,
-                  validators=[yt.ValidateSpatial(1,["pressure"])])
-def grad_pressure_x(field, data):
-
-    # We need to set up stencils
-
-    sl_left = slice(None, -2, None)
-    sl_right = slice(2, None, None)
-    div_fac = 2.0
-
-    dx = div_fac * data['dx'].flatten()[0]
-
-    px = data["pressure"][sl_right, 1:-1, 1:-1]/dx
-    px -= data["pressure"][sl_left, 1:-1, 1:-1]/dx
-
-    new_field = np.zeros(data["pressure"].shape, dtype='float64')*px.uq
-    new_field[1:-1, 1:-1, 1:-1] = px
-
-    return new_field
-
-
- at yt.derived_field(name='grad_pressure_y', units='g/(cm*s)**2', take_log=False,
-                  validators=[yt.ValidateSpatial(1,["pressure"])])
-def grad_pressure_y(field, data):
-
-    # We need to set up stencils
-
-    sl_left = slice(None, -2, None)
-    sl_right = slice(2, None, None)
-    div_fac = 2.0
-
-    dy = div_fac * data['dy'].flatten()[0]
-
-    py = data["pressure"][1:-1, sl_right, 1:-1]/dy
-    py -= data["pressure"][1:-1, sl_left, 1:-1]/dy
-
-    new_field = np.zeros(data["pressure"].shape, dtype='float64')*py.uq
-    new_field[1:-1, 1:-1, 1:-1] = py
-
-    return new_field
-
-
- at yt.derived_field(name='grad_pressure_z', units='g/(cm*s)**2', take_log=False,
-                  validators=[yt.ValidateSpatial(1,["pressure"])])
-def grad_pressure_z(field, data):
-
-    # We need to set up stencils
-
-    sl_left = slice(None, -2, None)
-    sl_right = slice(2, None, None)
-    div_fac = 2.0
-
-    dz = div_fac * data['dz'].flatten()[0]
-
-    pz = data["pressure"][1:-1, 1:-1, sl_right]/dz
-    pz -= data["pressure"][1:-1, 1:-1, sl_left]/dz
-
-    new_field = np.zeros(data["pressure"].shape, dtype='float64')*pz.uq
-    new_field[1:-1, 1:-1, 1:-1] = pz
-
-    return new_field
-
+ at register_field_plugin
+def setup_my_fields(registry, ftype="gas", slice_info=None):
+    setup_gradient_fields(registry, (ftype, "gravitational_potential"),
+                          "cm ** 2 / s ** 2", slice_info)
 
 # Define the "degree of hydrostatic equilibrium" field
 
+
 @yt.derived_field(name='HSE', units=None, take_log=False,
                   display_name='Hydrostatic Equilibrium')
 def HSE(field, data):
 
-    gx = data["density"]*data["gravitational_acceleration_x"]
-    gy = data["density"]*data["gravitational_acceleration_y"]
-    gz = data["density"]*data["gravitational_acceleration_z"]
+    gx = data["density"] * data["gravitational_potential_gradient_x"]
+    gy = data["density"] * data["gravitational_potential_gradient_y"]
+    gz = data["density"] * data["gravitational_potential_gradient_z"]
 
-    hx = data["grad_pressure_x"] - gx
-    hy = data["grad_pressure_y"] - gy
-    hz = data["grad_pressure_z"] - gz
+    hx = data["pressure_gradient_x"] - gx
+    hy = data["pressure_gradient_y"] - gy
+    hz = data["pressure_gradient_z"] - gz
 
-    h = np.sqrt((hx*hx+hy*hy+hz*hz)/(gx*gx+gy*gy+gz*gz))
+    h = np.sqrt((hx * hx + hy * hy + hz * hz) / (gx * gx + gy * gy + gz * gz))
 
     return h
 
@@ -166,6 +38,10 @@
 
 ds = yt.load("GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0350")
 
+# gradient operator requires periodic boundaries.  This dataset has
+# open boundary conditions.  We need to hack it for now (this will be fixed
+# in future version of yt)
+ds.periodicity = (True, True, True)
 
 # Take a slice through the center of the domain
 slc = yt.SlicePlot(ds, 2, ["density", "HSE"], width=(1, 'Mpc'))

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/cookbook/tests/test_cookbook.py
--- a/doc/source/cookbook/tests/test_cookbook.py
+++ b/doc/source/cookbook/tests/test_cookbook.py
@@ -11,19 +11,30 @@
 """
 import glob
 import os
-import sys
+import subprocess
 
-sys.path.append(os.path.join(os.getcwd(), "doc/source/cookbook"))
+
+PARALLEL_TEST = {"rockstar_nest.py": "3"}
 
 
 def test_recipe():
     '''Dummy test grabbing all cookbook's recipes'''
     for fname in glob.glob("doc/source/cookbook/*.py"):
-        module_name = os.path.splitext(os.path.basename(fname))[0]
-        yield check_recipe, module_name
+        recipe = os.path.basename(fname)
+        check_recipe.description = "Testing recipe: %s" % recipe
+        if recipe in PARALLEL_TEST:
+            yield check_recipe, \
+                ["mpiexec", "-n", PARALLEL_TEST[recipe], "python", fname]
+        else:
+            yield check_recipe, ["python", fname]
 
 
-def check_recipe(module_name):
+def check_recipe(cmd):
     '''Run single recipe'''
-    __import__(module_name)
-    assert True
+    try:
+        subprocess.check_call(cmd)
+        result = True
+    except subprocess.CalledProcessError, e:
+        print("Stdout output:\n", e.output)
+        result = False
+    assert result

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/developing/building_the_docs.rst
--- a/doc/source/developing/building_the_docs.rst
+++ b/doc/source/developing/building_the_docs.rst
@@ -105,7 +105,10 @@
 
 You will need to have the yt repository available on your computer, which
 is done by default if you have yt installed.  In addition, you need a 
-current version of Sphinx_ (1.1.3) documentation software installed.
+current version of Sphinx_ (1.1.3) documentation software installed, as
+well as the Sphinx
+`Bootstrap theme <https://pypi.python.org/pypi/sphinx-bootstrap-theme/>`_,
+which can be installed via ``pip install sphinx_bootstrap_theme``.
 
 In order to tell sphinx not to do all of the dynamical building, you must
 set the ``$READTHEDOCS`` environment variable to be True by typing this at 

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/developing/developing.rst
--- a/doc/source/developing/developing.rst
+++ b/doc/source/developing/developing.rst
@@ -173,7 +173,7 @@
 If you plan to develop yt on Windows, it is necessary to use the `MinGW
 <http://www.mingw.org/>`_ gcc compiler that can be installed using the `Anaconda
 Python Distribution <https://store.continuum.io/cshop/anaconda/>`_. The libpython package must be
- installed from Anaconda as well. These can both be installed with a single command:
+installed from Anaconda as well. These can both be installed with a single command:
 
 .. code-block:: bash
 
@@ -229,6 +229,19 @@
  
    If you end up doing considerable development, you can set an alias in the
    file ``.hg/hgrc`` to point to this path.
+
+   .. note::
+     Note that the above approach uses HTTPS as the transfer protocol
+     between your machine and BitBucket.  If you prefer to use SSH - or
+     perhaps you're behind a proxy that doesn't play well with SSL via
+     HTTPS - you may want to set up an `SSH key`_ on BitBucket.  Then, you use
+     the syntax ``ssh://hg@bitbucket.org/YourUsername/yt``, or equivalent, in
+     place of ``https://bitbucket.org/YourUsername/yt`` in Mercurial commands.
+     For consistency, all commands we list in this document will use the HTTPS
+     protocol.
+
+     .. _SSH key: https://confluence.atlassian.com/display/BITBUCKET/Set+up+SSH+for+Mercurial
+
 #. Issue a pull request at
    https://bitbucket.org/YourUsername/yt/pull-request/new
 
@@ -246,6 +259,88 @@
 
 #. Your pull request will be automatically updated.
 
+.. _multiple-PRs:
+
+Working with Multiple BitBucket Pull Requests
++++++++++++++++++++++++++++++++++++++++++++++
+
+Once you become active developing for yt, you may be working on
+various aspects of the code or bugfixes at the same time.  Currently,
+BitBucket's *modus operandi* for pull requests automatically updates
+your active pull request with every ``hg push`` of commits that are a
+descendant of the head of your pull request.  In a normal workflow,
+this means that if you have an active pull request, make some changes
+locally for, say, an unrelated bugfix, then push those changes back to
+your fork in the hopes of creating a *new* pull request, you'll
+actually end up updating your current pull request!
+
+There are a few ways around this feature of BitBucket that will allow
+for multiple pull requests to coexist; we outline one such method
+below.  We assume that you have a fork of yt at
+``http://bitbucket.org/YourUsername/Your_yt`` (see
+:ref:`sharing-changes` for instructions on creating a fork) and that
+you have an active pull request to the main repository.
+
+The main issue with starting another pull request is to make sure that
+your push to BitBucket doesn't go to the same head as your
+existing pull request and trigger BitBucket's auto-update feature.
+Here's how to get your local repository away from your current pull
+request head using `revsets <http://www.selenic.com/hg/help/revsets>`_
+and your ``hgrc`` file:
+   
+#. Set up a Mercurial path for the main yt repository (note this is a convenience
+   step and only needs to be done once).  Add the following to your
+   ``Your_yt/.hg/hgrc``::
+
+     [paths]
+     upstream = https://bitbucket.org/yt_analysis/yt
+
+   This will create a path called ``upstream`` that is aliased to the URL of the
+   main yt repository.
+#. Now we'll use revsets_ to update your local repository to the tip of the
+   ``upstream`` path:
+
+   .. code-block:: bash
+
+      $ hg pull upstream
+      $ hg update -r "remote(tip,'upstream')"
+
+After the above steps, your local repository should be at the tip of
+the main yt repository.  If you find yourself doing this a lot, it may
+be worth aliasing this task in your ``hgrc`` file by adding something like::
+
+  [alias]
+  myupdate = update -r "remote(tip,'upstream')"
+
+And then you can just issue ``hg myupdate`` to get at the tip of the main yt repository.
+
+Make sure you are on the branch you want to be on, and then you can
+make changes and ``hg commit`` them.  If you prefer working with
+`bookmarks <http://mercurial.selenic.com/wiki/Bookmarks>`_, you may
+want to make a bookmark before committing your changes, such as ``hg
+bookmark mybookmark``.
+
+To push to your fork on BitBucket if you didn't use a bookmark, you issue the following:
+
+.. code-block:: bash
+
+  $ hg push -r . -f https://bitbucket.org/YourUsername/Your_yt
+
+The ``-r .`` means "push only the commit I'm standing on and any ancestors."  The
+``-f`` is to force Mecurial to do the push since we are creating a new remote head.
+
+Note that if you *did* use a bookmark, you don't have to force the push, but you do
+need to push the bookmark; in other words do the following instead of the above:
+
+.. code-block:: bash
+		
+   $ hg push -B mybookmark https://bitbucket.org/YourUsername/Your_yt
+
+The ``-B`` means "publish my bookmark and any relevant changesets to the remote server."
+		
+You can then go to the BitBucket interface and issue a new pull request based on
+your last changes, as usual.
+
 How To Get The Source Code For Editing
 --------------------------------------
 

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -44,6 +44,16 @@
      <tr valign="top"><td width="25%"><p>
+           <a href="examining/index.html">Loading and Examining Data</a>
+         </p>
+       </td>
+       <td width="75%">
+         <p class="linkdescr">How to load and examine all dataset types in yt</p>
+       </td>
+     </tr>
+     <tr valign="top">
+       <td width="25%">
+         <p><a href="yt3differences.html">yt 3.0</a></p></td>
@@ -84,16 +94,6 @@
      <tr valign="top"><td width="25%"><p>
-           <a href="examining/index.html">Examining Data</a>
-         </p>
-       </td>
-       <td width="75%">
-         <p class="linkdescr">Load data and directly access raw values for low-level analysis</p>
-       </td>
-     </tr>
-     <tr valign="top">
-       <td width="25%">
-         <p><a href="developing/index.html">Developing in yt</a></p></td>

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 doc/source/visualizing/plots.rst
--- a/doc/source/visualizing/plots.rst
+++ b/doc/source/visualizing/plots.rst
@@ -239,13 +239,13 @@
 Types of Projections
 """"""""""""""""""""
 
-There are several different styles of projections that can be made either 
+There are several different methods of projections that can be made either 
 when creating a projection with ds.proj() or when making a ProjectionPlot.  
-In either construction method, set the ``style`` keyword to be one of the 
+In either construction method, set the ``method`` keyword to be one of the 
 following:
 
 ``integrate`` (unweighted)
-    This is the default projection style. It simply integrates the 
+    This is the default projection method. It simply integrates the 
     requested field  :math:`f(x)` along a line of sight  :math:`\hat{n}` , 
     given by the axis parameter (e.g. :math:`\hat{i},\hat{j},` or 
     :math:`\hat{k}`).  The units of the projected field  
@@ -258,7 +258,7 @@
     g(X) = {\int\ {f(x)\hat{n}\cdot{dx}}}
 
 ``integrate`` (weighted)
-    When using the ``integrate``  style, a ``weight_field`` argument may also 
+    When using the ``integrate``  method, a ``weight_field`` argument may also 
     be specified, which will produce a weighted projection.  :math:`w(x)` 
     is the field used as a weight. One common example would 
     be to weight the "temperature" field by the "density" field. In this case, 
@@ -269,15 +269,15 @@
     g(X) = \frac{\int\ {f(x)w(x)\hat{n}\cdot{dx}}}{\int\ {w(x)\hat{n}\cdot{dx}}}
 
 ``mip`` 
-    This style picks out the maximum value of a field along the line of 
+    This method picks out the maximum value of a field along the line of 
     sight given by the axis parameter.
 
 ``sum``
-    This style is the same as ``integrate``, except that it does not 
+    This method is the same as ``integrate``, except that it does not 
     multiply by a path length when performing the integration, and is just a 
     straight summation of the field along the given axis. The units of the 
     projected field will be the same as those of the unprojected field. This 
-    style is typically only useful for datasets such as 3D FITS cubes where 
+    method is typically only useful for datasets such as 3D FITS cubes where 
     the third axis of the dataset is something like velocity or frequency.
 
 .. _off-axis-projections:

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/data_objects/construction_data_containers.py
--- a/yt/data_objects/construction_data_containers.py
+++ b/yt/data_objects/construction_data_containers.py
@@ -186,11 +186,14 @@
     data_source : `yt.data_objects.api.AMRData`, optional
         If specified, this will be the data source used for selecting
         regions to project.
-    style : string, optional
-        The style of projection to be performed.
+    method : string, optional
+        The method of projection to be performed.
         "integrate" : integration along the axis
         "mip" : maximum intensity projection
         "sum" : same as "integrate", except that we don't multiply by the path length
+    style : string, optional
+        The same as the method keyword.  Deprecated as of version 3.0.2.  
+        Please use method keyword instead.
     field_parameters : dict of items
         Values to be passed as field parameters that can be
         accessed by generated fields.
@@ -208,20 +211,26 @@
     _container_fields = ('px', 'py', 'pdx', 'pdy', 'weight_field')
     def __init__(self, field, axis, weight_field = None,
                  center = None, ds = None, data_source = None,
-                 style = "integrate", field_parameters = None):
+                 style = None, method = "integrate", 
+                 field_parameters = None):
         YTSelectionContainer2D.__init__(self, axis, ds, field_parameters)
-        if style == "sum":
-            self.proj_style = "integrate"
+        # Style is deprecated, but if it is set, then it trumps method
+        # keyword.  TODO: Remove this keyword and this check at some point in 
+        # the future.
+        if style is not None:
+            method = style
+        if method == "sum":
+            self.method = "integrate"
             self._sum_only = True
         else:
-            self.proj_style = style
+            self.method = method
             self._sum_only = False
-        if style == "mip":
+        if self.method == "mip":
             self.func = np.max
-        elif style == "integrate" or style == "sum":
+        elif self.method == "integrate":
             self.func = np.sum # for the future
         else:
-            raise NotImplementedError(style)
+            raise NotImplementedError(self.method)
         self._set_center(center)
         if data_source is None: data_source = self.ds.all_data()
         for k, v in data_source.field_parameters.items():
@@ -260,7 +269,7 @@
                   self.ds.domain_left_edge[xax],
                   self.ds.domain_right_edge[yax])
         return QuadTree(np.array([xd,yd], dtype='int64'), nvals,
-                        bounds, style = self.proj_style)
+                        bounds, method = self.method)
 
     def get_data(self, fields = None):
         fields = fields or []
@@ -282,10 +291,10 @@
                     get_memory_usage()/1024.)
                 self._handle_chunk(chunk, fields, tree)
         # Note that this will briefly double RAM usage
-        if self.proj_style == "mip":
+        if self.method == "mip":
             merge_style = -1
             op = "max"
-        elif self.proj_style == "integrate":
+        elif self.method == "integrate":
             merge_style = 1
             op = "sum"
         else:
@@ -324,7 +333,10 @@
             finfo = self.ds._get_field_info(*field)
             mylog.debug("Setting field %s", field)
             units = finfo.units
-            if self.weight_field is None and not self._sum_only:
+            # add length units to "projected units" if non-weighted 
+            # integral projection
+            if self.weight_field is None and not self._sum_only and \
+               self.method == 'integrate':
                 # See _handle_chunk where we mandate cm
                 if units == '':
                     input_units = "cm"
@@ -336,7 +348,9 @@
             self[field] = YTArray(field_data[fi].ravel(),
                                   input_units=input_units,
                                   registry=self.ds.unit_registry)
-            if self.weight_field is None and not self._sum_only:
+            # convert units if non-weighted integral projection
+            if self.weight_field is None and not self._sum_only and \
+               self.method == 'integrate':
                 u_obj = Unit(units, registry=self.ds.unit_registry)
                 if ((u_obj.is_code_unit or self.ds.no_cgs_equiv_length) and
                     not u_obj.is_dimensionless) and input_units != units:
@@ -355,7 +369,7 @@
         tree.initialize_chunk(i1, i2, ilevel)
 
     def _handle_chunk(self, chunk, fields, tree):
-        if self.proj_style == "mip" or self._sum_only:
+        if self.method == "mip" or self._sum_only:
             dl = 1.0
         else:
             # This gets explicitly converted to cm

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/data_objects/data_containers.py
--- a/yt/data_objects/data_containers.py
+++ b/yt/data_objects/data_containers.py
@@ -496,7 +496,8 @@
 
                 # really ugly check to ensure that this field really does exist somewhere,
                 # in some naming convention, before returning it as a possible field type
-                if (ftype,fname) not in self.ds.field_list and \
+                if (ftype,fname) not in self.ds.field_info and \
+                        (ftype,fname) not in self.ds.field_list and \
                         fname not in self.ds.field_list and \
                         (ftype,fname) not in self.ds.derived_field_list and \
                         fname not in self.ds.derived_field_list and \

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/frontends/boxlib/data_structures.py
--- a/yt/frontends/boxlib/data_structures.py
+++ b/yt/frontends/boxlib/data_structures.py
@@ -683,7 +683,7 @@
         with open(fn, 'r') as f:
             lines = f.readlines()
             self.num_stars = int(lines[0].strip()[0])
-            for line in lines[1:]:
+            for num, line in enumerate(lines[1:]):
                 particle_position_x = float(line.split(' ')[1])
                 particle_position_y = float(line.split(' ')[2])
                 particle_position_z = float(line.split(' ')[3])
@@ -704,6 +704,12 @@
                     ind = np.where(self.grids == grid)[0][0]
                     self.grid_particle_count[ind] += 1
                     self.grids[ind].NumberOfParticles += 1
+
+                    # store the position in the particle file for fast access.
+                    try:
+                        self.grids[ind]._particle_line_numbers.append(num + 1)
+                    except AttributeError:
+                        self.grids[ind]._particle_line_numbers = [num + 1]
         return True
 
 

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/frontends/boxlib/fields.py
--- a/yt/frontends/boxlib/fields.py
+++ b/yt/frontends/boxlib/fields.py
@@ -15,6 +15,7 @@
 
 import numpy as np
 import string
+import re
 
 from yt.utilities.physical_constants import \
     mh, boltzmann_constant_cgs, amu_cgs
@@ -25,6 +26,9 @@
 mom_units = "code_mass / (code_time * code_length**2)"
 eden_units = "code_mass / (code_time**2 * code_length)" # erg / cm^3
 
+spec_finder = re.compile(r'.*\((\D*)(\d*)\).*')
+
+
 def _thermal_energy_density(field, data):
     # What we've got here is UEINT:
     # u here is velocity
@@ -35,18 +39,21 @@
                + data["momentum_z"]**2) / data["density"]
     return data["eden"] - ke
 
+
 def _thermal_energy(field, data):
     # This is little e, so we take thermal_energy_density and divide by density
     return data["thermal_energy_density"] / data["density"]
 
-def _temperature(field,data):
+
+def _temperature(field, data):
     mu = data.ds.parameters["mu"]
     gamma = data.ds.parameters["gamma"]
-    tr  = data["thermal_energy_density"] / data["density"]
+    tr = data["thermal_energy_density"] / data["density"]
     tr *= mu * amu_cgs / boltzmann_constant_cgs
     tr *= (gamma - 1.0)
     return tr
 
+
 class BoxlibFieldInfo(FieldInfoContainer):
     known_other_fields = (
         ("density", (rho_units, ["density"], None)),
@@ -91,11 +98,11 @@
         if any(f[1] == "xmom" for f in self.field_list):
             self.setup_momentum_to_velocity()
         self.add_field(("gas", "thermal_energy"),
-                       function = _thermal_energy,
-                       units = "erg/g")
+                       function=_thermal_energy,
+                       units="erg/g")
         self.add_field(("gas", "thermal_energy_density"),
-                       function = _thermal_energy_density,
-                       units = "erg/cm**3")
+                       function=_thermal_energy_density,
+                       units="erg/cm**3")
         if ("gas", "temperature") not in self.field_aliases:
             self.add_field(("gas", "temperature"),
                            function=_temperature,
@@ -107,8 +114,9 @@
                 return data["%smom" % axis]/data["density"]
         for ax in 'xyz':
             self.add_field(("gas", "velocity_%s" % ax),
-                           function = _get_vel(ax),
-                           units = "cm/s")
+                           function=_get_vel(ax),
+                           units="cm/s")
+
 
 class CastroFieldInfo(FieldInfoContainer):
 
@@ -125,22 +133,27 @@
         # internal energy density (not just thermal)
         ("rho_e", ("erg/cm**3", [], r"\rho e")),
         ("Temp", ("K", ["temperature"], r"T")),
-        ("grav_x", ("cm/s**2", [], r"g\cdot e_x")),
-        ("grav_y", ("cm/s**2", [], r"g\cdot e_y")),
-        ("grav_z", ("cm/s**2", [], r"g\cdot e_z")),
+        ("grav_x", ("cm/s**2", [],
+                    r"\left(\mathbf{g} \cdot \mathbf{e}\right)_x")),
+        ("grav_y", ("cm/s**2", [],
+                    r"\left(\mathbf{g} \cdot \mathbf{e}\right)_y")),
+        ("grav_z", ("cm/s**2", [],
+                    r"\left(\mathbf{g} \cdot \mathbf{e}\right)_z")),
         ("pressure", ("dyne/cm**2", [], r"p")),
-        ("kineng", ("erg/cm**3", [], r"\frac{1}{2}\rho|U|**2")),
-        ("soundspeed", ("cm/s", ["sound_speed"], None)),
-        ("Machnumber", ("", ["mach_number"], None)),
+        ("kineng", ("erg/cm**3", [], r"\frac{1}{2}\rho|\mathbf{U}|**2")),
+        ("soundspeed", ("cm/s", ["sound_speed"], "Sound Speed")),
+        ("Machnumber", ("", ["mach_number"], "Mach Number")),
         ("entropy", ("erg/(g*K)", ["entropy"], r"s")),
-        ("magvort", ("1/s", ["vorticity_magnitude"], r"|\nabla \times U|")),
-        ("divu", ("1/s", [], r"\nabla \cdot U")),
+        ("magvort", ("1/s", ["vorticity_magnitude"],
+                     r"|\nabla \times \mathbf{U}|")),
+        ("divu", ("1/s", [], r"\nabla \cdot \mathbf{U}")),
         ("eint_E", ("erg/g", [], r"e(E,U)")),
         ("eint_e", ("erg/g", [], r"e")),
-        ("magvel", ("cm/s", ["velocity_magnitude"], r"|U|")),
-        ("radvel", ("cm/s", [], r"U\cdot e_r")),
-        ("magmom", ("g*cm/s", ["momentum_magnitude"], r"|\rho U|")),
-        ("maggrav", ("cm/s**2", [], r"|g|")),
+        ("magvel", ("cm/s", ["velocity_magnitude"], r"|\mathbf{U}|")),
+        ("radvel", ("cm/s", [],
+                    r"\left(\mathbf{U} \cdot \mathbf{e}\right)_r")),
+        ("magmom", ("g*cm/s", ["momentum_magnitude"], r"|\rho \mathbf{U}|")),
+        ("maggrav", ("cm/s**2", [], r"|\mathbf{g}|")),
         ("phiGrav", ("erg/g", [], r"|\Phi|")),
     )
 
@@ -149,15 +162,12 @@
         for _, field in self.ds.field_list:
             if field.startswith("X("):
                 # We have a fraction
-                nice_name = field[2:-1]
-                self.alias(("gas", "%s_fraction" % nice_name), ("boxlib", field),
-                           units = "")
-                def _create_density_func(field_name):
-                    def _func(field, data):
-                        return data[field_name] * data["gas", "density"]
-                    return _func
+                nice_name, tex_label = _nice_species_name(field)
+                self.alias(("gas", "%s_fraction" % nice_name),
+                           ("boxlib", field),
+                           units="")
                 func = _create_density_func(("gas", "%s_fraction" % nice_name))
-                self.add_field(name = ("gas", "%s_density" % nice_name),
+                self.add_field(name=("gas", "%s_density" % nice_name),
                                function = func,
                                units = "g/cm**3")
                 # We know this will either have one letter, or two.
@@ -168,6 +178,7 @@
                 weight = int(weight)
                 # Here we can, later, add number density.
 
+
 class MaestroFieldInfo(FieldInfoContainer):
 
     known_other_fields = (
@@ -175,32 +186,35 @@
         ("x_vel", ("cm/s", ["velocity_x"], r"\tilde{u}")),
         ("y_vel", ("cm/s", ["velocity_y"], r"\tilde{v}")),
         ("z_vel", ("cm/s", ["velocity_z"], r"\tilde{w}")),
-        ("magvel", ("cm/s", ["velocity_magnitude"], r"|\tilde{U} + w_0 e_r|")),
+        ("magvel", ("cm/s", ["velocity_magnitude"],
+                    r"|\tilde{\mathbf{U}} + w_0 \mathbf{e}_r|")),
         ("radial_velocity", ("cm/s", [], r"U\cdot e_r")),
-        ("tfromp", ("K", [], None)),
-        ("tfromh", ("K", [], None)),
-        ("Machnumber", ("", ["mach_number"], None)),
+        ("tfromp", ("K", [], "T(\\rho,p,X)")),
+        ("tfromh", ("K", [], "T(\\rho,h,X)")),
+        ("Machnumber", ("", ["mach_number"], "Mach Number")),
         ("S", ("1/s", [], None)),
         ("ad_excess", ("", [], "Adiabatic Excess")),
-        ("deltaT", ("", [], None)),
-        ("deltagamma", ("", [], None)),
-        ("deltap", ("", [], None)),
-        ("divw0", ("1/s", [], None)),
+        ("deltaT", ("", [], "[T(\\rho,h,X) - T(\\rho,p,X)]/T(\\rho,h,X)")),
+        ("deltagamma", ("", [], "\Gamma_1 - \overline{\Gamma_1}")),
+        ("deltap", ("", [], "[p(\\rho,h,X) - p_0] / p_0")),
+        ("divw0", ("1/s", [], "\nabla \cdot \mathbf{w}_0")),
         # Specific entropy
-        ("entropy", ("erg/(g*K)", ["entropy"], None)),
-        ("entropypert", ("", [], None)),
-        ("enucdot", ("erg/(g*s)", [], None)),
-        ("gpi_x", ("dyne/cm**3", [], None)), # Perturbational pressure grad
-        ("gpi_y", ("dyne/cm**3", [], None)),
-        ("gpi_z", ("dyne/cm**3", [], None)),
-        ("h", ("erg/g", [], "Specific Enthalpy")),
-        ("h0", ("erg/g", [], "Base State Specific Enthalpy")),
+        ("entropy", ("erg/(g*K)", ["entropy"], "s")),
+        ("entropypert", ("", [], "[s - \overline{s}] / \overline{s}")),
+        ("enucdot", ("erg/(g*s)", [], "\dot{\epsilon_{nuc}}")),
+        ("Hext", ("erg/(g*s)", [], "H_{ext}")),
+        # Perturbational pressure grad
+        ("gpi_x", ("dyne/cm**3", [], "\left(\nabla\pi\right)_x")),
+        ("gpi_y", ("dyne/cm**3", [], "\left(\nabla\pi\right)_y")),
+        ("gpi_z", ("dyne/cm**3", [], "\left(\nabla\pi\right)_z")),
+        ("h", ("erg/g", [], "h")),
+        ("h0", ("erg/g", [], "h_0")),
         # Momentum cannot be computed because we need to include base and
         # full state.
         ("momentum", ("g*cm/s", ["momentum_magnitude"], None)),
         ("p0", ("erg/cm**3", [], "p_0")),
         ("p0pluspi", ("erg/cm**3", [], "p_0 + \pi")),
-        ("pi", ("erg/cm**3", [], None)),
+        ("pi", ("erg/cm**3", [], "\pi")),
         ("pioverp0", ("", [], "\pi/p_0")),
         # Base state density
         ("rho0", ("g/cm**3", [], "\\rho_0")),
@@ -211,39 +225,42 @@
         ("rhopert", ("g/cm**3", [], "\\rho^\prime")),
         ("soundspeed", ("cm/s", ["sound_speed"], None)),
         ("sponge", ("", [], None)),
-        ("tpert", ("K", [], None)),
+        ("tpert", ("K", [], "T - \overline{T}")),
         # Again, base state -- so we can't compute ourselves.
-        ("vort", ("1/s", ["vorticity_magnitude"], None)),
+        ("vort", ("1/s", ["vorticity_magnitude"], "|\nabla\times\tilde{U}|")),
         # Base state
-        ("w0_x", ("cm/s", [], None)),
-        ("w0_y", ("cm/s", [], None)),
-        ("w0_z", ("cm/s", [], None)),
+        ("w0_x", ("cm/s", [], "(w_0)_x")),
+        ("w0_y", ("cm/s", [], "(w_0)_y")),
+        ("w0_z", ("cm/s", [], "(w_0)_z")),
     )
 
     def setup_fluid_fields(self):
         # pick the correct temperature field
         if self.ds.parameters["use_tfromp"]:
             self.alias(("gas", "temperature"), ("boxlib", "tfromp"),
-                       units = "K")
+                       units="K")
         else:
             self.alias(("gas", "temperature"), ("boxlib", "tfromh"),
-                       units = "K")
+                       units="K")
 
         # Add X's and omegadots, units of 1/s
         for _, field in self.ds.field_list:
             if field.startswith("X("):
-                # We have a fraction
-                nice_name = field[2:-1]
-                self.alias(("gas", "%s_fraction" % nice_name), ("boxlib", field),
-                           units = "")
-                def _create_density_func(field_name):
-                    def _func(field, data):
-                        return data[field_name] * data["gas", "density"]
-                    return _func
+                # We have a mass fraction
+                nice_name, tex_label = _nice_species_name(field)
+                # Overwrite field to use nicer tex_label display_name
+                self.add_output_field(("boxlib", field),
+                                      units="",
+                                      display_name=tex_label)
+                self.alias(("gas", "%s_fraction" % nice_name),
+                           ("boxlib", field),
+                           units="")
                 func = _create_density_func(("gas", "%s_fraction" % nice_name))
-                self.add_field(name = ("gas", "%s_density" % nice_name),
-                               function = func,
-                               units = "g/cm**3")
+                self.add_field(name=("gas", "%s_density" % nice_name),
+                               function=func,
+                               units="g/cm**3",
+                               display_name=r'\rho %s' % tex_label)
+
                 # Most of the time our species will be of the form
                 # element name + atomic weight (e.g. C12), but
                 # sometimes we make up descriptive names (e.g. ash)
@@ -256,8 +273,30 @@
                     weight = int(weight)
 
                 # Here we can, later, add number density.
-            if field.startswith("omegadot("):
-                nice_name = field[9:-1]
-                self.add_output_field(("boxlib", field), units = "1/s")
+            elif field.startswith("omegadot("):
+                nice_name, tex_label = _nice_species_name(field)
+                display_name = r'\dot{\omega}\left[%s\right]' % tex_label
+                # Overwrite field to use nicer tex_label'ed display_name
+                self.add_output_field(("boxlib", field), units="1/s",
+                                      display_name=display_name)
                 self.alias(("gas", "%s_creation_rate" % nice_name),
-                           ("boxlib", field), units = "1/s")
+                           ("boxlib", field), units="1/s")
+
+
+def _nice_species_name(field):
+    spec_match = spec_finder.search(field)
+    nice_name = ''.join(spec_match.groups())
+    # if the species field is a descriptive name, then the match
+    # on the integer will be blank
+    # modify the tex string in this case to remove spurious tex spacing
+    lab = r"X\left(^{%s}%s\right)"
+    if spec_match.groups()[-1] == "":
+        lab = r"X\left(%s%s\right)"
+    tex_label = lab % spec_match.groups()[::-1]
+    return nice_name, tex_label
+
+
+def _create_density_func(field_name):
+    def _func(field, data):
+        return data[field_name] * data["gas", "density"]
+    return _func

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/frontends/boxlib/io.py
--- a/yt/frontends/boxlib/io.py
+++ b/yt/frontends/boxlib/io.py
@@ -125,30 +125,36 @@
                     rv[ftype, fname] = np.concatenate((data, rv[ftype, fname]))
         return rv
 
-    def _read_particles(self, grid, field): 
+    def _read_particles(self, grid, field):
         """
         parses the Orion Star Particle text files
 
         """
 
-        fn = self.particle_filename
+        particles = []
+
+        if grid.NumberOfParticles == 0:
+            return np.array(particles)
 
         def read(line, field):
             entry = line.strip().split(' ')[self.particle_field_index[field]]
             return np.float(entry)
 
-        with open(fn, 'r') as f:
-            lines = f.readlines()
-            particles = []
-            for line in lines[1:]:
-                if grid.NumberOfParticles > 0:
-                    coord = read(line, "particle_position_x"), \
-                            read(line, "particle_position_y"), \
-                            read(line, "particle_position_z") 
-                    if ( (grid.LeftEdge <= coord).all() and 
-                         (coord <= grid.RightEdge).all() ):
-                        particles.append(read(line, field))
-        return np.array(particles)
+        try:
+            lines = self._cached_lines
+            for num in grid._particle_line_numbers:
+                line = lines[num]
+                particles.append(read(line, field))
+            return np.array(particles)
+        except AttributeError:
+            fn = self.particle_filename
+            with open(fn, 'r') as f:
+                lines = f.readlines()
+                self._cached_lines = lines
+                for num in grid._particle_line_numbers:
+                    line = lines[num]
+                    particles.append(read(line, field))
+            return np.array(particles)
 
 
 class IOHandlerCastro(IOHandlerBoxlib):

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/frontends/chombo/data_structures.py
--- a/yt/frontends/chombo/data_structures.py
+++ b/yt/frontends/chombo/data_structures.py
@@ -255,18 +255,6 @@
         if D == 2:
             self.dataset_type = 'chombo2d_hdf5'
 
-        # some datasets will not be time-dependent, and to make
-        # matters worse, the simulation time is not always
-        # stored in the same place in the hdf file! Make
-        # sure we handle that here.
-        try:
-            self.current_time = self._handle.attrs['time']
-        except KeyError:
-            try:
-                self.current_time = self._handle['/level_0'].attrs['time']
-            except KeyError:
-                self.current_time = 0.0
-
         self.geometry = "cartesian"
         self.ini_filename = ini_filename
         self.fullplotdir = os.path.abspath(filename)
@@ -315,6 +303,20 @@
 
         self.refine_by = self._handle['/level_0'].attrs['ref_ratio']
         self._determine_periodic()
+        self._determine_current_time()
+
+    def _determine_current_time(self):
+        # some datasets will not be time-dependent, and to make
+        # matters worse, the simulation time is not always
+        # stored in the same place in the hdf file! Make
+        # sure we handle that here.
+        try:
+            self.current_time = self._handle.attrs['time']
+        except KeyError:
+            try:
+                self.current_time = self._handle['/level_0'].attrs['time']
+            except KeyError:
+                self.current_time = 0.0
 
     def _determine_periodic(self):
         # we default to true unless the HDF5 file says otherwise
@@ -498,6 +500,7 @@
             self.domain_right_edge = np.concatenate((self.domain_right_edge, [1.0]))
             self.domain_dimensions = np.concatenate((self.domain_dimensions, [1]))
 
+        self._determine_current_time()
 
     @classmethod
     def _is_valid(self, *args, **kwargs):
@@ -540,14 +543,14 @@
         with open(self.particle_filename, 'r') as f:
             lines = f.readlines()
             self.num_stars = int(lines[0].strip().split(' ')[0])
-            for line in lines[1:]:
+            for num, line in enumerate(lines[1:]):
                 particle_position_x = float(line.split(' ')[1])
                 particle_position_y = float(line.split(' ')[2])
                 particle_position_z = float(line.split(' ')[3])
                 coord = [particle_position_x, particle_position_y, particle_position_z]
                 # for each particle, determine which grids contain it
                 # copied from object_finding_mixin.py
-                mask=np.ones(self.num_grids)
+                mask = np.ones(self.num_grids)
                 for i in xrange(len(coord)):
                     np.choose(np.greater(self.grid_left_edge.d[:,i],coord[i]), (mask,0), mask)
                     np.choose(np.greater(self.grid_right_edge.d[:,i],coord[i]), (0,mask), mask)
@@ -562,6 +565,12 @@
                     self.grid_particle_count[ind] += 1
                     self.grids[ind].NumberOfParticles += 1
 
+                    # store the position in the *.sink file for fast access.
+                    try:
+                        self.grids[ind]._particle_line_numbers.append(num + 1)
+                    except AttributeError:
+                        self.grids[ind]._particle_line_numbers = [num + 1]
+
 
 class Orion2Dataset(ChomboDataset):
 
@@ -595,6 +604,7 @@
         self.domain_dimensions = self._calc_domain_dimensions()
         self.refine_by = self._handle['/level_0'].attrs['ref_ratio']
         self._determine_periodic()
+        self._determine_current_time()
 
     def _parse_inputs_file(self, ini_filename):
         self.fullplotdir = os.path.abspath(self.parameter_filename)

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/frontends/chombo/io.py
--- a/yt/frontends/chombo/io.py
+++ b/yt/frontends/chombo/io.py
@@ -69,11 +69,11 @@
         self._particle_field_index = field_dict
         return self._particle_field_index
 
-    def _read_field_names(self,grid):
+    def _read_field_names(self, grid):
         ncomp = int(self._handle.attrs['num_components'])
         fns = [c[1] for c in f.attrs.items()[-ncomp-1:-1]]
 
-    def _read_data(self,grid,field):
+    def _read_data(self, grid, field):
         lstring = 'level_%i' % grid.Level
         lev = self._handle[lstring]
         dims = grid.ActiveDimensions
@@ -289,20 +289,27 @@
 
         """
 
+        particles = []
+
+        if grid.NumberOfParticles == 0:
+            return np.array(particles)
+
         def read(line, field):
             entry = line.strip().split(' ')[self.particle_field_index[field]]
             return np.float(entry)
 
-        fn = grid.ds.fullplotdir[:-4] + "sink"
-        with open(fn, 'r') as f:
-            lines = f.readlines()
-            particles = []
-            for line in lines[1:]:
-                if grid.NumberOfParticles > 0:
-                    coord = read(line, "particle_position_x"), \
-                        read(line, "particle_position_y"), \
-                        read(line, "particle_position_z")
-                    if ((grid.LeftEdge <= coord).all() and
-                       (coord <= grid.RightEdge).all() ):
-                        particles.append(read(line, field))
-        return np.array(particles)
+        try:
+            lines = self._cached_lines
+            for num in grid._particle_line_numbers:
+                line = lines[num]
+                particles.append(read(line, field))
+            return np.array(particles)
+        except AttributeError:
+            fn = grid.ds.fullplotdir[:-4] + "sink"
+            with open(fn, 'r') as f:
+                lines = f.readlines()
+                self._cached_lines = lines
+                for num in grid._particle_line_numbers:
+                    line = lines[num]
+                    particles.append(read(line, field))
+            return np.array(particles)

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/frontends/gdf/data_structures.py
--- a/yt/frontends/gdf/data_structures.py
+++ b/yt/frontends/gdf/data_structures.py
@@ -26,12 +26,20 @@
     GridIndex
 from yt.data_objects.static_output import \
     Dataset
+from yt.utilities.exceptions import \
+    YTGDFUnknownGeometry
 from yt.utilities.lib.misc_utilities import \
     get_box_grids_level
-from yt.units.yt_array import \
-    uconcatenate, YTArray
+from .fields import GDFFieldInfo
 
-from .fields import GDFFieldInfo
+
+GEOMETRY_TRANS = {
+    0: "cartesian",
+    1: "polar",
+    2: "cylindrical",
+    3: "spherical",
+}
+
 
 class GDFGrid(AMRGridPatch):
     _id_offset = 0
@@ -65,6 +73,7 @@
             self.dds
         self.dds = self.ds.arr(self.dds, "code_length")
 
+
 class GDFHierarchy(GridIndex):
 
     grid = GDFGrid
@@ -145,8 +154,9 @@
         Gets back all the grids between a left edge and right edge
         """
         eps = np.finfo(np.float64).eps
-        grid_i = np.where(np.all((self.grid_right_edge - left_edge) > eps, axis=1) &
-                          np.all((right_edge - self.grid_left_edge) > eps, axis=1))
+        grid_i = np.where(
+            np.all((self.grid_right_edge - left_edge) > eps, axis=1) &
+            np.all((right_edge - self.grid_left_edge) > eps, axis=1))
 
         return self.grids[grid_i], grid_i
 
@@ -162,7 +172,7 @@
     _field_info_class = GDFFieldInfo
 
     def __init__(self, filename, dataset_type='grid_data_format',
-                 storage_filename=None, geometry = 'cartesian'):
+                 storage_filename=None, geometry=None):
         self.geometry = geometry
         self.fluid_types += ("gdf",)
         Dataset.__init__(self, filename, dataset_type)
@@ -198,7 +208,7 @@
                 current_unit = h5f["/dataset_units/%s" % unit_name]
                 value = current_unit.value
                 unit = current_unit.attrs["unit"]
-                setattr(self, unit_name, self.quan(value,unit))
+                setattr(self, unit_name, self.quan(value, unit))
         else:
             self.length_unit = self.quan(1.0, "cm")
             self.mass_unit = self.quan(1.0, "g")
@@ -214,6 +224,12 @@
         else:
             self.data_software = "unknown"
         sp = self._handle["/simulation_parameters"].attrs
+        if self.geometry is None:
+            geometry = just_one(sp.get("geometry", 0))
+            try:
+                self.geometry = GEOMETRY_TRANS[geometry]
+            except KeyError:
+                raise YTGDFUnknownGeometry(geometry)
         self.parameters.update(sp)
         self.domain_left_edge = sp["domain_left_edge"][:]
         self.domain_right_edge = sp["domain_right_edge"][:]

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/utilities/exceptions.py
--- a/yt/utilities/exceptions.py
+++ b/yt/utilities/exceptions.py
@@ -94,7 +94,7 @@
 
     def __str__(self):
         return ("The display name \"%s\" "
-                "of the derived field %s " 
+                "of the derived field %s "
                 "contains the following LaTeX parser errors:\n" ) \
                 % (self.display_name, self.field_name) + self.mathtext_error
 
@@ -106,7 +106,7 @@
 
     def __str__(self):
         return ("The unit display name \"%s\" "
-                "of the derived field %s " 
+                "of the derived field %s "
                 "contains the following LaTeX parser errors:\n" ) \
             % (self.unit_name, self.field_name) + self.mathtext_error
 
@@ -116,7 +116,7 @@
 
     def __str__(self):
         return self.message
-            
+
 class MissingParameter(YTException):
     def __init__(self, ds, parameter):
         YTException.__init__(self, ds=ds)
@@ -425,3 +425,14 @@
 
     def __str__(self):
         return "A file already exists at %s and clobber=False." % self.filename
+
+
+class YTGDFUnknownGeometry(Exception):
+    def __init__(self, geometry):
+        self.geometry = geometry
+
+    def __str__(self):
+        return '''Unknown geometry %i. Please refer to GDF standard
+                  for more information''' % self.geometry
+
+

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -341,7 +341,7 @@
     def __init__(self, ds, axis, fields, center="c", weight_field=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = get_sanitized_center(center, ds)
+        center = ds.coordinates.sanitize_center(center, axis)
         prj = ds.proj(fields[0], axis, weight_field=weight_field, **kwargs)
         w, frb = construct_image(prj, center=center)
         super(FITSProjection, self).__init__(frb, fields=fields, wcs=w)

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/utilities/lib/QuadTree.pyx
--- a/yt/utilities/lib/QuadTree.pyx
+++ b/yt/utilities/lib/QuadTree.pyx
@@ -113,10 +113,10 @@
     cdef np.int64_t last_dims[2]
 
     def __cinit__(self, np.ndarray[np.int64_t, ndim=1] top_grid_dims,
-                  int nvals, bounds, style = "integrate"):
-        if style == "integrate":
+                  int nvals, bounds, method = "integrate"):
+        if method == "integrate":
             self.combine = QTN_add_value
-        elif style == "mip":
+        elif method == "mip":
             self.combine = QTN_max_value
         else:
             raise NotImplementedError
@@ -212,10 +212,10 @@
     def frombuffer(self, np.ndarray[np.int32_t, ndim=1] refined,
                          np.ndarray[np.float64_t, ndim=2] values,
                          np.ndarray[np.float64_t, ndim=1] wval,
-                         style):
-        if style == "mip" or style == -1:
+                         method):
+        if method == "mip" or method == -1:
             self.merged = -1
-        elif style == "integrate" or style == 1:
+        elif method == "integrate" or method == 1:
             self.merged = 1
         cdef int curpos = 0
         cdef QuadTreeNode *root
@@ -348,11 +348,11 @@
     @cython.boundscheck(False)
     @cython.wraparound(False)
     @cython.cdivision(True)
-    def get_all(self, int count_only = 0, int style = 1):
+    def get_all(self, int count_only = 0, int method = 1):
         cdef int i, j, vi
         cdef int total = 0
         vals = []
-        self.merged = style
+        self.merged = method
         for i in range(self.top_grid_dims[0]):
             for j in range(self.top_grid_dims[1]):
                 total += self.count(self.root_nodes[i][j])
@@ -540,14 +540,14 @@
     else:
         raise RuntimeError
 
-def merge_quadtrees(QuadTree qt1, QuadTree qt2, style = 1):
+def merge_quadtrees(QuadTree qt1, QuadTree qt2, method = 1):
     cdef int i, j
     qt1.num_cells = 0
     cdef QTN_combine *func
-    if style == 1:
+    if method == 1:
         qt1.merged = 1
         func = QTN_add_value
-    elif style == -1:
+    elif method == -1:
         qt1.merged = -1
         func = QTN_max_value
     else:

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/visualization/base_plot_types.py
--- a/yt/visualization/base_plot_types.py
+++ b/yt/visualization/base_plot_types.py
@@ -121,8 +121,8 @@
                 bmap = brewer2mpl.get_map(*cmap)
                 cmap = bmap.get_mpl_colormap(N=cmap[2])
             else:
-                raise RuntimeError("Please install brewer2mpl to use colorbrewer colormaps")
-
+                raise RuntimeError(
+                    "Please install brewer2mpl to use colorbrewer colormaps")
         self.image = self.axes.imshow(data.to_ndarray(), origin='lower',
                                       extent=extent, norm=norm, vmin=self.zmin,
                                       aspect=aspect, vmax=self.zmax, cmap=cmap)

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/visualization/eps_writer.py
--- a/yt/visualization/eps_writer.py
+++ b/yt/visualization/eps_writer.py
@@ -262,7 +262,7 @@
 
         Parameters
         ----------
-        plot : `yt.visualization.plot_types.RavenPlot`
+        plot : `yt.visalization.plot_window.PlotWindow`
             yt plot on which the axes are based.
         units : string
             Unit description that overrides yt's unit description.  Only
@@ -422,7 +422,7 @@
 
         Parameters
         ----------
-        plot : `yt.visualization.plot_types.VMPlot`
+        plot : `yt.visalization.plot_window.PlotWindow`
             yt plot that provides the image
         pos : tuple of floats
             Position of the origin of the image in centimeters.
@@ -446,13 +446,6 @@
         shift = 0.0
         if self.canvas is None:
             self.canvas = pyx.canvas.canvas()
-        if isinstance(plot, VMPlot):
-            if plot.colorbar != None:
-                mylog.warning("Image (slices, projections, etc.) plots must not"\
-                              "have a colorbar.  Removing it.")
-                plot.colorbar = None
-            plot._redraw_image()
-            _p1 = plot._figure
         elif isinstance(plot, (PlotWindow, PhasePlot)):
             self.field = field
             if self.field == None:
@@ -627,7 +620,7 @@
 
         Parameters
         ----------
-        plot : `yt.visualization.plot_types.VMPlot`
+        plot : A yt plot
             yt plot from which the information is taken.
 
         Examples
@@ -649,14 +642,7 @@
                 _cmap = plot.cmap.name
         if _cmap == None:
             _cmap = 'algae'
-        if isinstance(plot, VMPlot):
-            proj = "Proj" in plot._type_name and \
-                plot.data._weight is None
-            _zlabel = plot.ds.field_info[plot.axis_names["Z"]].get_label(proj)
-            _zlabel = _zlabel.replace("_","\;")
-            _zlog = plot.log_field
-            _zrange = (plot.norm.vmin, plot.norm.vmax)
-        elif isinstance(plot, (PlotWindow, PhasePlot)):
+        if isinstance(plot, (PlotWindow, PhasePlot)):
             proj = plot._plot_type.endswith("Projection") and \
                 plot.data_source.weight_field == None
             if isinstance(plot, PlotWindow):
@@ -905,7 +891,7 @@
               shrink_cb=0.95, figsize=(8,8), margins=(0,0), titles=None,
               savefig=None, format="eps", yt_nocbar=False, bare_axes=False,
               xaxis_flags=None, yaxis_flags=None,
-              cb_flags=None, cb_location=None, plot_collection=False):
+              cb_flags=None, cb_location=None):
     r"""Convenience routine to create a multi-panel figure from yt plots or
     JPEGs.  The images are first placed from the origin, and then
     bottom-to-top and left-to-right.
@@ -916,7 +902,7 @@
         Number of columns in the figure.
     nrow : integer
         Number of rows in the figure.
-    yt_plots : list of `yt.visualization.plot_types.VMPlot`
+    yt_plots : list of yt plot instances
         yt plots to include in the figure.
     images : list of strings
         JPEG filenames to include in the figure.
@@ -953,8 +939,6 @@
     cb_location : list of strings
         Strings to control the location of the colorbar (left, right, 
         top, bottom)
-    plot_collection : boolean
-        Set to true to yt_plots is a PlotCollection
 
     Examples
     --------
@@ -1153,7 +1137,7 @@
 #=============================================================================
 
 def multiplot_yt(ncol, nrow, plots, fields=None, **kwargs):
-    r"""Wrapper for multiplot that takes a yt PlotWindow or PlotCollection.
+    r"""Wrapper for multiplot that takes a yt PlotWindow
 
     Accepts all parameters used in multiplot.
 
@@ -1163,8 +1147,8 @@
         Number of columns in the figure.
     nrow : integer
         Number of rows in the figure.
-    plots : `PlotCollection` or `PlotWindow`
-        yt PlotCollection or PlotWindow that has the plots to be used.
+    plots : `yt.visualization.plot_window.PlotWindow`
+        yt PlotWindow that has the plots to be used.
 
     Examples
     --------
@@ -1183,17 +1167,7 @@
     >>> mp = multiplot_yt(2,2,pc,savefig="yt",shrink_cb=0.9, bare_axes=False,
     >>>                   yt_nocbar=False, margins=(0.5,0.5))
     """
-    # Determine whether the plots are organized in a PlotCollection,
-    # PlotWindow, or list of PlotWindows
-    if isinstance(plots, PlotCollection):
-        if len(plots.plots) < nrow*ncol:
-            raise RuntimeError("Number of plots in PlotCollection is less "\
-                               "than nrow(%d) x ncol(%d)." % \
-                               (len(plots.plots), nrow, ncol))
-            return
-        figure = multiplot(ncol, nrow, yt_plots=plots.plots, 
-                           plot_collection=True, **kwargs)
-    elif isinstance(plots, PlotWindow):
+    if isinstance(plots, PlotWindow):
         if fields == None:
             fields = plots.fields
         if len(fields) < nrow*ncol:
@@ -1224,7 +1198,7 @@
 
     Parameters
     ----------
-    plot : `yt.visualization.plot_types.VMPlot`
+    plot : `yt.visalization.plot_window.PlotWindow`
         yt plot that provides the image and metadata
     figsize : tuple of floats
         Size of the figure in centimeters.

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -697,8 +697,8 @@
             # This will likely be replaced at some point by the coordinate handler
             # setting plot aspect.
             if self.aspect is None:
-                self.aspect = np.float64(self.ds.quan(1.0, unit_y) /
-                                         self.ds.quan(1.0, unit_x))
+                self.aspect = np.float64((self.ds.quan(1.0, unit_y) /
+                                         self.ds.quan(1.0, unit_x)).in_cgs())
 
             extentx = [(self.xlim[i] - xc).in_units(unit_x) for i in (0, 1)]
             extenty = [(self.ylim[i] - yc).in_units(unit_y) for i in (0, 1)]
@@ -1109,6 +1109,27 @@
          The maximum level to project to.
     fontsize : integer
          The size of the fonts for the axis, colorbar, and tick labels.
+    method : string
+         The method of projection.  Valid methods are: 
+
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "mip" : pick out the maximum value of the field in the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not 
+         multiply by a path length when performing the integration, and is 
+         just a straight summation of the field along the given axis. 
+    proj_style : string
+         The method of projection--same as method keyword.  Deprecated as of 
+         version 3.0.2.  Please use method instead.
+    window_size : float
+         The size of the window in inches. Set to 8 by default.
+    aspect : float
+         The aspect ratio of the plot.  Set to None for 1.
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
@@ -1130,20 +1151,25 @@
     def __init__(self, ds, axis, fields, center='c', width=None, axes_unit=None,
                  weight_field=None, max_level=None, origin='center-window',
                  fontsize=18, field_parameters=None, data_source=None,
-                 proj_style = "integrate", window_size=8.0, aspect=None):
+                 method = "integrate", proj_style = None, window_size=8.0, 
+                 aspect=None):
         ts = self._initialize_dataset(ds)
         self.ts = ts
         ds = self.ds = ts[0]
         axis = fix_axis(axis, ds)
+        # proj_style is deprecated, but if someone specifies then it trumps 
+        # method.
+        if proj_style is not None:
+            method = proj_style
         # If a non-weighted integral projection, assure field-label reflects that
-        if weight_field is None and proj_style == "integrate":
+        if weight_field is None and method == "integrate":
             self.projected = True
         (bounds, center, display_center) = \
                 get_window_parameters(axis, center, width, ds)
         if field_parameters is None: field_parameters = {}
         proj = ds.proj(fields, axis, weight_field=weight_field,
                        center=center, data_source=data_source,
-                       field_parameters = field_parameters, style = proj_style)
+                       field_parameters = field_parameters, method = method)
         PWViewerMPL.__init__(self, proj, bounds, fields=fields, origin=origin,
                              fontsize=fontsize, window_size=window_size, 
                              aspect=aspect)
@@ -1236,7 +1262,7 @@
 
 class OffAxisProjectionDummyDataSource(object):
     _type_name = 'proj'
-    proj_style = 'integrate'
+    method = 'integrate'
     _key_fields = []
     def __init__(self, center, ds, normal_vector, width, fields,
                  interpolated, resolution = (800,800), weight=None,
@@ -1348,7 +1374,7 @@
             weight=weight_field,  volume=volume, no_ghost=no_ghost,
             le=le, re=re, north_vector=north_vector)
         # If a non-weighted, integral projection, assure field-label reflects that
-        if weight_field is None and OffAxisProj.proj_style == "integrate":
+        if weight_field is None and OffAxisProj.method == "integrate":
             self.projected = True
         # Hard-coding the origin keyword since the other two options
         # aren't well-defined for off-axis data objects
@@ -1542,7 +1568,7 @@
         if source._type_name in ("slice", "cutting"):
             units = finfo.get_units()
         elif source._type_name == "proj":
-            if source.weight_field is not None or source.proj_style in ("mip", "sum"):
+            if source.weight_field is not None or source.method in ("mip", "sum"):
                 units = finfo.get_units()
             else:
                 units = finfo.get_projected_units()
@@ -1635,7 +1661,7 @@
         self._cb_size = 0.0375*fsize
         self._ax_text_size = [1.2*fontscale, 0.9*fontscale]
         self._top_buff_size = 0.30*fontscale
-        self._aspect = ((extent[1] - extent[0])/(extent[3] - extent[2]))
+        self._aspect = ((extent[1] - extent[0])/(extent[3] - extent[2])).in_cgs()
 
         size, axrect, caxrect = self._get_best_layout()
 

diff -r 24665f82c66eb316e3d4af2c9938b0b522aabc94 -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 yt/visualization/tests/test_plotwindow.py
--- a/yt/visualization/tests/test_plotwindow.py
+++ b/yt/visualization/tests/test_plotwindow.py
@@ -146,6 +146,12 @@
     ('gas', 'density'),
 )
 
+PROJECTION_METHODS = (
+    'integrate',
+    'sum',
+    'mip'
+)
+
 @requires_ds(M7)
 def test_attributes():
     """Test plot member functions that aren't callbacks"""
@@ -240,6 +246,7 @@
         projections_c = []
         projections_wf = []
         projections_w = {}
+        projections_m = []
         for dim in range(3):
             projections.append(ProjectionPlot(test_ds, dim, "density"))
             projections_ds.append(ProjectionPlot(test_ds, dim, "density",
@@ -253,6 +260,9 @@
         for wf in WEIGHT_FIELDS:
             projections_wf.append(ProjectionPlot(test_ds, dim, "density",
                                                  weight_field=wf))
+        for m in PROJECTION_METHODS:
+            projections_m.append(ProjectionPlot(test_ds, dim, "density",
+                                                 method=m))
 
         cls.slices = [SlicePlot(test_ds, dim, "density") for dim in range(3)]
         cls.projections = projections
@@ -260,6 +270,7 @@
         cls.projections_c = projections_c
         cls.projections_wf = projections_wf
         cls.projections_w = projections_w
+        cls.projections_m = projections_m
         cls.offaxis_slice = OffAxisSlicePlot(test_ds, normal, "density")
         cls.offaxis_proj = OffAxisProjectionPlot(test_ds, normal, "density")
 
@@ -296,6 +307,10 @@
     def test_projection_plot_wf(self, dim):
         self.projections_wf[dim].save()
 
+    @parameterized.expand([(i, ) for i in range(len(PROJECTION_METHODS))])
+    def test_projection_plot_m(self, dim):
+        self.projections_m[dim].save()
+
     @parameterized.expand(
         param.explicit((fname, ))
         for fname in TEST_FLNMS)


https://bitbucket.org/yt_analysis/yt/commits/503b3b3f28e5/
Changeset:   503b3b3f28e5
Branch:      yt
User:        jzuhone
Date:        2014-10-07 19:12:47+00:00
Summary:     Fixing thermal broadening and cleaning this up a bit
Affected #:  1 file

diff -r 7181ddfcb573e2c12e0655e94e981ae7651ab4f8 -r 503b3b3f28e5828703ebf4f8011ac509afeda39b yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -27,15 +27,15 @@
         return -vz
     return _v_los
 
-fits_info = {"velocity":("m/s","VELOCITY"),
-             "frequency":("Hz","FREQUENCY"),
-             "energy":("eV","ENERGY"),
-             "wavelength":("angstrom","WAVELENG")}
+fits_info = {"velocity":("m/s","VELOCITY","v"),
+             "frequency":("Hz","FREQUENCY","f"),
+             "energy":("eV","ENERGY","E"),
+             "wavelength":("angstrom","WAVELENG","lambda")}
 
 class PPVCube(object):
     def __init__(self, ds, normal, field, width=(1.0,"unitary"),
                  dims=(100,100,100), velocity_bounds=None, rest_value=None,
-                 ion_weight=None):
+                 thermal_broad=False, particle_weight=56.):
         r""" Initialize a PPVCube object.
 
         Parameters
@@ -60,9 +60,9 @@
             axis of the PPV cube. The spectral axis will be converted to the units and
             displaced by the value. Can be in units of energy, wavelength, or frequency.
             If not set the default is to leave the spectral axis in velocity units.
-        ion_weight : float, optional
-            Set this value to the atomic weight of the ion that is emitting the line
-            in order to include thermal broadening.
+        particle_weight : float, optional
+            Set this value to the atomic weight of the particle that is emitting the line
+            if *thermal_broad* is True.
 
         Examples
         --------
@@ -75,6 +75,8 @@
         self.field = field
         self.width = width
         self.rest_value = rest_value
+        self.particle_mass = particle_weight*mh
+        self.thermal_broad = thermal_broad
 
         self.nx = dims[0]
         self.ny = dims[1]
@@ -105,16 +107,16 @@
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
         self.dv = self.vbins[1]-self.vbins[0]
 
-        if ion_weight is None:
-            self.ion_mass = mh
-            self.phi_th = lambda v, v_th: 1.0
-        else:
-            self.ion_mass = ion_weight*mh
-            self.phi_th = lambda v, v_th: self.dv/(np.sqrt(np.pi)*v_th)*np.exp(-(v/v_th)**2)
-
         _vlos = create_vlos(orient.unit_vectors[2])
         self.ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
 
+        if thermal_broad:
+            self.v_th = lambda T: np.sqrt(2.*kboltz*T/self.particle_mass)
+            self.phi_th = lambda v, T: self.dv*np.exp(-(v/self.v_th(T))**2)/(np.sqrt(np.pi)*self.v_th(T))
+        else:
+            self.v_th = lambda T: 1.0
+            self.phi_th = lambda v, T: np.maximum(1.-np.abs(v-self.vmid[i])/self.dv.in_units(v.units),0.0)
+
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
         pbar = get_pbar("Generating cube.", self.nv)
         for i in xrange(self.nv):
@@ -133,7 +135,18 @@
             self.rest_value = self.ds.quan(rest_value[0], rest_value[1])
             self.vbins = self.rest_value*(1.-self.vbins.in_cgs()/clight)
             self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-            self.dv = self.v_bins[1]-self.v_bins[0]
+            self.dv = self.vbins[1]-self.vbins[0]
+
+        dims = self.dv.units.dimensions
+
+        if dims == ytdims.rate:
+            self.axis_type = "frequency"
+        elif dims == ytdims.length:
+            self.axis_type = "wavelength"
+        elif dims == ytdims.energy:
+            self.axis_type = "energy"
+        elif dims == ytdims.velocity:
+            self.axis_type = "velocity"
 
     def write_fits(self, filename, clobber=True, length_unit=(10.0, "kpc"),
                    sky_center=(30.,45.)):
@@ -163,21 +176,10 @@
             center = [0.0,0.0]
             types = ["LINEAR","LINEAR"]
 
-        dims = self.dv.units.dimensions
+        vunit = fits_info[self.axis_type][0]
+        vtype = fits_info[self.axis_type][1]
 
-        if dims == ytdims.rate:
-            axis_type = "frequency"
-        elif dims == ytdims.length:
-            axis_type = "wavelength"
-        elif dims == ytdims.energy:
-            axis_type = "energy"
-        elif dims == ytdims.velocity:
-            axis_type = "velocity"
-
-        vunit = fits_info[axis_type]
-        vtype = fits_info[axis_type]
-
-        v_center = 0.5*(self.v_bins[0]+self.v_bins[-1]).in_units(vunit).value
+        v_center = 0.5*(self.vbins[0]+self.vbins[-1]).in_units(vunit).value
 
         dx = length_unit[0]/self.nx
         dy = length_unit[0]/self.ny
@@ -201,10 +203,15 @@
 
     def _create_intensity(self, i):
         def _intensity(field, data):
-            vlos = data["v_los"]
-            v_th = np.sqrt(2*kboltz*data["temperature"]/self.ion_mass)
-            w = np.abs(vlos-self.vmid[i])/self.dv.in_units(vlos.units)
-            w = 1.-w
-            w[w < 0.0] = 0.0
-            return data[self.field]*self.phi_th(vlos, v_th)*w
+            w = self.phi_th(self.vmid[i].in_cgs()-data["v_los"], data["temperature"])
+            return data[self.field]*w
         return _intensity
+
+    def __repr__(self):
+        return "PPVCube [%d %d %d] (%s < %s < %s)" % (self.nx, self.ny, self.nv,
+                                                      self.vbins[0],
+                                                      fits_info[self.axis_type][2],
+                                                      self.vbins[-1])
+
+    def __getitem__(self, item):
+        return self.data[item]


https://bitbucket.org/yt_analysis/yt/commits/00b133a4ce33/
Changeset:   00b133a4ce33
Branch:      yt
User:        jzuhone
Date:        2014-10-07 19:36:58+00:00
Summary:     Adding special functions to the on-demand scipy import
Affected #:  1 file

diff -r 503b3b3f28e5828703ebf4f8011ac509afeda39b -r 00b133a4ce333c56525bd24904fc1dba42d36475 yt/utilities/on_demand_imports.py
--- a/yt/utilities/on_demand_imports.py
+++ b/yt/utilities/on_demand_imports.py
@@ -132,4 +132,15 @@
             self._interpolate = interpolate
         return self._interpolate
 
+    _special = None
+    @property
+    def special(self):
+        if self._special is None:
+            try:
+                import scipy.special as special
+            except ImportError:
+                special = NotAModule(self._name)
+            self._special = special
+        return self._special
+
 _scipy = scipy_imports()
\ No newline at end of file


https://bitbucket.org/yt_analysis/yt/commits/ad1fa6a03255/
Changeset:   ad1fa6a03255
Branch:      yt
User:        jzuhone
Date:        2014-10-07 19:37:35+00:00
Summary:     Bug fix, simplifying things a bit
Affected #:  1 file

diff -r 00b133a4ce333c56525bd24904fc1dba42d36475 -r ad1fa6a03255a214fa50991222232844612427d4 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -62,7 +62,7 @@
             If not set the default is to leave the spectral axis in velocity units.
         particle_weight : float, optional
             Set this value to the atomic weight of the particle that is emitting the line
-            if *thermal_broad* is True.
+            if *thermal_broad* is True. Defaults to 56 (Fe).
 
         Examples
         --------
@@ -71,6 +71,7 @@
         >>> cube = PPVCube(ds, L, "density", width=(10.,"kpc"),
         ...                velocity_bounds=(-5.,4.,"km/s"))
         """
+
         self.ds = ds
         self.field = field
         self.width = width
@@ -115,7 +116,7 @@
             self.phi_th = lambda v, T: self.dv*np.exp(-(v/self.v_th(T))**2)/(np.sqrt(np.pi)*self.v_th(T))
         else:
             self.v_th = lambda T: 1.0
-            self.phi_th = lambda v, T: np.maximum(1.-np.abs(v-self.vmid[i])/self.dv.in_units(v.units),0.0)
+            self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv.in_units(v.units),0.0)
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
         pbar = get_pbar("Generating cube.", self.nv)
@@ -203,7 +204,7 @@
 
     def _create_intensity(self, i):
         def _intensity(field, data):
-            w = self.phi_th(self.vmid[i].in_cgs()-data["v_los"], data["temperature"])
+            w = self.phi_th((self.vmid[i]-data["v_los"]).in_cgs(), data["temperature"])
             return data[self.field]*w
         return _intensity
 


https://bitbucket.org/yt_analysis/yt/commits/544aa453d825/
Changeset:   544aa453d825
Branch:      yt
User:        jzuhone
Date:        2014-10-08 02:00:55+00:00
Summary:     Making some improvements to the writing of FITS files.
Affected #:  1 file

diff -r ad1fa6a03255a214fa50991222232844612427d4 -r 544aa453d82535e53324331d869239df057b80cb yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -18,6 +18,8 @@
 from yt.funcs import get_pbar
 from yt.utilities.physical_constants import clight, mh, kboltz
 import yt.units.dimensions as ytdims
+from yt.units.yt_array import YTQuantity
+from yt.funcs import iterable
 
 def create_vlos(z_hat):
     def _v_los(field, data):
@@ -55,8 +57,8 @@
             A 3-tuple of (vmin, vmax, units) for the velocity bounds to
             integrate over. If None, the largest velocity of the
             dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())
-        rest_value : tuple, optional
-            A (value, unit) tuple indicating the rest value of the spectral
+        rest_value : tuple or YTQuantity, optional
+            A (value, unit) tuple or YTQuantity indicating the rest value of the spectral
             axis of the PPV cube. The spectral axis will be converted to the units and
             displaced by the value. Can be in units of energy, wavelength, or frequency.
             If not set the default is to leave the spectral axis in velocity units.
@@ -75,7 +77,10 @@
         self.ds = ds
         self.field = field
         self.width = width
-        self.rest_value = rest_value
+        if isinstance(rest_value, YTQuantity):
+            self.rest_value = rest_value
+        else:
+            self.rest_value = ds.quan(rest_value[0], rest_value[1])
         self.particle_mass = particle_weight*mh
         self.thermal_broad = thermal_broad
 
@@ -128,12 +133,10 @@
             self.data[:,:,i] = prj[:,:]
             ds.field_info.pop(("gas","intensity"))
             pbar.update(i)
-
         pbar.finish()
 
         if self.rest_value is not None:
             # If we want units other than velocity, we re-calculate these quantities
-            self.rest_value = self.ds.quan(rest_value[0], rest_value[1])
             self.vbins = self.rest_value*(1.-self.vbins.in_cgs()/clight)
             self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
             self.dv = self.vbins[1]-self.vbins[0]
@@ -149,7 +152,13 @@
         elif dims == ytdims.velocity:
             self.axis_type = "velocity"
 
-    def write_fits(self, filename, clobber=True, length_unit=(10.0, "kpc"),
+        # Now fix the width
+        if iterable(self.width):
+            self.width = ds.quan(self.width[0], self.width[1])
+        else:
+            self.width = ds.quan(self.width, "code_length")
+
+    def write_fits(self, filename, clobber=True, sky_scale=None,
                    sky_center=(30.,45.)):
         r""" Write the PPVCube to a FITS file.
 
@@ -159,41 +168,52 @@
             The name of the file to write.
         clobber : boolean
             Whether or not to clobber an existing file with the same name.
-        length_unit : tuple, optional
-            The length that corresponds to the width of the projection in
-            (value, unit) form. Accepts a length unit or 'deg'.
+        sky_scale : tuple or YTQuantity
+            Conversion between an angle unit and a length unit, if sky
+            coordinates are desired.
+            Examples: (1.0, "arcsec/kpc"), YTQuantity(0.001, "deg/kpc")
         sky_center : tuple, optional
             The (RA, Dec) coordinate in degrees of the central pixel if
-            *length_unit* is 'deg'.
+            *sky_scale* has been specified.
 
         Examples
         --------
-        >>> cube.write_fits("my_cube.fits", clobber=False, length_unit=(5,"deg"))
+        >>> cube.write_fits("my_cube.fits", clobber=False, sky_scale=(1.0,"arcsec/kpc"))
         """
-        if length_unit[1] == "deg":
-            center = sky_center
-            types = ["RA---SIN","DEC--SIN"]
+        if sky_scale is None:
+            center = (0.0,0.0)
+            types = ["LINEAR","LINEAR"]
         else:
-            center = [0.0,0.0]
-            types = ["LINEAR","LINEAR"]
+            if iterable(sky_scale):
+                sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
+            if sky_center is None:
+                center = (30.,45.)
+            else:
+                center = sky_center
+            types = ["RA---TAN","DEC--TAN"]
 
         vunit = fits_info[self.axis_type][0]
         vtype = fits_info[self.axis_type][1]
 
         v_center = 0.5*(self.vbins[0]+self.vbins[-1]).in_units(vunit).value
 
-        dx = length_unit[0]/self.nx
-        dy = length_unit[0]/self.ny
-        dv = self.dv.in_units(vunit).value
+        if sky_scale:
+            dx = (self.width*sky_scale).in_units("deg")/self.nx
+            units = "deg"
+        else:
+            dx = self.width/self.nx
+            units = str(self.width.units)
+        dy = dx
+        dv = self.dv.in_units(vunit)
 
-        if length_unit[1] == "deg":
+        if sky_scale:
             dx *= -1.
 
         w = _astropy.pywcs.WCS(naxis=3)
         w.wcs.crpix = [0.5*(self.nx+1), 0.5*(self.ny+1), 0.5*(self.nv+1)]
-        w.wcs.cdelt = [dx,dy,dv]
-        w.wcs.crval = [center[0], center[1], v_center]
-        w.wcs.cunit = [length_unit[1],length_unit[1],vunit]
+        w.wcs.cdelt = [dx.v,dy.v,dv.v]
+        w.wcs.crval = [center[0],center[1],v_center]
+        w.wcs.cunit = [units,units,vunit]
         w.wcs.ctype = [types[0],types[1],vtype]
 
         fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)


https://bitbucket.org/yt_analysis/yt/commits/54366a025b64/
Changeset:   54366a025b64
Branch:      yt
User:        jzuhone
Date:        2014-10-08 02:07:45+00:00
Summary:     Applying the lessons learned from PPVCube here.
Affected #:  2 files

diff -r 544aa453d82535e53324331d869239df057b80cb -r 54366a025b64acf856e3caac3a923940ee05796f yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -159,7 +159,7 @@
             self.width = ds.quan(self.width, "code_length")
 
     def write_fits(self, filename, clobber=True, sky_scale=None,
-                   sky_center=(30.,45.)):
+                   sky_center=None):
         r""" Write the PPVCube to a FITS file.
 
         Parameters

diff -r 544aa453d82535e53324331d869239df057b80cb -r 54366a025b64acf856e3caac3a923940ee05796f yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -279,8 +279,7 @@
         self.data["TeSZ"] = self.ds.arr(Te, "keV")
 
     @parallel_root_only
-    def write_fits(self, filename, units="kpc", sky_center=None, sky_scale=None,
-                   time_units="Gyr", clobber=True):
+    def write_fits(self, filename, sky_scale=None, sky_center=None, clobber=True):
         r""" Export images to a FITS file. Writes the SZ distortion in all
         specified frequencies as well as the mass-weighted temperature and the
         optical depth. Distance units are in kpc, unless *sky_center*
@@ -290,12 +289,13 @@
         ----------
         filename : string
             The name of the FITS file to be written. 
-        sky_center : tuple of floats, optional
-            The center of the observation in (RA, Dec) in degrees. Only used if
-            converting to sky coordinates.          
-        sky_scale : float, optional
-            Scale between degrees and kpc. Only used if
-            converting to sky coordinates.
+        sky_scale : tuple or YTQuantity
+            Conversion between an angle unit and a length unit, if sky
+            coordinates are desired.
+            Examples: (1.0, "arcsec/kpc"), YTQuantity(0.001, "deg/kpc")
+        sky_center : tuple, optional
+            The (RA, Dec) coordinate in degrees of the central pixel if
+            *sky_scale* has been specified.
         clobber : boolean, optional
             If the file already exists, do we overwrite?
 
@@ -309,21 +309,30 @@
         >>> szprj.write_fits("SZbullet.fits", sky_center=sky_center, sky_scale=sky_scale)
         """
 
-        deltas = np.array([self.dx.in_units(units),
-                           self.dy.in_units(units)])
+        if sky_scale is None:
+            center = (0.0,0.0)
+        else:
+            if iterable(sky_scale):
+                sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
+            if sky_center is None:
+                center = (30.,45.)
+            else:
+                center = sky_center
 
-        if sky_center is None:
-            center = [0.0]*2
-        else:
-            center = sky_center
+        units = self.ds.get_smallest_appropriate_unit(self.width)
+        # Hack because FITS is stupid and doesn't understand case
+        if units == "Mpc": units = "kpc"
+        dx = self.dx.in_units(units)
+        if sky_scale:
+            dx = (dx*sky_scale).in_units("deg")
             units = "deg"
-            deltas *= sky_scale
-            deltas[0] *= -1.
+        dy = dx
+        if sky_scale:
+            dx *= -1.
 
         from yt.utilities.fits_image import FITSImageBuffer
         fib = FITSImageBuffer(self.data, fields=self.data.keys(),
-                              center=center, units=units,
-                              scale=deltas)
+                              center=center, units=units, scale=[dx.v,dy.v])
         fib.update_all_headers("Time", float(self.ds.current_time.in_units(time_units).value))
         fib.writeto(filename, clobber=clobber)
         


https://bitbucket.org/yt_analysis/yt/commits/ca5e04b25ae1/
Changeset:   ca5e04b25ae1
Branch:      yt
User:        jzuhone
Date:        2014-10-08 16:02:42+00:00
Summary:     Only want the root process to write the FITS file
Affected #:  1 file

diff -r 54366a025b64acf856e3caac3a923940ee05796f -r ca5e04b25ae1a1e86452d0c363c91a84d76ebbf1 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -20,6 +20,8 @@
 import yt.units.dimensions as ytdims
 from yt.units.yt_array import YTQuantity
 from yt.funcs import iterable
+from yt.utilities.parallel_tools.parallel_analysis_interface import \
+    parallel_root_only
 
 def create_vlos(z_hat):
     def _v_los(field, data):
@@ -158,6 +160,7 @@
         else:
             self.width = ds.quan(self.width, "code_length")
 
+    @parallel_root_only
     def write_fits(self, filename, clobber=True, sky_scale=None,
                    sky_center=None):
         r""" Write the PPVCube to a FITS file.


https://bitbucket.org/yt_analysis/yt/commits/8e6040366a3e/
Changeset:   8e6040366a3e
Branch:      yt
User:        jzuhone
Date:        2014-10-08 20:41:29+00:00
Summary:     Adding a center keyword, fixing units
Affected #:  1 file

diff -r ca5e04b25ae1a1e86452d0c363c91a84d76ebbf1 -r 8e6040366a3e417b02899ddcefd92cc047515c75 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -37,7 +37,7 @@
              "wavelength":("angstrom","WAVELENG","lambda")}
 
 class PPVCube(object):
-    def __init__(self, ds, normal, field, width=(1.0,"unitary"),
+    def __init__(self, ds, normal, field, center="c", width=(1.0,"unitary"),
                  dims=(100,100,100), velocity_bounds=None, rest_value=None,
                  thermal_broad=False, particle_weight=56.):
         r""" Initialize a PPVCube object.
@@ -50,6 +50,8 @@
             The normal vector along with to make the projections.
         field : string
             The field to project.
+        center : float, tuple, or string
+
         width : float or tuple, optional
             The width of the projection in length units. Specify a float
             for code_length units or a tuple (value, units).
@@ -86,6 +88,8 @@
         self.particle_mass = particle_weight*mh
         self.thermal_broad = thermal_broad
 
+        self.center = ds.coordinates.sanitize_center(center, normal)
+
         self.nx = dims[0]
         self.ny = dims[1]
         self.nv = dims[2]
@@ -130,7 +134,7 @@
         for i in xrange(self.nv):
             _intensity = self._create_intensity(i)
             ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
-            prj = off_axis_projection(ds, ds.domain_center, normal, width,
+            prj = off_axis_projection(ds, self.center, normal, width,
                                       (self.nx, self.ny), "intensity")
             self.data[:,:,i] = prj[:,:]
             ds.field_info.pop(("gas","intensity"))
@@ -206,6 +210,8 @@
         else:
             dx = self.width/self.nx
             units = str(self.width.units)
+        # Hack because FITS is stupid and doesn't understand case
+        if units == "Mpc": units = "kpc"
         dy = dx
         dv = self.dv.in_units(vunit)
 


https://bitbucket.org/yt_analysis/yt/commits/7b3da18a262a/
Changeset:   7b3da18a262a
Branch:      yt
User:        jzuhone
Date:        2014-10-08 20:45:49+00:00
Summary:     Fix
Affected #:  1 file

diff -r 8e6040366a3e417b02899ddcefd92cc047515c75 -r 7b3da18a262a2b973b55972beabf8c087d1ae8bd yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -81,7 +81,7 @@
         self.ds = ds
         self.field = field
         self.width = width
-        if isinstance(rest_value, YTQuantity):
+        if isinstance(rest_value, YTQuantity) or rest_value is None:
             self.rest_value = rest_value
         else:
             self.rest_value = ds.quan(rest_value[0], rest_value[1])


https://bitbucket.org/yt_analysis/yt/commits/56e41015df45/
Changeset:   56e41015df45
Branch:      yt
User:        jzuhone
Date:        2014-10-08 21:03:00+00:00
Summary:     Bugfix
Affected #:  1 file

diff -r 7b3da18a262a2b973b55972beabf8c087d1ae8bd -r 56e41015df452bd77d28204c5ce75c8f41ba2afa yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -88,7 +88,7 @@
         self.particle_mass = particle_weight*mh
         self.thermal_broad = thermal_broad
 
-        self.center = ds.coordinates.sanitize_center(center, normal)
+        self.center = ds.coordinates.sanitize_center(center, normal)[0]
 
         self.nx = dims[0]
         self.ny = dims[1]


https://bitbucket.org/yt_analysis/yt/commits/9c9767f99106/
Changeset:   9c9767f99106
Branch:      yt
User:        jzuhone
Date:        2014-10-09 13:51:43+00:00
Summary:     Use ds.proj for on-axis projections
Affected #:  1 file

diff -r 56e41015df452bd77d28204c5ce75c8f41ba2afa -r 9c9767f99106f184a0e6bec615359bb6c87fe48e yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -46,8 +46,10 @@
         ----------
         ds : dataset
             The dataset.
-        normal : array_like
-            The normal vector along with to make the projections.
+        normal : array_like or string
+            The normal vector along with to make the projections. If an array, it
+            will be normalized. If a string, it will be assumed to be along one of the
+            principal axes of the domain ("x","y", or "z").
         field : string
             The field to project.
         center : float, tuple, or string
@@ -94,13 +96,18 @@
         self.ny = dims[1]
         self.nv = dims[2]
 
-        normal = np.array(normal)
-        normal /= np.sqrt(np.dot(normal, normal))
-        vecs = np.identity(3)
-        t = np.cross(normal, vecs).sum(axis=1)
-        ax = t.argmax()
-        north = np.cross(normal, vecs[ax,:]).ravel()
-        orient = Orientation(normal, north_vector=north)
+        if isinstance(normal, basestring):
+            los_vec = np.zeros(3)
+            los_vec[ds.coordinates.axis_id[normal]] = 1.0
+        else:
+            normal = np.array(normal)
+            normal /= np.sqrt(np.dot(normal, normal))
+            vecs = np.identity(3)
+            t = np.cross(normal, vecs).sum(axis=1)
+            ax = t.argmax()
+            north = np.cross(normal, vecs[ax,:]).ravel()
+            orient = Orientation(normal, north_vector=north)
+            los_vec = orient.unit_vectors[2]
 
         dd = ds.all_data()
 
@@ -119,7 +126,7 @@
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
         self.dv = self.vbins[1]-self.vbins[0]
 
-        _vlos = create_vlos(orient.unit_vectors[2])
+        _vlos = create_vlos(los_vec)
         self.ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
 
         if thermal_broad:
@@ -134,9 +141,13 @@
         for i in xrange(self.nv):
             _intensity = self._create_intensity(i)
             ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
-            prj = off_axis_projection(ds, self.center, normal, width,
-                                      (self.nx, self.ny), "intensity")
-            self.data[:,:,i] = prj[:,:]
+            if isinstance(normal, basestring):
+                prj = ds.proj("intensity", ds.coordinates.axis_id[normal])
+                buf = prj.to_frb(width, self.nx, center=self.center)["intensity"]
+            else:
+                buf = off_axis_projection(ds, self.center, normal, width,
+                                          (self.nx, self.ny), "intensity")
+            self.data[:,:,i] = buf[:,:]
             ds.field_info.pop(("gas","intensity"))
             pbar.update(i)
         pbar.finish()


https://bitbucket.org/yt_analysis/yt/commits/b97cc0aaa47c/
Changeset:   b97cc0aaa47c
Branch:      yt
User:        jzuhone
Date:        2014-10-10 20:25:49+00:00
Summary:     Fixing some unit issues and trying to marginally speed things up
Affected #:  1 file

diff -r 9c9767f99106f184a0e6bec615359bb6c87fe48e -r b97cc0aaa47c1ccdf7e9e9eb9685c8509300a835 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -100,13 +100,7 @@
             los_vec = np.zeros(3)
             los_vec[ds.coordinates.axis_id[normal]] = 1.0
         else:
-            normal = np.array(normal)
-            normal /= np.sqrt(np.dot(normal, normal))
-            vecs = np.identity(3)
-            t = np.cross(normal, vecs).sum(axis=1)
-            ax = t.argmax()
-            north = np.cross(normal, vecs[ax,:]).ravel()
-            orient = Orientation(normal, north_vector=north)
+            orient = Orientation(normal)
             los_vec = orient.unit_vectors[2]
 
         dd = ds.all_data()
@@ -124,17 +118,19 @@
 
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.vmid_cgs = self.vmid.in_cgs()
         self.dv = self.vbins[1]-self.vbins[0]
+        self.dv_cgs = self.dv.in_cgs()
 
         _vlos = create_vlos(los_vec)
         self.ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
 
         if thermal_broad:
-            self.v_th = lambda T: np.sqrt(2.*kboltz*T/self.particle_mass)
-            self.phi_th = lambda v, T: self.dv*np.exp(-(v/self.v_th(T))**2)/(np.sqrt(np.pi)*self.v_th(T))
+            self.v2_th = lambda T: 2.*kboltz*T/self.particle_mass
+            self.phi_th = lambda v, T: self.dv_cgs*np.exp(-v*v/self.v2_th(T))/(np.sqrt(np.pi*self.v2_th(T)))
         else:
-            self.v_th = lambda T: 1.0
-            self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv.in_units(v.units),0.0)
+            self.v2_th = lambda T: 1.0
+            self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv_cgs,0.0)
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
         pbar = get_pbar("Generating cube.", self.nv)
@@ -152,6 +148,8 @@
             pbar.update(i)
         pbar.finish()
 
+        self.proj_units = self.data.units
+
         if self.rest_value is not None:
             # If we want units other than velocity, we re-calculate these quantities
             self.vbins = self.rest_value*(1.-self.vbins.in_cgs()/clight)
@@ -237,14 +235,14 @@
         w.wcs.ctype = [types[0],types[1],vtype]
 
         fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
-        fib[0].header["bunit"] = self.field_units
+        fib[0].header["bunit"] = str(self.proj_units)
         fib[0].header["btype"] = self.field
 
         fib.writeto(filename, clobber=clobber)
 
     def _create_intensity(self, i):
         def _intensity(field, data):
-            w = self.phi_th((self.vmid[i]-data["v_los"]).in_cgs(), data["temperature"])
+            w = self.phi_th(self.vmid_cgs[i]-data["v_los"], data["temperature"])
             return data[self.field]*w
         return _intensity
 


https://bitbucket.org/yt_analysis/yt/commits/1454d63732c7/
Changeset:   1454d63732c7
Branch:      yt
User:        jzuhone
Date:        2014-10-10 20:46:58+00:00
Summary:     Fixing units in several places
Affected #:  3 files

diff -r b97cc0aaa47c1ccdf7e9e9eb9685c8509300a835 -r 1454d63732c755dba2e3a930ca1a67cf212d46b9 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -18,10 +18,12 @@
 from yt.funcs import get_pbar
 from yt.utilities.physical_constants import clight, mh, kboltz
 import yt.units.dimensions as ytdims
+import yt.units as u
 from yt.units.yt_array import YTQuantity
 from yt.funcs import iterable
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     parallel_root_only
+import re
 
 def create_vlos(z_hat):
     def _v_los(field, data):
@@ -132,7 +134,9 @@
             self.v2_th = lambda T: 1.0
             self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv_cgs,0.0)
 
-        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
+        self.proj_units = self.field_units * u.cm.units
+
+        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
         pbar = get_pbar("Generating cube.", self.nv)
         for i in xrange(self.nv):
             _intensity = self._create_intensity(i)
@@ -148,8 +152,6 @@
             pbar.update(i)
         pbar.finish()
 
-        self.proj_units = self.data.units
-
         if self.rest_value is not None:
             # If we want units other than velocity, we re-calculate these quantities
             self.vbins = self.rest_value*(1.-self.vbins.in_cgs()/clight)
@@ -220,7 +222,9 @@
             dx = self.width/self.nx
             units = str(self.width.units)
         # Hack because FITS is stupid and doesn't understand case
-        if units == "Mpc": units = "kpc"
+        if units == "Mpc":
+            units = "kpc"
+            dx *= 1000.
         dy = dx
         dv = self.dv.in_units(vunit)
 
@@ -235,7 +239,7 @@
         w.wcs.ctype = [types[0],types[1],vtype]
 
         fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
-        fib[0].header["bunit"] = str(self.proj_units)
+        fib[0].header["bunit"] = re.sub('()', '', str(self.proj_units))
         fib[0].header["btype"] = self.field
 
         fib.writeto(filename, clobber=clobber)

diff -r b97cc0aaa47c1ccdf7e9e9eb9685c8509300a835 -r 1454d63732c755dba2e3a930ca1a67cf212d46b9 yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -321,7 +321,8 @@
 
         units = self.ds.get_smallest_appropriate_unit(self.width)
         # Hack because FITS is stupid and doesn't understand case
-        if units == "Mpc": units = "kpc"
+        if units == "Mpc":
+            units = "kpc"
         dx = self.dx.in_units(units)
         if sky_scale:
             dx = (dx*sky_scale).in_units("deg")

diff -r b97cc0aaa47c1ccdf7e9e9eb9685c8509300a835 -r 1454d63732c755dba2e3a930ca1a67cf212d46b9 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -16,6 +16,7 @@
 from yt.data_objects.construction_data_containers import YTCoveringGridBase
 from yt.utilities.on_demand_imports import _astropy
 from yt.units.yt_array import YTQuantity
+import re
 
 pyfits = _astropy.pyfits
 pywcs = _astropy.pywcs
@@ -109,7 +110,7 @@
                 hdu.name = key
                 hdu.header["btype"] = key
                 if hasattr(img_data[key], "units"):
-                    hdu.header["bunit"] = str(img_data[key].units)
+                    hdu.header["bunit"] = re.sub('()', '', str(img_data[key].units))
                 self.append(hdu)
 
         self.dimensionality = len(self[0].data.shape)


https://bitbucket.org/yt_analysis/yt/commits/e1f943c57fcb/
Changeset:   e1f943c57fcb
Branch:      yt
User:        jzuhone
Date:        2014-10-10 20:47:30+00:00
Summary:     Trying to get PPV cubes recognized where the units of the lat/lon axes are in some kind of length units.
Affected #:  1 file

diff -r 1454d63732c755dba2e3a930ca1a67cf212d46b9 -r e1f943c57fcb04b70bace997e862370fe8068219 yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -44,8 +44,8 @@
 from yt.units.yt_array import YTQuantity
 from yt.utilities.on_demand_imports import _astropy
 
-lon_prefixes = ["X","RA","GLON"]
-lat_prefixes = ["Y","DEC","GLAT"]
+lon_prefixes = ["X","RA","GLON","LINEAR"]
+lat_prefixes = ["Y","DEC","GLAT","LINEAR"]
 delimiters = ["*", "/", "-", "^"]
 delimiters += [str(i) for i in xrange(10)]
 regex_pattern = '|'.join(re.escape(_) for _ in delimiters)


https://bitbucket.org/yt_analysis/yt/commits/090c9d808536/
Changeset:   090c9d808536
Branch:      yt
User:        jzuhone
Date:        2014-10-10 20:50:33+00:00
Summary:     Bug fix
Affected #:  1 file

diff -r e1f943c57fcb04b70bace997e862370fe8068219 -r 090c9d80853662c9f8c275b66f7dde2464702fe3 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -134,7 +134,7 @@
             self.v2_th = lambda T: 1.0
             self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv_cgs,0.0)
 
-        self.proj_units = self.field_units * u.cm.units
+        self.proj_units = self.field_units+"*cm"
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
         pbar = get_pbar("Generating cube.", self.nv)


https://bitbucket.org/yt_analysis/yt/commits/4193b690a440/
Changeset:   4193b690a440
Branch:      yt
User:        jzuhone
Date:        2014-10-10 22:34:46+00:00
Summary:     Allow for PPV cubes with actual length units to use them in plots.
Affected #:  3 files

diff -r e1f943c57fcb04b70bace997e862370fe8068219 -r 4193b690a44029933a3f07244a5b5acf97c2aedb yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -549,7 +549,7 @@
         x = 0
         for p in lon_prefixes+lat_prefixes+spec_names.keys():
             y = np_char.startswith(self.axis_names[:self.dimensionality], p)
-            x += y.sum()
+            x += np.any(y)
         if x == self.dimensionality: self._setup_spec_cube()
 
     def _setup_spec_cube(self):
@@ -579,6 +579,12 @@
         self.lon_axis = np.where(self.lon_axis)[0][0]
         self.lon_name = ctypes[self.lon_axis].split("-")[0].lower()
 
+        if self.lat_axis == self.lon_axis and self.lat_name == self.lon_name:
+            self.lat_axis = 1
+            self.lon_axis = 0
+            self.lat_name = "Y"
+            self.lon_name = "X"
+
         if self.wcs.naxis > 2:
 
             self.spec_axis = np.zeros((end-1), dtype="bool")

diff -r e1f943c57fcb04b70bace997e862370fe8068219 -r 4193b690a44029933a3f07244a5b5acf97c2aedb yt/geometry/coordinates/spec_cube_coordinates.py
--- a/yt/geometry/coordinates/spec_cube_coordinates.py
+++ b/yt/geometry/coordinates/spec_cube_coordinates.py
@@ -26,8 +26,19 @@
         self.axis_name = {}
         self.axis_id = {}
 
-        for axis, axis_name in zip([ds.lon_axis, ds.lat_axis, ds.spec_axis],
-                                   ["Image\ x", "Image\ y", ds.spec_name]):
+        self.default_unit_label = {}
+        if ds.lon_name == "X" and ds.lat_name == "Y":
+            names = ["x","y"]
+        else:
+            names = ["Image\ x", "Image\ y"]
+            self.default_unit_label[ds.lon_axis] = "pixel"
+            self.default_unit_label[ds.lat_axis] = "pixel"
+        names.append(ds.spec_name)
+        axes = [ds.lon_axis, ds.lat_axis, ds.spec_axis]
+        self.default_unit_label[ds.spec_axis] = ds.spec_unit
+
+        for axis, axis_name in zip(axes, names):
+
             lower_ax = "xyz"[axis]
             upper_ax = lower_ax.upper()
 
@@ -40,11 +51,6 @@
             self.axis_id[axis] = axis
             self.axis_id[axis_name] = axis
 
-        self.default_unit_label = {}
-        self.default_unit_label[ds.lon_axis] = "pixel"
-        self.default_unit_label[ds.lat_axis] = "pixel"
-        self.default_unit_label[ds.spec_axis] = ds.spec_unit
-
         def _spec_axis(ax, x, y):
             p = (x,y)[ax]
             return [self.ds.pixel2spec(pp).v for pp in p]

diff -r e1f943c57fcb04b70bace997e862370fe8068219 -r 4193b690a44029933a3f07244a5b5acf97c2aedb yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -756,9 +756,10 @@
             for i, un in enumerate((unit_x, unit_y)):
                 if hasattr(self.ds.coordinates, "default_unit_label"):
                     axax = getattr(self.ds.coordinates, "%s_axis" % ("xy"[i]))[axis_index]
-                    un = self.ds.coordinates.default_unit_label[axax]
-                    axes_unit_labels[i] = '\/\/('+un+')'
-                    continue
+                    unn = self.ds.coordinates.default_unit_label.get(axax, "")
+                    if unn != "":
+                        axes_unit_labels[i] = '\/\/('+unn+')'
+                        continue
                 # Use sympy to factor h out of the unit.  In this context 'un'
                 # is a string, so we call the Unit constructor.
                 expr = Unit(un, registry=self.ds.unit_registry).expr


https://bitbucket.org/yt_analysis/yt/commits/60c151c985e5/
Changeset:   60c151c985e5
Branch:      yt
User:        jzuhone
Date:        2014-10-10 22:35:00+00:00
Summary:     Merge
Affected #:  1 file

diff -r 4193b690a44029933a3f07244a5b5acf97c2aedb -r 60c151c985e58c6eab5f93ceab30dfaef3d58c55 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -134,7 +134,7 @@
             self.v2_th = lambda T: 1.0
             self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv_cgs,0.0)
 
-        self.proj_units = self.field_units * u.cm.units
+        self.proj_units = self.field_units+"*cm"
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
         pbar = get_pbar("Generating cube.", self.nv)


https://bitbucket.org/yt_analysis/yt/commits/62c4c9d1b8bc/
Changeset:   62c4c9d1b8bc
Branch:      yt
User:        jzuhone
Date:        2014-10-13 23:18:03+00:00
Summary:     This shouldn't be transposed.
Affected #:  1 file

diff -r 60c151c985e58c6eab5f93ceab30dfaef3d58c55 -r 62c4c9d1b8bcf8fe735ebb24c160cc82ba433a46 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -238,7 +238,7 @@
         w.wcs.cunit = [units,units,vunit]
         w.wcs.ctype = [types[0],types[1],vtype]
 
-        fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
+        fib = FITSImageBuffer(self.data, fields=self.field, wcs=w)
         fib[0].header["bunit"] = re.sub('()', '', str(self.proj_units))
         fib[0].header["btype"] = self.field
 


https://bitbucket.org/yt_analysis/yt/commits/cd298f3c8e57/
Changeset:   cd298f3c8e57
Branch:      yt
User:        jzuhone
Date:        2014-10-14 21:30:44+00:00
Summary:     Rotating indices around
Affected #:  1 file

diff -r 62c4c9d1b8bcf8fe735ebb24c160cc82ba433a46 -r cd298f3c8e5764e157dffae4ec64605553c1c90e yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -146,7 +146,7 @@
                 buf = prj.to_frb(width, self.nx, center=self.center)["intensity"]
             else:
                 buf = off_axis_projection(ds, self.center, normal, width,
-                                          (self.nx, self.ny), "intensity")
+                                          (self.nx, self.ny), "intensity")[::-1]
             self.data[:,:,i] = buf[:,:]
             ds.field_info.pop(("gas","intensity"))
             pbar.update(i)
@@ -238,7 +238,7 @@
         w.wcs.cunit = [units,units,vunit]
         w.wcs.ctype = [types[0],types[1],vtype]
 
-        fib = FITSImageBuffer(self.data, fields=self.field, wcs=w)
+        fib = FITSImageBuffer(self.data.transpose(2,0,1), fields=self.field, wcs=w)
         fib[0].header["bunit"] = re.sub('()', '', str(self.proj_units))
         fib[0].header["btype"] = self.field
 


https://bitbucket.org/yt_analysis/yt/commits/4d04c5d2b6bd/
Changeset:   4d04c5d2b6bd
Branch:      yt
User:        jzuhone
Date:        2014-10-21 23:46:54+00:00
Summary:     Refactoring FITSImageBuffer to make the options simpler--we only create coordinate information if a WCS is specified or if the underlying object is an FRB or covering grid.
Affected #:  3 files

diff -r cd298f3c8e5764e157dffae4ec64605553c1c90e -r 4d04c5d2b6bdb152c8ec9f1f521295ac1fd70e52 yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -25,6 +25,7 @@
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
      communication_system, parallel_root_only
 from yt import units
+from yt.utilities.on_demand_imports import _astropy
 
 import numpy as np
 
@@ -304,13 +305,15 @@
         >>> # This example just writes out a FITS file with kpc coords
         >>> szprj.write_fits("SZbullet.fits", clobber=False)
         >>> # This example uses sky coords
-        >>> sky_scale = 1./3600. # One arcsec per kpc
+        >>> sky_scale = (1., "arcsec/kpc") # One arcsec per kpc
         >>> sky_center = (30., 45.) # In degrees
         >>> szprj.write_fits("SZbullet.fits", sky_center=sky_center, sky_scale=sky_scale)
         """
+        from yt.utilities.fits_image import FITSImageBuffer
 
         if sky_scale is None:
             center = (0.0,0.0)
+            types = ["LINEAR","LINEAR"]
         else:
             if iterable(sky_scale):
                 sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
@@ -318,6 +321,7 @@
                 center = (30.,45.)
             else:
                 center = sky_center
+            types = ["RA---TAN","DEC--TAN"]
 
         units = self.ds.get_smallest_appropriate_unit(self.width)
         # Hack because FITS is stupid and doesn't understand case
@@ -331,10 +335,14 @@
         if sky_scale:
             dx *= -1.
 
-        from yt.utilities.fits_image import FITSImageBuffer
-        fib = FITSImageBuffer(self.data, fields=self.data.keys(),
-                              center=center, units=units, scale=[dx.v,dy.v])
-        fib.update_all_headers("Time", float(self.ds.current_time.in_units(time_units).value))
+        w = _astropy.pywcs.WCS(naxis=2)
+        w.wcs.crpix = [0.5*(self.nx+1)]*2
+        w.wcs.cdelt = [dx.v,dy.v]
+        w.wcs.crval = center
+        w.wcs.cunit = [units]*2
+        w.wcs.ctype = types
+
+        fib = FITSImageBuffer(self.data, fields=self.data.keys(), wcs=w)
         fib.writeto(filename, clobber=clobber)
         
     @parallel_root_only

diff -r cd298f3c8e5764e157dffae4ec64605553c1c90e -r 4d04c5d2b6bdb152c8ec9f1f521295ac1fd70e52 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -28,8 +28,7 @@
 
 class FITSImageBuffer(HDUList):
 
-    def __init__(self, data, fields=None, units="cm",
-                 center=None, scale=None, wcs=None):
+    def __init__(self, data, fields=None, units="cm", wcs=None):
         r""" Initialize a FITSImageBuffer object.
 
         FITSImageBuffer contains a list of FITS ImageHDU instances, and
@@ -50,29 +49,32 @@
             keys, it will use these for the fields. If *data* is just a
             single array one field name must be specified.
         units : string
-            The units of the WCS coordinates, default "cm". 
-        center : array_like, optional
-            The coordinates [xctr,yctr] of the images in units
-            *units*. If *units* is not specified, defaults to the origin. 
-        scale : tuple of floats, optional
-            Pixel scale in unit *units*. Will be ignored if *data* is
-            a FixedResolutionBuffer or a YTCoveringGrid. Must be
-            specified otherwise, or if *units* is "deg".
+            The units of the WCS coordinates. Only applies
+            to FixedResolutionBuffers or YTCoveringGrids. Defaults to "cm".
         wcs : `astropy.wcs.WCS` instance, optional
-            Supply an AstroPy WCS instance to override automatic WCS creation.
+            Supply an AstroPy WCS instance. Will override automatic WCS
+            creation from FixedResolutionBuffers and YTCoveringGrids.
 
         Examples
         --------
 
+        >>> # This example uses a FRB.
         >>> ds = load("sloshing_nomag2_hdf5_plt_cnt_0150")
         >>> prj = ds.proj(2, "kT", weight_field="density")
         >>> frb = prj.to_frb((0.5, "Mpc"), 800)
         >>> # This example just uses the FRB and puts the coords in kpc.
         >>> f_kpc = FITSImageBuffer(frb, fields="kT", units="kpc")
-        >>> # This example specifies sky coordinates.
-        >>> scale = [1./3600.]*2 # One arcsec per pixel
-        >>> f_deg = FITSImageBuffer(frb, fields="kT", units="deg",
-                                    scale=scale, center=(30., 45.))
+        >>> # This example specifies a specific WCS.
+        >>> from astropy.wcs import WCS
+        >>> w = WCS(naxis=self.dimensionality)
+        >>> w.wcs.crval = [30., 45.] # RA, Dec in degrees
+        >>> w.wcs.cunit = ["deg"]*2
+        >>> nx, ny = 800, 800
+        >>> w.wcs.crpix = [0.5*(nx+1), 0.5*(ny+1)]
+        >>> w.wcs.ctype = ["RA---TAN","DEC--TAN"]
+        >>> scale = 1./3600. # One arcsec per pixel
+        >>> w.wcs.cdelt = [-scale, scale]
+        >>> f_deg = FITSImageBuffer(frb, fields="kT", wcs=w)
         >>> f_deg.writeto("temp.fits")
         """
         
@@ -122,25 +124,11 @@
 
         has_coords = (isinstance(img_data, FixedResolutionBuffer) or
                       isinstance(img_data, YTCoveringGridBase))
-        
-        if center is None:
-            if units == "deg":
-                mylog.error("Please specify center=(RA, Dec) in degrees.")
-                raise ValueError
-            elif not has_coords:
-                mylog.warning("Setting center to the origin.")
-                center = [0.0]*self.dimensionality
-
-        if scale is None:
-            if units == "deg" or not has_coords and wcs is None:
-                mylog.error("Please specify scale=(dx,dy[,dz]) in %s." % (units))
-                raise ValueError
 
         if wcs is None:
             w = pywcs.WCS(header=self[0].header, naxis=self.dimensionality)
             w.wcs.crpix = 0.5*(np.array(self.shape)+1)
-            proj_type = ["linear"]*self.dimensionality
-            if isinstance(img_data, FixedResolutionBuffer) and units != "deg":
+            if isinstance(img_data, FixedResolutionBuffer):
                 # FRBs are a special case where we have coordinate
                 # information, so we take advantage of this and
                 # construct the WCS object
@@ -152,28 +140,20 @@
             elif isinstance(img_data, YTCoveringGridBase):
                 dx, dy, dz = img_data.dds.in_units(units)
                 center = 0.5*(img_data.left_edge+img_data.right_edge).in_units(units)
-            elif units == "deg" and self.dimensionality == 2:
-                dx = -scale[0]
-                dy = scale[1]
-                proj_type = ["RA---TAN","DEC--TAN"]
             else:
-                dx = scale[0]
-                dy = scale[1]
-                if self.dimensionality == 3: dz = scale[2]
-            
+                # We default to pixel coordinates if nothing is provided
+                dx, dy, dz = 1.0, 1.0, 1.0
+                center = 0.5*(np.array(self.shape)+1)
             w.wcs.crval = center
-            w.wcs.cunit = [units]*self.dimensionality
-            w.wcs.ctype = proj_type
-        
+            if has_coords:
+                w.wcs.cunit = [units]*self.dimensionality
             if self.dimensionality == 2:
                 w.wcs.cdelt = [dx,dy]
             elif self.dimensionality == 3:
                 w.wcs.cdelt = [dx,dy,dz]
-
+            w.wcs.ctype = ["linear"]*self.dimensionality
             self._set_wcs(w)
-
         else:
-
             self._set_wcs(wcs)
 
     def _set_wcs(self, wcs):

diff -r cd298f3c8e5764e157dffae4ec64605553c1c90e -r 4d04c5d2b6bdb152c8ec9f1f521295ac1fd70e52 yt/visualization/volume_rendering/image_handling.py
--- a/yt/visualization/volume_rendering/image_handling.py
+++ b/yt/visualization/volume_rendering/image_handling.py
@@ -40,9 +40,7 @@
         data["b"] = image[:,:,2]
         data["a"] = image[:,:,3]
         nx, ny = data["r"].shape
-        fib = FITSImageBuffer(data, units="pixel",
-                              center=[0.5*(nx+1), 0.5*(ny+1)],
-                              scale=[1.]*2)
+        fib = FITSImageBuffer(data)
         fib.writeto('%s.fits'%fn,clobber=True)
 
 def import_rgba(name, h5=True):


https://bitbucket.org/yt_analysis/yt/commits/0113185edacf/
Changeset:   0113185edacf
Branch:      yt
User:        jzuhone
Date:        2014-10-22 00:07:46+00:00
Summary:     Small improvements
Affected #:  1 file

diff -r 4d04c5d2b6bdb152c8ec9f1f521295ac1fd70e52 -r 0113185edacf2b757d0ca2c3bdad85a63c613f44 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -41,7 +41,7 @@
 class PPVCube(object):
     def __init__(self, ds, normal, field, center="c", width=(1.0,"unitary"),
                  dims=(100,100,100), velocity_bounds=None, rest_value=None,
-                 thermal_broad=False, particle_weight=56.):
+                 thermal_broad=False, atomic_weight=56.):
         r""" Initialize a PPVCube object.
 
         Parameters
@@ -55,7 +55,7 @@
         field : string
             The field to project.
         center : float, tuple, or string
-
+            The coordinates of the dataset *ds* on which to center the PPVCube.
         width : float or tuple, optional
             The width of the projection in length units. Specify a float
             for code_length units or a tuple (value, units).
@@ -70,7 +70,7 @@
             axis of the PPV cube. The spectral axis will be converted to the units and
             displaced by the value. Can be in units of energy, wavelength, or frequency.
             If not set the default is to leave the spectral axis in velocity units.
-        particle_weight : float, optional
+        atomic_weight : float, optional
             Set this value to the atomic weight of the particle that is emitting the line
             if *thermal_broad* is True. Defaults to 56 (Fe).
 
@@ -89,7 +89,7 @@
             self.rest_value = rest_value
         else:
             self.rest_value = ds.quan(rest_value[0], rest_value[1])
-        self.particle_mass = particle_weight*mh
+        self.particle_mass = atomic_weight*mh
         self.thermal_broad = thermal_broad
 
         self.center = ds.coordinates.sanitize_center(center, normal)[0]
@@ -216,24 +216,24 @@
         v_center = 0.5*(self.vbins[0]+self.vbins[-1]).in_units(vunit).value
 
         if sky_scale:
-            dx = (self.width*sky_scale).in_units("deg")/self.nx
+            dx = (self.width*sky_scale).in_units("deg").v/self.nx
             units = "deg"
         else:
-            dx = self.width/self.nx
-            units = str(self.width.units)
+            units = str(self.ds.get_smallest_appropriate_unit(self.width))
+            dx = self.width.v/self.nx
         # Hack because FITS is stupid and doesn't understand case
         if units == "Mpc":
             units = "kpc"
             dx *= 1000.
         dy = dx
-        dv = self.dv.in_units(vunit)
+        dv = self.dv.in_units(vunit).v
 
         if sky_scale:
             dx *= -1.
 
         w = _astropy.pywcs.WCS(naxis=3)
         w.wcs.crpix = [0.5*(self.nx+1), 0.5*(self.ny+1), 0.5*(self.nv+1)]
-        w.wcs.cdelt = [dx.v,dy.v,dv.v]
+        w.wcs.cdelt = [dx,dy,dv]
         w.wcs.crval = [center[0],center[1],v_center]
         w.wcs.cunit = [units,units,vunit]
         w.wcs.ctype = [types[0],types[1],vtype]


https://bitbucket.org/yt_analysis/yt/commits/fe25428c10cc/
Changeset:   fe25428c10cc
Branch:      yt
User:        jzuhone
Date:        2014-10-22 00:20:54+00:00
Summary:     Some more small improvements
Affected #:  1 file

diff -r 0113185edacf2b757d0ca2c3bdad85a63c613f44 -r fe25428c10cc24caa03497ede62615b2717c292e yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -102,7 +102,13 @@
             los_vec = np.zeros(3)
             los_vec[ds.coordinates.axis_id[normal]] = 1.0
         else:
-            orient = Orientation(normal)
+            normal = np.array(normal)
+            normal /= np.sqrt(np.dot(normal, normal))
+            vecs = np.identity(3)
+            t = np.cross(normal, vecs).sum(axis=1)
+            ax = t.argmax()
+            north = np.cross(normal, vecs[ax,:]).ravel()
+            orient = Orientation(normal, north_vector=north)
             los_vec = orient.unit_vectors[2]
 
         dd = ds.all_data()
@@ -176,8 +182,8 @@
             self.width = ds.quan(self.width, "code_length")
 
     @parallel_root_only
-    def write_fits(self, filename, clobber=True, sky_scale=None,
-                   sky_center=None):
+    def write_fits(self, filename, clobber=True, length_unit=None,
+                   sky_scale=None, sky_center=None):
         r""" Write the PPVCube to a FITS file.
 
         Parameters
@@ -186,6 +192,8 @@
             The name of the file to write.
         clobber : boolean
             Whether or not to clobber an existing file with the same name.
+        length_unit : string
+            The units to convert the coordinates to in the file.
         sky_scale : tuple or YTQuantity
             Conversion between an angle unit and a length unit, if sky
             coordinates are desired.
@@ -219,12 +227,17 @@
             dx = (self.width*sky_scale).in_units("deg").v/self.nx
             units = "deg"
         else:
-            units = str(self.ds.get_smallest_appropriate_unit(self.width))
+            if length_unit is None:
+                units = str(self.ds.get_smallest_appropriate_unit(self.width))
+            else:
+                units = length_unit
             dx = self.width.v/self.nx
-        # Hack because FITS is stupid and doesn't understand case
+        # Hacks because FITS is stupid and doesn't understand case
         if units == "Mpc":
             units = "kpc"
             dx *= 1000.
+        elif units == "au":
+            units = "AU"
         dy = dx
         dv = self.dv.in_units(vunit).v
 


https://bitbucket.org/yt_analysis/yt/commits/88cbf01c5e36/
Changeset:   88cbf01c5e36
Branch:      yt
User:        jzuhone
Date:        2014-10-22 00:39:59+00:00
Summary:     Bug fixes
Affected #:  1 file

diff -r fe25428c10cc24caa03497ede62615b2717c292e -r 88cbf01c5e36312567488636451181d123ac1ee5 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -231,7 +231,7 @@
                 units = str(self.ds.get_smallest_appropriate_unit(self.width))
             else:
                 units = length_unit
-            dx = self.width.v/self.nx
+            dx = self.width.in_units(units).v/self.nx
         # Hacks because FITS is stupid and doesn't understand case
         if units == "Mpc":
             units = "kpc"
@@ -251,7 +251,7 @@
         w.wcs.cunit = [units,units,vunit]
         w.wcs.ctype = [types[0],types[1],vtype]
 
-        fib = FITSImageBuffer(self.data.transpose(2,0,1), fields=self.field, wcs=w)
+        fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
         fib[0].header["bunit"] = re.sub('()', '', str(self.proj_units))
         fib[0].header["btype"] = self.field
 


https://bitbucket.org/yt_analysis/yt/commits/79c073d69c59/
Changeset:   79c073d69c59
Branch:      yt
User:        jzuhone
Date:        2014-10-22 00:49:36+00:00
Summary:     Bug fixes
Affected #:  1 file

diff -r 88cbf01c5e36312567488636451181d123ac1ee5 -r 79c073d69c59a044a3d07cd2bbd01aac94981eb4 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -140,7 +140,7 @@
             self.v2_th = lambda T: 1.0
             self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv_cgs,0.0)
 
-        self.proj_units = self.field_units+"*cm"
+        self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
         pbar = get_pbar("Generating cube.", self.nv)
@@ -260,6 +260,7 @@
     def _create_intensity(self, i):
         def _intensity(field, data):
             w = self.phi_th(self.vmid_cgs[i]-data["v_los"], data["temperature"])
+            w[np.isnan(w)] = 0.0
             return data[self.field]*w
         return _intensity
 


https://bitbucket.org/yt_analysis/yt/commits/8498a9431dd7/
Changeset:   8498a9431dd7
Branch:      yt
User:        jzuhone
Date:        2014-10-22 02:32:27+00:00
Summary:     Moved the spectral axis transformation to its own method.
Affected #:  1 file

diff -r 79c073d69c59a044a3d07cd2bbd01aac94981eb4 -r 8498a9431dd7ea87c831f70833035cdefaf2fd6d yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -40,8 +40,8 @@
 
 class PPVCube(object):
     def __init__(self, ds, normal, field, center="c", width=(1.0,"unitary"),
-                 dims=(100,100,100), velocity_bounds=None, rest_value=None,
-                 thermal_broad=False, atomic_weight=56.):
+                 dims=(100,100,100), velocity_bounds=None, thermal_broad=False,
+                 atomic_weight=56.):
         r""" Initialize a PPVCube object.
 
         Parameters
@@ -65,11 +65,6 @@
             A 3-tuple of (vmin, vmax, units) for the velocity bounds to
             integrate over. If None, the largest velocity of the
             dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())
-        rest_value : tuple or YTQuantity, optional
-            A (value, unit) tuple or YTQuantity indicating the rest value of the spectral
-            axis of the PPV cube. The spectral axis will be converted to the units and
-            displaced by the value. Can be in units of energy, wavelength, or frequency.
-            If not set the default is to leave the spectral axis in velocity units.
         atomic_weight : float, optional
             Set this value to the atomic weight of the particle that is emitting the line
             if *thermal_broad* is True. Defaults to 56 (Fe).
@@ -85,10 +80,6 @@
         self.ds = ds
         self.field = field
         self.width = width
-        if isinstance(rest_value, YTQuantity) or rest_value is None:
-            self.rest_value = rest_value
-        else:
-            self.rest_value = ds.quan(rest_value[0], rest_value[1])
         self.particle_mass = atomic_weight*mh
         self.thermal_broad = thermal_broad
 
@@ -125,6 +116,7 @@
                           ds.quan(velocity_bounds[1], velocity_bounds[2]))
 
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
+        self._vbins = self.vbins.copy()
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
         self.vmid_cgs = self.vmid.in_cgs()
         self.dv = self.vbins[1]-self.vbins[0]
@@ -158,14 +150,31 @@
             pbar.update(i)
         pbar.finish()
 
-        if self.rest_value is not None:
-            # If we want units other than velocity, we re-calculate these quantities
-            self.vbins = self.rest_value*(1.-self.vbins.in_cgs()/clight)
-            self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-            self.dv = self.vbins[1]-self.vbins[0]
+        self.axis_type = "velocity"
 
+        # Now fix the width
+        if iterable(self.width):
+            self.width = ds.quan(self.width[0], self.width[1])
+        else:
+            self.width = ds.quan(self.width, "code_length")
+
+    def transform_spectral_axis(self, rest_value, units):
+        """
+        Change the units of the spectral axis to some equivalent unit, such
+        as energy, wavelength, or frequency, by providing a *rest_value* and the
+        *units* of the new spectral axis. This corresponds to the Doppler-shifting
+        of lines due to gas motions and thermal broadening.
+        """
+        if self.axis_type != "velocity":
+            self.reset_spectral_axis()
+        x0 = self.ds.quan(rest_value, units)
+        if x0.units.dimensions == ytdims.rate or x0.units.dimensions == ytdims.energy:
+            self.vbins = x0*(1.-self.vbins.in_cgs()/clight)
+        elif x0.units.dimensions == ytdims.length:
+            self.vbins = x0/(1.-self.vbins.in_cgs()/clight)
+        self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.dv = self.vbins[1]-self.vbins[0]
         dims = self.dv.units.dimensions
-
         if dims == ytdims.rate:
             self.axis_type = "frequency"
         elif dims == ytdims.length:
@@ -175,11 +184,13 @@
         elif dims == ytdims.velocity:
             self.axis_type = "velocity"
 
-        # Now fix the width
-        if iterable(self.width):
-            self.width = ds.quan(self.width[0], self.width[1])
-        else:
-            self.width = ds.quan(self.width, "code_length")
+    def reset_spectral_axis(self):
+        """
+        Reset the spectral axis to the original velocity range and units.
+        """
+        self.vbins = self._vbins.copy()
+        self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.dv = self.vbins[1]-self.vbins[0]
 
     @parallel_root_only
     def write_fits(self, filename, clobber=True, length_unit=None,


https://bitbucket.org/yt_analysis/yt/commits/5d42ecd21485/
Changeset:   5d42ecd21485
Branch:      yt
User:        jzuhone
Date:        2014-10-22 14:20:00+00:00
Summary:     Bringing PPVCube docs up-to-date
Affected #:  1 file

diff -r 8498a9431dd7ea87c831f70833035cdefaf2fd6d -r 5d42ecd2148572be9cec39c552443987aece9ffd doc/source/analyzing/analysis_modules/PPVCube.ipynb
--- a/doc/source/analyzing/analysis_modules/PPVCube.ipynb
+++ b/doc/source/analyzing/analysis_modules/PPVCube.ipynb
@@ -1,7 +1,7 @@
 {
  "metadata": {
   "name": "",
-  "signature": "sha256:56a8d72735e3cc428ff04b241d4b2ce6f653019818c6fc7a4148840d99030c85"
+  "signature": "sha256:b83e125278c2e58da4d99ac9d2ca2a136d01f1094e1b83497925e0f9b9b056c2"
  },
  "nbformat": 3,
  "nbformat_minor": 0,
@@ -32,7 +32,7 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "To demonstrate this functionality, we'll create a simple unigrid dataset from scratch of a rotating disk galaxy. We create a thin disk in the x-y midplane of the domain of three cells in height in either direction, and a radius of 10 kpc. The density and azimuthal velocity profiles of the disk as a function of radius will be given by the following functions:"
+      "To demonstrate this functionality, we'll create a simple unigrid dataset from scratch of a rotating disk. We create a thin disk in the x-y midplane of the domain of three cells in height in either direction, and a radius of 10 kpc. The density and azimuthal velocity profiles of the disk as a function of radius will be given by the following functions:"
      ]
     },
     {
@@ -84,7 +84,7 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Second, we'll construct the data arrays for the density and the velocity of the disk. Since we have the tangential velocity profile, we have to use the polar coordinates we derived earlier to compute `velx` and `vely`. Everywhere outside the disk, all fields are set to zero.  "
+      "Second, we'll construct the data arrays for the density, temperature, and velocity of the disk. Since we have the tangential velocity profile, we have to use the polar coordinates we derived earlier to compute `velx` and `vely`. Everywhere outside the disk, all fields are set to zero.  "
      ]
     },
     {
@@ -93,12 +93,15 @@
      "input": [
       "dens = np.zeros((nx,ny,nz))\n",
       "dens[:,:,nz/2-3:nz/2+3] = (r**alpha).reshape(nx,ny,1) # the density profile of the disk\n",
-      "vel_theta = r/(1.+(r/r_0)**beta) # the azimuthal velocity profile of the disk\n",
+      "temp = np.zeros((nx,ny,nz))\n",
+      "temp[:,:,nz/2-3:nz/2+3] = 1.0e5 # Isothermal\n",
+      "vel_theta = 100.*r/(1.+(r/r_0)**beta) # the azimuthal velocity profile of the disk\n",
       "velx = np.zeros((nx,ny,nz))\n",
       "vely = np.zeros((nx,ny,nz))\n",
       "velx[:,:,nz/2-3:nz/2+3] = (-vel_theta*np.sin(theta)).reshape(nx,ny,1) # convert polar to cartesian\n",
       "vely[:,:,nz/2-3:nz/2+3] = (vel_theta*np.cos(theta)).reshape(nx,ny,1) # convert polar to cartesian\n",
       "dens[r > R] = 0.0\n",
+      "temp[r > R] = 0.0\n",
       "velx[r > R] = 0.0\n",
       "vely[r > R] = 0.0"
      ],
@@ -119,6 +122,7 @@
      "input": [
       "data = {}\n",
       "data[\"density\"] = (dens,\"g/cm**3\")\n",
+      "data[\"temperature\"] = (temp, \"K\")\n",
       "data[\"velocity_x\"] = (velx, \"km/s\")\n",
       "data[\"velocity_y\"] = (vely, \"km/s\")\n",
       "data[\"velocity_z\"] = (np.zeros((nx,ny,nz)), \"km/s\") # zero velocity in the z-direction\n",
@@ -189,7 +193,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "cube = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-1.5,1.5,\"km/s\"))"
+      "cube = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-150.,150.,\"km/s\"))"
      ],
      "language": "python",
      "metadata": {},
@@ -199,14 +203,33 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Following this, we can now write this cube to a FITS file:"
+      "Following this, we can now write this cube to a FITS file. The x and y axes of the file can be in length units, which can be optionally specified by `length_unit`:"
      ]
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "cube.write_fits(\"cube.fits\", clobber=True, length_unit=(5.0,\"deg\"))"
+      "cube.write_fits(\"cube.fits\", clobber=True, length_unit=\"kpc\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Or one can use the `sky_scale` and `sky_center` keywords to set up the coordinates in RA and Dec:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "sky_scale = (1.0, \"arcsec/kpc\")\n",
+      "sky_center = (30., 45.) # RA, Dec in degrees\n",
+      "cube.write_fits(\"cube_sky.fits\", clobber=True, sky_scale=sky_scale, sky_center=sky_center)"
      ],
      "language": "python",
      "metadata": {},
@@ -223,7 +246,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "ds = yt.load(\"cube.fits\")"
+      "ds_cube = yt.load(\"cube.fits\")"
      ],
      "language": "python",
      "metadata": {},
@@ -234,7 +257,7 @@
      "collapsed": false,
      "input": [
       "# Specifying no center gives us the center slice\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"])\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"])\n",
       "slc.show()"
      ],
      "language": "python",
@@ -247,9 +270,9 @@
      "input": [
       "import yt.units as u\n",
       "# Picking different velocities for the slices\n",
-      "new_center = ds.domain_center\n",
-      "new_center[2] = ds.spec2pixel(-1.0*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center = ds_cube.domain_center\n",
+      "new_center[2] = ds_cube.spec2pixel(-100.*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -260,8 +283,8 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "new_center[2] = ds.spec2pixel(0.7*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center[2] = ds_cube.spec2pixel(70.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -272,8 +295,8 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "new_center[2] = ds.spec2pixel(-0.3*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center[2] = ds_cube.spec2pixel(-30.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -291,7 +314,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "prj = yt.ProjectionPlot(ds, \"z\", [\"density\"], method=\"sum\")\n",
+      "prj = yt.ProjectionPlot(ds_cube, \"z\", [\"density\"], method=\"sum\")\n",
       "prj.set_log(\"density\", True)\n",
       "prj.set_zlim(\"density\", 1.0e-3, 0.2)\n",
       "prj.show()"
@@ -299,9 +322,100 @@
      "language": "python",
      "metadata": {},
      "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The `thermal_broad` keyword allows one to simulate thermal line broadening based on the temperature, and the `atomic_weight` argument is used to specify the atomic weight of the particle that is doing the emitting."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube2 = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-150,150,\"km/s\"), thermal_broad=True, \n",
+      "                atomic_weight=12.0)\n",
+      "cube2.write_fits(\"cube2.fits\", clobber=True, length_unit=\"kpc\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Taking a slice of this cube shows:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "ds_cube2 = yt.load(\"cube2.fits\")\n",
+      "new_center = ds_cube2.domain_center\n",
+      "new_center[2] = ds_cube2.spec2pixel(70.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube2, \"z\", [\"density\"], center=new_center)\n",
+      "slc.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "new_center[2] = ds_cube2.spec2pixel(-100.*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube2, \"z\", [\"density\"], center=new_center)\n",
+      "slc.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "where we can see the emission has been smeared into this velocity slice from neighboring slices due to the thermal broadening. \n",
+      "\n",
+      "Finally, the \"velocity\" or \"spectral\" axis of the cube can be changed to a different unit, such as wavelength, frequency, or energy: "
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print cube2.vbins[0], cube2.vbins[-1]\n",
+      "cube2.transform_spectral_axis(400.0,\"nm\")\n",
+      "print cube2.vbins[0], cube2.vbins[-1]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "If a FITS file is now written from the cube, the spectral axis will be in the new units. To reset the spectral axis back to the original velocity units:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube2.reset_spectral_axis()\n",
+      "print cube2.vbins[0], cube2.vbins[-1]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
     }
    ],
    "metadata": {}
   }
  ]
-}
+}
\ No newline at end of file


https://bitbucket.org/yt_analysis/yt/commits/93d77640b416/
Changeset:   93d77640b416
Branch:      yt
User:        jzuhone
Date:        2014-10-22 14:24:45+00:00
Summary:     Merge
Affected #:  38 files

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/install_script.sh
--- a/doc/install_script.sh
+++ b/doc/install_script.sh
@@ -987,6 +987,7 @@
 if !( ( ${DEST_DIR}/bin/python2.7 -c "import readline" 2>&1 )>> ${LOG_FILE})
 then
     if !( ( ${DEST_DIR}/bin/python2.7 -c "import gnureadline" 2>&1 )>> ${LOG_FILE})
+    then
         echo "Installing pure-python readline"
         ( ${DEST_DIR}/bin/pip install gnureadline 2>&1 ) 1>> ${LOG_FILE}
     fi

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/analyzing/analysis_modules/halo_transition.rst
--- a/doc/source/analyzing/analysis_modules/halo_transition.rst
+++ b/doc/source/analyzing/analysis_modules/halo_transition.rst
@@ -52,7 +52,7 @@
    data_ds = yt.load('Enzo_64/RD0006/RedshiftOutput0006')
    hc = HaloCatalog(data_ds=data_ds, finder_method='hop')
    hc.create()
-   ad = hc.all_data()
+   ad = hc.halos_ds.all_data()
    masses = ad['particle_mass'][:]
 
 

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/cookbook/gadget_notebook.rst
--- /dev/null
+++ b/doc/source/cookbook/gadget_notebook.rst
@@ -0,0 +1,7 @@
+.. _gadget-notebook:
+
+Using yt to view and analyze Gadget outputs
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+.. notebook:: yt_gadget_analysis.ipynb
+

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/cookbook/tipsy_and_yt.ipynb
--- a/doc/source/cookbook/tipsy_and_yt.ipynb
+++ b/doc/source/cookbook/tipsy_and_yt.ipynb
@@ -1,7 +1,16 @@
 {
  "metadata": {
+  "kernelspec": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "display_name": "IPython (Python 2)",
+   "language": "python",
+   "name": "python2"
+  },
   "name": "",
-  "signature": "sha256:2ae8b1599fa35495fa1bb8deb1c67094e3529e70093b30e20354122cd9403d9d"
+  "signature": "sha256:1f6e5cf50123ad75676f035a2a36cd60f4987832462907b9cb78cb25548d8afd"
  },
  "nbformat": 3,
  "nbformat_minor": 0,
@@ -10,14 +19,6 @@
    "cells": [
     {
      "cell_type": "heading",
-     "level": 1,
-     "metadata": {},
-     "source": [
-      "Using yt to view and analyze Tipsy outputs from Gasoline"
-     ]
-    },
-    {
-     "cell_type": "heading",
      "level": 2,
      "metadata": {},
      "source": [
@@ -193,4 +194,4 @@
    "metadata": {}
   }
  ]
-}
+}
\ No newline at end of file

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/cookbook/yt_gadget_analysis.ipynb
--- /dev/null
+++ b/doc/source/cookbook/yt_gadget_analysis.ipynb
@@ -0,0 +1,263 @@
+{
+ "metadata": {
+  "kernelspec": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "display_name": "IPython (Python 2)",
+   "language": "python",
+   "name": "python2"
+  },
+  "name": "",
+  "signature": "sha256:42e2b7cc4c70a501432f24bc0d62d0723605d50196399148dd365d28387dd55d"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "heading",
+     "level": 2,
+     "metadata": {},
+     "source": [
+      "Loading the data"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "First we set up our imports:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import yt\n",
+      "import numpy as np\n",
+      "import yt.units as units\n",
+      "import pylab"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "First we load the data set, specifying both the unit length/mass/velocity, as well as the size of the bounding box (which should encapsulate all the particles in the data set)\n",
+      "\n",
+      "At the end, we flatten the data into \"ad\" in case we want access to the raw simulation data"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      ">This dataset is available for download at http://yt-project.org/data/GadgetDiskGalaxy.tar.gz (430 MB)."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "fname = 'GadgetDiskGalaxy/snapshot_200.hdf5'\n",
+      "\n",
+      "unit_base = {'UnitLength_in_cm'         : 3.08568e+21,\n",
+      "             'UnitMass_in_g'            :   1.989e+43,\n",
+      "             'UnitVelocity_in_cm_per_s' :      100000}\n",
+      "\n",
+      "bbox_lim = 1e5 #kpc\n",
+      "\n",
+      "bbox = [[-bbox_lim,bbox_lim],\n",
+      "        [-bbox_lim,bbox_lim],\n",
+      "        [-bbox_lim,bbox_lim]]\n",
+      " \n",
+      "ds = yt.load(fname,unit_base=unit_base,bounding_box=bbox)\n",
+      "ds.index\n",
+      "ad= ds.all_data()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Let's make a projection plot to look at the entire volume"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "px = yt.ProjectionPlot(ds, 'x', ('gas', 'density'))\n",
+      "px.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Let's print some quantities about the domain, as well as the physical properties of the simulation\n"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print 'left edge: ',ds.domain_left_edge\n",
+      "print 'right edge: ',ds.domain_right_edge\n",
+      "print 'center: ',ds.domain_center"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "We can also see the fields that are available to query in the dataset"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "sorted(ds.field_list)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Let's create a data object that represents the full simulation domain, and find the total mass in gas and dark matter particles contained in it:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "ad = ds.all_data()\n",
+      "\n",
+      "# total_mass returns a list, representing the total gas and dark matter + stellar mass, respectively\n",
+      "print [tm.in_units('Msun') for tm in ad.quantities.total_mass()]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Now let's say we want to zoom in on the box (since clearly the bounding we chose initially is much larger than the volume containing the gas particles!), and center on wherever the highest gas density peak is.  First, let's find this peak:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "density = ad[(\"PartType0\",\"density\")]\n",
+      "wdens = np.where(density == np.max(density))\n",
+      "coordinates = ad[(\"PartType0\",\"Coordinates\")]\n",
+      "center = coordinates[wdens][0]\n",
+      "print 'center = ',center"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Set up the box to zoom into"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "new_box_size = ds.quan(250,'code_length')\n",
+      "\n",
+      "left_edge = center - new_box_size/2\n",
+      "right_edge = center + new_box_size/2\n",
+      "\n",
+      "print new_box_size.in_units('Mpc')\n",
+      "print left_edge.in_units('Mpc')\n",
+      "print right_edge.in_units('Mpc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "ad2= ds.region(center=center, left_edge=left_edge, right_edge=right_edge)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Using this new data object, let's confirm that we're only looking at a subset of the domain by first calculating thte total mass in gas and particles contained in the subvolume:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print [tm.in_units('Msun') for tm in ad.quantities.total_mass()]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "And then by visualizing what the new zoomed region looks like"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "px = yt.ProjectionPlot(ds, 'x', ('gas', 'density'), center=center, width=new_box_size)\n",
+      "px.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Cool - there's a disk galaxy there!"
+     ]
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/developing/creating_frontend.rst
--- a/doc/source/developing/creating_frontend.rst
+++ b/doc/source/developing/creating_frontend.rst
@@ -7,14 +7,14 @@
             have a question about making a custom derived quantity, please
             contact the mailing list.
 
-yt is designed to support analysis and visualization of data from multiple
-different simulation codes, although it has so far been most successfully
-applied to Adaptive Mesh Refinement (AMR) data. For a list of codes and the
-level of support they enjoy, see :ref:`code-support`.
+yt is designed to support analysis and visualization of data from
+multiple different simulation codes. For a list of codes and the level
+of support they enjoy, see :ref:`code-support`.
 
-We'd like to support a broad range of codes, both AMR-based and otherwise. To
-add support for a new code, a few things need to be put into place. These
-necessary structures can be classified into a couple categories:
+We'd like to support a broad range of codes, both Adaptive Mesh
+Refinement (AMR)-based and otherwise. To add support for a new code, a
+few things need to be put into place. These necessary structures can
+be classified into a couple categories:
 
  * Data meaning: This is the set of parameters that convert the data into
    physically relevant units; things like spatial and mass conversions, time
@@ -33,73 +33,147 @@
 If you are interested in adding a new code, be sure to drop us a line on
 `yt-dev <http://lists.spacepope.org/listinfo.cgi/yt-dev-spacepope.org>`_!
 
-To get started, make a new directory in ``yt/frontends`` with the name of your
-code -- you can start by copying into it the contents of the ``stream``
-directory, which is a pretty empty format. You'll then have to create a subclass
-of ``Dataset``. This subclass will need to handle conversion between the
-different physical units and the code units; for the most part, the examples of
-``OrionDataset`` and ``EnzoDataset`` should be followed, but
-``ChomboDataset``, as a slightly newer addition, can also be used as an
-instructive example -- be sure to add an ``_is_valid`` classmethod that will
-verify if a filename is valid for that output type, as that is how "load" works.
+To get started, make a new directory in ``yt/frontends`` with the name
+of your code.  Copying the contents of the ``yt/frontends/_skeleton``
+directory will add a lot of boilerplate for the required classes and
+methods that are needed.  In particular, you'll have to create a
+subclass of ``Dataset`` in the data_structures.py file. This subclass
+will need to handle conversion between the different physical units
+and the code units (typically in the ``_set_code_unit_attributes()``
+method), read in metadata describing the overall data on disk (via the
+``_parse_parameter_file()`` method), and provide a ``classmethod``
+called ``_is_valid()`` that lets the ``yt.load`` method help identify an
+input file as belonging to *this* particular ``Dataset`` subclass.
+For the most part, the examples of
+``yt.frontends.boxlib.data_structures.OrionDataset`` and
+``yt.frontends.enzo.data_structures.EnzoDataset`` should be followed,
+but ``yt.frontends.chombo.data_structures.ChomboDataset``, as a
+slightly newer addition, can also be used as an instructive example.
 
-A new set of fields must be added in the file ``fields.py`` in that directory.
-For the most part this means subclassing ``CodeFieldInfoContainer`` and adding
-the necessary fields specific to that code. Here is the Chombo field container:
+A new set of fields must be added in the file ``fields.py`` in your
+new directory.  For the most part this means subclassing 
+``FieldInfoContainer`` and adding the necessary fields specific to
+your code. Here is a snippet from the base BoxLib field container:
 
 .. code-block:: python
 
-    from UniversalFields import *
-    class ChomboFieldContainer(CodeFieldInfoContainer):
-        _shared_state = {}
-        _field_list = {}
-    ChomboFieldInfo = ChomboFieldContainer()
-    add_chombo_field = ChomboFieldInfo.add_field
+    from yt.fields.field_info_container import FieldInfoContainer
+    class BoxlibFieldInfo(FieldInfoContainer):
+        known_other_fields = (
+            ("density", (rho_units, ["density"], None)),
+	    ("eden", (eden_units, ["energy_density"], None)),
+	    ("xmom", (mom_units, ["momentum_x"], None)),
+	    ("ymom", (mom_units, ["momentum_y"], None)),
+	    ("zmom", (mom_units, ["momentum_z"], None)),
+	    ("temperature", ("K", ["temperature"], None)),
+	    ("Temp", ("K", ["temperature"], None)),
+	    ("x_velocity", ("cm/s", ["velocity_x"], None)),
+	    ("y_velocity", ("cm/s", ["velocity_y"], None)),
+	    ("z_velocity", ("cm/s", ["velocity_z"], None)),
+	    ("xvel", ("cm/s", ["velocity_x"], None)),
+	    ("yvel", ("cm/s", ["velocity_y"], None)),
+	    ("zvel", ("cm/s", ["velocity_z"], None)),
+	)
 
-The field container is a shared state object, which is why we explicitly set
-``_shared_state`` equal to a mutable.
+	known_particle_fields = (
+	    ("particle_mass", ("code_mass", [], None)),
+	    ("particle_position_x", ("code_length", [], None)),
+	    ("particle_position_y", ("code_length", [], None)),
+	    ("particle_position_z", ("code_length", [], None)),
+	    ("particle_momentum_x", (mom_units, [], None)),
+	    ("particle_momentum_y", (mom_units, [], None)),
+	    ("particle_momentum_z", (mom_units, [], None)),
+	    ("particle_angmomen_x", ("code_length**2/code_time", [], None)),
+	    ("particle_angmomen_y", ("code_length**2/code_time", [], None)),
+	    ("particle_angmomen_z", ("code_length**2/code_time", [], None)),
+	    ("particle_id", ("", ["particle_index"], None)),
+	    ("particle_mdot", ("code_mass/code_time", [], None)),
+	)
+
+The tuples, ``known_other_fields`` and ``known_particle_fields``
+contain entries, which are tuples of the form ``("name", ("units",
+["fields", "to", "alias"], "display_name"))``.  ``"name"`` is the name
+of a field stored on-disk in the dataset. ``"units"`` corresponds to
+the units of that field.  The list ``["fields", "to", "alias"]``
+allows you to specify additional aliases to this particular field; for
+example, if your on-disk field for the x-direction velocity were
+``"x-direction-velocity"``, maybe you'd prefer to alias to the more
+terse name of ``"xvel"``.  ``"display_name"`` is an optional parameter
+that can be used to specify how you want the field to be displayed on
+a plot; this can be LaTeX code, for example the density field could
+have a display name of ``r"\rho"``.  Omitting the ``"display_name"``
+will result in using a capitalized version of the ``"name"``.
 
 Data Localization Structures
 ----------------------------
 
-As of right now, the "grid patch" mechanism is going to remain in yt, however in
-the future that may change. As such, some other output formats -- like Gadget --
-may be shoe-horned in, slightly.
+These functions and classes let yt know about how the arrangement of
+data on disk corresponds to the physical arrangement of data within
+the simulation.  yt has grid datastructures for handling both
+patch-based and octree-based AMR codes.  The terms 'patch-based'
+and 'octree-based' are used somewhat loosely here.  For example,
+traditionally, the FLASH code used the paramesh AMR library, which is
+based on a tree structure, but the FLASH frontend in yt utilizes yt's
+patch-based datastructures.  It is up to the frontend developer to
+determine which yt datastructures best match the datastructures of
+their simulation code.
 
-Hierarchy
-^^^^^^^^^
+Both approaches -- patch-based and octree-based -- have a concept of a
+*Hierarchy* or *Index* (used somewhat interchangeably in the code) of
+datastructures and something that describes the elements that make up
+the Hierarchy or Index.  For patch-based codes, the Index is a
+collection of ``AMRGridPatch`` objects that describe a block of zones.
+For octree-based codes, the Index contains datastructures that hold
+information about the individual octs, namely an ``OctreeContainer``.
 
-To set up data localization, an ``AMRHierarchy`` subclass must be added in the
-file ``data_structures.py``. The index object must override the following
-methods:
+Hierarchy or Index
+^^^^^^^^^^^^^^^^^^
 
- * ``_detect_fields``: ``self.field_list`` must be populated as a list of
-   strings corresponding to "native" fields in the data files.
- * ``_setup_classes``: it's probably safe to crib this from one of the other
-   ``AMRHierarchy`` subclasses.
- * ``_count_grids``: this must set self.num_grids to be the total number of
-   grids in the simulation.
- * ``_parse_index``: this must fill in ``grid_left_edge``,
+To set up data localization, a ``GridIndex`` subclass for patch-based
+codes or an ``OctreeIndex`` subclass for octree-based codes must be
+added in the file ``data_structures.py``. Examples of these different
+types of ``Index`` can be found in, for example, the
+``yt.frontends.chombo.data_structures.ChomboHierarchy`` for patch-based
+codes and ``yt.frontends.ramses.data_structures.RAMSESIndex`` for
+octree-based codes.  
+
+For the most part, the ``GridIndex`` subclass must override (at a
+minimum) the following methods:
+
+ * ``_detect_output_fields()``: ``self.field_list`` must be populated as a list
+   of strings corresponding to "native" fields in the data files.
+ * ``_count_grids()``: this must set ``self.num_grids`` to be the total number
+   of grids (equivalently ``AMRGridPatch``'es) in the simulation.
+ * ``_parse_index()``: this must fill in ``grid_left_edge``,
    ``grid_right_edge``, ``grid_particle_count``, ``grid_dimensions`` and
-   ``grid_levels`` with the appropriate information. Additionally, ``grids``
-   must be an array of grid objects that already know their IDs.
- * ``_populate_grid_objects``: this initializes the grids by calling
-   ``_prepare_grid`` and ``_setup_dx`` on all of them.  Additionally, it should
-   set up ``Children`` and ``Parent`` lists on each grid object.
- * ``_setup_unknown_fields``: If a field is in the data file that yt doesn't
-   already know, this is where you make a guess at it.
- * ``_setup_derived_fields``: ``self.derived_field_list`` needs to be made a
-   list of strings that correspond to all derived fields valid for this
-   index.
+   ``grid_levels`` with the appropriate information.  Each of these variables 
+   is an array, with an entry for each of the ``self.num_grids`` grids.  
+   Additionally, ``grids``  must be an array of ``AMRGridPatch`` objects that 
+   already know their IDs.
+ * ``_populate_grid_objects()``: this initializes the grids by calling
+   ``_prepare_grid()`` and ``_setup_dx()`` on all of them.  Additionally, it 
+   should set up ``Children`` and ``Parent`` lists on each grid object.
 
-For the most part, the ``ChomboHierarchy`` should be the first place to look for
-hints on how to do this; ``EnzoHierarchy`` is also instructive.
+The ``OctreeIndex`` has somewhat analogous methods, but often with
+different names; both ``OctreeIndex`` and ``GridIndex`` are subclasses
+of the ``Index`` class.  In particular, for the ``OctreeIndex``, the
+method ``_initialize_oct_handler()`` setups up much of the oct
+metadata that is analogous to the grid metadata created in the
+``GridIndex`` methods ``_count_grids()``, ``_parse_index()``, and
+``_populate_grid_objects()``.
 
 Grids
 ^^^^^
 
-A new grid object, subclassing ``AMRGridPatch``, will also have to be added.
-This should go in ``data_structures.py``. For the most part, this may be all
+.. note:: This section only applies to the approach using yt's patch-based
+	  datastructures.  For the octree-based approach, one does not create
+	  a grid object, but rather an ``OctreeSubset``, which has methods
+	  for filling out portions of the octree structure.  Again, see the
+	  code in ``yt.frontends.ramses.data_structures`` for an example of
+	  the octree approach.
+
+A new grid object, subclassing ``AMRGridPatch``, will also have to be added in
+``data_structures.py``. For the most part, this may be all
 that is needed:
 
 .. code-block:: python
@@ -115,32 +189,46 @@
             self.Level = level
 
 
-Even the most complex grid object, ``OrionGrid``, is still relatively simple.
+Even one of the more complex grid objects,
+``yt.frontends.boxlib.BoxlibGrid``, is still relatively simple.
 
 Data Reading Functions
 ----------------------
 
-In ``io.py``, there are a number of IO handlers that handle the mechanisms by
-which data is read off disk.  To implement a new data reader, you must subclass
-``BaseIOHandler`` and override the following methods:
+In ``io.py``, there are a number of IO handlers that handle the
+mechanisms by which data is read off disk.  To implement a new data
+reader, you must subclass ``BaseIOHandler``.  The various frontend IO
+handlers are stored in an IO registry - essentially a dictionary that
+uses the name of the frontend as a key, and the specific IO handler as
+a value.  It is important, therefore, to set the ``dataset_type``
+attribute of your subclass, which is what is used as the key in the IO
+registry.  For example:
 
- * ``_read_field_names``: this routine accepts a grid object and must return all
-   the fields in the data file affiliated with that grid. It is used at the
-   initialization of the ``AMRHierarchy`` but likely not later.
- * ``modify``: This accepts a field from a data file and returns it ready to be
-   used by yt. This is used in Enzo data for preloading.
- * ``_read_data_set``: This accepts a grid object and a field name and must
-   return that field, ready to be used by yt as a NumPy array. Note that this
-   presupposes that any actions done in ``modify`` (above) have been executed.
- * ``_read_data_slice``: This accepts a grid object, a field name, an axis and
-   an (integer) coordinate, and it must return a slice through the array at that
-   value.
- * ``preload``: (optional) This accepts a list of grids and a list of datasets
-   and it populates ``self.queue`` (a dict keyed by grid id) with dicts of
-   datasets.
- * ``_read_exception``: (property) This is a tuple of exceptions that can be
-   raised by the data reading to indicate a field does not exist in the file.
+.. code-block:: python
 
+    class IOHandlerBoxlib(BaseIOHandler):
+        _dataset_type = "boxlib_native"
+	...
+
+At a minimum, one should also override the following methods
+
+* ``_read_fluid_selection()``: this receives a collection of data "chunks", a 
+  selector describing which "chunks" you are concerned with, a list of fields,
+  and the size of the data to read.  It should create and return a dictionary 
+  whose keys are the fields, and whose values are numpy arrays containing the 
+  data.  The data should actually be read via the ``_read_chunk_data()`` 
+  method.
+* ``_read_chunk_data()``: this method receives a "chunk" of data along with a 
+  list of fields we want to read.  It loops over all the grid objects within 
+  the "chunk" of data and reads from disk the specific fields, returning a 
+  dictionary whose keys are the fields and whose values are numpy arrays of
+  the data.
+
+If your dataset has particle information, you'll want to override the
+``_read_particle_coords()`` and ``read_particle_fields()`` methods as
+well.  Each code is going to read data from disk in a different
+fashion, but the ``yt.frontends.boxlib.io.IOHandlerBoxlib`` is a
+decent place to start.
 
 And that just about covers it. Please feel free to email
 `yt-users <http://lists.spacepope.org/listinfo.cgi/yt-users-spacepope.org>`_ or

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/examining/loading_data.rst
--- a/doc/source/examining/loading_data.rst
+++ b/doc/source/examining/loading_data.rst
@@ -983,6 +983,8 @@
 onto the grid, you can also effectively mimic what your data would look like at
 lower resolution.
 
+See :ref:`gadget-notebook` for an example.
+
 .. _loading-tipsy-data:
 
 Tipsy Data

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 doc/source/visualizing/plots.rst
--- a/doc/source/visualizing/plots.rst
+++ b/doc/source/visualizing/plots.rst
@@ -469,6 +469,21 @@
    slc.set_log('density', False)
    slc.save()
 
+Specifically, a field containing both positive and negative values can be plotted
+with symlog scale, by seting the boolean to be ``True`` and providing an extra
+parameter ``linthresh``. In the region around zero (when the log scale approaches
+to infinity), the linear scale will be applied to the region ``(-linthresh, linthresh)``
+and stretched relative to the logarithmic range. You can also plot a positive field 
+under symlog scale with the linear range of ``(0, linthresh)``.
+
+.. python-script::
+
+   import yt
+   ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
+   slc = yt.SlicePlot(ds, 'z', 'x-velocity', width=(30,'kpc'))
+   slc.set_log('x-velocity', True, linthresh=1.e1)
+   slc.save()
+
 Lastly, the :meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_zlim`
 function makes it possible to set a custom colormap range.
 
@@ -531,6 +546,26 @@
    slc.set_buff_size(1600)
    slc.save()
 
+Turning off minorticks
+~~~~~~~~~~~~~~~~~~~~~~
+
+By default minorticks for the x and y axes are turned on.
+The minorticks may be removed using the
+:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_minorticks`
+function, which either accepts a specific field name including the 'all' alias
+and the desired state for the plot as 'on' or 'off'. There is also an analogous
+:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_cbar_minorticks`
+function for the colorbar axis.
+
+.. python-script::
+
+   import yt
+   ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
+   slc = yt.SlicePlot(ds, 'z', 'density', width=(10,'kpc'))
+   slc.set_minorticks('all', 'off')
+   slc.set_cbar_minorticks('all', 'off')
+   slc.save()
+
 .. _matplotlib-customization:
 
 Further customization via matplotlib
@@ -743,7 +778,7 @@
 Adjusting the plot units does not require recreating the histogram, so adjusting
 units will always be inexpensive, requiring only an in-place unit conversion.
 
-In the following example we create a a plot of the average density in solar
+In the following example we create a plot of the average density in solar
 masses per cubic parsec as a function of radius in kiloparsecs.
 
 .. python-script::
@@ -892,7 +927,7 @@
 ``fractional`` keyword to ``True``.  When set to ``True``, the value in each bin
 is divided by the sum total from all bins.  These can be turned into cumulative
 distribution functions (CDFs) by setting the ``accumulation`` keyword to
-``True``.  This will make is so that the value in any bin N is the cumulative
+``True``.  This will make it so that the value in any bin N is the cumulative
 sum of all bins from 0 to N.  The direction of the summation can be reversed by
 setting ``accumulation`` to ``-True``.  For ``PhasePlot``, the accumulation can
 be set independently for each axis by setting ``accumulation`` to a list of

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 setup.py
--- a/setup.py
+++ b/setup.py
@@ -6,6 +6,12 @@
 import subprocess
 import shutil
 import glob
+
+if sys.version_info < (2, 7):
+    print("yt currently requires Python version 2.7")
+    print("certain features may fail unexpectedly and silently with older versions.")
+    sys.exit(1)
+
 import setuptools
 from distutils.version import StrictVersion
 if StrictVersion(setuptools.__version__) < StrictVersion('0.7.0'):

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/analysis_modules/cosmological_observation/light_cone/light_cone.py
--- a/yt/analysis_modules/cosmological_observation/light_cone/light_cone.py
+++ b/yt/analysis_modules/cosmological_observation/light_cone/light_cone.py
@@ -186,13 +186,13 @@
             # Simple error check to make sure more than 100% of box depth
             # is never required.
             if self.light_cone_solution[q]["box_depth_fraction"] > 1.0:
-                mylog.debug(("Warning: box fraction required to go from " +
+                mylog.error(("Warning: box fraction required to go from " +
                              "z = %f to %f is %f") %
                             (self.light_cone_solution[q]["redshift"], z_next,
                              self.light_cone_solution[q]["box_depth_fraction"]))
-                mylog.debug(("Full box delta z is %f, but it is %f to the " +
+                mylog.error(("Full box delta z is %f, but it is %f to the " +
                              "next data dump.") %
-                            (self.light_cone_solution[q]["deltazMax"],
+                            (self.light_cone_solution[q]["dz_max"],
                              self.light_cone_solution[q]["redshift"]-z_next))
 
             # Get projection axis and center.

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/analysis_modules/cosmological_observation/light_ray/light_ray.py
--- a/yt/analysis_modules/cosmological_observation/light_ray/light_ray.py
+++ b/yt/analysis_modules/cosmological_observation/light_ray/light_ray.py
@@ -169,7 +169,7 @@
                                 (self.light_ray_solution[q]['redshift'], z_next,
                                  self.light_ray_solution[q]['traversal_box_fraction']))
                     mylog.error("Full box delta z is %f, but it is %f to the next data dump." %
-                                (self.light_ray_solution[q]['deltazMax'],
+                                (self.light_ray_solution[q]['dz_max'],
                                  self.light_ray_solution[q]['redshift']-z_next))
 
                 # Get dataset axis and center.

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/analysis_modules/photon_simulator/photon_simulator.py
--- a/yt/analysis_modules/photon_simulator/photon_simulator.py
+++ b/yt/analysis_modules/photon_simulator/photon_simulator.py
@@ -729,6 +729,7 @@
         events[tblhdu.header["CHANTYPE"]] = dchannel.astype(int)
 
         info = {"ChannelType" : tblhdu.header["CHANTYPE"],
+                "Mission" : tblhdu.header["MISSION"],
                 "Telescope" : tblhdu.header["TELESCOP"],
                 "Instrument" : tblhdu.header["INSTRUME"]}
         
@@ -789,6 +790,8 @@
             parameters["ARF"] = f["/arf"].value
         if "channel_type" in f:
             parameters["ChannelType"] = f["/channel_type"].value
+        if "mission" in f:
+            parameters["Mission"] = f["/mission"].value
         if "telescope" in f:
             parameters["Telescope"] = f["/telescope"].value
         if "instrument" in f:
@@ -831,6 +834,8 @@
             parameters["ARF"] = tblhdu["ARF"]
         if "CHANTYPE" in tblhdu.header:
             parameters["ChannelType"] = tblhdu["CHANTYPE"]
+        if "MISSION" in tblhdu.header:
+            parameters["Mission"] = tblhdu["MISSION"]
         if "TELESCOP" in tblhdu.header:
             parameters["Telescope"] = tblhdu["TELESCOP"]
         if "INSTRUME" in tblhdu.header:
@@ -920,11 +925,13 @@
         tbhdu.header["RADECSYS"] = "FK5"
         tbhdu.header["EQUINOX"] = 2000.0
         if "RMF" in self.parameters:
-            tbhdu.header["RMF"] = self.parameters["RMF"]
+            tbhdu.header["RESPFILE"] = self.parameters["RMF"]
         if "ARF" in self.parameters:
-            tbhdu.header["ARF"] = self.parameters["ARF"]
+            tbhdu.header["ANCRFILE"] = self.parameters["ARF"]
         if "ChannelType" in self.parameters:
             tbhdu.header["CHANTYPE"] = self.parameters["ChannelType"]
+        if "Mission" in self.parameters:
+            tbhdu.header["MISSION"] = self.parameters["Mission"]
         if "Telescope" in self.parameters:
             tbhdu.header["TELESCOP"] = self.parameters["Telescope"]
         if "Instrument" in self.parameters:
@@ -1041,6 +1048,8 @@
             f.create_dataset("/rmf", data=self.parameters["RMF"])
         if "ChannelType" in self.parameters:
             f.create_dataset("/channel_type", data=self.parameters["ChannelType"])
+        if "Mission" in self.parameters:
+            f.create_dataset("/mission", data=self.parameters["Mission"]) 
         if "Telescope" in self.parameters:
             f.create_dataset("/telescope", data=self.parameters["Telescope"])
         if "Instrument" in self.parameters:
@@ -1209,6 +1218,10 @@
                 tbhdu.header["ANCRFILE"] = self.parameters["ARF"]
             else:        
                 tbhdu.header["ANCRFILE"] = "none"
+            if self.parameters.has_key("Mission"):
+                tbhdu.header["MISSION"] = self.parameters["Mission"]
+            else:
+                tbhdu.header["MISSION"] = "none"
             if self.parameters.has_key("Telescope"):
                 tbhdu.header["TELESCOP"] = self.parameters["Telescope"]
             else:

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/data_objects/data_containers.py
--- a/yt/data_objects/data_containers.py
+++ b/yt/data_objects/data_containers.py
@@ -807,6 +807,8 @@
         self.fields = [k for k in self.field_data if k not in skip]
         if fields is not None:
             self.fields = ensure_list(fields) + self.fields
+        if len(self.fields) == 0:
+            raise ValueError("No fields found to plot in get_pw")
         (bounds, center, display_center) = \
             get_window_parameters(axis, center, width, self.ds)
         pw = PWViewerMPL(self, bounds, fields=self.fields, origin=origin,

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/data_objects/selection_data_containers.py
--- a/yt/data_objects/selection_data_containers.py
+++ b/yt/data_objects/selection_data_containers.py
@@ -333,61 +333,6 @@
     def normal(self):
         return self._norm_vec
 
-    def to_frb(self, width, resolution, height=None,
-               periodic=False):
-        r"""This function returns an ObliqueFixedResolutionBuffer generated
-        from this object.
-
-        An ObliqueFixedResolutionBuffer is an object that accepts a
-        variable-resolution 2D object and transforms it into an NxM bitmap that
-        can be plotted, examined or processed.  This is a convenience function
-        to return an FRB directly from an existing 2D data object.  Unlike the
-        corresponding to_frb function for other YTSelectionContainer2D objects, 
-        this does not accept a 'center' parameter as it is assumed to be 
-        centered at the center of the cutting plane.
-
-        Parameters
-        ----------
-        width : width specifier
-            This can either be a floating point value, in the native domain
-            units of the simulation, or a tuple of the (value, unit) style.
-            This will be the width of the FRB.
-        height : height specifier, optional
-            This will be the height of the FRB, by default it is equal to width.
-        resolution : int or tuple of ints
-            The number of pixels on a side of the final FRB.
-
-        Returns
-        -------
-        frb : :class:`~yt.visualization.fixed_resolution.ObliqueFixedResolutionBuffer`
-            A fixed resolution buffer, which can be queried for fields.
-
-        Examples
-        --------
-
-        >>> v, c = ds.find_max("density")
-        >>> sp = ds.sphere(c, (100.0, 'au'))
-        >>> L = sp.quantities.angular_momentum_vector()
-        >>> cutting = ds.cutting(L, c)
-        >>> frb = cutting.to_frb( (1.0, 'pc'), 1024)
-        >>> write_image(np.log10(frb["Density"]), 'density_1pc.png')
-        """
-        if iterable(width):
-            w, u = width
-            width = self.ds.quan(w, input_units = u)
-        if height is None:
-            height = width
-        elif iterable(height):
-            h, u = height
-            height = self.ds.quan(w, input_units = u)
-        if not iterable(resolution):
-            resolution = (resolution, resolution)
-        from yt.visualization.fixed_resolution import ObliqueFixedResolutionBuffer
-        bounds = (-width/2.0, width/2.0, -height/2.0, height/2.0)
-        frb = ObliqueFixedResolutionBuffer(self, bounds, resolution,
-                    periodic = periodic)
-        return frb
-
     def _generate_container_field(self, field):
         if self._current_chunk is None:
             self.index._identify_base_chunk(self)
@@ -455,7 +400,7 @@
         pw._setup_plots()
         return pw
 
-    def to_frb(self, width, resolution, height=None):
+    def to_frb(self, width, resolution, height=None, periodic=False):
         r"""This function returns an ObliqueFixedResolutionBuffer generated
         from this object.
 
@@ -477,6 +422,9 @@
             This will be the height of the FRB, by default it is equal to width.
         resolution : int or tuple of ints
             The number of pixels on a side of the final FRB.
+        periodic : boolean
+            This can be true or false, and governs whether the pixelization
+            will span the domain boundaries.
 
         Returns
         -------
@@ -505,7 +453,8 @@
             resolution = (resolution, resolution)
         from yt.visualization.fixed_resolution import ObliqueFixedResolutionBuffer
         bounds = (-width/2.0, width/2.0, -height/2.0, height/2.0)
-        frb = ObliqueFixedResolutionBuffer(self, bounds, resolution)
+        frb = ObliqueFixedResolutionBuffer(self, bounds, resolution,
+                                           periodic=periodic)
         return frb
 
 class YTDiskBase(YTSelectionContainer3D):
@@ -593,7 +542,7 @@
     """
     _type_name = "data_collection"
     _con_args = ("_obj_list",)
-    def __init__(self, center, obj_list, ds = None, field_parameters = None):
+    def __init__(self, obj_list, ds=None, field_parameters=None, center=None):
         YTSelectionContainer3D.__init__(self, center, ds, field_parameters)
         self._obj_ids = np.array([o.id - o._id_offset for o in obj_list],
                                 dtype="int64")

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/data_objects/static_output.py
--- a/yt/data_objects/static_output.py
+++ b/yt/data_objects/static_output.py
@@ -328,10 +328,10 @@
             mylog.debug("Creating Particle Union 'all'")
             pu = ParticleUnion("all", list(self.particle_types_raw))
             self.add_particle_union(pu)
+        mylog.info("Loading field plugins.")
+        self.field_info.load_all_plugins()
         deps, unloaded = self.field_info.check_derived_fields()
         self.field_dependencies.update(deps)
-        mylog.info("Loading field plugins.")
-        self.field_info.load_all_plugins()
 
     def setup_deprecated_fields(self):
         from yt.fields.field_aliases import _field_name_aliases

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/data_objects/tests/test_data_collection.py
--- a/yt/data_objects/tests/test_data_collection.py
+++ b/yt/data_objects/tests/test_data_collection.py
@@ -8,7 +8,7 @@
     # We decompose in different ways
     for nprocs in [1, 2, 4, 8]:
         ds = fake_random_ds(16, nprocs = nprocs)
-        coll = ds.data_collection(ds.domain_center, ds.index.grids)
+        coll = ds.data_collection(ds.index.grids)
         crho = coll["density"].sum(dtype="float64").to_ndarray()
         grho = np.sum([g["density"].sum(dtype="float64") for g in ds.index.grids],
                       dtype="float64")
@@ -16,7 +16,7 @@
         yield assert_equal, coll.size, ds.domain_dimensions.prod()
         for gi in range(ds.index.num_grids):
             grids = ds.index.grids[:gi+1]
-            coll = ds.data_collection(ds.domain_center, grids)
+            coll = ds.data_collection(grids)
             crho = coll["density"].sum(dtype="float64")
             grho = np.sum([g["density"].sum(dtype="float64") for g in grids],
                           dtype="float64")

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/fields/species_fields.py
--- a/yt/fields/species_fields.py
+++ b/yt/fields/species_fields.py
@@ -178,3 +178,4 @@
             # Skip it
             continue
         func(registry, ftype, species, particle_type)
+    add_nuclei_density_fields(registry, ftype, particle_type=particle_type)

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/_skeleton/api.py
--- a/yt/frontends/_skeleton/api.py
+++ b/yt/frontends/_skeleton/api.py
@@ -19,8 +19,7 @@
       SkeletonDataset
 
 from .fields import \
-      SkeletonFieldInfo, \
-      add_skeleton_field
+      SkeletonFieldInfo
 
 from .io import \
       IOHandlerSkeleton

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/_skeleton/data_structures.py
--- a/yt/frontends/_skeleton/data_structures.py
+++ b/yt/frontends/_skeleton/data_structures.py
@@ -13,18 +13,13 @@
 # The full license is in the file COPYING.txt, distributed with this software.
 #-----------------------------------------------------------------------------
 
-import numpy as np
-
-from yt.data_objects.grid_patch import \
-    AMRGridPatch
 from yt.data_objects.grid_patch import \
     AMRGridPatch
 from yt.geometry.grid_geometry_handler import \
     GridIndex
 from yt.data_objects.static_output import \
     Dataset
-from yt.utilities.lib.misc_utilities import \
-    get_box_grids_level
+from .fields import SkeletonFieldInfo
 
 class SkeletonGrid(AMRGridPatch):
     _id_offset = 0
@@ -41,20 +36,15 @@
     def __repr__(self):
         return "SkeletonGrid_%04i (%s)" % (self.id, self.ActiveDimensions)
 
-class SkeletonHierarchy(AMRHierarchy):
-
+class SkeletonHierarchy(GridIndex):
     grid = SkeletonGrid
     
     def __init__(self, ds, dataset_type='skeleton'):
         self.dataset_type = dataset_type
-        self.dataset = weakref.proxy(ds)
         # for now, the index file is the dataset!
         self.index_filename = self.dataset.parameter_filename
         self.directory = os.path.dirname(self.index_filename)
-        AMRHierarchy.__init__(self, ds, dataset_type)
-
-    def _initialize_data_storage(self):
-        pass
+        GridIndex.__init__(self, ds, dataset_type)
 
     def _detect_output_fields(self):
         # This needs to set a self.field_list that contains all the available,

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/_skeleton/io.py
--- a/yt/frontends/_skeleton/io.py
+++ b/yt/frontends/_skeleton/io.py
@@ -13,9 +13,6 @@
 # The full license is in the file COPYING.txt, distributed with this software.
 #-----------------------------------------------------------------------------
 
-import numpy as np
-import h5py
-
 from yt.utilities.io_handler import \
     BaseIOHandler
 

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/chombo/data_structures.py
--- a/yt/frontends/chombo/data_structures.py
+++ b/yt/frontends/chombo/data_structures.py
@@ -113,7 +113,8 @@
         self.directory = ds.fullpath
         self._handle = ds._handle
 
-        self.float_type = self._handle['Chombo_global'].attrs['testReal'].dtype.name
+        tr = self._handle['Chombo_global'].attrs.get("testReal", "float32")
+            
         self._levels = [key for key in self._handle.keys() if key.startswith('level')]
         GridIndex.__init__(self, ds, dataset_type)
 
@@ -156,12 +157,18 @@
         for key, val in self._handle.attrs.items():
             if key.startswith("particle"):
                 particle_fields.append(val)
-        self.field_list.extend([("io", c) for c in particle_fields])        
+        self.field_list.extend([("io", c) for c in particle_fields])
 
     def _count_grids(self):
         self.num_grids = 0
         for lev in self._levels:
-            self.num_grids += self._handle[lev]['Processors'].len()
+            d = self._handle[lev]
+            if 'Processors' in d:
+                self.num_grids += d['Processors'].len()
+            elif 'boxes' in d:
+                self.num_grids += d['boxes'].len()
+            else:
+                raise RuntimeError("Uknown file specification")
 
     def _parse_index(self):
         f = self._handle # shortcut
@@ -534,7 +541,8 @@
 
         # look for particle fields
         self.particle_filename = self.index_filename[:-4] + 'sink'
-        if not os.path.exists(self.particle_filename): return
+        if not os.path.exists(self.particle_filename):
+            return
         pfield_list = [("io", c) for c in self.io.particle_field_index.keys()]
         self.field_list.extend(pfield_list)
 

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/chombo/io.py
--- a/yt/frontends/chombo/io.py
+++ b/yt/frontends/chombo/io.py
@@ -25,6 +25,7 @@
     _dataset_type = "chombo_hdf5"
     _offset_string = 'data:offsets=0'
     _data_string = 'data:datatype=0'
+    _offsets = None
 
     def __init__(self, ds, *args, **kwargs):
         BaseIOHandler.__init__(self, ds, *args, **kwargs)
@@ -32,6 +33,29 @@
         self._handle = ds._handle
         self.dim = self._handle['Chombo_global/'].attrs['SpaceDim']
         self._read_ghost_info()
+        if self._offset_string not in self._handle['level_0']:
+            self._calculate_offsets()
+
+    def _calculate_offsets(self):
+        def box_size(corners):
+            size = 1
+            for idim in range(self.dim):
+                size *= (corners[idim+self.dim] - corners[idim] + 1)
+            return size
+
+        self._offsets = {}
+        num_comp = self._handle.attrs['num_components']
+        level = 0
+        while 1:
+            lname = 'level_%i' % level
+            if lname not in self._handle: break
+            boxes = self._handle['level_0']['boxes'].value
+            box_sizes = np.array([box_size(box) for box in boxes])
+
+            offsets = np.cumsum(box_sizes*num_comp, dtype='int64') 
+            offsets -= offsets[0]
+            self._offsets[level] = offsets
+            level += 1
 
     def _read_ghost_info(self):
         try:
@@ -41,7 +65,7 @@
             self.ghost = np.array(self.ghost)
         except KeyError:
             # assume zero ghosts if outputGhosts not present
-            self.ghost = np.zeros(self.dim)
+            self.ghost = np.zeros(self.dim, 'int64')
 
     _field_dict = None
     @property
@@ -80,7 +104,10 @@
         shape = grid.ActiveDimensions + 2*self.ghost
         boxsize = shape.prod()
 
-        grid_offset = lev[self._offset_string][grid._level_id]
+        if self._offsets is not None:
+            grid_offset = self._offsets[grid.Level][grid._level_id]
+        else:
+            grid_offset = lev[self._offset_string][grid._level_id]
         start = grid_offset+self.field_dict[field]*boxsize
         stop = start + boxsize
         data = lev[self._data_string][start:stop]
@@ -223,7 +250,14 @@
     # Figure out the format of the particle file
     with open(fn, 'r') as f:
         lines = f.readlines()
-    line = lines[1]
+
+    try:
+        line = lines[1]
+    except IndexError:
+        # a particle file exists, but there is only one line,
+        # so no sinks have been created yet.
+        index = {}
+        return index
 
     # The basic fields that all sink particles have
     index = {'particle_mass': 0,

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/enzo/fields.py
--- a/yt/frontends/enzo/fields.py
+++ b/yt/frontends/enzo/fields.py
@@ -20,9 +20,6 @@
     FieldInfoContainer
 from yt.units.yt_array import \
     YTArray
-from yt.fields.species_fields import \
-    add_nuclei_density_fields, \
-    add_species_field_by_density
 from yt.utilities.physical_constants import \
     mh, me, mp, \
     mass_sun_cgs
@@ -152,7 +149,6 @@
         for sp in species_names:
             self.add_species_field(sp)
             self.species_names.append(known_species_names[sp])
-        add_nuclei_density_fields(self, "gas")
 
     def setup_fluid_fields(self):
         # Now we conditionally load a few other things.

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/enzo/tests/test_outputs.py
--- a/yt/frontends/enzo/tests/test_outputs.py
+++ b/yt/frontends/enzo/tests/test_outputs.py
@@ -90,3 +90,13 @@
     ds = data_dir_load(ecp)
     # Now we test our species fields
     yield check_color_conservation(ds)
+
+ at requires_ds(ecp, big_data=True)
+def test_nuclei_density_fields():
+    ds = data_dir_load(ecp)
+    ad = ds.all_data()
+    yield assert_array_equal, ad["H_nuclei_density"], \
+      (ad["H_number_density"] + ad["H_p1_number_density"])
+    yield assert_array_equal, ad["He_nuclei_density"], \
+      (ad["He_number_density"] + ad["He_p1_number_density"] +
+       ad["He_p2_number_density"])

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -157,6 +157,8 @@
             naxis4 = 1
         for i, fits_file in enumerate(self.dataset._handle._fits_files):
             for j, hdu in enumerate(fits_file):
+                if isinstance(hdu, _astropy.pyfits.BinTableHDU):
+                    continue
                 if self._ensure_same_dims(hdu):
                     units = self._determine_image_units(hdu.header, known_units)
                     try:

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/halo_catalogs/owls_subfind/data_structures.py
--- a/yt/frontends/halo_catalogs/owls_subfind/data_structures.py
+++ b/yt/frontends/halo_catalogs/owls_subfind/data_structures.py
@@ -80,7 +80,7 @@
         # TODO: Add additional fields
         dsl = []
         units = {}
-        for dom in self.data_files[:1]:
+        for dom in self.data_files:
             fl, _units = self.io._identify_fields(dom)
             units.update(_units)
             dom._calculate_offsets(fl)
@@ -208,14 +208,15 @@
 
     @classmethod
     def _is_valid(self, *args, **kwargs):
+        need_groups = ['Constants', 'Header', 'Parameters', 'Units', 'FOF']
+        veto_groups = []
+        valid = True
         try:
-            fileh = h5py.File(args[0], mode='r')
-            if "Constants" in fileh["/"].keys() and \
-               "Header" in fileh["/"].keys() and \
-               "SUBFIND" in fileh["/"].keys():
-                fileh.close()
-                return True
-            fileh.close()
+            fh = h5py.File(args[0], mode='r')
+            valid = all(ng in fh["/"] for ng in need_groups) and \
+              not any(vg in fh["/"] for vg in veto_groups)
+            fh.close()
         except:
+            valid = False
             pass
-        return False
+        return valid

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/halo_catalogs/owls_subfind/io.py
--- a/yt/frontends/halo_catalogs/owls_subfind/io.py
+++ b/yt/frontends/halo_catalogs/owls_subfind/io.py
@@ -82,12 +82,13 @@
             with h5py.File(data_file.filename, "r") as f:
                 for ptype, field_list in sorted(ptf.items()):
                     pcount = data_file.total_particles[ptype]
+                    if pcount == 0: continue
                     coords = f[ptype]["CenterOfMass"].value.astype("float64")
                     coords = np.resize(coords, (pcount, 3))
                     x = coords[:, 0]
                     y = coords[:, 1]
                     z = coords[:, 2]
-                    mask = selector.select_points(x, y, z)
+                    mask = selector.select_points(x, y, z, 0.0)
                     del x, y, z
                     if mask is None: continue
                     for field in field_list:
@@ -113,8 +114,9 @@
                         yield (ptype, field), data
 
     def _initialize_index(self, data_file, regions):
-        pcount = sum(self._count_particles(data_file).values())
+        pcount = sum(data_file.total_particles.values())
         morton = np.empty(pcount, dtype='uint64')
+        if pcount == 0: return morton
         mylog.debug("Initializing index % 5i (% 7i particles)",
                     data_file.file_id, pcount)
         ind = 0
@@ -122,12 +124,11 @@
             if not f.keys(): return None
             dx = np.finfo(f["FOF"]['CenterOfMass'].dtype).eps
             dx = 2.0*self.ds.quan(dx, "code_length")
-            
-            for ptype, pattr in zip(["FOF", "SUBFIND"],
-                                    ["Number_of_groups", "Number_of_subgroups"]):
-                my_pcount = f[ptype].attrs[pattr]
+
+            for ptype in data_file.ds.particle_types_raw:
+                if data_file.total_particles[ptype] == 0: continue
                 pos = f[ptype]["CenterOfMass"].value.astype("float64")
-                pos = np.resize(pos, (my_pcount, 3))
+                pos = np.resize(pos, (data_file.total_particles[ptype], 3))
                 pos = data_file.ds.arr(pos, "code_length")
                 
                 # These are 32 bit numbers, so we give a little lee-way.
@@ -151,17 +152,24 @@
 
     def _count_particles(self, data_file):
         with h5py.File(data_file.filename, "r") as f:
-            # We need this to figure out where the offset fields are stored.
-            data_file.total_offset = f["SUBFIND"].attrs["Number_of_groups"]
-            return {"FOF": f["FOF"].attrs["Number_of_groups"],
-                    "SUBFIND": f["FOF"].attrs["Number_of_subgroups"]}
+            pcount = {"FOF": f["FOF"].attrs["Number_of_groups"]}
+            if "SUBFIND" in f:
+                # We need this to figure out where the offset fields are stored.
+                data_file.total_offset = f["SUBFIND"].attrs["Number_of_groups"]
+                pcount["SUBFIND"] = f["FOF"].attrs["Number_of_subgroups"]
+            else:
+                data_file.total_offset = 0
+                pcount["SUBFIND"] = 0
+            return pcount
 
     def _identify_fields(self, data_file):
-        fields = [(ptype, "particle_identifier")
-                  for ptype in self.ds.particle_types_raw]
+        fields = []
         pcount = data_file.total_particles
+        if sum(pcount.values()) == 0: return fields, {}
         with h5py.File(data_file.filename, "r") as f:
             for ptype in self.ds.particle_types_raw:
+                if data_file.total_particles[ptype] == 0: continue
+                fields.append((ptype, "particle_identifier"))
                 my_fields, my_offset_fields = \
                   subfind_field_list(f[ptype], ptype, data_file.total_particles)
                 fields.extend(my_fields)

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/halo_catalogs/owls_subfind/tests/test_outputs.py
--- /dev/null
+++ b/yt/frontends/halo_catalogs/owls_subfind/tests/test_outputs.py
@@ -0,0 +1,40 @@
+"""
+OWLSSubfind frontend tests using owls_fof_halos datasets
+
+
+
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, 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.
+#-----------------------------------------------------------------------------
+
+from yt.testing import *
+from yt.utilities.answer_testing.framework import \
+    FieldValuesTest, \
+    requires_ds, \
+    data_dir_load
+
+_fields = ("particle_position_x", "particle_position_y",
+           "particle_position_z", "particle_mass")
+
+g8 = "owls_fof_halos/groups_008/group_008.0.hdf5"
+ at requires_ds(g8)
+def test_fields_g8():
+    ds = data_dir_load(g8)
+    yield assert_equal, str(ds), "group_008.0.hdf5"
+    for field in _fields:
+        yield FieldValuesTest(g8, field)
+
+# a dataset with empty files
+g3 = "owls_fof_halos/groups_003/group_003.0.hdf5"
+ at requires_ds(g3)
+def test_fields_g3():
+    ds = data_dir_load(g3)
+    yield assert_equal, str(ds), "group_003.0.hdf5"
+    for field in _fields:
+        yield FieldValuesTest(g3, field)

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/sph/data_structures.py
--- a/yt/frontends/sph/data_structures.py
+++ b/yt/frontends/sph/data_structures.py
@@ -379,7 +379,7 @@
     @classmethod
     def _is_valid(self, *args, **kwargs):
         need_groups = ['Constants', 'Header', 'Parameters', 'Units']
-        veto_groups = ['SUBFIND',
+        veto_groups = ['SUBFIND', 'FOF',
                        'PartType0/ChemistryAbundances', 
                        'PartType0/ChemicalAbundances',
                        'RuntimePars', 'HashTable']

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/stream/data_structures.py
--- a/yt/frontends/stream/data_structures.py
+++ b/yt/frontends/stream/data_structures.py
@@ -364,8 +364,10 @@
     _additional_fields = ()
 
     @property
-    def all_fields(self): 
-        fields = list(self._additional_fields) + self[0].keys()
+    def all_fields(self):
+        self_fields = chain.from_iterable(s.keys() for s in self.values())
+        self_fields = list(set(self_fields))
+        fields = list(self._additional_fields) + self_fields
         fields = list(set(fields))
         return fields
 
@@ -471,9 +473,8 @@
             field_units[k] = v.units
             new_data[k] = v.copy().d
         data = new_data
-    elif all([isinstance(val, np.ndarray) for val in data.values()]):
-        field_units = {field:'' for field in data.keys()}
-    elif all([(len(val) == 2) for val in data.values()]):
+    elif all([((not isinstance(val, np.ndarray)) and (len(val) == 2))
+             for val in data.values()]):
         new_data, field_units = {}, {}
         for field in data:
             try:
@@ -489,6 +490,9 @@
                 raise RuntimeError("The data dict appears to be invalid.\n" +
                                    str(e))
         data = new_data
+    elif all([iterable(val) for val in data.values()]):
+        field_units = {field:'' for field in data.keys()}
+        data = dict((field, np.array(val)) for field, val in data.iteritems())
     else:
         raise RuntimeError("The data dict appears to be invalid. "
                            "The data dictionary must map from field "
@@ -697,7 +701,7 @@
                    field_units=None, bbox=None, sim_time=0.0, length_unit=None,
                    mass_unit=None, time_unit=None, velocity_unit=None,
                    magnetic_unit=None, periodicity=(True, True, True),
-                   geometry = "cartesian"):
+                   geometry = "cartesian", refine_by=2):
     r"""Load a set of grids of data into yt as a
     :class:`~yt.frontends.stream.data_structures.StreamHandler`.
     This should allow a sequence of grids of varying resolution of data to be
@@ -747,6 +751,8 @@
         each axis
     geometry : string
         "cartesian", "cylindrical" or "polar"
+    refine_by : integer
+        Specifies the refinement ratio between levels.  Defaults to 2.
 
     Examples
     --------
@@ -763,12 +769,13 @@
     ...          dimensions = [32, 32, 32],
     ...          number_of_particles = 0)
     ... ]
-    ... 
+    ...
     >>> for g in grid_data:
-    ...     g["Density"] = np.random.random(g["dimensions"]) * 2**g["level"]
+    ...     g["density"] = np.random.random(g["dimensions"]) * 2**g["level"]
     ...
-    >>> units = dict(Density='g/cm**3')
-    >>> ds = load_amr_grids(grid_data, [32, 32, 32], 1.0)
+    >>> units = dict(density='g/cm**3')
+    >>> ds = load_amr_grids(grid_data, [32, 32, 32], field_units=units,
+    ...                     length_unit=1.0)
     """
 
     domain_dimensions = np.array(domain_dimensions)
@@ -822,6 +829,11 @@
     if magnetic_unit is None:
         magnetic_unit = 'code_magnetic'
 
+    particle_types = {}
+
+    for grid in grid_data:
+        particle_types.update(set_particle_types(grid))
+
     handler = StreamHandler(
         grid_left_edges,
         grid_right_edges,
@@ -833,13 +845,13 @@
         sfh,
         field_units,
         (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),
-        particle_types=set_particle_types(grid_data[0])
+        particle_types=particle_types
     )
 
     handler.name = "AMRGridData"
     handler.domain_left_edge = domain_left_edge
     handler.domain_right_edge = domain_right_edge
-    handler.refine_by = 2
+    handler.refine_by = refine_by
     handler.dimensionality = 3
     handler.domain_dimensions = domain_dimensions
     handler.simulation_time = sim_time
@@ -1018,7 +1030,7 @@
     magnetic_unit : float
         Conversion factor from simulation magnetic units to gauss
     bbox : array_like (xdim:zdim, LE:RE), optional
-        Size of computational domain in units sim_unit_to_cm
+        Size of computational domain in units of the length_unit
     sim_time : float, optional
         The simulation time in seconds
     periodicity : tuple of booleans
@@ -1191,10 +1203,8 @@
     coordinates : array_like
         This should be of size (M,3) where M is the number of vertices
         indicated in the connectivity matrix.
-    sim_unit_to_cm : float
-        Conversion factor from simulation units to centimeters
     bbox : array_like (xdim:zdim, LE:RE), optional
-        Size of computational domain in units sim_unit_to_cm
+        Size of computational domain in units of the length unit.
     sim_time : float, optional
         The simulation time in seconds
     mass_unit : string

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/frontends/stream/tests/test_stream_amrgrids.py
--- a/yt/frontends/stream/tests/test_stream_amrgrids.py
+++ b/yt/frontends/stream/tests/test_stream_amrgrids.py
@@ -26,3 +26,25 @@
         p = ProjectionPlot(spf, 'x', ["density"], center='c', origin='native')
         return p
     yield assert_raises, YTIntDomainOverflow, make_proj
+
+def test_refine_by():
+    grid_data = []
+    ref_by = 4
+    lo = 0.0
+    hi = 1.0
+    fine_grid_width = (hi - lo) / ref_by
+    for level in range(2):
+        grid_dict = {}
+
+        grid_dict['left_edge'] = [0.0 + 0.5*fine_grid_width*level]*3
+        grid_dict['right_edge'] = [1.0 - 0.5*fine_grid_width*level]*3
+        grid_dict['dimensions'] = [8, 8, 8]
+        grid_dict['level'] = level
+
+        grid_dict['density'] = np.ones((8,8,8))
+
+        grid_data.append(grid_dict)
+
+    domain_dimensions = np.array([8, 8, 8])
+
+    spf = load_amr_grids(grid_data, domain_dimensions, refine_by=ref_by)

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/geometry/selection_routines.pyx
--- a/yt/geometry/selection_routines.pyx
+++ b/yt/geometry/selection_routines.pyx
@@ -727,9 +727,14 @@
                 if dobj.left_edge[i] < dobj.ds.domain_left_edge[i] or \
                    dobj.right_edge[i] > dobj.ds.domain_right_edge[i]:
                     raise RuntimeError(
-                        "Error: bad Region in non-periodic domain along dimension %s. "
-                        "Region left edge = %s, Region right edge = %s"
-                        "Dataset left edge = %s, Dataset right edge = %s" % \
+                        "Error: yt attempted to read outside the boundaries of "
+                        "a non-periodic domain along dimension %s.\n"
+                        "Region left edge = %s, Region right edge = %s\n"
+                        "Dataset left edge = %s, Dataset right edge = %s\n\n"
+                        "This commonly happens when trying to compute ghost cells "
+                        "up to the domain boundary. Two possible solutions are to "
+                        "load a smaller region that does not border the edge or "
+                        "override the periodicity for this dataset." % \
                         (i, dobj.left_edge[i], dobj.right_edge[i],
                          dobj.ds.domain_left_edge[i], dobj.ds.domain_right_edge[i])
                     )

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/units/yt_array.py
--- a/yt/units/yt_array.py
+++ b/yt/units/yt_array.py
@@ -44,29 +44,6 @@
     except: return False
     return True
 
-def ensure_unitless(func):
-    @wraps(func)
-    def wrapped(unit):
-        if unit != Unit():
-            raise RuntimeError(
-                "This operation is only defined for unitless quantities. "
-                "Received unit (%s)" % unit
-                )
-        return func(unit)
-    return wrapped
-
-def ensure_same_dimensions(func):
-    @wraps(func)
-    def wrapped(unit1, unit2):
-        if unit1 is None and not unit2.is_dimensionless:
-            raise RuntimeError
-        elif unit2 is None and not unit1.is_dimensionless:
-            raise RuntimeError
-        elif unit1.dimensions != unit2.dimensions:
-            raise RuntimeError
-        return func(unit1, unit2)
-    return wrapped
-
 def return_arr(func):
     @wraps(func)
     def wrapped(*args, **kwargs):
@@ -84,7 +61,6 @@
 def multiply_units(unit1, unit2):
     return unit1 * unit2
 
- at ensure_same_dimensions
 def preserve_units(unit1, unit2):
     return unit1
 
@@ -106,15 +82,9 @@
 def return_without_unit(unit):
     return None
 
- at ensure_unitless
-def unitless(unit):
-    return Unit()
-
- at ensure_same_dimensions
 def arctan2_unit(unit1, unit2):
     return Unit()
 
- at ensure_same_dimensions
 def comparison_unit(unit1, unit2):
     return None
 
@@ -979,12 +949,7 @@
             u = getattr(context[1][0], 'units', None)
             if u is None:
                 u = Unit()
-            try:
-                unit = self._ufunc_registry[context[0]](u)
-            # Catch the RuntimeError raised inside of ensure_same_dimensions
-            # Raise YTUnitOperationError up here since we know the context now
-            except RuntimeError:
-                raise YTUnitOperationError(context[0], u)
+            unit = self._ufunc_registry[context[0]](u)
             ret_class = type(self)
         elif context[0] in binary_operators:
             oper1 = coerce_iterable_units(context[1][0])
@@ -1014,12 +979,7 @@
                         raise YTUnitOperationError(context[0], unit1, unit2)
                     else:
                         raise YTUfuncUnitError(context[0], unit1, unit2)
-            try:
-                unit = self._ufunc_registry[context[0]](unit1, unit2)
-            # Catch the RuntimeError raised inside of ensure_same_dimensions
-            # Raise YTUnitOperationError up here since we know the context now
-            except RuntimeError:
-                raise YTUnitOperationError(context[0], unit1, unit2)
+            unit = self._ufunc_registry[context[0]](unit1, unit2)
         else:
             raise RuntimeError("Operation is not defined.")
         if unit is None:

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/visualization/base_plot_types.py
--- a/yt/visualization/base_plot_types.py
+++ b/yt/visualization/base_plot_types.py
@@ -109,12 +109,16 @@
             cax.set_position(caxrect)
             self.cax = cax
 
-    def _init_image(self, data, cbnorm, cmap, extent, aspect):
+    def _init_image(self, data, cbnorm, cblinthresh, cmap, extent, aspect):
         """Store output of imshow in image variable"""
         if (cbnorm == 'log10'):
             norm = matplotlib.colors.LogNorm()
         elif (cbnorm == 'linear'):
             norm = matplotlib.colors.Normalize()
+        elif (cbnorm == 'symlog'):
+            if cblinthresh is None:
+                cblinthresh = (data.max()-data.min())/10.
+            norm = matplotlib.colors.SymLogNorm(cblinthresh,vmin=data.min(), vmax=data.max())
         extent = [float(e) for e in extent]
         if isinstance(cmap, tuple):
             if has_brewer:
@@ -126,7 +130,16 @@
         self.image = self.axes.imshow(data.to_ndarray(), origin='lower',
                                       extent=extent, norm=norm, vmin=self.zmin,
                                       aspect=aspect, vmax=self.zmax, cmap=cmap)
-        self.cb = self.figure.colorbar(self.image, self.cax)
+        if (cbnorm == 'symlog'):
+            formatter = matplotlib.ticker.LogFormatterMathtext()
+            self.cb = self.figure.colorbar(self.image, self.cax, format=formatter)
+            yticks = list(-10**np.arange(np.floor(np.log10(-data.min())),\
+                          np.rint(np.log10(cblinthresh))-1, -1)) + [0] + \
+                     list(10**np.arange(np.rint(np.log10(cblinthresh)),\
+                          np.ceil(np.log10(data.max()))+1))
+            self.cb.set_ticks(yticks)
+        else:
+            self.cb = self.figure.colorbar(self.image, self.cax)
 
     def _repr_png_(self):
         canvas = FigureCanvasAgg(self.figure)

diff -r 5d42ecd2148572be9cec39c552443987aece9ffd -r 93d77640b4161deab1d3652a364bd902a60a3aa1 yt/visualization/plot_container.py
--- a/yt/visualization/plot_container.py
+++ b/yt/visualization/plot_container.py
@@ -80,6 +80,53 @@
         return args[0]
     return newfunc
 
+def get_log_minorticks(vmin, vmax):
+    """calculate positions of linear minorticks on a log colorbar
+
+    Parameters
+    ----------
+    vmin : float
+        the minimum value in the colorbar
+    vmax : float
+        the maximum value in the colorbar
+
+    """
+    expA = np.floor(np.log10(vmin))
+    expB = np.floor(np.log10(vmax))
+    cofA = np.ceil(vmin/10**expA)
+    cofB = np.floor(vmax/10**expB)
+    lmticks = []
+    while cofA*10**expA <= cofB*10**expB:
+        if expA < expB:
+            lmticks = np.hstack( (lmticks, np.linspace(cofA, 9, 10-cofA)*10**expA) )
+            cofA = 1
+            expA += 1
+        else:
+            lmticks = np.hstack( (lmticks, np.linspace(cofA, cofB, cofB-cofA+1)*10**expA) )
+            expA += 1
+    return np.array(lmticks)
+
+def get_symlog_minorticks(linthresh, vmin, vmax):
+    """calculate positions of linear minorticks on a symmetric log colorbar
+
+    Parameters
+    ----------
+    linthresh: float
+        the threshold for the linear region
+    vmin : float
+        the minimum value in the colorbar
+    vmax : float
+        the maximum value in the colorbar
+
+    """
+    if vmin >= 0 or vmax <= 0:
+        raise RuntimeError(
+            '''attempting to set minorticks for
+              a symlog plot with one-sided data:
+              got vmin = %s, vmax = %s''' % (vmin, vmax))
+    return np.hstack( (-get_log_minorticks(linthresh,-vmin)[::-1], 0,
+                        get_log_minorticks(linthresh, vmax)) )
+
 field_transforms = {}
 
 
@@ -102,6 +149,7 @@
 
 log_transform = FieldTransform('log10', np.log10, LogLocator())
 linear_transform = FieldTransform('linear', lambda x: x, LinearLocator())
+symlog_transform = FieldTransform('symlog', None, LogLocator())
 
 class PlotDictionary(defaultdict):
     def __getitem__(self, item):
@@ -143,11 +191,13 @@
         self._font_color = None
         self._xlabel = None
         self._ylabel = None
+        self._minorticks = {}
+        self._cbar_minorticks = {}
         self._colorbar_label = PlotDictionary(
             self.data_source, lambda: None)
 
     @invalidate_plot
-    def set_log(self, field, log):
+    def set_log(self, field, log, linthresh=None):
         """set a field to log or linear.
 
         Parameters
@@ -156,6 +206,8 @@
             the field to set a transform
         log : boolean
             Log on/off.
+        linthresh : float (must be positive)
+            linthresh will be enabled for symlog scale only when log is true
 
         """
         if field == 'all':
@@ -164,7 +216,13 @@
             fields = [field]
         for field in self.data_source._determine_fields(fields):
             if log:
-                self._field_transform[field] = log_transform
+                if linthresh is not None:
+                    if not linthresh > 0.: 
+                        raise ValueError('\"linthresh\" must be positive')
+                    self._field_transform[field] = symlog_transform
+                    self._field_transform[field].func = linthresh 
+                else:
+                    self._field_transform[field] = log_transform
             else:
                 self._field_transform[field] = linear_transform
         return self
@@ -271,6 +329,58 @@
             self.plots[field].zmax = myzmax
         return self
 
+    @invalidate_plot
+    def set_minorticks(self, field, state):
+        """turn minor ticks on or off in the current plot
+
+        Displaying minor ticks reduces performance; turn them off
+        using set_minorticks('all', 'off') if drawing speed is a problem.
+
+        Parameters
+        ----------
+        field : string
+            the field to remove minorticks
+        state : string
+            the state indicating 'on' or 'off'
+
+        """
+        if field == 'all':
+            fields = self.plots.keys()
+        else:
+            fields = [field]
+        for field in self.data_source._determine_fields(fields):
+            if state == 'on':
+                self._minorticks[field] = True
+            else:
+                self._minorticks[field] = False
+        return self
+
+    @invalidate_plot
+    def set_cbar_minorticks(self, field, state):
+        """turn colorbar minor ticks on or off in the current plot
+
+        Displaying minor ticks reduces performance; turn them off 
+        using set_cbar_minorticks('all', 'off') if drawing speed is a problem.
+
+        Parameters
+        ----------
+        field : string
+            the field to remove colorbar minorticks
+        state : string
+            the state indicating 'on' or 'off'
+
+        """
+        if field == 'all':
+            fields = self.plots.keys()
+        else:
+            fields = [field]
+        for field in self.data_source._determine_fields(fields):
+            if state == 'on':
+                self._cbar_minorticks[field] = True
+            else:
+                self._cbar_minorticks[field] = False
+        return self
+
     def setup_callbacks(self):
         # Left blank to be overriden in subclasses
         pass

This diff is so big that we needed to truncate the remainder.

https://bitbucket.org/yt_analysis/yt/commits/32e52b00477a/
Changeset:   32e52b00477a
Branch:      yt
User:        jzuhone
Date:        2014-10-22 19:51:48+00:00
Summary:     Some speedups
Affected #:  3 files

diff -r 93d77640b4161deab1d3652a364bd902a60a3aa1 -r 32e52b00477a8f3bc3e080901db354da1a13f67d yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -24,13 +24,20 @@
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     parallel_root_only
 import re
+import ppv_utils
 
-def create_vlos(z_hat):
-    def _v_los(field, data):
-        vz = data["velocity_x"]*z_hat[0] + \
-             data["velocity_y"]*z_hat[1] + \
-             data["velocity_z"]*z_hat[2]
-        return -vz
+def create_vlos(normal):
+    if isinstance(normal, basestring):
+        def _v_los(field, data): 
+            return -data["velocity_%s" % normal]
+    else:
+        orient = Orientation(normal)
+        los_vec = orient.unit_vectors[2]
+        def _v_los(field, data):
+            vz = data["velocity_x"]*los_vec[0] + \
+                data["velocity_y"]*los_vec[1] + \
+                data["velocity_z"]*los_vec[2]
+            return -vz
     return _v_los
 
 fits_info = {"velocity":("m/s","VELOCITY","v"),
@@ -89,19 +96,6 @@
         self.ny = dims[1]
         self.nv = dims[2]
 
-        if isinstance(normal, basestring):
-            los_vec = np.zeros(3)
-            los_vec[ds.coordinates.axis_id[normal]] = 1.0
-        else:
-            normal = np.array(normal)
-            normal /= np.sqrt(np.dot(normal, normal))
-            vecs = np.identity(3)
-            t = np.cross(normal, vecs).sum(axis=1)
-            ax = t.argmax()
-            north = np.cross(normal, vecs[ax,:]).ravel()
-            orient = Orientation(normal, north_vector=north)
-            los_vec = orient.unit_vectors[2]
-
         dd = ds.all_data()
 
         fd = dd._determine_fields(field)[0]
@@ -118,35 +112,31 @@
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
         self._vbins = self.vbins.copy()
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-        self.vmid_cgs = self.vmid.in_cgs()
+        self.vmid_cgs = self.vmid.in_cgs().v
         self.dv = self.vbins[1]-self.vbins[0]
-        self.dv_cgs = self.dv.in_cgs()
+        self.dv_cgs = self.dv.in_cgs().v
 
-        _vlos = create_vlos(los_vec)
-        self.ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+        self.current_v = 0.0
 
-        if thermal_broad:
-            self.v2_th = lambda T: 2.*kboltz*T/self.particle_mass
-            self.phi_th = lambda v, T: self.dv_cgs*np.exp(-v*v/self.v2_th(T))/(np.sqrt(np.pi*self.v2_th(T)))
-        else:
-            self.v2_th = lambda T: 1.0
-            self.phi_th = lambda v, T: np.maximum(1.-np.abs(v)/self.dv_cgs,0.0)
+        _vlos = create_vlos(normal)
+        self.ds.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+
+        _intensity = self.create_intensity()
+        self.ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
 
         self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
         pbar = get_pbar("Generating cube.", self.nv)
         for i in xrange(self.nv):
-            _intensity = self._create_intensity(i)
-            ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
+            self.current_v = self.vmid_cgs[i]
             if isinstance(normal, basestring):
                 prj = ds.proj("intensity", ds.coordinates.axis_id[normal])
                 buf = prj.to_frb(width, self.nx, center=self.center)["intensity"]
             else:
                 buf = off_axis_projection(ds, self.center, normal, width,
-                                          (self.nx, self.ny), "intensity")[::-1]
+                                          (self.nx, self.ny), "intensity", no_ghost=True)[::-1]
             self.data[:,:,i] = buf[:,:]
-            ds.field_info.pop(("gas","intensity"))
             pbar.update(i)
         pbar.finish()
 
@@ -158,6 +148,16 @@
         else:
             self.width = ds.quan(self.width, "code_length")
 
+    def create_intensity(self):
+        def _intensity(field, data):
+            v = self.current_v-data["v_los"].v
+            T = data["temperature"].v
+            w = ppv_utils.compute_weight(self.thermal_broad, self.dv_cgs,                                                                               
+                                         self.particle_mass, v.flatten(), T.flatten())                                                        
+            w[np.isnan(w)] = 0.0                                                                                                                        
+            return data[self.field]*w.reshape(v.shape)                                                                                                  
+        return _intensity
+
     def transform_spectral_axis(self, rest_value, units):
         """
         Change the units of the spectral axis to some equivalent unit, such
@@ -268,13 +268,6 @@
 
         fib.writeto(filename, clobber=clobber)
 
-    def _create_intensity(self, i):
-        def _intensity(field, data):
-            w = self.phi_th(self.vmid_cgs[i]-data["v_los"], data["temperature"])
-            w[np.isnan(w)] = 0.0
-            return data[self.field]*w
-        return _intensity
-
     def __repr__(self):
         return "PPVCube [%d %d %d] (%s < %s < %s)" % (self.nx, self.ny, self.nv,
                                                       self.vbins[0],

diff -r 93d77640b4161deab1d3652a364bd902a60a3aa1 -r 32e52b00477a8f3bc3e080901db354da1a13f67d yt/analysis_modules/ppv_cube/ppv_utils.pyx
--- /dev/null
+++ b/yt/analysis_modules/ppv_cube/ppv_utils.pyx
@@ -0,0 +1,40 @@
+import numpy as np
+cimport numpy as np
+cimport cython
+from yt.utilities.physical_constants import kboltz
+
+cdef extern from "math.h":
+    double exp(double x) nogil
+    double fabs(double x) nogil
+    double sqrt(double x) nogil
+
+cdef double kb = kboltz.v
+cdef double pi = np.pi
+
+ at cython.cdivision(True)
+ at cython.boundscheck(False)
+ at cython.wraparound(False)
+def compute_weight(np.uint8_t thermal_broad,
+    	           double dv,
+                   double m_part,
+	           np.ndarray[np.float64_t, ndim=1] v,
+    		   np.ndarray[np.float64_t, ndim=1] T):
+
+    cdef int i, n
+    cdef double v2_th, x
+    cdef np.ndarray[np.float64_t, ndim=1] w
+
+    n = v.shape[0]
+    w = np.zeros(n)
+
+    for i in range(n):
+        if thermal_broad:
+            if T[i] == 0.0:
+                v2_th = 2.*kb*T[i]/m_part
+                w[i] = dv*exp(-v[i]*v[i]/v2_th)/(sqrt(v2_th)*pi)
+        else:
+            x = 1.-fabs(v[i])/dv
+            if x > 0.0:
+                w[i] = x
+                
+    return w

diff -r 93d77640b4161deab1d3652a364bd902a60a3aa1 -r 32e52b00477a8f3bc3e080901db354da1a13f67d yt/analysis_modules/ppv_cube/setup.py
--- a/yt/analysis_modules/ppv_cube/setup.py
+++ b/yt/analysis_modules/ppv_cube/setup.py
@@ -8,6 +8,9 @@
 def configuration(parent_package='', top_path=None):
     from numpy.distutils.misc_util import Configuration
     config = Configuration('ppv_cube', parent_package, top_path)
+    config.add_extension("ppv_utils", 
+                         ["yt/analysis_modules/ppv_cube/ppv_utils.pyx"],
+                         libraries=["m"])
     #config.add_subpackage("tests")
     config.make_config_py()  # installs __config__.py
     #config.make_svn_version_py()


https://bitbucket.org/yt_analysis/yt/commits/b4a9fc99a183/
Changeset:   b4a9fc99a183
Branch:      yt
User:        jzuhone
Date:        2014-10-24 15:29:10+00:00
Summary:     Stoopid
Affected #:  1 file

diff -r 32e52b00477a8f3bc3e080901db354da1a13f67d -r b4a9fc99a18392e3d7c89aa476af282e23788525 yt/analysis_modules/ppv_cube/ppv_utils.pyx
--- a/yt/analysis_modules/ppv_cube/ppv_utils.pyx
+++ b/yt/analysis_modules/ppv_cube/ppv_utils.pyx
@@ -17,8 +17,8 @@
 def compute_weight(np.uint8_t thermal_broad,
     	           double dv,
                    double m_part,
-	           np.ndarray[np.float64_t, ndim=1] v,
-    		   np.ndarray[np.float64_t, ndim=1] T):
+	               np.ndarray[np.float64_t, ndim=1] v,
+    		       np.ndarray[np.float64_t, ndim=1] T):
 
     cdef int i, n
     cdef double v2_th, x
@@ -29,7 +29,7 @@
 
     for i in range(n):
         if thermal_broad:
-            if T[i] == 0.0:
+            if T[i] > 0.0:
                 v2_th = 2.*kb*T[i]/m_part
                 w[i] = dv*exp(-v[i]*v[i]/v2_th)/(sqrt(v2_th)*pi)
         else:


https://bitbucket.org/yt_analysis/yt/commits/f3183595882f/
Changeset:   f3183595882f
Branch:      yt
User:        jzuhone
Date:        2014-10-24 17:21:00+00:00
Summary:     Bug fixes
Affected #:  2 files

diff -r b4a9fc99a18392e3d7c89aa476af282e23788525 -r f3183595882fe7057b806054b87f90ab7917e687 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -152,8 +152,8 @@
         def _intensity(field, data):
             v = self.current_v-data["v_los"].v
             T = data["temperature"].v
-            w = ppv_utils.compute_weight(self.thermal_broad, self.dv_cgs,                                                                               
-                                         self.particle_mass, v.flatten(), T.flatten())                                                        
+            w = ppv_utils.compute_weight(self.thermal_broad, self.dv_cgs,
+                                         self.particle_mass, v.flatten(), T.flatten())
             w[np.isnan(w)] = 0.0                                                                                                                        
             return data[self.field]*w.reshape(v.shape)                                                                                                  
         return _intensity

diff -r b4a9fc99a18392e3d7c89aa476af282e23788525 -r f3183595882fe7057b806054b87f90ab7917e687 yt/analysis_modules/ppv_cube/ppv_utils.pyx
--- a/yt/analysis_modules/ppv_cube/ppv_utils.pyx
+++ b/yt/analysis_modules/ppv_cube/ppv_utils.pyx
@@ -31,7 +31,7 @@
         if thermal_broad:
             if T[i] > 0.0:
                 v2_th = 2.*kb*T[i]/m_part
-                w[i] = dv*exp(-v[i]*v[i]/v2_th)/(sqrt(v2_th)*pi)
+                w[i] = dv*exp(-v[i]*v[i]/v2_th)/sqrt(v2_th*pi)
         else:
             x = 1.-fabs(v[i])/dv
             if x > 0.0:


https://bitbucket.org/yt_analysis/yt/commits/c845dc77c701/
Changeset:   c845dc77c701
Branch:      yt
User:        jzuhone
Date:        2014-10-24 17:21:11+00:00
Summary:     Adding a PPVCube test.
Affected #:  2 files

diff -r f3183595882fe7057b806054b87f90ab7917e687 -r c845dc77c701403783cad26030908d094608ff74 yt/analysis_modules/ppv_cube/setup.py
--- a/yt/analysis_modules/ppv_cube/setup.py
+++ b/yt/analysis_modules/ppv_cube/setup.py
@@ -11,7 +11,7 @@
     config.add_extension("ppv_utils", 
                          ["yt/analysis_modules/ppv_cube/ppv_utils.pyx"],
                          libraries=["m"])
-    #config.add_subpackage("tests")
+    config.add_subpackage("tests")
     config.make_config_py()  # installs __config__.py
     #config.make_svn_version_py()
     return config


https://bitbucket.org/yt_analysis/yt/commits/609efd317364/
Changeset:   609efd317364
Branch:      yt
User:        jzuhone
Date:        2014-10-24 17:55:03+00:00
Summary:     How about we actually add the test this time?
Affected #:  1 file

diff -r c845dc77c701403783cad26030908d094608ff74 -r 609efd317364ae59d6f1418dda21ddb09d699caa yt/analysis_modules/ppv_cube/tests/test_ppv.py
--- /dev/null
+++ b/yt/analysis_modules/ppv_cube/tests/test_ppv.py
@@ -0,0 +1,60 @@
+"""
+Unit test the sunyaev_zeldovich analysis module.
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, 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.
+#-----------------------------------------------------------------------------
+
+from yt.frontends.stream.api import load_uniform_grid
+from yt.analysis_modules.ppv_cube.api import PPVCube
+import yt.units as u
+from yt.utilities.physical_constants import kboltz, mh, clight
+import numpy as np
+from yt.testing import *
+
+def setup():
+    """Test specific setup."""
+    from yt.config import ytcfg
+    ytcfg["yt", "__withintesting"] = "True"
+
+def test_ppv():
+
+    dims = (100,100,100)
+    v_shift = 1.0e7*u.cm/u.s
+    sigma_v = 2.0e7*u.cm/u.s
+    T_0 = 1.0e8*u.Kelvin
+    data = {"density":(np.ones(dims),"g/cm**3"),
+            "temperature":(T_0.v*np.ones(dims), "K"),
+            "velocity_x":(np.zeros(dims),"cm/s"),
+            "velocity_y":(np.zeros(dims),"cm/s"),
+            "velocity_z":(np.random.normal(loc=v_shift.v,scale=sigma_v.v,size=dims), "cm/s")}
+
+    ds = load_uniform_grid(data, dims)
+
+    cube = PPVCube(ds, "z", "density", dims=dims,
+                   velocity_bounds=(-300., 300., "km/s"),
+                   thermal_broad=True)
+
+    dv = cube.dv
+    v_th = np.sqrt(2.*kboltz*T_0/(56.*mh) + 2.*sigma_v**2).in_units("km/s")
+    a = cube.data.mean(axis=(0,1)).v
+    b = dv*np.exp(-((cube.vmid+v_shift)/v_th)**2)/(np.sqrt(np.pi)*v_th)
+
+    yield assert_allclose, a, b, 2.0e-3
+
+    E_0 = 6.8*u.keV
+
+    cube.transform_spectral_axis(E_0.v, str(E_0.units))
+
+    dE = -cube.dv
+    delta_E = E_0*v_th.in_cgs()/clight
+    E_shift = E_0*(1.+v_shift/clight)
+
+    c = dE*np.exp(-((cube.vmid-E_shift)/delta_E)**2)/(np.sqrt(np.pi)*delta_E)
+
+    yield assert_allclose, a, c, 2.0e-3


https://bitbucket.org/yt_analysis/yt/commits/ea7e7a2406fd/
Changeset:   ea7e7a2406fd
Branch:      yt
User:        jzuhone
Date:        2014-10-24 18:15:44+00:00
Summary:     Parallelizing PPVCube.
Affected #:  1 file

diff -r 609efd317364ae59d6f1418dda21ddb09d699caa -r ea7e7a2406fd0676485649f63dd97745d5d532ff yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -16,15 +16,14 @@
 from yt.utilities.fits_image import FITSImageBuffer
 from yt.visualization.volume_rendering.camera import off_axis_projection
 from yt.funcs import get_pbar
-from yt.utilities.physical_constants import clight, mh, kboltz
+from yt.utilities.physical_constants import clight, mh
 import yt.units.dimensions as ytdims
-import yt.units as u
-from yt.units.yt_array import YTQuantity
 from yt.funcs import iterable
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
-    parallel_root_only
+    parallel_root_only, parallel_objects
 import re
 import ppv_utils
+from yt.funcs import is_root
 
 def create_vlos(normal):
     if isinstance(normal, basestring):
@@ -127,8 +126,9 @@
         self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
+        storage = {}
         pbar = get_pbar("Generating cube.", self.nv)
-        for i in xrange(self.nv):
+        for sto, i in parallel_objects(xrange(self.nv), storage=storage):
             self.current_v = self.vmid_cgs[i]
             if isinstance(normal, basestring):
                 prj = ds.proj("intensity", ds.coordinates.axis_id[normal])
@@ -136,10 +136,16 @@
             else:
                 buf = off_axis_projection(ds, self.center, normal, width,
                                           (self.nx, self.ny), "intensity", no_ghost=True)[::-1]
-            self.data[:,:,i] = buf[:,:]
+            sto.result_id = i
+            sto.result = buf
             pbar.update(i)
         pbar.finish()
 
+        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
+        if is_root():
+            for i, buf in sorted(storage.items()):
+                self.data[:,:,i] = buf[:,:]
+
         self.axis_type = "velocity"
 
         # Now fix the width


https://bitbucket.org/yt_analysis/yt/commits/31917ddf10bd/
Changeset:   31917ddf10bd
Branch:      yt
User:        jzuhone
Date:        2014-10-25 16:25:25+00:00
Summary:     This is all we can reasonably expect out of this test
Affected #:  1 file

diff -r ea7e7a2406fd0676485649f63dd97745d5d532ff -r 31917ddf10bd1c43eacd26b6963e32544254e976 yt/analysis_modules/ppv_cube/tests/test_ppv.py
--- a/yt/analysis_modules/ppv_cube/tests/test_ppv.py
+++ b/yt/analysis_modules/ppv_cube/tests/test_ppv.py
@@ -24,7 +24,7 @@
 
 def test_ppv():
 
-    dims = (100,100,100)
+    dims = (8,8,1024)
     v_shift = 1.0e7*u.cm/u.s
     sigma_v = 2.0e7*u.cm/u.s
     T_0 = 1.0e8*u.Kelvin
@@ -45,7 +45,7 @@
     a = cube.data.mean(axis=(0,1)).v
     b = dv*np.exp(-((cube.vmid+v_shift)/v_th)**2)/(np.sqrt(np.pi)*v_th)
 
-    yield assert_allclose, a, b, 2.0e-3
+    yield assert_allclose, a, b, 1.0e-2
 
     E_0 = 6.8*u.keV
 
@@ -57,4 +57,4 @@
 
     c = dE*np.exp(-((cube.vmid-E_shift)/delta_E)**2)/(np.sqrt(np.pi)*delta_E)
 
-    yield assert_allclose, a, c, 2.0e-3
+    yield assert_allclose, a, c, 1.0e-2


https://bitbucket.org/yt_analysis/yt/commits/676347e40516/
Changeset:   676347e40516
Branch:      yt
User:        jzuhone
Date:        2014-10-27 17:34:49+00:00
Summary:     Allowing for custom widths in constructing FITS images
Affected #:  1 file

diff -r 31917ddf10bd1c43eacd26b6963e32544254e976 -r 676347e40516e01be2a50f3099bf87ddf7e6744b yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -232,31 +232,46 @@
 
 axis_wcs = [[1,2],[0,2],[0,1]]
 
-def construct_image(data_source, center=None):
+def sanitize_fits_unit(unit, dx):
+    if unit == "Mpc":
+        mylog.info("Changing FITS file unit to kpc.")
+        unit = "kpc"
+        dx *= 1000.
+    elif unit == "au":
+        unit = "AU"
+    return unit, dx
+
+def construct_image(data_source, center=None, width=None):
     ds = data_source.ds
     axis = data_source.axis
+    if center is None or width is None:
+        center = ds.domain_center[axis_wcs[axis]]
+    if width is None:
+        width = ds.domain_width[axis_wcs[axis]]
+        mylog.info("Making an image of the entire domain, "+
+                   "so setting the center to the domain center.")
+    else:
+        width = ds.coordinates.sanitize_width(axis, width, None)
+    dx = ds.index.get_smallest_dx()
+    nx, ny = [int((w/dx).in_units("dimensionless")) for w in width]
+    crpix = [0.5*(nx+1), 0.5*(ny+1)]
     if hasattr(ds, "wcs"):
-        # This is a FITS dataset
-        nx, ny = ds.domain_dimensions[axis_wcs[axis]]
-        crpix = [ds.wcs.wcs.crpix[idx] for idx in axis_wcs[axis]]
-        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
-        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
+        # This is a FITS dataset, so we use it to construct the WCS
         cunit = [str(ds.wcs.wcs.cunit[idx]) for idx in axis_wcs[axis]]
         ctype = [ds.wcs.wcs.ctype[idx] for idx in axis_wcs[axis]]
+        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
+        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
     else:
-        # This is some other kind of dataset
-        unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
-        if center is None:
-            crval = [0.0,0.0]
-        else:
-            crval = [(ds.domain_center-center)[idx].in_units(unit) for idx in axis_wcs[axis]]
-        dx = ds.index.get_smallest_dx()
-        nx, ny = (ds.domain_width[axis_wcs[axis]]/dx).ndarray_view().astype("int")
-        crpix = [0.5*(nx+1), 0.5*(ny+1)]
-        cdelt = [dx.in_units(unit)]*2
+        # This is some other kind of dataset                                                                                    
+        unit = str(width[0].units)
+        if unit == "unitary":
+            unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
+        unit = sanitize_fits_unit(unit)
         cunit = [unit]*2
         ctype = ["LINEAR"]*2
-    frb = data_source.to_frb((1.0,"unitary"), (nx,ny))
+        crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
+        cdelt = [dx.in_units(unit)]*2
+    frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix
     w.wcs.cdelt = cdelt
@@ -285,13 +300,14 @@
          as a tuple containing a coordinate and string unit name or by passing
          in a YTArray.  If a list or unitless array is supplied, code units are
          assumed.
+    width : 
     """
-    def __init__(self, ds, axis, fields, center="c", **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
         center = ds.coordinates.sanitize_center(center, axis)
         slc = ds.slice(axis, center[axis], **kwargs)
-        w, frb = construct_image(slc, center=center)
+        w, frb = construct_image(slc, center=dcenter, width=width)
         super(FITSSlice, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)
@@ -318,13 +334,14 @@
         as a tuple containing a coordinate and string unit name or by passing
         in a YTArray.  If a list or unitless array is supplied, code units are
         assumed.
+    width :
     """
-    def __init__(self, ds, axis, fields, center="c", weight_field=None, **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, weight_field=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         prj = ds.proj(fields[0], axis, weight_field=weight_field, **kwargs)
-        w, frb = construct_image(prj, center=center)
+        w, frb = construct_image(prj, center=dcenter, width=width)
         super(FITSProjection, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)


https://bitbucket.org/yt_analysis/yt/commits/08db763a0286/
Changeset:   08db763a0286
Branch:      yt
User:        jzuhone
Date:        2014-10-27 17:36:04+00:00
Summary:     Using sanitize_fits_width here
Affected #:  2 files

diff -r 31917ddf10bd1c43eacd26b6963e32544254e976 -r 08db763a0286ccb433961d38cc7644bb8b46f7f4 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -13,7 +13,7 @@
 import numpy as np
 from yt.utilities.on_demand_imports import _astropy
 from yt.utilities.orientation import Orientation
-from yt.utilities.fits_image import FITSImageBuffer
+from yt.utilities.fits_image import FITSImageBuffer, sanitize_fits_unit
 from yt.visualization.volume_rendering.camera import off_axis_projection
 from yt.funcs import get_pbar
 from yt.utilities.physical_constants import clight, mh
@@ -248,13 +248,8 @@
                 units = str(self.ds.get_smallest_appropriate_unit(self.width))
             else:
                 units = length_unit
-            dx = self.width.in_units(units).v/self.nx
-        # Hacks because FITS is stupid and doesn't understand case
-        if units == "Mpc":
-            units = "kpc"
-            dx *= 1000.
-        elif units == "au":
-            units = "AU"
+        units = sanitize_fits_unit(units)
+        dx = self.width.in_units(units).v/self.nx
         dy = dx
         dv = self.dv.in_units(vunit).v
 

diff -r 31917ddf10bd1c43eacd26b6963e32544254e976 -r 08db763a0286ccb433961d38cc7644bb8b46f7f4 yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -309,7 +309,7 @@
         >>> sky_center = (30., 45.) # In degrees
         >>> szprj.write_fits("SZbullet.fits", sky_center=sky_center, sky_scale=sky_scale)
         """
-        from yt.utilities.fits_image import FITSImageBuffer
+        from yt.utilities.fits_image import FITSImageBuffer, sanitize_fits_unit
 
         if sky_scale is None:
             center = (0.0,0.0)
@@ -324,9 +324,8 @@
             types = ["RA---TAN","DEC--TAN"]
 
         units = self.ds.get_smallest_appropriate_unit(self.width)
+        units = sanitize_fits_unit(units)
         # Hack because FITS is stupid and doesn't understand case
-        if units == "Mpc":
-            units = "kpc"
         dx = self.dx.in_units(units)
         if sky_scale:
             dx = (dx*sky_scale).in_units("deg")


https://bitbucket.org/yt_analysis/yt/commits/3f9b94d346e8/
Changeset:   3f9b94d346e8
Branch:      yt
User:        jzuhone
Date:        2014-10-27 17:36:18+00:00
Summary:     Merge
Affected #:  1 file

diff -r 08db763a0286ccb433961d38cc7644bb8b46f7f4 -r 3f9b94d346e8a71be62debc9c3740e9612d6204b yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -232,31 +232,46 @@
 
 axis_wcs = [[1,2],[0,2],[0,1]]
 
-def construct_image(data_source, center=None):
+def sanitize_fits_unit(unit, dx):
+    if unit == "Mpc":
+        mylog.info("Changing FITS file unit to kpc.")
+        unit = "kpc"
+        dx *= 1000.
+    elif unit == "au":
+        unit = "AU"
+    return unit, dx
+
+def construct_image(data_source, center=None, width=None):
     ds = data_source.ds
     axis = data_source.axis
+    if center is None or width is None:
+        center = ds.domain_center[axis_wcs[axis]]
+    if width is None:
+        width = ds.domain_width[axis_wcs[axis]]
+        mylog.info("Making an image of the entire domain, "+
+                   "so setting the center to the domain center.")
+    else:
+        width = ds.coordinates.sanitize_width(axis, width, None)
+    dx = ds.index.get_smallest_dx()
+    nx, ny = [int((w/dx).in_units("dimensionless")) for w in width]
+    crpix = [0.5*(nx+1), 0.5*(ny+1)]
     if hasattr(ds, "wcs"):
-        # This is a FITS dataset
-        nx, ny = ds.domain_dimensions[axis_wcs[axis]]
-        crpix = [ds.wcs.wcs.crpix[idx] for idx in axis_wcs[axis]]
-        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
-        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
+        # This is a FITS dataset, so we use it to construct the WCS
         cunit = [str(ds.wcs.wcs.cunit[idx]) for idx in axis_wcs[axis]]
         ctype = [ds.wcs.wcs.ctype[idx] for idx in axis_wcs[axis]]
+        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
+        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
     else:
-        # This is some other kind of dataset
-        unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
-        if center is None:
-            crval = [0.0,0.0]
-        else:
-            crval = [(ds.domain_center-center)[idx].in_units(unit) for idx in axis_wcs[axis]]
-        dx = ds.index.get_smallest_dx()
-        nx, ny = (ds.domain_width[axis_wcs[axis]]/dx).ndarray_view().astype("int")
-        crpix = [0.5*(nx+1), 0.5*(ny+1)]
-        cdelt = [dx.in_units(unit)]*2
+        # This is some other kind of dataset                                                                                    
+        unit = str(width[0].units)
+        if unit == "unitary":
+            unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
+        unit = sanitize_fits_unit(unit)
         cunit = [unit]*2
         ctype = ["LINEAR"]*2
-    frb = data_source.to_frb((1.0,"unitary"), (nx,ny))
+        crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
+        cdelt = [dx.in_units(unit)]*2
+    frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix
     w.wcs.cdelt = cdelt
@@ -285,13 +300,14 @@
          as a tuple containing a coordinate and string unit name or by passing
          in a YTArray.  If a list or unitless array is supplied, code units are
          assumed.
+    width : 
     """
-    def __init__(self, ds, axis, fields, center="c", **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
         center = ds.coordinates.sanitize_center(center, axis)
         slc = ds.slice(axis, center[axis], **kwargs)
-        w, frb = construct_image(slc, center=center)
+        w, frb = construct_image(slc, center=dcenter, width=width)
         super(FITSSlice, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)
@@ -318,13 +334,14 @@
         as a tuple containing a coordinate and string unit name or by passing
         in a YTArray.  If a list or unitless array is supplied, code units are
         assumed.
+    width :
     """
-    def __init__(self, ds, axis, fields, center="c", weight_field=None, **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, weight_field=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         prj = ds.proj(fields[0], axis, weight_field=weight_field, **kwargs)
-        w, frb = construct_image(prj, center=center)
+        w, frb = construct_image(prj, center=dcenter, width=width)
         super(FITSProjection, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)


https://bitbucket.org/yt_analysis/yt/commits/49ea8a1c2c0f/
Changeset:   49ea8a1c2c0f
Branch:      yt
User:        jzuhone
Date:        2014-10-27 17:38:04+00:00
Summary:     Bug fix
Affected #:  1 file

diff -r 3f9b94d346e8a71be62debc9c3740e9612d6204b -r 49ea8a1c2c0f9470c415ad879251e3851ea2a402 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -232,14 +232,13 @@
 
 axis_wcs = [[1,2],[0,2],[0,1]]
 
-def sanitize_fits_unit(unit, dx):
+def sanitize_fits_unit(unit):
     if unit == "Mpc":
         mylog.info("Changing FITS file unit to kpc.")
         unit = "kpc"
-        dx *= 1000.
     elif unit == "au":
         unit = "AU"
-    return unit, dx
+    return unit
 
 def construct_image(data_source, center=None, width=None):
     ds = data_source.ds


https://bitbucket.org/yt_analysis/yt/commits/82510469b48b/
Changeset:   82510469b48b
Branch:      yt
User:        jzuhone
Date:        2014-10-27 18:55:38+00:00
Summary:     Adding a way to represent unit prefixes as LaTeX. Currently only works for "u" / "mu" (micro).
Affected #:  3 files

diff -r 49ea8a1c2c0f9470c415ad879251e3851ea2a402 -r 82510469b48b6e805d285098cb1b667017d448ed yt/units/unit_lookup_table.py
--- a/yt/units/unit_lookup_table.py
+++ b/yt/units/unit_lookup_table.py
@@ -138,6 +138,10 @@
     if key not in latex_symbol_lut:
         latex_symbol_lut[key] = "\\rm{" + key + "}"
 
+latex_prefixes = {
+    "u" : "\\mu",
+}
+
 # This dictionary formatting from magnitude package, credit to Juan Reyero.
 unit_prefixes = {
     'Y': 1e24,   # yotta
@@ -177,6 +181,7 @@
     "Hz",
     "W",
     "gauss",
+    "G",
     "Jy",
 )
 

diff -r 49ea8a1c2c0f9470c415ad879251e3851ea2a402 -r 82510469b48b6e805d285098cb1b667017d448ed yt/units/unit_object.py
--- a/yt/units/unit_object.py
+++ b/yt/units/unit_object.py
@@ -26,7 +26,7 @@
 from yt.units.unit_lookup_table import \
     latex_symbol_lut, unit_prefixes, \
     prefixable_units, cgs_base_units, \
-    mks_base_units
+    mks_base_units, latex_prefixes
 from yt.units.unit_registry import UnitRegistry
 
 import copy
@@ -512,9 +512,14 @@
             prefix_value = unit_prefixes[possible_prefix]
 
             if symbol_str not in latex_symbol_lut:
+                if possible_prefix in latex_prefixes:
+                    sstr = symbol_str.replace(possible_prefix, 
+                                              '{'+latex_prefixes[possible_prefix]+'}')
+                else:
+                    sstr = symbol_str
                 latex_symbol_lut[symbol_str] = \
                     latex_symbol_lut[symbol_wo_prefix].replace(
-                                   '{'+symbol_wo_prefix+'}', '{'+symbol_str+'}')
+                                   '{'+symbol_wo_prefix+'}', '{'+sstr+'}')
 
             # don't forget to account for the prefix value!
             return (unit_data[0] * prefix_value, unit_data[1])

diff -r 49ea8a1c2c0f9470c415ad879251e3851ea2a402 -r 82510469b48b6e805d285098cb1b667017d448ed yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -15,7 +15,7 @@
 from yt.visualization.fixed_resolution import FixedResolutionBuffer
 from yt.data_objects.construction_data_containers import YTCoveringGridBase
 from yt.utilities.on_demand_imports import _astropy
-from yt.units.yt_array import YTQuantity
+from yt.units.yt_array import YTQuantity, YTArray
 import re
 
 pyfits = _astropy.pyfits
@@ -101,8 +101,14 @@
 
         first = True
 
+        self.field_units = {}
+
         for key in fields:
             if key not in exclude_fields:
+                if hasattr(img_data[key], "units"):
+                    self.field_units[key] = str(img_data[key].units)
+                else:
+                    self.field_units[key] = "dimensionless"
                 mylog.info("Making a FITS image of field %s" % (key))
                 if first:
                     hdu = pyfits.PrimaryHDU(np.array(img_data[key]))
@@ -230,6 +236,18 @@
         import aplpy
         return aplpy.FITSFigure(self, **kwargs)
 
+    def get_data(self, field):
+        return YTArray(self[field].data, self.field_units[field])
+
+    def set_unit(self, field, units):
+        """
+        Set the units of *field* to *units*.
+        """
+        new_data = YTArray(self[field].data, self.field_units[field]).in_units(units)
+        self[field].data = new_data.v
+        self[field].header["bunit"] = units
+        self.field_units[field] = units
+        
 axis_wcs = [[1,2],[0,2],[0,1]]
 
 def sanitize_fits_unit(unit):
@@ -270,6 +288,7 @@
         ctype = ["LINEAR"]*2
         crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
         cdelt = [dx.in_units(unit)]*2
+        crpix = [0.5*(nx+1), 0.5*(ny+1)]
     frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix
@@ -335,7 +354,8 @@
         assumed.
     width :
     """
-    def __init__(self, ds, axis, fields, center="c", width=None, weight_field=None, **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, 
+                 weight_field=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
         center, dcenter = ds.coordinates.sanitize_center(center, axis)


https://bitbucket.org/yt_analysis/yt/commits/ffa9561da09a/
Changeset:   ffa9561da09a
Branch:      yt
User:        jzuhone
Date:        2014-10-27 19:11:40+00:00
Summary:     Attempting to get this right for FITS datasets
Affected #:  1 file

diff -r 82510469b48b6e805d285098cb1b667017d448ed -r ffa9561da09ae368c9f70a9831d35b4300d29eae yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -276,8 +276,10 @@
         # This is a FITS dataset, so we use it to construct the WCS
         cunit = [str(ds.wcs.wcs.cunit[idx]) for idx in axis_wcs[axis]]
         ctype = [ds.wcs.wcs.ctype[idx] for idx in axis_wcs[axis]]
-        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
         cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
+        ctr_pix = center.in_units("code_length")[:self.dimensionality].v
+        crval = ds.wcs.wcs_pix2world(ctr_pix.reshape(1,self.dimensionality))[0]
+        crval = [crval[idx] for idx in axis_wcs[axis]]
     else:
         # This is some other kind of dataset                                                                                    
         unit = str(width[0].units)
@@ -286,9 +288,9 @@
         unit = sanitize_fits_unit(unit)
         cunit = [unit]*2
         ctype = ["LINEAR"]*2
+        cdelt = [dx.in_units(unit)]*2
         crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
-        cdelt = [dx.in_units(unit)]*2
-        crpix = [0.5*(nx+1), 0.5*(ny+1)]
+    crpix = [0.5*(nx+1), 0.5*(ny+1)]
     frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix
@@ -323,7 +325,7 @@
     def __init__(self, ds, axis, fields, center="c", width=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         slc = ds.slice(axis, center[axis], **kwargs)
         w, frb = construct_image(slc, center=dcenter, width=width)
         super(FITSSlice, self).__init__(frb, fields=fields, wcs=w)


https://bitbucket.org/yt_analysis/yt/commits/c22ef53e667c/
Changeset:   c22ef53e667c
Branch:      yt
User:        jzuhone
Date:        2014-10-27 19:13:14+00:00
Summary:     Removing extra line
Affected #:  1 file

diff -r ffa9561da09ae368c9f70a9831d35b4300d29eae -r c22ef53e667c9943357bee7ac5fac164eb896a95 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -290,7 +290,6 @@
         ctype = ["LINEAR"]*2
         cdelt = [dx.in_units(unit)]*2
         crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
-    crpix = [0.5*(nx+1), 0.5*(ny+1)]
     frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix


https://bitbucket.org/yt_analysis/yt/commits/752db2db1ae0/
Changeset:   752db2db1ae0
Branch:      yt
User:        jzuhone
Date:        2014-10-27 19:45:04+00:00
Summary:     make keys lowercase
Affected #:  1 file

diff -r c22ef53e667c9943357bee7ac5fac164eb896a95 -r 752db2db1ae0cba8e823303a5cc34d1b3a9c6ad0 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -181,7 +181,7 @@
         for img in self: img.header[key] = value
             
     def keys(self):
-        return [f.name for f in self]
+        return [f.name.lower() for f in self]
 
     def has_key(self, key):
         return key in self.keys()


https://bitbucket.org/yt_analysis/yt/commits/9a4b5263deaa/
Changeset:   9a4b5263deaa
Branch:      yt
User:        jzuhone
Date:        2014-10-28 19:35:22+00:00
Summary:     Adding parentheses to the delimiters that shouldn't be checked
Affected #:  2 files

diff -r 752db2db1ae0cba8e823303a5cc34d1b3a9c6ad0 -r 9a4b5263deaa33b9bd2ea07834c1d6d6daf45740 yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -46,7 +46,7 @@
 
 lon_prefixes = ["X","RA","GLON","LINEAR"]
 lat_prefixes = ["Y","DEC","GLAT","LINEAR"]
-delimiters = ["*", "/", "-", "^"]
+delimiters = ["*", "/", "-", "^", "(", ")"]
 delimiters += [str(i) for i in xrange(10)]
 regex_pattern = '|'.join(re.escape(_) for _ in delimiters)
 

diff -r 752db2db1ae0cba8e823303a5cc34d1b3a9c6ad0 -r 9a4b5263deaa33b9bd2ea07834c1d6d6daf45740 yt/units/unit_lookup_table.py
--- a/yt/units/unit_lookup_table.py
+++ b/yt/units/unit_lookup_table.py
@@ -138,10 +138,6 @@
     if key not in latex_symbol_lut:
         latex_symbol_lut[key] = "\\rm{" + key + "}"
 
-latex_prefixes = {
-    "u" : "\\mu",
-}
-
 # This dictionary formatting from magnitude package, credit to Juan Reyero.
 unit_prefixes = {
     'Y': 1e24,   # yotta
@@ -164,6 +160,10 @@
     'y': 1e-24,  # yocto
 }
 
+latex_prefixes = {
+    "u" : "\\mu",
+    }
+
 prefixable_units = (
     "m",
     "pc",


https://bitbucket.org/yt_analysis/yt/commits/cb4b5f56b749/
Changeset:   cb4b5f56b749
Branch:      yt
User:        jzuhone
Date:        2014-10-28 23:37:02+00:00
Summary:     Adding sum method for off-axis projections
Affected #:  3 files

diff -r 9a4b5263deaa33b9bd2ea07834c1d6d6daf45740 -r cb4b5f56b749d8ac76fa8f7b53a855f5f1390d4b yt/visualization/fixed_resolution.py
--- a/yt/visualization/fixed_resolution.py
+++ b/yt/visualization/fixed_resolution.py
@@ -418,7 +418,7 @@
                                    width, dd.resolution, item,
                                    weight=dd.weight_field, volume=dd.volume,
                                    no_ghost=dd.no_ghost, interpolated=dd.interpolated,
-                                   north_vector=dd.north_vector)
+                                   north_vector=dd.north_vector, method=dd.method)
         units = Unit(dd.ds.field_info[item].units, registry=dd.ds.unit_registry)
         if dd.weight_field is None:
             units *= Unit('cm', registry=dd.ds.unit_registry)

diff -r 9a4b5263deaa33b9bd2ea07834c1d6d6daf45740 -r cb4b5f56b749d8ac76fa8f7b53a855f5f1390d4b yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -1304,12 +1304,11 @@
 
 class OffAxisProjectionDummyDataSource(object):
     _type_name = 'proj'
-    method = 'integrate'
     _key_fields = []
     def __init__(self, center, ds, normal_vector, width, fields,
                  interpolated, resolution = (800,800), weight=None,
                  volume=None, no_ghost=False, le=None, re=None,
-                 north_vector=None):
+                 north_vector=None, method="integrate"):
         self.center = center
         self.ds = ds
         self.axis = 4 # always true for oblique data objects
@@ -1326,6 +1325,7 @@
         self.le = le
         self.re = re
         self.north_vector = north_vector
+        self.method = method
 
     def _determine_fields(self, *args):
         return self.dd._determine_fields(*args)
@@ -1396,7 +1396,18 @@
          set, an arbitrary grid-aligned north-vector is chosen.
     fontsize : integer
          The size of the fonts for the axis, colorbar, and tick labels.
+    method : string
+         The method of projection.  Valid methods are:
 
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not
+         multiply by a path length when performing the integration, and is
+         just a straight summation of the field along the given axis.
     """
     _plot_type = 'OffAxisProjection'
     _frb_generator = OffAxisProjectionFixedResolutionBuffer
@@ -1404,7 +1415,7 @@
     def __init__(self, ds, normal, fields, center='c', width=None,
                  depth=(1, '1'), axes_unit=None, weight_field=None,
                  max_level=None, north_vector=None, volume=None, no_ghost=False,
-                 le=None, re=None, interpolated=False, fontsize=18):
+                 le=None, re=None, interpolated=False, fontsize=18, method="integrate"):
         (bounds, center_rot) = \
           get_oblique_window_parameters(normal,center,width,ds,depth=depth)
         fields = ensure_list(fields)[:]
@@ -1414,7 +1425,7 @@
         OffAxisProj = OffAxisProjectionDummyDataSource(
             center_rot, ds, normal, oap_width, fields, interpolated,
             weight=weight_field,  volume=volume, no_ghost=no_ghost,
-            le=le, re=re, north_vector=north_vector)
+            le=le, re=re, north_vector=north_vector, method=method)
         # If a non-weighted, integral projection, assure field-label reflects that
         if weight_field is None and OffAxisProj.method == "integrate":
             self.projected = True

diff -r 9a4b5263deaa33b9bd2ea07834c1d6d6daf45740 -r cb4b5f56b749d8ac76fa8f7b53a855f5f1390d4b yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -2161,7 +2161,8 @@
 class ProjectionCamera(Camera):
     def __init__(self, center, normal_vector, width, resolution,
             field, weight=None, volume=None, no_ghost = False, 
-            north_vector=None, ds=None, interpolated=False):
+            north_vector=None, ds=None, interpolated=False,
+            method="integrate"):
 
         if not interpolated:
             volume = 1
@@ -2170,6 +2171,7 @@
         self.field = field
         self.weight = weight
         self.resolution = resolution
+        self.method = method
 
         fields = [field]
         if self.weight is not None:
@@ -2240,11 +2242,12 @@
         dd = ds.all_data()
         field = dd._determine_fields([self.field])[0]
         finfo = ds._get_field_info(*field)
-        if self.weight is None:
-            dl = self.width[2]
-            image *= dl
-        else:
-            image[:,:,0] /= image[:,:,1]
+        if self.method == "integrate":
+            if self.weight is None:
+                dl = self.width[2]
+                image *= dl
+            else:
+                image[:,:,0] /= image[:,:,1]
 
         return image[:,:,0]
 
@@ -2332,7 +2335,7 @@
 def off_axis_projection(ds, center, normal_vector, width, resolution,
                         field, weight = None, 
                         volume = None, no_ghost = False, interpolated = False,
-                        north_vector = None):
+                        north_vector = None, method = "integrate"):
     r"""Project through a dataset, off-axis, and return the image plane.
 
     This function will accept the necessary items to integrate through a volume
@@ -2376,6 +2379,18 @@
         If True, the data is first interpolated to vertex-centered data, 
         then tri-linearly interpolated along the ray. Not suggested for 
         quantitative studies.
+    method : string
+         The method of projection.  Valid methods are:
+
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not
+         multiply by a path length when performing the integration, and is
+         just a straight summation of the field along the given axis.
 
     Returns
     -------
@@ -2393,7 +2408,7 @@
     projcam = ProjectionCamera(center, normal_vector, width, resolution,
                                field, weight=weight, ds=ds, volume=volume,
                                no_ghost=no_ghost, interpolated=interpolated, 
-                               north_vector=north_vector)
+                               north_vector=north_vector, method=method)
     image = projcam.snapshot()
     return image[:,:]
 


https://bitbucket.org/yt_analysis/yt/commits/4add1016c9d2/
Changeset:   4add1016c9d2
Branch:      yt
User:        jzuhone
Date:        2014-10-28 23:38:39+00:00
Summary:     Make sure we use the same random seed for this test every time
Affected #:  1 file

diff -r cb4b5f56b749d8ac76fa8f7b53a855f5f1390d4b -r 4add1016c9d2e2097cf91458a96c0108434b53eb yt/analysis_modules/ppv_cube/tests/test_ppv.py
--- a/yt/analysis_modules/ppv_cube/tests/test_ppv.py
+++ b/yt/analysis_modules/ppv_cube/tests/test_ppv.py
@@ -24,6 +24,8 @@
 
 def test_ppv():
 
+    np.random.seed(seed=0x4d3d3d3)
+
     dims = (8,8,1024)
     v_shift = 1.0e7*u.cm/u.s
     sigma_v = 2.0e7*u.cm/u.s


https://bitbucket.org/yt_analysis/yt/commits/13c7bfbb36e7/
Changeset:   13c7bfbb36e7
Branch:      yt
User:        jzuhone
Date:        2014-10-28 23:47:21+00:00
Summary:     Allow for integrate or sum projection methods.
Affected #:  1 file

diff -r 4add1016c9d2e2097cf91458a96c0108434b53eb -r 13c7bfbb36e7b67d1e4dd9d13e865bb26faef054 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -47,7 +47,7 @@
 class PPVCube(object):
     def __init__(self, ds, normal, field, center="c", width=(1.0,"unitary"),
                  dims=(100,100,100), velocity_bounds=None, thermal_broad=False,
-                 atomic_weight=56.):
+                 atomic_weight=56., method="integrate"):
         r""" Initialize a PPVCube object.
 
         Parameters
@@ -74,6 +74,10 @@
         atomic_weight : float, optional
             Set this value to the atomic weight of the particle that is emitting the line
             if *thermal_broad* is True. Defaults to 56 (Fe).
+        method : string, optional
+            Set the projection method to be used.
+            "integrate" : line of sight integration over the line element.
+            "sum" : straight summation over the line of sight.
 
         Examples
         --------
@@ -95,6 +99,10 @@
         self.ny = dims[1]
         self.nv = dims[2]
 
+        if method not in ["integrate","sum"]:
+            raise RuntimeError("Only the 'integrate' and 'sum' projection +"
+                               "methods are supported in PPVCube.")
+
         dd = ds.all_data()
 
         fd = dd._determine_fields(field)[0]
@@ -123,7 +131,10 @@
         _intensity = self.create_intensity()
         self.ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
 
-        self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
+        if method == "integrate":
+            self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
+        elif method == "sum":
+            self.proj_units = self.field_units
 
         self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
         storage = {}
@@ -131,11 +142,12 @@
         for sto, i in parallel_objects(xrange(self.nv), storage=storage):
             self.current_v = self.vmid_cgs[i]
             if isinstance(normal, basestring):
-                prj = ds.proj("intensity", ds.coordinates.axis_id[normal])
+                prj = ds.proj("intensity", ds.coordinates.axis_id[normal], method=method)
                 buf = prj.to_frb(width, self.nx, center=self.center)["intensity"]
             else:
                 buf = off_axis_projection(ds, self.center, normal, width,
-                                          (self.nx, self.ny), "intensity", no_ghost=True)[::-1]
+                                          (self.nx, self.ny), "intensity",
+                                          no_ghost=True, method=method)[::-1]
             sto.result_id = i
             sto.result = buf
             pbar.update(i)


https://bitbucket.org/yt_analysis/yt/commits/edd198e1dbcc/
Changeset:   edd198e1dbcc
Branch:      yt
User:        jzuhone
Date:        2014-10-31 15:36:41+00:00
Summary:     More options for specifying resolution of image, and more general way of determining nx, ny, dx, dy
Affected #:  1 file

diff -r 13c7bfbb36e7b67d1e4dd9d13e865bb26faef054 -r edd198e1dbcc6f0459add7e5f7a58914eabbea55 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -258,7 +258,7 @@
         unit = "AU"
     return unit
 
-def construct_image(data_source, center=None, width=None):
+def construct_image(data_source, center=None, width=None, image_res=None):
     ds = data_source.ds
     axis = data_source.axis
     if center is None or width is None:
@@ -269,8 +269,18 @@
                    "so setting the center to the domain center.")
     else:
         width = ds.coordinates.sanitize_width(axis, width, None)
-    dx = ds.index.get_smallest_dx()
-    nx, ny = [int((w/dx).in_units("dimensionless")) for w in width]
+    if image_res is None:
+        dd = ds.all_data()
+        dx, dy = [dd.quantities.extrema("d%s" % "xyz"[idx])[0]
+                  for idx in axis_wcs[axis]]
+        nx = int(width[0]/dx).in_units("dimensionless")
+        ny = int(width[1]/dy).in_units("dimensionless")
+    else:
+        if iterable(image_res):
+            nx, ny = image_res
+        else:
+            nx, ny = image_res, image_res
+        dx, dy = width[0]/nx, width[1]/ny
     crpix = [0.5*(nx+1), 0.5*(ny+1)]
     if hasattr(ds, "wcs"):
         # This is a FITS dataset, so we use it to construct the WCS
@@ -326,7 +336,8 @@
         axis = fix_axis(axis, ds)
         center, dcenter = ds.coordinates.sanitize_center(center, axis)
         slc = ds.slice(axis, center[axis], **kwargs)
-        w, frb = construct_image(slc, center=dcenter, width=width)
+        w, frb = construct_image(slc, center=dcenter, width=width,
+                                 image_res=image_res)
         super(FITSSlice, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)
@@ -356,12 +367,13 @@
     width :
     """
     def __init__(self, ds, axis, fields, center="c", width=None, 
-                 weight_field=None, **kwargs):
+                 weight_field=None, image_res=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
         center, dcenter = ds.coordinates.sanitize_center(center, axis)
         prj = ds.proj(fields[0], axis, weight_field=weight_field, **kwargs)
-        w, frb = construct_image(prj, center=dcenter, width=width)
+        w, frb = construct_image(prj, center=dcenter, width=width,
+                                 image_res=image_res)
         super(FITSProjection, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)


https://bitbucket.org/yt_analysis/yt/commits/b44d52f2e9bf/
Changeset:   b44d52f2e9bf
Branch:      yt
User:        jzuhone
Date:        2014-10-31 15:40:01+00:00
Summary:     Docstring improvements
Affected #:  1 file

diff -r edd198e1dbcc6f0459add7e5f7a58914eabbea55 -r b44d52f2e9bff6f9782c921bc5689dcc04fc74fa yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -329,7 +329,29 @@
          as a tuple containing a coordinate and string unit name or by passing
          in a YTArray.  If a list or unitless array is supplied, code units are
          assumed.
-    width : 
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+    image_res : an int or 2-tuple of ints
+        Specify the resolution of the resulting image. If not provided, it will be
+        determined based on the minimum cell size of the dataset.
     """
     def __init__(self, ds, axis, fields, center="c", width=None, **kwargs):
         fields = ensure_list(fields)
@@ -364,7 +386,29 @@
         as a tuple containing a coordinate and string unit name or by passing
         in a YTArray.  If a list or unitless array is supplied, code units are
         assumed.
-    width :
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+    image_res : an int or 2-tuple of ints
+        Specify the resolution of the resulting image. If not provided, it will be
+        determined based on the minimum cell size of the dataset.
     """
     def __init__(self, ds, axis, fields, center="c", width=None, 
                  weight_field=None, image_res=None, **kwargs):


https://bitbucket.org/yt_analysis/yt/commits/64fab868c335/
Changeset:   64fab868c335
Branch:      yt
User:        jzuhone
Date:        2014-10-31 15:42:32+00:00
Summary:     Bugfix
Affected #:  1 file

diff -r b44d52f2e9bff6f9782c921bc5689dcc04fc74fa -r 64fab868c335a36008544aea17e840d493f9a259 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -273,8 +273,8 @@
         dd = ds.all_data()
         dx, dy = [dd.quantities.extrema("d%s" % "xyz"[idx])[0]
                   for idx in axis_wcs[axis]]
-        nx = int(width[0]/dx).in_units("dimensionless")
-        ny = int(width[1]/dy).in_units("dimensionless")
+        nx = int((width[0]/dx).in_units("dimensionless"))
+        ny = int((width[1]/dy).in_units("dimensionless"))
     else:
         if iterable(image_res):
             nx, ny = image_res


https://bitbucket.org/yt_analysis/yt/commits/c7af5e4ddcdf/
Changeset:   c7af5e4ddcdf
Branch:      yt
User:        jzuhone
Date:        2014-10-31 20:13:08+00:00
Summary:     Min/max of any variable for sanitze_center
Affected #:  1 file

diff -r c91388a959906a04a5db1e773537e94bcb636a62 -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 yt/geometry/coordinates/coordinate_handler.py
--- a/yt/geometry/coordinates/coordinate_handler.py
+++ b/yt/geometry/coordinates/coordinate_handler.py
@@ -183,7 +183,15 @@
         elif isinstance(center, YTArray):
             return self.ds.arr(center), self.convert_to_cartesian(center)
         elif iterable(center):
-            if iterable(center[0]) and isinstance(center[1], basestring):
+            if isinstance(center[0], basestring) and isinstance(center[1], basestring):
+                if center[0].lower() == "min":
+                    v, center = self.ds.find_min(center[1])
+                elif center[0].lower() == "max":
+                    v, center = self.ds.find_max(center[1])
+                else:
+                    raise RuntimeError("center keyword \"%s\" not recognized" % center)
+                center = self.ds.arr(center, 'code_length')
+            elif iterable(center[0]) and isinstance(center[1], basestring):
                 center = self.ds.arr(center[0], center[1])
             else:
                 center = self.ds.arr(center, 'code_length')


https://bitbucket.org/yt_analysis/yt/commits/d0b8664501d0/
Changeset:   d0b8664501d0
Branch:      yt
User:        jzuhone
Date:        2014-10-31 20:13:30+00:00
Summary:     Merge
Affected #:  17 files

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 doc/source/analyzing/analysis_modules/PPVCube.ipynb
--- a/doc/source/analyzing/analysis_modules/PPVCube.ipynb
+++ b/doc/source/analyzing/analysis_modules/PPVCube.ipynb
@@ -1,7 +1,7 @@
 {
  "metadata": {
   "name": "",
-  "signature": "sha256:56a8d72735e3cc428ff04b241d4b2ce6f653019818c6fc7a4148840d99030c85"
+  "signature": "sha256:b83e125278c2e58da4d99ac9d2ca2a136d01f1094e1b83497925e0f9b9b056c2"
  },
  "nbformat": 3,
  "nbformat_minor": 0,
@@ -32,7 +32,7 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "To demonstrate this functionality, we'll create a simple unigrid dataset from scratch of a rotating disk galaxy. We create a thin disk in the x-y midplane of the domain of three cells in height in either direction, and a radius of 10 kpc. The density and azimuthal velocity profiles of the disk as a function of radius will be given by the following functions:"
+      "To demonstrate this functionality, we'll create a simple unigrid dataset from scratch of a rotating disk. We create a thin disk in the x-y midplane of the domain of three cells in height in either direction, and a radius of 10 kpc. The density and azimuthal velocity profiles of the disk as a function of radius will be given by the following functions:"
      ]
     },
     {
@@ -84,7 +84,7 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Second, we'll construct the data arrays for the density and the velocity of the disk. Since we have the tangential velocity profile, we have to use the polar coordinates we derived earlier to compute `velx` and `vely`. Everywhere outside the disk, all fields are set to zero.  "
+      "Second, we'll construct the data arrays for the density, temperature, and velocity of the disk. Since we have the tangential velocity profile, we have to use the polar coordinates we derived earlier to compute `velx` and `vely`. Everywhere outside the disk, all fields are set to zero.  "
      ]
     },
     {
@@ -93,12 +93,15 @@
      "input": [
       "dens = np.zeros((nx,ny,nz))\n",
       "dens[:,:,nz/2-3:nz/2+3] = (r**alpha).reshape(nx,ny,1) # the density profile of the disk\n",
-      "vel_theta = r/(1.+(r/r_0)**beta) # the azimuthal velocity profile of the disk\n",
+      "temp = np.zeros((nx,ny,nz))\n",
+      "temp[:,:,nz/2-3:nz/2+3] = 1.0e5 # Isothermal\n",
+      "vel_theta = 100.*r/(1.+(r/r_0)**beta) # the azimuthal velocity profile of the disk\n",
       "velx = np.zeros((nx,ny,nz))\n",
       "vely = np.zeros((nx,ny,nz))\n",
       "velx[:,:,nz/2-3:nz/2+3] = (-vel_theta*np.sin(theta)).reshape(nx,ny,1) # convert polar to cartesian\n",
       "vely[:,:,nz/2-3:nz/2+3] = (vel_theta*np.cos(theta)).reshape(nx,ny,1) # convert polar to cartesian\n",
       "dens[r > R] = 0.0\n",
+      "temp[r > R] = 0.0\n",
       "velx[r > R] = 0.0\n",
       "vely[r > R] = 0.0"
      ],
@@ -119,6 +122,7 @@
      "input": [
       "data = {}\n",
       "data[\"density\"] = (dens,\"g/cm**3\")\n",
+      "data[\"temperature\"] = (temp, \"K\")\n",
       "data[\"velocity_x\"] = (velx, \"km/s\")\n",
       "data[\"velocity_y\"] = (vely, \"km/s\")\n",
       "data[\"velocity_z\"] = (np.zeros((nx,ny,nz)), \"km/s\") # zero velocity in the z-direction\n",
@@ -189,7 +193,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "cube = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-1.5,1.5,\"km/s\"))"
+      "cube = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-150.,150.,\"km/s\"))"
      ],
      "language": "python",
      "metadata": {},
@@ -199,14 +203,33 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Following this, we can now write this cube to a FITS file:"
+      "Following this, we can now write this cube to a FITS file. The x and y axes of the file can be in length units, which can be optionally specified by `length_unit`:"
      ]
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "cube.write_fits(\"cube.fits\", clobber=True, length_unit=(5.0,\"deg\"))"
+      "cube.write_fits(\"cube.fits\", clobber=True, length_unit=\"kpc\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Or one can use the `sky_scale` and `sky_center` keywords to set up the coordinates in RA and Dec:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "sky_scale = (1.0, \"arcsec/kpc\")\n",
+      "sky_center = (30., 45.) # RA, Dec in degrees\n",
+      "cube.write_fits(\"cube_sky.fits\", clobber=True, sky_scale=sky_scale, sky_center=sky_center)"
      ],
      "language": "python",
      "metadata": {},
@@ -223,7 +246,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "ds = yt.load(\"cube.fits\")"
+      "ds_cube = yt.load(\"cube.fits\")"
      ],
      "language": "python",
      "metadata": {},
@@ -234,7 +257,7 @@
      "collapsed": false,
      "input": [
       "# Specifying no center gives us the center slice\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"])\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"])\n",
       "slc.show()"
      ],
      "language": "python",
@@ -247,9 +270,9 @@
      "input": [
       "import yt.units as u\n",
       "# Picking different velocities for the slices\n",
-      "new_center = ds.domain_center\n",
-      "new_center[2] = ds.spec2pixel(-1.0*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center = ds_cube.domain_center\n",
+      "new_center[2] = ds_cube.spec2pixel(-100.*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -260,8 +283,8 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "new_center[2] = ds.spec2pixel(0.7*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center[2] = ds_cube.spec2pixel(70.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -272,8 +295,8 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "new_center[2] = ds.spec2pixel(-0.3*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center[2] = ds_cube.spec2pixel(-30.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -291,7 +314,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "prj = yt.ProjectionPlot(ds, \"z\", [\"density\"], method=\"sum\")\n",
+      "prj = yt.ProjectionPlot(ds_cube, \"z\", [\"density\"], method=\"sum\")\n",
       "prj.set_log(\"density\", True)\n",
       "prj.set_zlim(\"density\", 1.0e-3, 0.2)\n",
       "prj.show()"
@@ -299,9 +322,100 @@
      "language": "python",
      "metadata": {},
      "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The `thermal_broad` keyword allows one to simulate thermal line broadening based on the temperature, and the `atomic_weight` argument is used to specify the atomic weight of the particle that is doing the emitting."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube2 = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-150,150,\"km/s\"), thermal_broad=True, \n",
+      "                atomic_weight=12.0)\n",
+      "cube2.write_fits(\"cube2.fits\", clobber=True, length_unit=\"kpc\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Taking a slice of this cube shows:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "ds_cube2 = yt.load(\"cube2.fits\")\n",
+      "new_center = ds_cube2.domain_center\n",
+      "new_center[2] = ds_cube2.spec2pixel(70.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube2, \"z\", [\"density\"], center=new_center)\n",
+      "slc.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "new_center[2] = ds_cube2.spec2pixel(-100.*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube2, \"z\", [\"density\"], center=new_center)\n",
+      "slc.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "where we can see the emission has been smeared into this velocity slice from neighboring slices due to the thermal broadening. \n",
+      "\n",
+      "Finally, the \"velocity\" or \"spectral\" axis of the cube can be changed to a different unit, such as wavelength, frequency, or energy: "
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print cube2.vbins[0], cube2.vbins[-1]\n",
+      "cube2.transform_spectral_axis(400.0,\"nm\")\n",
+      "print cube2.vbins[0], cube2.vbins[-1]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "If a FITS file is now written from the cube, the spectral axis will be in the new units. To reset the spectral axis back to the original velocity units:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube2.reset_spectral_axis()\n",
+      "print cube2.vbins[0], cube2.vbins[-1]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
     }
    ],
    "metadata": {}
   }
  ]
-}
+}
\ No newline at end of file

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -13,31 +13,55 @@
 import numpy as np
 from yt.utilities.on_demand_imports import _astropy
 from yt.utilities.orientation import Orientation
-from yt.utilities.fits_image import FITSImageBuffer
+from yt.utilities.fits_image import FITSImageBuffer, sanitize_fits_unit
 from yt.visualization.volume_rendering.camera import off_axis_projection
 from yt.funcs import get_pbar
+from yt.utilities.physical_constants import clight, mh
+import yt.units.dimensions as ytdims
+from yt.funcs import iterable
+from yt.utilities.parallel_tools.parallel_analysis_interface import \
+    parallel_root_only, parallel_objects
+import re
+import ppv_utils
+from yt.funcs import is_root
 
-def create_vlos(z_hat):
-    def _v_los(field, data):
-        vz = data["velocity_x"]*z_hat[0] + \
-             data["velocity_y"]*z_hat[1] + \
-             data["velocity_z"]*z_hat[2]
-        return -vz
+def create_vlos(normal):
+    if isinstance(normal, basestring):
+        def _v_los(field, data): 
+            return -data["velocity_%s" % normal]
+    else:
+        orient = Orientation(normal)
+        los_vec = orient.unit_vectors[2]
+        def _v_los(field, data):
+            vz = data["velocity_x"]*los_vec[0] + \
+                data["velocity_y"]*los_vec[1] + \
+                data["velocity_z"]*los_vec[2]
+            return -vz
     return _v_los
 
+fits_info = {"velocity":("m/s","VELOCITY","v"),
+             "frequency":("Hz","FREQUENCY","f"),
+             "energy":("eV","ENERGY","E"),
+             "wavelength":("angstrom","WAVELENG","lambda")}
+
 class PPVCube(object):
-    def __init__(self, ds, normal, field, width=(1.0,"unitary"),
-                 dims=(100,100,100), velocity_bounds=None):
+    def __init__(self, ds, normal, field, center="c", width=(1.0,"unitary"),
+                 dims=(100,100,100), velocity_bounds=None, thermal_broad=False,
+                 atomic_weight=56., method="integrate"):
         r""" Initialize a PPVCube object.
 
         Parameters
         ----------
         ds : dataset
             The dataset.
-        normal : array_like
-            The normal vector along with to make the projections.
+        normal : array_like or string
+            The normal vector along with to make the projections. If an array, it
+            will be normalized. If a string, it will be assumed to be along one of the
+            principal axes of the domain ("x","y", or "z").
         field : string
             The field to project.
+        center : float, tuple, or string
+            The coordinates of the dataset *ds* on which to center the PPVCube.
         width : float or tuple, optional
             The width of the projection in length units. Specify a float
             for code_length units or a tuple (value, units).
@@ -47,6 +71,13 @@
             A 3-tuple of (vmin, vmax, units) for the velocity bounds to
             integrate over. If None, the largest velocity of the
             dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())
+        atomic_weight : float, optional
+            Set this value to the atomic weight of the particle that is emitting the line
+            if *thermal_broad* is True. Defaults to 56 (Fe).
+        method : string, optional
+            Set the projection method to be used.
+            "integrate" : line of sight integration over the line element.
+            "sum" : straight summation over the line of sight.
 
         Examples
         --------
@@ -55,21 +86,22 @@
         >>> cube = PPVCube(ds, L, "density", width=(10.,"kpc"),
         ...                velocity_bounds=(-5.,4.,"km/s"))
         """
+
         self.ds = ds
         self.field = field
         self.width = width
+        self.particle_mass = atomic_weight*mh
+        self.thermal_broad = thermal_broad
+
+        self.center = ds.coordinates.sanitize_center(center, normal)[0]
 
         self.nx = dims[0]
         self.ny = dims[1]
         self.nv = dims[2]
 
-        normal = np.array(normal)
-        normal /= np.sqrt(np.dot(normal, normal))
-        vecs = np.identity(3)
-        t = np.cross(normal, vecs).sum(axis=1)
-        ax = t.argmax()
-        north = np.cross(normal, vecs[ax,:]).ravel()
-        orient = Orientation(normal, north_vector=north)
+        if method not in ["integrate","sum"]:
+            raise RuntimeError("Only the 'integrate' and 'sum' projection +"
+                               "methods are supported in PPVCube.")
 
         dd = ds.all_data()
 
@@ -85,27 +117,102 @@
                           ds.quan(velocity_bounds[1], velocity_bounds[2]))
 
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
+        self._vbins = self.vbins.copy()
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-        self.dv = (self.v_bnd[1]-self.v_bnd[0])/self.nv
+        self.vmid_cgs = self.vmid.in_cgs().v
+        self.dv = self.vbins[1]-self.vbins[0]
+        self.dv_cgs = self.dv.in_cgs().v
 
-        _vlos = create_vlos(orient.unit_vectors[2])
-        ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+        self.current_v = 0.0
 
-        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
+        _vlos = create_vlos(normal)
+        self.ds.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+
+        _intensity = self.create_intensity()
+        self.ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
+
+        if method == "integrate":
+            self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
+        elif method == "sum":
+            self.proj_units = self.field_units
+
+        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
+        storage = {}
         pbar = get_pbar("Generating cube.", self.nv)
-        for i in xrange(self.nv):
-            _intensity = self._create_intensity(i)
-            ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
-            prj = off_axis_projection(ds, ds.domain_center, normal, width,
-                                      (self.nx, self.ny), "intensity")
-            self.data[:,:,i] = prj[:,:]
-            ds.field_info.pop(("gas","intensity"))
+        for sto, i in parallel_objects(xrange(self.nv), storage=storage):
+            self.current_v = self.vmid_cgs[i]
+            if isinstance(normal, basestring):
+                prj = ds.proj("intensity", ds.coordinates.axis_id[normal], method=method)
+                buf = prj.to_frb(width, self.nx, center=self.center)["intensity"]
+            else:
+                buf = off_axis_projection(ds, self.center, normal, width,
+                                          (self.nx, self.ny), "intensity",
+                                          no_ghost=True, method=method)[::-1]
+            sto.result_id = i
+            sto.result = buf
             pbar.update(i)
-
         pbar.finish()
 
-    def write_fits(self, filename, clobber=True, length_unit=(10.0, "kpc"),
-                   sky_center=(30.,45.)):
+        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
+        if is_root():
+            for i, buf in sorted(storage.items()):
+                self.data[:,:,i] = buf[:,:]
+
+        self.axis_type = "velocity"
+
+        # Now fix the width
+        if iterable(self.width):
+            self.width = ds.quan(self.width[0], self.width[1])
+        else:
+            self.width = ds.quan(self.width, "code_length")
+
+    def create_intensity(self):
+        def _intensity(field, data):
+            v = self.current_v-data["v_los"].v
+            T = data["temperature"].v
+            w = ppv_utils.compute_weight(self.thermal_broad, self.dv_cgs,
+                                         self.particle_mass, v.flatten(), T.flatten())
+            w[np.isnan(w)] = 0.0                                                                                                                        
+            return data[self.field]*w.reshape(v.shape)                                                                                                  
+        return _intensity
+
+    def transform_spectral_axis(self, rest_value, units):
+        """
+        Change the units of the spectral axis to some equivalent unit, such
+        as energy, wavelength, or frequency, by providing a *rest_value* and the
+        *units* of the new spectral axis. This corresponds to the Doppler-shifting
+        of lines due to gas motions and thermal broadening.
+        """
+        if self.axis_type != "velocity":
+            self.reset_spectral_axis()
+        x0 = self.ds.quan(rest_value, units)
+        if x0.units.dimensions == ytdims.rate or x0.units.dimensions == ytdims.energy:
+            self.vbins = x0*(1.-self.vbins.in_cgs()/clight)
+        elif x0.units.dimensions == ytdims.length:
+            self.vbins = x0/(1.-self.vbins.in_cgs()/clight)
+        self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.dv = self.vbins[1]-self.vbins[0]
+        dims = self.dv.units.dimensions
+        if dims == ytdims.rate:
+            self.axis_type = "frequency"
+        elif dims == ytdims.length:
+            self.axis_type = "wavelength"
+        elif dims == ytdims.energy:
+            self.axis_type = "energy"
+        elif dims == ytdims.velocity:
+            self.axis_type = "velocity"
+
+    def reset_spectral_axis(self):
+        """
+        Reset the spectral axis to the original velocity range and units.
+        """
+        self.vbins = self._vbins.copy()
+        self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.dv = self.vbins[1]-self.vbins[0]
+
+    @parallel_root_only
+    def write_fits(self, filename, clobber=True, length_unit=None,
+                   sky_scale=None, sky_center=None):
         r""" Write the PPVCube to a FITS file.
 
         Parameters
@@ -114,51 +221,71 @@
             The name of the file to write.
         clobber : boolean
             Whether or not to clobber an existing file with the same name.
-        length_unit : tuple, optional
-            The length that corresponds to the width of the projection in
-            (value, unit) form. Accepts a length unit or 'deg'.
+        length_unit : string
+            The units to convert the coordinates to in the file.
+        sky_scale : tuple or YTQuantity
+            Conversion between an angle unit and a length unit, if sky
+            coordinates are desired.
+            Examples: (1.0, "arcsec/kpc"), YTQuantity(0.001, "deg/kpc")
         sky_center : tuple, optional
             The (RA, Dec) coordinate in degrees of the central pixel if
-            *length_unit* is 'deg'.
+            *sky_scale* has been specified.
 
         Examples
         --------
-        >>> cube.write_fits("my_cube.fits", clobber=False, length_unit=(5,"deg"))
+        >>> cube.write_fits("my_cube.fits", clobber=False, sky_scale=(1.0,"arcsec/kpc"))
         """
-        if length_unit[1] == "deg":
-            center = sky_center
-            types = ["RA---SIN","DEC--SIN"]
+        if sky_scale is None:
+            center = (0.0,0.0)
+            types = ["LINEAR","LINEAR"]
         else:
-            center = [0.0,0.0]
-            types = ["LINEAR","LINEAR"]
+            if iterable(sky_scale):
+                sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
+            if sky_center is None:
+                center = (30.,45.)
+            else:
+                center = sky_center
+            types = ["RA---TAN","DEC--TAN"]
 
-        v_center = 0.5*(self.v_bnd[0]+self.v_bnd[1]).in_units("m/s").value
+        vunit = fits_info[self.axis_type][0]
+        vtype = fits_info[self.axis_type][1]
 
-        dx = length_unit[0]/self.nx
-        dy = length_unit[0]/self.ny
-        dv = self.dv.in_units("m/s").value
+        v_center = 0.5*(self.vbins[0]+self.vbins[-1]).in_units(vunit).value
 
-        if length_unit[1] == "deg":
+        if sky_scale:
+            dx = (self.width*sky_scale).in_units("deg").v/self.nx
+            units = "deg"
+        else:
+            if length_unit is None:
+                units = str(self.ds.get_smallest_appropriate_unit(self.width))
+            else:
+                units = length_unit
+        units = sanitize_fits_unit(units)
+        dx = self.width.in_units(units).v/self.nx
+        dy = dx
+        dv = self.dv.in_units(vunit).v
+
+        if sky_scale:
             dx *= -1.
 
         w = _astropy.pywcs.WCS(naxis=3)
         w.wcs.crpix = [0.5*(self.nx+1), 0.5*(self.ny+1), 0.5*(self.nv+1)]
         w.wcs.cdelt = [dx,dy,dv]
-        w.wcs.crval = [center[0], center[1], v_center]
-        w.wcs.cunit = [length_unit[1],length_unit[1],"m/s"]
-        w.wcs.ctype = [types[0],types[1],"VELO-LSR"]
+        w.wcs.crval = [center[0],center[1],v_center]
+        w.wcs.cunit = [units,units,vunit]
+        w.wcs.ctype = [types[0],types[1],vtype]
 
         fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
-        fib[0].header["bunit"] = self.field_units
+        fib[0].header["bunit"] = re.sub('()', '', str(self.proj_units))
         fib[0].header["btype"] = self.field
 
         fib.writeto(filename, clobber=clobber)
 
-    def _create_intensity(self, i):
-        def _intensity(field, data):
-            vlos = data["v_los"]
-            w = np.abs(vlos-self.vmid[i])/self.dv.in_units(vlos.units)
-            w = 1.-w
-            w[w < 0.0] = 0.0
-            return data[self.field]*w
-        return _intensity
+    def __repr__(self):
+        return "PPVCube [%d %d %d] (%s < %s < %s)" % (self.nx, self.ny, self.nv,
+                                                      self.vbins[0],
+                                                      fits_info[self.axis_type][2],
+                                                      self.vbins[-1])
+
+    def __getitem__(self, item):
+        return self.data[item]

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/analysis_modules/ppv_cube/ppv_utils.pyx
--- /dev/null
+++ b/yt/analysis_modules/ppv_cube/ppv_utils.pyx
@@ -0,0 +1,40 @@
+import numpy as np
+cimport numpy as np
+cimport cython
+from yt.utilities.physical_constants import kboltz
+
+cdef extern from "math.h":
+    double exp(double x) nogil
+    double fabs(double x) nogil
+    double sqrt(double x) nogil
+
+cdef double kb = kboltz.v
+cdef double pi = np.pi
+
+ at cython.cdivision(True)
+ at cython.boundscheck(False)
+ at cython.wraparound(False)
+def compute_weight(np.uint8_t thermal_broad,
+    	           double dv,
+                   double m_part,
+	               np.ndarray[np.float64_t, ndim=1] v,
+    		       np.ndarray[np.float64_t, ndim=1] T):
+
+    cdef int i, n
+    cdef double v2_th, x
+    cdef np.ndarray[np.float64_t, ndim=1] w
+
+    n = v.shape[0]
+    w = np.zeros(n)
+
+    for i in range(n):
+        if thermal_broad:
+            if T[i] > 0.0:
+                v2_th = 2.*kb*T[i]/m_part
+                w[i] = dv*exp(-v[i]*v[i]/v2_th)/sqrt(v2_th*pi)
+        else:
+            x = 1.-fabs(v[i])/dv
+            if x > 0.0:
+                w[i] = x
+                
+    return w

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/analysis_modules/ppv_cube/setup.py
--- a/yt/analysis_modules/ppv_cube/setup.py
+++ b/yt/analysis_modules/ppv_cube/setup.py
@@ -8,7 +8,10 @@
 def configuration(parent_package='', top_path=None):
     from numpy.distutils.misc_util import Configuration
     config = Configuration('ppv_cube', parent_package, top_path)
-    #config.add_subpackage("tests")
+    config.add_extension("ppv_utils", 
+                         ["yt/analysis_modules/ppv_cube/ppv_utils.pyx"],
+                         libraries=["m"])
+    config.add_subpackage("tests")
     config.make_config_py()  # installs __config__.py
     #config.make_svn_version_py()
     return config

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/analysis_modules/ppv_cube/tests/test_ppv.py
--- /dev/null
+++ b/yt/analysis_modules/ppv_cube/tests/test_ppv.py
@@ -0,0 +1,62 @@
+"""
+Unit test the sunyaev_zeldovich analysis module.
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, 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.
+#-----------------------------------------------------------------------------
+
+from yt.frontends.stream.api import load_uniform_grid
+from yt.analysis_modules.ppv_cube.api import PPVCube
+import yt.units as u
+from yt.utilities.physical_constants import kboltz, mh, clight
+import numpy as np
+from yt.testing import *
+
+def setup():
+    """Test specific setup."""
+    from yt.config import ytcfg
+    ytcfg["yt", "__withintesting"] = "True"
+
+def test_ppv():
+
+    np.random.seed(seed=0x4d3d3d3)
+
+    dims = (8,8,1024)
+    v_shift = 1.0e7*u.cm/u.s
+    sigma_v = 2.0e7*u.cm/u.s
+    T_0 = 1.0e8*u.Kelvin
+    data = {"density":(np.ones(dims),"g/cm**3"),
+            "temperature":(T_0.v*np.ones(dims), "K"),
+            "velocity_x":(np.zeros(dims),"cm/s"),
+            "velocity_y":(np.zeros(dims),"cm/s"),
+            "velocity_z":(np.random.normal(loc=v_shift.v,scale=sigma_v.v,size=dims), "cm/s")}
+
+    ds = load_uniform_grid(data, dims)
+
+    cube = PPVCube(ds, "z", "density", dims=dims,
+                   velocity_bounds=(-300., 300., "km/s"),
+                   thermal_broad=True)
+
+    dv = cube.dv
+    v_th = np.sqrt(2.*kboltz*T_0/(56.*mh) + 2.*sigma_v**2).in_units("km/s")
+    a = cube.data.mean(axis=(0,1)).v
+    b = dv*np.exp(-((cube.vmid+v_shift)/v_th)**2)/(np.sqrt(np.pi)*v_th)
+
+    yield assert_allclose, a, b, 1.0e-2
+
+    E_0 = 6.8*u.keV
+
+    cube.transform_spectral_axis(E_0.v, str(E_0.units))
+
+    dE = -cube.dv
+    delta_E = E_0*v_th.in_cgs()/clight
+    E_shift = E_0*(1.+v_shift/clight)
+
+    c = dE*np.exp(-((cube.vmid-E_shift)/delta_E)**2)/(np.sqrt(np.pi)*delta_E)
+
+    yield assert_allclose, a, c, 1.0e-2

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -25,6 +25,7 @@
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
      communication_system, parallel_root_only
 from yt import units
+from yt.utilities.on_demand_imports import _astropy
 
 import numpy as np
 
@@ -279,8 +280,7 @@
         self.data["TeSZ"] = self.ds.arr(Te, "keV")
 
     @parallel_root_only
-    def write_fits(self, filename, units="kpc", sky_center=None, sky_scale=None,
-                   time_units="Gyr", clobber=True):
+    def write_fits(self, filename, sky_scale=None, sky_center=None, clobber=True):
         r""" Export images to a FITS file. Writes the SZ distortion in all
         specified frequencies as well as the mass-weighted temperature and the
         optical depth. Distance units are in kpc, unless *sky_center*
@@ -290,12 +290,13 @@
         ----------
         filename : string
             The name of the FITS file to be written. 
-        sky_center : tuple of floats, optional
-            The center of the observation in (RA, Dec) in degrees. Only used if
-            converting to sky coordinates.          
-        sky_scale : float, optional
-            Scale between degrees and kpc. Only used if
-            converting to sky coordinates.
+        sky_scale : tuple or YTQuantity
+            Conversion between an angle unit and a length unit, if sky
+            coordinates are desired.
+            Examples: (1.0, "arcsec/kpc"), YTQuantity(0.001, "deg/kpc")
+        sky_center : tuple, optional
+            The (RA, Dec) coordinate in degrees of the central pixel if
+            *sky_scale* has been specified.
         clobber : boolean, optional
             If the file already exists, do we overwrite?
 
@@ -304,27 +305,43 @@
         >>> # This example just writes out a FITS file with kpc coords
         >>> szprj.write_fits("SZbullet.fits", clobber=False)
         >>> # This example uses sky coords
-        >>> sky_scale = 1./3600. # One arcsec per kpc
+        >>> sky_scale = (1., "arcsec/kpc") # One arcsec per kpc
         >>> sky_center = (30., 45.) # In degrees
         >>> szprj.write_fits("SZbullet.fits", sky_center=sky_center, sky_scale=sky_scale)
         """
+        from yt.utilities.fits_image import FITSImageBuffer, sanitize_fits_unit
 
-        deltas = np.array([self.dx.in_units(units),
-                           self.dy.in_units(units)])
+        if sky_scale is None:
+            center = (0.0,0.0)
+            types = ["LINEAR","LINEAR"]
+        else:
+            if iterable(sky_scale):
+                sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
+            if sky_center is None:
+                center = (30.,45.)
+            else:
+                center = sky_center
+            types = ["RA---TAN","DEC--TAN"]
 
-        if sky_center is None:
-            center = [0.0]*2
-        else:
-            center = sky_center
+        units = self.ds.get_smallest_appropriate_unit(self.width)
+        units = sanitize_fits_unit(units)
+        # Hack because FITS is stupid and doesn't understand case
+        dx = self.dx.in_units(units)
+        if sky_scale:
+            dx = (dx*sky_scale).in_units("deg")
             units = "deg"
-            deltas *= sky_scale
-            deltas[0] *= -1.
+        dy = dx
+        if sky_scale:
+            dx *= -1.
 
-        from yt.utilities.fits_image import FITSImageBuffer
-        fib = FITSImageBuffer(self.data, fields=self.data.keys(),
-                              center=center, units=units,
-                              scale=deltas)
-        fib.update_all_headers("Time", float(self.ds.current_time.in_units(time_units).value))
+        w = _astropy.pywcs.WCS(naxis=2)
+        w.wcs.crpix = [0.5*(self.nx+1)]*2
+        w.wcs.cdelt = [dx.v,dy.v]
+        w.wcs.crval = center
+        w.wcs.cunit = [units]*2
+        w.wcs.ctype = types
+
+        fib = FITSImageBuffer(self.data, fields=self.data.keys(), wcs=w)
         fib.writeto(filename, clobber=clobber)
         
     @parallel_root_only

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -44,9 +44,9 @@
 from yt.units.yt_array import YTQuantity
 from yt.utilities.on_demand_imports import _astropy
 
-lon_prefixes = ["X","RA","GLON"]
-lat_prefixes = ["Y","DEC","GLAT"]
-delimiters = ["*", "/", "-", "^"]
+lon_prefixes = ["X","RA","GLON","LINEAR"]
+lat_prefixes = ["Y","DEC","GLAT","LINEAR"]
+delimiters = ["*", "/", "-", "^", "(", ")"]
 delimiters += [str(i) for i in xrange(10)]
 regex_pattern = '|'.join(re.escape(_) for _ in delimiters)
 
@@ -552,7 +552,7 @@
         x = 0
         for p in lon_prefixes+lat_prefixes+spec_names.keys():
             y = np_char.startswith(self.axis_names[:self.dimensionality], p)
-            x += y.sum()
+            x += np.any(y)
         if x == self.dimensionality: self._setup_spec_cube()
 
     def _setup_spec_cube(self):
@@ -582,6 +582,12 @@
         self.lon_axis = np.where(self.lon_axis)[0][0]
         self.lon_name = ctypes[self.lon_axis].split("-")[0].lower()
 
+        if self.lat_axis == self.lon_axis and self.lat_name == self.lon_name:
+            self.lat_axis = 1
+            self.lon_axis = 0
+            self.lat_name = "Y"
+            self.lon_name = "X"
+
         if self.wcs.naxis > 2:
 
             self.spec_axis = np.zeros((end-1), dtype="bool")

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/geometry/coordinates/spec_cube_coordinates.py
--- a/yt/geometry/coordinates/spec_cube_coordinates.py
+++ b/yt/geometry/coordinates/spec_cube_coordinates.py
@@ -26,8 +26,19 @@
         self.axis_name = {}
         self.axis_id = {}
 
-        for axis, axis_name in zip([ds.lon_axis, ds.lat_axis, ds.spec_axis],
-                                   ["Image\ x", "Image\ y", ds.spec_name]):
+        self.default_unit_label = {}
+        if ds.lon_name == "X" and ds.lat_name == "Y":
+            names = ["x","y"]
+        else:
+            names = ["Image\ x", "Image\ y"]
+            self.default_unit_label[ds.lon_axis] = "pixel"
+            self.default_unit_label[ds.lat_axis] = "pixel"
+        names.append(ds.spec_name)
+        axes = [ds.lon_axis, ds.lat_axis, ds.spec_axis]
+        self.default_unit_label[ds.spec_axis] = ds.spec_unit
+
+        for axis, axis_name in zip(axes, names):
+
             lower_ax = "xyz"[axis]
             upper_ax = lower_ax.upper()
 
@@ -40,11 +51,6 @@
             self.axis_id[axis] = axis
             self.axis_id[axis_name] = axis
 
-        self.default_unit_label = {}
-        self.default_unit_label[ds.lon_axis] = "pixel"
-        self.default_unit_label[ds.lat_axis] = "pixel"
-        self.default_unit_label[ds.spec_axis] = ds.spec_unit
-
         def _spec_axis(ax, x, y):
             p = (x,y)[ax]
             return [self.ds.pixel2spec(pp).v for pp in p]

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/units/unit_lookup_table.py
--- a/yt/units/unit_lookup_table.py
+++ b/yt/units/unit_lookup_table.py
@@ -160,6 +160,10 @@
     'y': 1e-24,  # yocto
 }
 
+latex_prefixes = {
+    "u" : "\\mu",
+    }
+
 prefixable_units = (
     "m",
     "pc",
@@ -177,6 +181,7 @@
     "Hz",
     "W",
     "gauss",
+    "G",
     "Jy",
 )
 

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/units/unit_object.py
--- a/yt/units/unit_object.py
+++ b/yt/units/unit_object.py
@@ -26,7 +26,7 @@
 from yt.units.unit_lookup_table import \
     latex_symbol_lut, unit_prefixes, \
     prefixable_units, cgs_base_units, \
-    mks_base_units
+    mks_base_units, latex_prefixes
 from yt.units.unit_registry import UnitRegistry
 
 import copy
@@ -512,9 +512,14 @@
             prefix_value = unit_prefixes[possible_prefix]
 
             if symbol_str not in latex_symbol_lut:
+                if possible_prefix in latex_prefixes:
+                    sstr = symbol_str.replace(possible_prefix, 
+                                              '{'+latex_prefixes[possible_prefix]+'}')
+                else:
+                    sstr = symbol_str
                 latex_symbol_lut[symbol_str] = \
                     latex_symbol_lut[symbol_wo_prefix].replace(
-                                   '{'+symbol_wo_prefix+'}', '{'+symbol_str+'}')
+                                   '{'+symbol_wo_prefix+'}', '{'+sstr+'}')
 
             # don't forget to account for the prefix value!
             return (unit_data[0] * prefix_value, unit_data[1])

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -15,7 +15,8 @@
 from yt.visualization.fixed_resolution import FixedResolutionBuffer
 from yt.data_objects.construction_data_containers import YTCoveringGridBase
 from yt.utilities.on_demand_imports import _astropy
-from yt.units.yt_array import YTQuantity
+from yt.units.yt_array import YTQuantity, YTArray
+import re
 
 pyfits = _astropy.pyfits
 pywcs = _astropy.pywcs
@@ -27,8 +28,7 @@
 
 class FITSImageBuffer(HDUList):
 
-    def __init__(self, data, fields=None, units="cm",
-                 center=None, scale=None, wcs=None):
+    def __init__(self, data, fields=None, units="cm", wcs=None):
         r""" Initialize a FITSImageBuffer object.
 
         FITSImageBuffer contains a list of FITS ImageHDU instances, and
@@ -49,29 +49,32 @@
             keys, it will use these for the fields. If *data* is just a
             single array one field name must be specified.
         units : string
-            The units of the WCS coordinates, default "cm". 
-        center : array_like, optional
-            The coordinates [xctr,yctr] of the images in units
-            *units*. If *units* is not specified, defaults to the origin. 
-        scale : tuple of floats, optional
-            Pixel scale in unit *units*. Will be ignored if *data* is
-            a FixedResolutionBuffer or a YTCoveringGrid. Must be
-            specified otherwise, or if *units* is "deg".
+            The units of the WCS coordinates. Only applies
+            to FixedResolutionBuffers or YTCoveringGrids. Defaults to "cm".
         wcs : `astropy.wcs.WCS` instance, optional
-            Supply an AstroPy WCS instance to override automatic WCS creation.
+            Supply an AstroPy WCS instance. Will override automatic WCS
+            creation from FixedResolutionBuffers and YTCoveringGrids.
 
         Examples
         --------
 
+        >>> # This example uses a FRB.
         >>> ds = load("sloshing_nomag2_hdf5_plt_cnt_0150")
         >>> prj = ds.proj(2, "kT", weight_field="density")
         >>> frb = prj.to_frb((0.5, "Mpc"), 800)
         >>> # This example just uses the FRB and puts the coords in kpc.
         >>> f_kpc = FITSImageBuffer(frb, fields="kT", units="kpc")
-        >>> # This example specifies sky coordinates.
-        >>> scale = [1./3600.]*2 # One arcsec per pixel
-        >>> f_deg = FITSImageBuffer(frb, fields="kT", units="deg",
-                                    scale=scale, center=(30., 45.))
+        >>> # This example specifies a specific WCS.
+        >>> from astropy.wcs import WCS
+        >>> w = WCS(naxis=self.dimensionality)
+        >>> w.wcs.crval = [30., 45.] # RA, Dec in degrees
+        >>> w.wcs.cunit = ["deg"]*2
+        >>> nx, ny = 800, 800
+        >>> w.wcs.crpix = [0.5*(nx+1), 0.5*(ny+1)]
+        >>> w.wcs.ctype = ["RA---TAN","DEC--TAN"]
+        >>> scale = 1./3600. # One arcsec per pixel
+        >>> w.wcs.cdelt = [-scale, scale]
+        >>> f_deg = FITSImageBuffer(frb, fields="kT", wcs=w)
         >>> f_deg.writeto("temp.fits")
         """
         
@@ -98,8 +101,14 @@
 
         first = True
 
+        self.field_units = {}
+
         for key in fields:
             if key not in exclude_fields:
+                if hasattr(img_data[key], "units"):
+                    self.field_units[key] = str(img_data[key].units)
+                else:
+                    self.field_units[key] = "dimensionless"
                 mylog.info("Making a FITS image of field %s" % (key))
                 if first:
                     hdu = pyfits.PrimaryHDU(np.array(img_data[key]))
@@ -109,7 +118,7 @@
                 hdu.name = key
                 hdu.header["btype"] = key
                 if hasattr(img_data[key], "units"):
-                    hdu.header["bunit"] = str(img_data[key].units)
+                    hdu.header["bunit"] = re.sub('()', '', str(img_data[key].units))
                 self.append(hdu)
 
         self.dimensionality = len(self[0].data.shape)
@@ -121,25 +130,11 @@
 
         has_coords = (isinstance(img_data, FixedResolutionBuffer) or
                       isinstance(img_data, YTCoveringGridBase))
-        
-        if center is None:
-            if units == "deg":
-                mylog.error("Please specify center=(RA, Dec) in degrees.")
-                raise ValueError
-            elif not has_coords:
-                mylog.warning("Setting center to the origin.")
-                center = [0.0]*self.dimensionality
-
-        if scale is None:
-            if units == "deg" or not has_coords and wcs is None:
-                mylog.error("Please specify scale=(dx,dy[,dz]) in %s." % (units))
-                raise ValueError
 
         if wcs is None:
             w = pywcs.WCS(header=self[0].header, naxis=self.dimensionality)
             w.wcs.crpix = 0.5*(np.array(self.shape)+1)
-            proj_type = ["linear"]*self.dimensionality
-            if isinstance(img_data, FixedResolutionBuffer) and units != "deg":
+            if isinstance(img_data, FixedResolutionBuffer):
                 # FRBs are a special case where we have coordinate
                 # information, so we take advantage of this and
                 # construct the WCS object
@@ -151,28 +146,20 @@
             elif isinstance(img_data, YTCoveringGridBase):
                 dx, dy, dz = img_data.dds.in_units(units)
                 center = 0.5*(img_data.left_edge+img_data.right_edge).in_units(units)
-            elif units == "deg" and self.dimensionality == 2:
-                dx = -scale[0]
-                dy = scale[1]
-                proj_type = ["RA---TAN","DEC--TAN"]
             else:
-                dx = scale[0]
-                dy = scale[1]
-                if self.dimensionality == 3: dz = scale[2]
-            
+                # We default to pixel coordinates if nothing is provided
+                dx, dy, dz = 1.0, 1.0, 1.0
+                center = 0.5*(np.array(self.shape)+1)
             w.wcs.crval = center
-            w.wcs.cunit = [units]*self.dimensionality
-            w.wcs.ctype = proj_type
-        
+            if has_coords:
+                w.wcs.cunit = [units]*self.dimensionality
             if self.dimensionality == 2:
                 w.wcs.cdelt = [dx,dy]
             elif self.dimensionality == 3:
                 w.wcs.cdelt = [dx,dy,dz]
-
+            w.wcs.ctype = ["linear"]*self.dimensionality
             self._set_wcs(w)
-
         else:
-
             self._set_wcs(wcs)
 
     def _set_wcs(self, wcs):
@@ -194,7 +181,7 @@
         for img in self: img.header[key] = value
             
     def keys(self):
-        return [f.name for f in self]
+        return [f.name.lower() for f in self]
 
     def has_key(self, key):
         return key in self.keys()
@@ -249,33 +236,71 @@
         import aplpy
         return aplpy.FITSFigure(self, **kwargs)
 
+    def get_data(self, field):
+        return YTArray(self[field].data, self.field_units[field])
+
+    def set_unit(self, field, units):
+        """
+        Set the units of *field* to *units*.
+        """
+        new_data = YTArray(self[field].data, self.field_units[field]).in_units(units)
+        self[field].data = new_data.v
+        self[field].header["bunit"] = units
+        self.field_units[field] = units
+        
 axis_wcs = [[1,2],[0,2],[0,1]]
 
-def construct_image(data_source, center=None):
+def sanitize_fits_unit(unit):
+    if unit == "Mpc":
+        mylog.info("Changing FITS file unit to kpc.")
+        unit = "kpc"
+    elif unit == "au":
+        unit = "AU"
+    return unit
+
+def construct_image(data_source, center=None, width=None, image_res=None):
     ds = data_source.ds
     axis = data_source.axis
+    if center is None or width is None:
+        center = ds.domain_center[axis_wcs[axis]]
+    if width is None:
+        width = ds.domain_width[axis_wcs[axis]]
+        mylog.info("Making an image of the entire domain, "+
+                   "so setting the center to the domain center.")
+    else:
+        width = ds.coordinates.sanitize_width(axis, width, None)
+    if image_res is None:
+        dd = ds.all_data()
+        dx, dy = [dd.quantities.extrema("d%s" % "xyz"[idx])[0]
+                  for idx in axis_wcs[axis]]
+        nx = int((width[0]/dx).in_units("dimensionless"))
+        ny = int((width[1]/dy).in_units("dimensionless"))
+    else:
+        if iterable(image_res):
+            nx, ny = image_res
+        else:
+            nx, ny = image_res, image_res
+        dx, dy = width[0]/nx, width[1]/ny
+    crpix = [0.5*(nx+1), 0.5*(ny+1)]
     if hasattr(ds, "wcs"):
-        # This is a FITS dataset
-        nx, ny = ds.domain_dimensions[axis_wcs[axis]]
-        crpix = [ds.wcs.wcs.crpix[idx] for idx in axis_wcs[axis]]
-        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
-        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
+        # This is a FITS dataset, so we use it to construct the WCS
         cunit = [str(ds.wcs.wcs.cunit[idx]) for idx in axis_wcs[axis]]
         ctype = [ds.wcs.wcs.ctype[idx] for idx in axis_wcs[axis]]
+        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
+        ctr_pix = center.in_units("code_length")[:self.dimensionality].v
+        crval = ds.wcs.wcs_pix2world(ctr_pix.reshape(1,self.dimensionality))[0]
+        crval = [crval[idx] for idx in axis_wcs[axis]]
     else:
-        # This is some other kind of dataset
-        unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
-        if center is None:
-            crval = [0.0,0.0]
-        else:
-            crval = [(ds.domain_center-center)[idx].in_units(unit) for idx in axis_wcs[axis]]
-        dx = ds.index.get_smallest_dx()
-        nx, ny = (ds.domain_width[axis_wcs[axis]]/dx).ndarray_view().astype("int")
-        crpix = [0.5*(nx+1), 0.5*(ny+1)]
-        cdelt = [dx.in_units(unit)]*2
+        # This is some other kind of dataset                                                                                    
+        unit = str(width[0].units)
+        if unit == "unitary":
+            unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
+        unit = sanitize_fits_unit(unit)
         cunit = [unit]*2
         ctype = ["LINEAR"]*2
-    frb = data_source.to_frb((1.0,"unitary"), (nx,ny))
+        cdelt = [dx.in_units(unit)]*2
+        crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
+    frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix
     w.wcs.cdelt = cdelt
@@ -304,13 +329,37 @@
          as a tuple containing a coordinate and string unit name or by passing
          in a YTArray.  If a list or unitless array is supplied, code units are
          assumed.
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+    image_res : an int or 2-tuple of ints
+        Specify the resolution of the resulting image. If not provided, it will be
+        determined based on the minimum cell size of the dataset.
     """
-    def __init__(self, ds, axis, fields, center="c", **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         slc = ds.slice(axis, center[axis], **kwargs)
-        w, frb = construct_image(slc, center=center)
+        w, frb = construct_image(slc, center=dcenter, width=width,
+                                 image_res=image_res)
         super(FITSSlice, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)
@@ -337,13 +386,38 @@
         as a tuple containing a coordinate and string unit name or by passing
         in a YTArray.  If a list or unitless array is supplied, code units are
         assumed.
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+    image_res : an int or 2-tuple of ints
+        Specify the resolution of the resulting image. If not provided, it will be
+        determined based on the minimum cell size of the dataset.
     """
-    def __init__(self, ds, axis, fields, center="c", weight_field=None, **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, 
+                 weight_field=None, image_res=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         prj = ds.proj(fields[0], axis, weight_field=weight_field, **kwargs)
-        w, frb = construct_image(prj, center=center)
+        w, frb = construct_image(prj, center=dcenter, width=width,
+                                 image_res=image_res)
         super(FITSProjection, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/utilities/on_demand_imports.py
--- a/yt/utilities/on_demand_imports.py
+++ b/yt/utilities/on_demand_imports.py
@@ -132,4 +132,15 @@
             self._interpolate = interpolate
         return self._interpolate
 
+    _special = None
+    @property
+    def special(self):
+        if self._special is None:
+            try:
+                import scipy.special as special
+            except ImportError:
+                special = NotAModule(self._name)
+            self._special = special
+        return self._special
+
 _scipy = scipy_imports()
\ No newline at end of file

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/visualization/fixed_resolution.py
--- a/yt/visualization/fixed_resolution.py
+++ b/yt/visualization/fixed_resolution.py
@@ -418,7 +418,7 @@
                                    width, dd.resolution, item,
                                    weight=dd.weight_field, volume=dd.volume,
                                    no_ghost=dd.no_ghost, interpolated=dd.interpolated,
-                                   north_vector=dd.north_vector)
+                                   north_vector=dd.north_vector, method=dd.method)
         units = Unit(dd.ds.field_info[item].units, registry=dd.ds.unit_registry)
         if dd.weight_field is None:
             units *= Unit('cm', registry=dd.ds.unit_registry)

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -771,9 +771,10 @@
                 if hasattr(self.ds.coordinates, "default_unit_label"):
                     axax = getattr(self.ds.coordinates,
                                    "%s_axis" % ("xy"[i]))[axis_index]
-                    un = self.ds.coordinates.default_unit_label[axax]
-                    axes_unit_labels[i] = '\/\/\left('+un+'\right)'
-                    continue
+                    unn = self.ds.coordinates.default_unit_label.get(axax, "")
+                    if unn != "":
+                        axes_unit_labels[i] = '\/\/\left('+unn+'\right)'
+                        continue
                 # Use sympy to factor h out of the unit.  In this context 'un'
                 # is a string, so we call the Unit constructor.
                 expr = Unit(un, registry=self.ds.unit_registry).expr
@@ -1303,12 +1304,11 @@
 
 class OffAxisProjectionDummyDataSource(object):
     _type_name = 'proj'
-    method = 'integrate'
     _key_fields = []
     def __init__(self, center, ds, normal_vector, width, fields,
                  interpolated, resolution = (800,800), weight=None,
                  volume=None, no_ghost=False, le=None, re=None,
-                 north_vector=None):
+                 north_vector=None, method="integrate"):
         self.center = center
         self.ds = ds
         self.axis = 4 # always true for oblique data objects
@@ -1325,6 +1325,7 @@
         self.le = le
         self.re = re
         self.north_vector = north_vector
+        self.method = method
 
     def _determine_fields(self, *args):
         return self.dd._determine_fields(*args)
@@ -1395,7 +1396,18 @@
          set, an arbitrary grid-aligned north-vector is chosen.
     fontsize : integer
          The size of the fonts for the axis, colorbar, and tick labels.
+    method : string
+         The method of projection.  Valid methods are:
 
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not
+         multiply by a path length when performing the integration, and is
+         just a straight summation of the field along the given axis.
     """
     _plot_type = 'OffAxisProjection'
     _frb_generator = OffAxisProjectionFixedResolutionBuffer
@@ -1403,7 +1415,7 @@
     def __init__(self, ds, normal, fields, center='c', width=None,
                  depth=(1, '1'), axes_unit=None, weight_field=None,
                  max_level=None, north_vector=None, volume=None, no_ghost=False,
-                 le=None, re=None, interpolated=False, fontsize=18):
+                 le=None, re=None, interpolated=False, fontsize=18, method="integrate"):
         (bounds, center_rot) = \
           get_oblique_window_parameters(normal,center,width,ds,depth=depth)
         fields = ensure_list(fields)[:]
@@ -1413,7 +1425,7 @@
         OffAxisProj = OffAxisProjectionDummyDataSource(
             center_rot, ds, normal, oap_width, fields, interpolated,
             weight=weight_field,  volume=volume, no_ghost=no_ghost,
-            le=le, re=re, north_vector=north_vector)
+            le=le, re=re, north_vector=north_vector, method=method)
         # If a non-weighted, integral projection, assure field-label reflects that
         if weight_field is None and OffAxisProj.method == "integrate":
             self.projected = True

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -2171,7 +2171,8 @@
 class ProjectionCamera(Camera):
     def __init__(self, center, normal_vector, width, resolution,
             field, weight=None, volume=None, no_ghost = False, 
-            north_vector=None, ds=None, interpolated=False):
+            north_vector=None, ds=None, interpolated=False,
+            method="integrate"):
 
         if not interpolated:
             volume = 1
@@ -2180,6 +2181,7 @@
         self.field = field
         self.weight = weight
         self.resolution = resolution
+        self.method = method
 
         fields = [field]
         if self.weight is not None:
@@ -2250,11 +2252,12 @@
         dd = ds.all_data()
         field = dd._determine_fields([self.field])[0]
         finfo = ds._get_field_info(*field)
-        if self.weight is None:
-            dl = self.width[2]
-            image *= dl
-        else:
-            image[:,:,0] /= image[:,:,1]
+        if self.method == "integrate":
+            if self.weight is None:
+                dl = self.width[2]
+                image *= dl
+            else:
+                image[:,:,0] /= image[:,:,1]
 
         return image[:,:,0]
 
@@ -2342,7 +2345,7 @@
 def off_axis_projection(ds, center, normal_vector, width, resolution,
                         field, weight = None, 
                         volume = None, no_ghost = False, interpolated = False,
-                        north_vector = None):
+                        north_vector = None, method = "integrate"):
     r"""Project through a dataset, off-axis, and return the image plane.
 
     This function will accept the necessary items to integrate through a volume
@@ -2386,6 +2389,18 @@
         If True, the data is first interpolated to vertex-centered data, 
         then tri-linearly interpolated along the ray. Not suggested for 
         quantitative studies.
+    method : string
+         The method of projection.  Valid methods are:
+
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not
+         multiply by a path length when performing the integration, and is
+         just a straight summation of the field along the given axis.
 
     Returns
     -------
@@ -2403,7 +2418,7 @@
     projcam = ProjectionCamera(center, normal_vector, width, resolution,
                                field, weight=weight, ds=ds, volume=volume,
                                no_ghost=no_ghost, interpolated=interpolated, 
-                               north_vector=north_vector)
+                               north_vector=north_vector, method=method)
     image = projcam.snapshot()
     return image[:,:]
 

diff -r c7af5e4ddcdfb579a5d8b370ef48ef5f5f52f019 -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 yt/visualization/volume_rendering/image_handling.py
--- a/yt/visualization/volume_rendering/image_handling.py
+++ b/yt/visualization/volume_rendering/image_handling.py
@@ -40,9 +40,7 @@
         data["b"] = image[:,:,2]
         data["a"] = image[:,:,3]
         nx, ny = data["r"].shape
-        fib = FITSImageBuffer(data, units="pixel",
-                              center=[0.5*(nx+1), 0.5*(ny+1)],
-                              scale=[1.]*2)
+        fib = FITSImageBuffer(data)
         fib.writeto('%s.fits'%fn,clobber=True)
 
 def import_rgba(name, h5=True):


https://bitbucket.org/yt_analysis/yt/commits/f99d8e3d1b45/
Changeset:   f99d8e3d1b45
Branch:      yt
User:        jzuhone
Date:        2014-11-03 20:01:07+00:00
Summary:     Getting units right for off-axis projections
Affected #:  1 file

diff -r d0b8664501d0d314d4c2a020a330dbb724e0e9e1 -r f99d8e3d1b45748ba0b6acfefcff74f68807ef08 yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -2252,14 +2252,14 @@
         dd = ds.all_data()
         field = dd._determine_fields([self.field])[0]
         finfo = ds._get_field_info(*field)
+        dl = 1.0
         if self.method == "integrate":
             if self.weight is None:
-                dl = self.width[2]
-                image *= dl
+                dl = self.width[2].in_units("cm")
             else:
                 image[:,:,0] /= image[:,:,1]
 
-        return image[:,:,0]
+        return ImageArray(image[:,:,0], finfo.units)*dl
 
 
     def _render(self, double_check, num_threads, image, sampler):


https://bitbucket.org/yt_analysis/yt/commits/82e07ddca08e/
Changeset:   82e07ddca08e
Branch:      yt
User:        jzuhone
Date:        2014-11-03 20:10:38+00:00
Summary:     Issue special warning when AstroPy isn't installed and it appears that we are trying to load a FITS file.
Affected #:  1 file

diff -r f99d8e3d1b45748ba0b6acfefcff74f68807ef08 -r 82e07ddca08e2a166fa2e6ed6caffefa24d32471 yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -42,7 +42,7 @@
     unit_prefixes
 from yt.units import dimensions
 from yt.units.yt_array import YTQuantity
-from yt.utilities.on_demand_imports import _astropy
+from yt.utilities.on_demand_imports import _astropy, NotAModule
 
 lon_prefixes = ["X","RA","GLON","LINEAR"]
 lat_prefixes = ["Y","DEC","GLAT","LINEAR"]
@@ -664,6 +664,8 @@
             ext = args[0].rsplit(".", 1)[0].rsplit(".", 1)[-1]
         if ext.upper() not in ("FITS", "FTS"):
             return False
+        elif isinstance(_astropy.pyfits, NotAModule):
+            mylog.warning("This appears to be a FITS file, but AstroPy is not installed.")
         try:
             with warnings.catch_warnings():
                 warnings.filterwarnings('ignore', category=UserWarning, append=True)


https://bitbucket.org/yt_analysis/yt/commits/1b18659fba23/
Changeset:   1b18659fba23
Branch:      yt
User:        jzuhone
Date:        2014-11-03 20:19:10+00:00
Summary:     Throw a RuntimeError instead.
Affected #:  1 file

diff -r 82e07ddca08e2a166fa2e6ed6caffefa24d32471 -r 1b18659fba23a51604134b163716a7cbb6f609e5 yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -665,7 +665,7 @@
         if ext.upper() not in ("FITS", "FTS"):
             return False
         elif isinstance(_astropy.pyfits, NotAModule):
-            mylog.warning("This appears to be a FITS file, but AstroPy is not installed.")
+            raise RuntimeError("This appears to be a FITS file, but AstroPy is not installed.")
         try:
             with warnings.catch_warnings():
                 warnings.filterwarnings('ignore', category=UserWarning, append=True)


https://bitbucket.org/yt_analysis/yt/commits/3d3bdd8cbf98/
Changeset:   3d3bdd8cbf98
Branch:      yt
User:        jzuhone
Date:        2014-11-03 20:30:28+00:00
Summary:     Merge
Affected #:  16 files

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 doc/source/analyzing/units/2)_Fields_and_unit_conversion.ipynb
--- a/doc/source/analyzing/units/2)_Fields_and_unit_conversion.ipynb
+++ b/doc/source/analyzing/units/2)_Fields_and_unit_conversion.ipynb
@@ -1,7 +1,7 @@
 {
  "metadata": {
   "name": "",
-  "signature": "sha256:2faff88abc93fe2bc9d91467db786a8b69ec3ece6783a7055942ecc7c47a0817"
+  "signature": "sha256:c7cfb2db456d127bb633b7eee7ad6fe14290aa622ac62694c7840d80137afaba"
  },
  "nbformat": 3,
  "nbformat_minor": 0,
@@ -36,7 +36,7 @@
      "input": [
       "import yt\n",
       "ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')\n",
-      "          \n",
+      "\n",
       "dd = ds.all_data()\n",
       "maxval, maxloc = ds.find_max('density')\n",
       "\n",
@@ -222,6 +222,69 @@
      "level": 3,
      "metadata": {},
      "source": [
+      "Electrostatic/Electromagnetic Units"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Electromagnetic units can be a bit tricky, because the units for such quantities in different unit systems can have entirely different dimensions, even if they are meant to represent the same physical quantities. For example, in the SI system of units, current in Amperes is a fundamental unit of measure, so the unit of charge \"coulomb\" is equal to one ampere-second. On the other hand, in the Gaussian/CGS system, there is no equivalent base electromagnetic unit, and the electrostatic charge unit \"esu\" is equal to one $\\mathrm{cm^{3/2}g^{-1/2}s^{-1}}$ (which does not have any apparent physical significance). `yt` recognizes this difference:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "q1 = yt.YTArray(1.0,\"C\") # Coulombs\n",
+      "q2 = yt.YTArray(1.0,\"esu\") # electrostatic units / statcoulomb\n",
+      "\n",
+      "print \"units =\", q1.in_mks().units, \", dims =\", q1.units.dimensions\n",
+      "print \"units =\", q2.in_cgs().units, \", dims =\", q2.units.dimensions"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Under the hood, the `yt` units system has a translation layer that converts between these two systems, without any further effort required. For example:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from yt.utilities.physical_constants import elementary_charge\n",
+      "\n",
+      "print elementary_charge\n",
+      "elementary_charge_C = elementary_charge.in_units(\"C\")\n",
+      "print elementary_charge_C"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The electromagnetic unit translations `yt` understands are:\n",
+      "\n",
+      "* Charge: 1 coulomb (C) $\\leftrightarrow$ 0.1c electrostatic unit (esu, Fr)\n",
+      "* Current: 1 ampere (A, C/s) $\\leftrightarrow$ 0.1c statampere (statA, esu/s, Fr) \n",
+      "* Magnetic Field: 1 tesla (T) $\\leftrightarrow 10^4$ gauss (G)\n",
+      "\n",
+      "where \"Fr\" is the franklin, an alternative name for the electrostatic unit, and c is the speed of light. "
+     ]
+    },
+    {
+     "cell_type": "heading",
+     "level": 3,
+     "metadata": {},
+     "source": [
       "Working with views and converting to ndarray"
      ]
     },
@@ -324,10 +387,9 @@
      "collapsed": false,
      "input": [
       "from astropy import units as u\n",
-      "from yt import YTQuantity, YTArray\n",
       "\n",
       "x = 42.0 * u.meter\n",
-      "y = YTQuantity.from_astropy(x) "
+      "y = yt.YTQuantity.from_astropy(x) "
      ],
      "language": "python",
      "metadata": {},
@@ -349,7 +411,7 @@
      "collapsed": false,
      "input": [
       "a = np.random.random(size=10) * u.km/u.s\n",
-      "b = YTArray.from_astropy(a)"
+      "b = yt.YTArray.from_astropy(a)"
      ],
      "language": "python",
      "metadata": {},
@@ -436,7 +498,7 @@
      "collapsed": false,
      "input": [
       "k1 = kboltz.to_astropy()\n",
-      "k2 = YTQuantity.from_astropy(kb)\n",
+      "k2 = yt.YTQuantity.from_astropy(kb)\n",
       "print k1 == k2"
      ],
      "language": "python",
@@ -447,7 +509,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "c = YTArray.from_astropy(a)\n",
+      "c = yt.YTArray.from_astropy(a)\n",
       "d = c.to_astropy()\n",
       "print a == d"
      ],

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 doc/source/analyzing/units/6)_Unit_Equivalencies.ipynb
--- /dev/null
+++ b/doc/source/analyzing/units/6)_Unit_Equivalencies.ipynb
@@ -0,0 +1,179 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:cee652d703dd3369d81ebc670882d3734f73d0274aab98823a784d8039355480"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Some physical quantities are directly related to other unitful quantities by a constant, but otherwise do not have the same units. To facilitate conversions between these quantities, `yt` implements a system of unit equivalencies (inspired by the [AstroPy implementation](http://docs.astropy.org/en/latest/units/equivalencies.html)). The possible unit equivalencies are:\n",
+      "\n",
+      "* `\"thermal\"`: conversions between temperature and energy ($E = k_BT$)\n",
+      "* `\"spectral\"`: conversions between wavelength, frequency, and energy for photons ($E = h\\nu = hc/\\lambda, c = \\lambda\\nu$)\n",
+      "* `\"mass_energy\"`: conversions between mass and energy ($E = mc^2$)\n",
+      "* `\"lorentz\"`: conversions between velocity and Lorentz factor ($\\gamma = 1/\\sqrt{1-(v/c)^2}$)\n",
+      "* `\"schwarzschild\"`: conversions between mass and Schwarzschild radius ($R_S = 2GM/c^2$)\n",
+      "* `\"compton\"`: conversions between mass and Compton wavelength ($\\lambda = h/mc$)\n",
+      "\n",
+      "The following unit equivalencies only apply under conditions applicable for an ideal gas with a constant mean molecular weight $\\mu$ and ratio of specific heats $\\gamma$:\n",
+      "\n",
+      "* `\"number_density\"`: conversions between density and number density ($n = \\rho/\\mu{m_p}$)\n",
+      "* `\"sound_speed\"`: conversions between temperature and sound speed for an ideal gas ($c_s^2 = \\gamma{k_BT}/\\mu{m_p}$)\n",
+      "\n",
+      "A `YTArray` or `YTQuantity` can be converted to an equivalent using `to_equivalent`, where the unit and the equivalence name are provided as arguments:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import yt\n",
+      "import numpy as np\n",
+      "\n",
+      "ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')\n",
+      "\n",
+      "dd = ds.all_data()\n",
+      "\n",
+      "print dd[\"temperature\"].to_equivalent(\"erg\", \"thermal\")\n",
+      "print dd[\"temperature\"].to_equivalent(\"eV\", \"thermal\")\n",
+      "\n",
+      "# Rest energy of the proton\n",
+      "from yt.utilities.physical_constants import mp\n",
+      "E_p = mp.to_equivalent(\"GeV\", \"mass_energy\")\n",
+      "print E_p"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Equivalencies can go in both directions, without any information required other than the unit you want to convert to:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from yt.utilities.physical_constants import clight\n",
+      "v = 0.1*clight\n",
+      "g = v.to_equivalent(\"dimensionless\", \"lorentz\")\n",
+      "print g\n",
+      "print g.to_equivalent(\"c\", \"lorentz\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "heading",
+     "level": 3,
+     "metadata": {},
+     "source": [
+      "Special Equivalencies"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Some equivalencies can take supplemental information. The `\"number_density\"` equivalence can take a custom mean molecular weight (default is $\\mu = 0.6$):"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print dd[\"density\"].max()\n",
+      "print dd[\"density\"].to_equivalent(\"cm**-3\", \"number_density\").max()\n",
+      "print dd[\"density\"].to_equivalent(\"cm**-3\", \"number_density\", mu=0.75).max()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The `\"sound_speed\"` equivalence optionally takes the ratio of specific heats $\\gamma$ and the mean molecular weight $\\mu$ (defaults are $\\gamma$ = 5/3, $\\mu = 0.6$):"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print dd[\"temperature\"].to_equivalent(\"km/s\", \"sound_speed\").mean()\n",
+      "print dd[\"temperature\"].to_equivalent(\"km/s\", \"sound_speed\", gamma=4./3., mu=0.5).mean()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "These options must be used with caution, and only if you know the underlying data adheres to these assumptions!"
+     ]
+    },
+    {
+     "cell_type": "heading",
+     "level": 3,
+     "metadata": {},
+     "source": [
+      "Determining Valid Equivalencies"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "If a certain equivalence does not exist for a particular unit, then an error will be thrown:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from yt.utilities.exceptions import YTInvalidUnitEquivalence\n",
+      "\n",
+      "try:\n",
+      "    x = v.to_equivalent(\"angstrom\", \"spectral\")\n",
+      "except YTInvalidUnitEquivalence as e:\n",
+      "    print e"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "To list the equivalencies available for a given `YTArray` or `YTQuantity`, use the `list_equivalencies` method:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "E_p.list_equivalencies()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 doc/source/analyzing/units/index.rst
--- a/doc/source/analyzing/units/index.rst
+++ b/doc/source/analyzing/units/index.rst
@@ -33,6 +33,7 @@
    comoving_units_and_code_units
    comparing_units_from_different_datasets
    units_and_plotting
+   unit_equivalencies
 
 .. note::
 

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 doc/source/analyzing/units/unit_equivalencies.rst
--- /dev/null
+++ b/doc/source/analyzing/units/unit_equivalencies.rst
@@ -0,0 +1,7 @@
+.. _symbolic_units:
+
+Symbolic units: :code:`yt.units`
+================================
+
+.. notebook:: 6)_Unit_Equivalencies.ipynb
+   :skip_exceptions:

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/analysis_modules/photon_simulator/spectral_models.py
--- a/yt/analysis_modules/photon_simulator/spectral_models.py
+++ b/yt/analysis_modules/photon_simulator/spectral_models.py
@@ -39,7 +39,7 @@
         self.emax = emax*units.keV
         self.nchan = nchan
         self.ebins = np.linspace(emin, emax, nchan+1)*units.keV
-        self.de = np.diff(self.ebins)*units.keV
+        self.de = np.diff(self.ebins)
         self.emid = 0.5*(self.ebins[1:]+self.ebins[:-1])
         
     def prepare(self):

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/fields/astro_fields.py
--- a/yt/fields/astro_fields.py
+++ b/yt/fields/astro_fields.py
@@ -102,6 +102,15 @@
                        function=_xray_emissivity,
                        units="") # add correct units here
 
+    def _mazzotta_weighting(field, data):
+        # Spectroscopic-like weighting field for galaxy clusters
+        # Only useful as a weight_field for temperature, metallicity, velocity
+        return data["density"]*data["density"]*data["kT"]**-0.25/mh/mh
+
+    registry.add_field((ftype,"mazzotta_weighting"),
+                       function=_mazzotta_weighting,
+                       units="keV**-0.25*cm**-6")
+    
     def _sz_kinetic(field, data):
         scale = 0.88 * sigma_thompson / mh / clight
         vel_axis = data.get_field_parameter("axis")

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/dimensions.py
--- a/yt/units/dimensions.py
+++ b/yt/units/dimensions.py
@@ -19,9 +19,11 @@
 time = Symbol("(time)", positive=True)
 temperature = Symbol("(temperature)", positive=True)
 angle = Symbol("(angle)", positive=True)
+current_mks = Symbol("(current_mks)", positive=True)
 dimensionless = sympify(1)
 
-base_dimensions = [mass, length, time, temperature, angle, dimensionless]
+base_dimensions = [mass, length, time, temperature, angle, current_mks,
+                   dimensionless]
 
 #
 # Derived dimensions
@@ -44,16 +46,31 @@
 power    = energy / time
 flux     = power / area
 specific_flux = flux / rate
-charge   = (energy * length)**Rational(1, 2)  # proper 1/2 power
+number_density = 1/(length*length*length)
+density = mass * number_density
 
-electric_field = charge / length**2
-magnetic_field = electric_field
+# Gaussian electromagnetic units
+charge_cgs  = (energy * length)**Rational(1, 2)  # proper 1/2 power
+current_cgs = charge_cgs / time
+electric_field_cgs = charge_cgs / length**2
+magnetic_field_cgs = electric_field_cgs
+
+# SI electromagnetic units
+charge_mks = current_mks * time
+electric_field_mks = force / charge_mks
+magnetic_field_mks = electric_field_mks / velocity
+
+# Since cgs is our default, I'm adding these aliases for backwards-compatibility
+charge = charge_cgs
+electric_field = electric_field_cgs
+magnetic_field = magnetic_field_cgs
 
 solid_angle = angle * angle
 
 derived_dimensions = [rate, velocity, acceleration, jerk, snap, crackle, pop, 
-                      momentum, force, energy, power, charge, electric_field, 
-                      magnetic_field, solid_angle, flux, specific_flux, volume,
-                      area]
+                      momentum, force, energy, power, charge_cgs, electric_field_cgs,
+                      magnetic_field_cgs, solid_angle, flux, specific_flux, volume,
+                      area, current_cgs, charge_mks, electric_field_mks,
+                      magnetic_field_mks]
 
 dimensions = base_dimensions + derived_dimensions

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/equivalencies.py
--- /dev/null
+++ b/yt/units/equivalencies.py
@@ -0,0 +1,166 @@
+"""
+Equivalencies between different kinds of units
+
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, 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 yt.utilities.physical_constants as pc
+from yt.units.dimensions import temperature, mass, energy, length, rate, \
+    velocity, dimensionless, density, number_density, flux
+from yt.extern.six import add_metaclass
+import numpy as np
+
+equivalence_registry = {}
+
+class RegisteredEquivalence(type):
+    def __init__(cls, name, b, d):
+        type.__init__(cls, name, b, d)
+        if hasattr(cls, "_type_name") and not cls._skip_add:
+            equivalence_registry[cls._type_name] = cls
+
+ at add_metaclass(RegisteredEquivalence)
+class Equivalence(object):
+    _skip_add = False
+
+class NumberDensityEquivalence(Equivalence):
+    _type_name = "number_density"
+    dims = (density,number_density,)
+
+    def convert(self, x, new_dims, mu=0.6):
+        if new_dims == number_density:
+            return x/(mu*pc.mh)
+        elif new_dims == density:
+            return x*mu*pc.mh
+
+    def __str__(self):
+        return "number density: density <-> number density"
+
+class ThermalEquivalence(Equivalence):
+    _type_name = "thermal"
+    dims = (temperature,energy,)
+
+    def convert(self, x, new_dims):
+        if new_dims == energy:
+            return pc.kboltz*x
+        elif new_dims == temperature:
+            return x/pc.kboltz
+
+    def __str__(self):
+        return "thermal: temperature <-> energy"
+
+class MassEnergyEquivalence(Equivalence):
+    _type_name = "mass_energy"
+    dims = (mass,energy,)
+
+    def convert(self, x, new_dims):
+        if new_dims == energy:
+            return x*pc.clight*pc.clight
+        elif new_dims == mass:
+            return x/(pc.clight*pc.clight)
+
+    def __str__(self):
+        return "mass_energy: mass <-> energy"
+
+class SpectralEquivalence(Equivalence):
+    _type_name = "spectral"
+    dims = (length,rate,energy,)
+
+    def convert(self, x, new_dims):
+        if new_dims == energy:
+            if x.units.dimensions == length:
+                nu = pc.clight/x
+            elif x.units.dimensions == rate:
+                nu = x
+            return pc.hcgs*nu
+        elif new_dims == length:
+            if x.units.dimensions == rate:
+                return pc.clight/x
+            elif x.units.dimensions == energy:
+                return pc.hcgs*pc.clight/x
+        elif new_dims == rate:
+            if x.units.dimensions == length:
+                return pc.clight/x
+            elif x.units.dimensions == energy:
+                return x/pc.hcgs
+
+    def __str__(self):
+        return "spectral: length <-> rate <-> energy"
+
+class SoundSpeedEquivalence(Equivalence):
+    _type_name = "sound_speed"
+    dims = (velocity,temperature,energy,)
+
+    def convert(self, x, new_dims, mu=0.6, gamma=5./3.):
+        if new_dims == velocity:
+            if x.units.dimensions == temperature:
+                kT = pc.kboltz*x
+            elif x.units.dimensions == energy:
+                kT = x
+            return np.sqrt(gamma*kT/(mu*pc.mh))
+        else:
+            kT = x*x*mu*pc.mh/gamma
+            if new_dims == temperature:
+                return kT/pc.kboltz
+            else:
+                return kT
+
+    def __str__(self):
+        return "sound_speed (ideal gas): velocity <-> temperature <-> energy"
+
+class LorentzEquivalence(Equivalence):
+    _type_name = "lorentz"
+    dims = (dimensionless,velocity,)
+
+    def convert(self, x, new_dims):
+        if new_dims == dimensionless:
+            beta = x.in_cgs()/pc.clight
+            return 1./np.sqrt(1.-beta**2)
+        elif new_dims == velocity:
+            return pc.clight*np.sqrt(1.-1./(x*x))
+
+    def __str__(self):
+        return "lorentz: velocity <-> dimensionless"
+
+class SchwarzschildEquivalence(Equivalence):
+    _type_name = "schwarzschild"
+    dims = (mass,length,)
+
+    def convert(self, x, new_dims):
+        if new_dims == length:
+            return 2.*pc.G*x/(pc.clight*pc.clight)
+        elif new_dims == mass:
+            return 0.5*x*pc.clight*pc.clight/pc.G
+
+    def __str__(self):
+        return "schwarzschild: mass <-> length"
+
+class ComptonEquivalence(Equivalence):
+    _type_name = "compton"
+    dims = (mass,length,)
+
+    def convert(self, x, new_dims):
+        return pc.hcgs/(x*pc.clight)
+
+    def __str__(self):
+        return "compton: mass <-> length"
+
+class EffectiveTemperature(Equivalence):
+    _type_name = "effective_temperature"
+    dims = (flux,temperature,)
+
+    def convert(self, x, new_dims):
+        if new_dims == flux:
+            return pc.stefan_boltzmann_constant_cgs*x**4
+        elif new_dims == temperature:
+            return (x/pc.stefan_boltzmann_constant_cgs)**0.25
+
+    def __str__(self):
+        return "effective_temperature: flux <-> temperature"
+

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/tests/test_ytarray.py
--- a/yt/units/tests/test_ytarray.py
+++ b/yt/units/tests/test_ytarray.py
@@ -28,7 +28,8 @@
     assert_array_equal, \
     assert_equal, assert_raises, \
     assert_array_almost_equal_nulp, \
-    assert_array_almost_equal
+    assert_array_almost_equal, \
+    assert_allclose
 from numpy import array
 from yt.units.yt_array import \
     YTArray, YTQuantity, \
@@ -471,21 +472,21 @@
 
     km = YTQuantity(1, 'km')
     balmy = YTQuantity(300, 'K')
-    balmy_F = YTQuantity(80.33, 'F')
-    balmy_C = YTQuantity(26.85, 'C')
+    balmy_F = YTQuantity(80.33, 'degF')
+    balmy_C = YTQuantity(26.85, 'degC')
     balmy_R = YTQuantity(540, 'R')
 
-    assert_array_almost_equal(balmy.in_units('F'), balmy_F)
-    assert_array_almost_equal(balmy.in_units('C'), balmy_C)
+    assert_array_almost_equal(balmy.in_units('degF'), balmy_F)
+    assert_array_almost_equal(balmy.in_units('degC'), balmy_C)
     assert_array_almost_equal(balmy.in_units('R'), balmy_R)
 
     balmy_view = balmy.ndarray_view()
 
-    balmy.convert_to_units('F')
+    balmy.convert_to_units('degF')
     yield assert_true, balmy_view.base is balmy.base
     yield assert_array_almost_equal, np.array(balmy), np.array(balmy_F)
 
-    balmy.convert_to_units('C')
+    balmy.convert_to_units('degC')
     yield assert_true, balmy_view.base is balmy.base
     yield assert_array_almost_equal, np.array(balmy), np.array(balmy_C)
 
@@ -493,13 +494,13 @@
     yield assert_true, balmy_view.base is balmy.base
     yield assert_array_almost_equal, np.array(balmy), np.array(balmy_R)
 
-    balmy.convert_to_units('F')
+    balmy.convert_to_units('degF')
     yield assert_true, balmy_view.base is balmy.base
     yield assert_array_almost_equal, np.array(balmy), np.array(balmy_F)
 
     yield assert_raises, InvalidUnitOperation, np.multiply, balmy, km
 
-    # Does CGS convergion from F to K work?
+    # Does CGS conversion from F to K work?
     yield assert_array_almost_equal, balmy.in_cgs(), YTQuantity(300, 'K')
 
 
@@ -859,6 +860,114 @@
     os.chdir(curdir)
     shutil.rmtree(tmpdir)
 
+def test_cgs_conversions():
+    from yt.utilities.physical_constants import qp, eps_0, clight
+    from yt.units.dimensions import current_mks, time
+
+    qp_mks = qp.in_units("C")
+    yield assert_equal, qp_mks.units.dimensions, current_mks*time
+    yield assert_array_almost_equal, qp_mks.in_cgs(), qp
+    qp_mks_k = qp_mks.in_units("kC")
+    yield assert_equal, qp_mks.units.dimensions, current_mks*time
+    yield assert_array_almost_equal, qp_mks_k.in_cgs(), qp
+    yield assert_array_almost_equal, qp_mks_k.in_units("kesu"), qp.in_units("kesu")
+
+    K = 1.0/(4*np.pi*eps_0)
+    yield assert_array_almost_equal, K.in_cgs(), 1.0
+
+    B = YTQuantity(1.0, "T")
+    yield assert_array_almost_equal, B.in_units("gauss"), YTQuantity(1.0e4, "gauss")
+
+    I = YTQuantity(1.0, "A")
+    yield assert_array_almost_equal, I.in_units("statA"), YTQuantity(0.1*clight, "statA")
+
+def test_equivalencies():
+    from yt.utilities.physical_constants import clight, mp, kboltz, hcgs, mh, me, \
+        mass_sun_cgs, G, stefan_boltzmann_constant_cgs
+    import yt.units as u
+
+    # Mass-energy
+
+    E = mp.to_equivalent("keV","mass_energy")
+    yield assert_equal, E, mp*clight*clight
+    yield assert_allclose, mp, E.to_equivalent("g", "mass_energy")
+
+    # Thermal
+
+    T = YTQuantity(1.0e8,"K")
+    E = T.to_equivalent("W*hr","thermal")
+    yield assert_equal, E, (kboltz*T).in_units("W*hr")
+    yield assert_allclose, T, E.to_equivalent("K", "thermal")
+
+    # Spectral
+
+    l = YTQuantity(4000.,"angstrom")
+    nu = l.to_equivalent("Hz","spectral")
+    yield assert_equal, nu, clight/l
+    E = hcgs*nu
+    l2 = E.to_equivalent("angstrom", "spectral")
+    yield assert_allclose, l, l2
+    nu2 = clight/l2.in_units("cm")
+    yield assert_allclose, nu, nu2
+    E2 = nu2.to_equivalent("keV", "spectral")
+    yield assert_allclose, E2, E.in_units("keV")
+
+    # Sound-speed
+
+    mu = 0.6
+    gg = 5./3.
+    c_s = T.to_equivalent("km/s","sound_speed")
+    yield assert_equal, c_s, np.sqrt(gg*kboltz*T/(mu*mh))
+    yield assert_allclose, T, c_s.to_equivalent("K","sound_speed")
+
+    mu = 0.5
+    gg = 4./3.
+    c_s = T.to_equivalent("km/s","sound_speed", mu=mu, gamma=gg)
+    yield assert_equal, c_s, np.sqrt(gg*kboltz*T/(mu*mh))
+    yield assert_allclose, T, c_s.to_equivalent("K","sound_speed",
+                                                    mu=mu, gamma=gg)
+
+    # Lorentz
+
+    v = 0.8*clight
+    g = v.to_equivalent("dimensionless","lorentz")
+    g2 = YTQuantity(1./np.sqrt(1.-0.8*0.8), "dimensionless")
+    yield assert_allclose, g, g2
+    v2 = g2.to_equivalent("mile/hr", "lorentz")
+    yield assert_allclose, v2, v.in_units("mile/hr")
+
+    # Schwarzschild
+
+    R = mass_sun_cgs.to_equivalent("kpc","schwarzschild")
+    yield assert_equal, R.in_cgs(), 2*G*mass_sun_cgs/(clight*clight)
+    yield assert_allclose, mass_sun_cgs, R.to_equivalent("g", "schwarzschild")
+
+    # Compton
+
+    l = me.to_equivalent("angstrom","compton")
+    yield assert_equal, l, hcgs/(me*clight)
+    yield assert_allclose, me, l.to_equivalent("g", "compton")
+
+    # Number density
+
+    rho = mp/u.cm**3
+
+    n = rho.to_equivalent("cm**-3","number_density")
+    yield assert_equal, n, rho/(mh*0.6)
+    yield assert_allclose, rho, n.to_equivalent("g/cm**3","number_density")
+
+    n = rho.to_equivalent("cm**-3","number_density", mu=0.75)
+    yield assert_equal, n, rho/(mh*0.75)
+    yield assert_allclose, rho, n.to_equivalent("g/cm**3","number_density", mu=0.75)
+
+    # Effective temperature
+
+    T = YTQuantity(1.0e4, "K")
+    F = T.to_equivalent("erg/s/cm**2","effective_temperature")
+    yield assert_equal, F, stefan_boltzmann_constant_cgs*T**4
+    yield assert_allclose, T, F.to_equivalent("K", "effective_temperature")
+
+
 def test_numpy_wrappers():
     a1 = YTArray([1, 2, 3], 'cm')
     a2 = YTArray([2, 3, 4, 5, 6], 'cm')

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/unit_lookup_table.py
--- a/yt/units/unit_lookup_table.py
+++ b/yt/units/unit_lookup_table.py
@@ -20,7 +20,8 @@
     metallicity_sun, erg_per_eV, amu_grams, mass_electron_grams, \
     cm_per_ang, jansky_cgs, mass_jupiter_grams, mass_earth_grams, \
     boltzmann_constant_erg_per_K, kelvin_per_rankine, \
-    speed_of_light_cm_per_s
+    speed_of_light_cm_per_s, planck_length_cm, planck_charge_esu, \
+    planck_energy_erg, planck_mass_grams, planck_temperature_K, planck_time_s
 import numpy as np
 
 # Lookup a unit symbol with the symbol string, and provide a tuple with the
@@ -37,20 +38,25 @@
     # other cgs
     "dyne": (1.0, dimensions.force),
     "erg":  (1.0, dimensions.energy),
-    "esu":  (1.0, dimensions.charge),
-    "gauss": (1.0, dimensions.magnetic_field),
-    "C" : (1.0, dimensions.temperature, -273.15),
+    "esu":  (1.0, dimensions.charge_cgs),
+    "gauss": (1.0, dimensions.magnetic_field_cgs),
+    "degC": (1.0, dimensions.temperature, -273.15),
+    "statA": (1.0, dimensions.current_cgs),
 
     # some SI
     "m": (1.0e2, dimensions.length),
     "J": (1.0e7, dimensions.energy),
     "W": (1.0e7, dimensions.power),
     "Hz": (1.0, dimensions.rate),
+    "N": (1.0e5, dimensions.force),
+    "C": (0.1*speed_of_light_cm_per_s, dimensions.charge_mks),
+    "A": (0.1*speed_of_light_cm_per_s, dimensions.current_mks),
+    "T": (1.0e4, dimensions.magnetic_field_mks),
 
     # Imperial units
     "ft": (30.48, dimensions.length),
     "mile": (160934, dimensions.length),
-    "F": (kelvin_per_rankine, dimensions.temperature, -459.67),
+    "degF": (kelvin_per_rankine, dimensions.temperature, -459.67),
     "R": (kelvin_per_rankine, dimensions.temperature),
 
     # dimensionless stuff
@@ -93,13 +99,11 @@
     # misc
     "eV": (erg_per_eV, dimensions.energy),
     "amu": (amu_grams, dimensions.mass),
-    "me": (mass_electron_grams, dimensions.mass),
     "angstrom": (cm_per_ang, dimensions.length),
     "Jy": (jansky_cgs, dimensions.specific_flux),
     "counts": (1.0, dimensions.dimensionless),
-    "kB": (boltzmann_constant_erg_per_K,
-           dimensions.energy/dimensions.temperature),
     "photons": (1.0, dimensions.dimensionless),
+    "me": (mass_electron_grams, dimensions.mass),
 
     # for AstroPy compatibility
     "solMass": (mass_sun_grams, dimensions.mass),
@@ -109,11 +113,19 @@
     "sr": (1.0, dimensions.solid_angle),
     "rad": (1.0, dimensions.solid_angle),
     "deg": (np.pi/180., dimensions.angle),
-    "Fr":  (1.0, dimensions.charge),
-    "G": (1.0, dimensions.magnetic_field),
+    "Fr":  (1.0, dimensions.charge_cgs),
+    "G": (1.0, dimensions.magnetic_field_cgs),
     "d": (1.0, dimensions.time),
     "Angstrom": (cm_per_ang, dimensions.length),
 
+    # Planck units
+    "m_pl": (planck_mass_grams, dimensions.mass),
+    "l_pl": (planck_length_cm, dimensions.length),
+    "t_pl": (planck_time_s, dimensions.time),
+    "T_pl": (planck_temperature_K, dimensions.temperature),
+    "q_pl": (planck_charge_esu, dimensions.charge_cgs),
+    "E_pl": (planck_energy_erg, dimensions.energy),
+
 }
 
 # Add LaTeX representations for units with trivial representations.
@@ -183,6 +195,10 @@
     "gauss",
     "G",
     "Jy",
+    "N",
+    "T",
+    "A",
+    "C",
 )
 
 cgs_base_units = {
@@ -199,4 +215,16 @@
     dimensions.time:'s',
     dimensions.temperature:'K',
     dimensions.angle:'radian',
+    dimensions.current_mks:'A',
 }
+
+cgs_conversions = {
+    "C":"esu",
+    "T":"gauss",
+    "A":"statA",
+}
+
+for conv in cgs_conversions.keys():
+    if conv in prefixable_units:
+        for p in unit_prefixes:
+            cgs_conversions[p+conv] = p+cgs_conversions[conv]

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/unit_object.py
--- a/yt/units/unit_object.py
+++ b/yt/units/unit_object.py
@@ -17,7 +17,7 @@
     Pow, Symbol, Integer, \
     Float, Basic, Rational, sqrt
 from sympy.core.numbers import One
-from sympy import sympify, latex
+from sympy import sympify, latex, symbols
 from sympy.parsing.sympy_parser import \
     parse_expr, auto_number, rationalize
 from keyword import iskeyword
@@ -26,7 +26,8 @@
 from yt.units.unit_lookup_table import \
     latex_symbol_lut, unit_prefixes, \
     prefixable_units, cgs_base_units, \
-    mks_base_units, latex_prefixes
+    mks_base_units, latex_prefixes, \
+    cgs_conversions
 from yt.units.unit_registry import UnitRegistry
 
 import copy
@@ -116,7 +117,7 @@
 
     # Extra attributes
     __slots__ = ["expr", "is_atomic", "cgs_value", "cgs_offset", "dimensions",
-                 "registry"]
+                 "registry", "cgs_conversion", "is_mks"]
 
     def __new__(cls, unit_expr=sympy_one, cgs_value=None, cgs_offset=0.0,
                 dimensions=None, registry=None, **assumptions):
@@ -191,12 +192,11 @@
             validate_dimensions(dimensions)
         else:
             # lookup the unit symbols
-            try:
-                cgs_value, dimensions = \
-                    _get_unit_data_from_expr(unit_expr, registry.lut)
-            except ValueError:
-                cgs_value, dimensions, cgs_offset = \
-                    _get_unit_data_from_expr(unit_expr, registry.lut)
+            unit_data = _get_unit_data_from_expr(unit_expr, registry.lut)
+            cgs_value = unit_data[0]
+            dimensions = unit_data[1]
+            if len(unit_data) == 3:
+                cgs_offset = unit_data[2]
 
         # Create obj with superclass construct.
         obj = Expr.__new__(cls, **assumptions)
@@ -209,6 +209,22 @@
         obj.dimensions = dimensions
         obj.registry = registry
 
+        check_atoms = [atom for atom in unit_expr.free_symbols
+                       if str(atom) in cgs_conversions]
+        if len(check_atoms) > 0:
+            conversions = []
+            for atom in check_atoms:
+                conversions.append((atom,symbols(cgs_conversions[str(atom)])))
+            conversion = unit_expr.subs(conversions)
+            conversion = Unit(unit_expr=conversion, cgs_value=1.0,
+                               dimensions=None, registry=registry)
+            is_mks = True
+        else:
+            conversion = None
+            is_mks = False
+        obj.cgs_conversion = conversion
+        obj.is_mks = is_mks
+
         if unit_key:
             registry.unit_objs[unit_key] = obj
 
@@ -264,7 +280,7 @@
                 cgs_offset = self.cgs_offset
             else:
                 raise InvalidUnitOperation("Quantities with units of Fahrenheit "
-                                           "and Celcius cannot be multiplied.")
+                                           "and Celsius cannot be multiplied.")
 
         return Unit(self.expr * u.expr,
                     cgs_value=(self.cgs_value * u.cgs_value),
@@ -287,7 +303,7 @@
                 cgs_offset = self.cgs_offset
             else:
                 raise InvalidUnitOperation("Quantities with units of Farhenheit "
-                                           "and Celcius cannot be multiplied.")
+                                           "and Celsius cannot be multiplied.")
 
         return Unit(self.expr / u.expr,
                     cgs_value=(self.cgs_value / u.cgs_value),
@@ -307,7 +323,8 @@
                                        "it to a float." % (p, type(p)) )
 
         return Unit(self.expr**p, cgs_value=(self.cgs_value**p),
-                    dimensions=(self.dimensions**p), registry=self.registry)
+                    dimensions=(self.dimensions**p),
+                    registry=self.registry)
 
     def __eq__(self, u):
         """ Test unit equality. """
@@ -340,6 +357,14 @@
 
     def same_dimensions_as(self, other_unit):
         """ Test if dimensions are the same. """
+        first_check = False
+        second_check = False
+        if self.cgs_conversion:
+            first_check = self.cgs_conversion.dimensions / other_unit.dimensions == sympy_one
+        if other_unit.cgs_conversion:
+            second_check = other_unit.cgs_conversion.dimensions / self.dimensions == sympy_one
+        if first_check or second_check:
+            return True
         return (self.dimensions / other_unit.dimensions) == sympy_one
 
     @property
@@ -372,20 +397,28 @@
         Create and return dimensionally-equivalent cgs units.
 
         """
-        units_string = self._get_system_unit_string(cgs_base_units)
+        if self.cgs_conversion:
+            units = self.cgs_conversion
+        else:
+            units = self
+        units_string = units._get_system_unit_string(cgs_base_units)
         return Unit(units_string, cgs_value=1.0,
-                    dimensions=self.dimensions, registry=self.registry)
+                    dimensions=units.dimensions, registry=self.registry)
 
     def get_mks_equivalent(self):
         """
         Create and return dimensionally-equivalent mks units.
 
         """
-        units_string = self._get_system_unit_string(mks_base_units)
-        cgs_value = (get_conversion_factor(self, self.get_cgs_equivalent())[0] /
-                     get_conversion_factor(self, Unit(units_string))[0])
+        if self.cgs_conversion and not self.is_mks:
+            units = self.cgs_conversion
+        else:
+            units = self
+        units_string = units._get_system_unit_string(mks_base_units)
+        cgs_value = (get_conversion_factor(units, units.get_cgs_equivalent())[0] /
+                     get_conversion_factor(units, Unit(units_string))[0])
         return Unit(units_string, cgs_value=cgs_value,
-                    dimensions=self.dimensions, registry=self.registry)
+                    dimensions=units.dimensions, registry=self.registry)
 
     def get_conversion_factor(self, other_units):
         return get_conversion_factor(self, other_units)
@@ -430,7 +463,7 @@
             return ratio, ratio*old_units.cgs_offset - new_units.cgs_offset
         else:
             raise InvalidUnitOperation(
-                "Fahrenheit and Celsius are not absoulte temperature scales "
+                "Fahrenheit and Celsius are not absolute temperature scales "
                 "and cannot be used in compound unit symbols.")
 
 #

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/unit_symbols.py
--- a/yt/units/unit_symbols.py
+++ b/yt/units/unit_symbols.py
@@ -140,8 +140,8 @@
 MeV = mega_electron_volt = quan(1.0, "MeV")
 GeV = giga_electron_volt = quan(1.0, "GeV")
 amu = atomic_mass_unit = quan(1.0, "amu")
+angstrom = quan(1.0, "angstrom")
 me  = electron_mass = quan(1.0, "me")
-angstrom = quan(1.0, "angstrom")
 
 #
 # Angle units

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/units/yt_array.py
--- a/yt/units/yt_array.py
+++ b/yt/units/yt_array.py
@@ -33,10 +33,12 @@
 from yt.units.dimensions import dimensionless
 from yt.utilities.exceptions import \
     YTUnitOperationError, YTUnitConversionError, \
-    YTUfuncUnitError, YTIterableUnitCoercionError
+    YTUfuncUnitError, YTIterableUnitCoercionError, \
+    YTInvalidUnitEquivalence
 from numbers import Number as numeric_type
 from yt.utilities.on_demand_imports import _astropy
 from sympy import Rational
+from yt.units.unit_lookup_table import unit_prefixes, prefixable_units
 
 NULL_UNIT = Unit()
 
@@ -458,6 +460,44 @@
         """
         return self.in_units(self.units.get_mks_equivalent())
 
+    def to_equivalent(self, unit, equiv, **kwargs):
+        """
+        Convert a YTArray or YTQuantity to an equivalent, e.g., something that is
+        related by only a constant factor but not in the same units.
+
+        Parameters
+        ----------
+        unit : string
+            The unit that you wish to convert to.
+        equiv : string
+            The equivalence you wish to use. To see which equivalencies are
+            supported for this unitful quantity, try the :method:`list_equivalencies`
+            method.
+
+        Examples
+        --------
+        >>> a = yt.YTArray(1.0e7,"K")
+        >>> a.to_equivalent("keV", "thermal")
+        """
+        from equivalencies import equivalence_registry
+        this_equiv = equivalence_registry[equiv]()
+        old_dims = self.units.dimensions
+        new_dims = YTQuantity(1.0, unit, registry=self.units.registry).units.dimensions
+        if old_dims in this_equiv.dims and new_dims in this_equiv.dims:
+            return this_equiv.convert(self, new_dims, **kwargs).in_units(unit)
+        else:
+            raise YTInvalidUnitEquivalence(equiv, self.units, unit)
+
+    def list_equivalencies(self):
+        """
+        Lists the possible equivalencies associated with this YTArray or
+        YTQuantity.
+        """
+        from equivalencies import equivalence_registry
+        for k,v in equivalence_registry.items():
+            if self.units.dimensions in v.dims:
+                print v()
+
     def ndarray_view(self):
         """
         Returns a view into the array, but as an ndarray rather than ytarray.

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/utilities/exceptions.py
--- a/yt/utilities/exceptions.py
+++ b/yt/utilities/exceptions.py
@@ -426,7 +426,6 @@
     def __str__(self):
         return "A file already exists at %s and clobber=False." % self.filename
 
-
 class YTGDFUnknownGeometry(Exception):
     def __init__(self, geometry):
         self.geometry = geometry
@@ -435,4 +434,14 @@
         return '''Unknown geometry %i. Please refer to GDF standard
                   for more information''' % self.geometry
 
+class YTInvalidUnitEquivalence(Exception):
+    def __init__(self, equiv, unit1, unit2):
+        self.equiv = equiv
+        self.unit1 = unit1
+        self.unit2 = unit2
 
+    def __str__(self):
+        return "The unit equivalence '%s' does not exist for the units '%s' and '%s.'" % (self.equiv,
+                                                                                          self.unit1,
+                                                                                          self.unit2)
+

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/utilities/physical_constants.py
--- a/yt/utilities/physical_constants.py
+++ b/yt/utilities/physical_constants.py
@@ -1,5 +1,6 @@
 from yt.utilities.physical_ratios import *
 from yt.units.yt_array import YTQuantity
+from math import pi
 
 mass_electron_cgs = YTQuantity(mass_electron_grams, 'g')
 amu_cgs           = YTQuantity(amu_grams, 'g')
@@ -14,6 +15,7 @@
 
 # Charge
 charge_proton_cgs = YTQuantity(4.8032056e-10, 'esu')
+elementary_charge = charge_proton_cgs
 
 # Physical Constants
 boltzmann_constant_cgs = YTQuantity(boltzmann_constant_erg_per_K, 'erg/K')
@@ -35,7 +37,7 @@
 mass_mars_cgs = YTQuantity(mass_mars_grams, 'g')
 mass_saturn_cgs = YTQuantity(mass_saturn_grams, 'g')
 mass_uranus_cgs = YTQuantity(mass_uranus_grams, 'g')
-mass_neptun_cgs = YTQuantity(mass_neptun_grams, 'g')
+mass_neptune_cgs = YTQuantity(mass_neptune_grams, 'g')
 
 #Short cuts
 G = gravitational_constant_cgs
@@ -48,5 +50,17 @@
 kboltz = boltzmann_constant_cgs
 kb = kboltz
 hcgs = planck_constant_cgs
+hbar = 0.5*hcgs/pi
 sigma_thompson = cross_section_thompson_cgs
 Na = 1 / amu_cgs
+
+#Planck units
+m_pl = planck_mass = YTQuantity(planck_mass_grams, "g")
+l_pl = planck_length = YTQuantity(planck_length_cm, "cm")
+t_pl = planck_time = YTQuantity(planck_time_s, "s")
+E_pl = planck_energy = YTQuantity(planck_energy_erg, "erg")
+q_pl = planck_charge = YTQuantity(planck_charge_esu, "esu")
+T_pl = planck_temperature = YTQuantity(planck_temperature_K, "K")
+
+mu_0 = YTQuantity(4.0e-7*pi, "N/A**2")
+eps_0 = (1.0/(clight.in_mks()**2*mu_0)).in_units("C**2/N/m**2")

diff -r 1b18659fba23a51604134b163716a7cbb6f609e5 -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 yt/utilities/physical_ratios.py
--- a/yt/utilities/physical_ratios.py
+++ b/yt/utilities/physical_ratios.py
@@ -92,7 +92,7 @@
 mass_mars_grams = mass_sun_grams / 3098708.0
 mass_saturn_grams = mass_sun_grams / 3497.898
 mass_uranus_grams = mass_sun_grams / 22902.98
-mass_neptun_grams = mass_sun_grams / 19412.24
+mass_neptune_grams = mass_sun_grams / 19412.24
 
 # flux
 jansky_cgs = 1.0e-23
@@ -109,3 +109,11 @@
 # Miscellaneous
 HUGE = 1.0e90
 TINY = 1.0e-40
+
+# Planck units
+planck_mass_grams = 2.17650925245e-05
+planck_length_cm = 1.6161992557e-33
+planck_time_s = planck_length_cm / speed_of_light_cm_per_s
+planck_energy_erg = planck_mass_grams * speed_of_light_cm_per_s * speed_of_light_cm_per_s
+planck_temperature_K = planck_energy_erg / boltzmann_constant_erg_per_K
+planck_charge_esu = 5.62274532302e-09


https://bitbucket.org/yt_analysis/yt/commits/2030b40ee37e/
Changeset:   2030b40ee37e
Branch:      yt
User:        jzuhone
Date:        2014-11-03 22:12:22+00:00
Summary:     Updating docstrings everywhere to reflect the new options. Updating center and width implementations for SZ projections and PPVCube.
Affected #:  6 files

diff -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -18,6 +18,7 @@
 from yt.funcs import get_pbar
 from yt.utilities.physical_constants import clight, mh
 import yt.units.dimensions as ytdims
+from yt.units.yt_array import YTQuantity
 from yt.funcs import iterable
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     parallel_root_only, parallel_objects
@@ -60,11 +61,20 @@
             principal axes of the domain ("x","y", or "z").
         field : string
             The field to project.
-        center : float, tuple, or string
-            The coordinates of the dataset *ds* on which to center the PPVCube.
-        width : float or tuple, optional
-            The width of the projection in length units. Specify a float
-            for code_length units or a tuple (value, units).
+        center : A sequence of floats, a string, or a tuple.
+            The coordinate of the center of the image. If set to 'c', 'center' or
+            left blank, the plot is centered on the middle of the domain. If set to
+            'max' or 'm', the center will be located at the maximum of the
+            ('gas', 'density') field. Centering on the max or min of a specific
+            field is supported by providing a tuple such as ("min","temperature") or
+            ("max","dark_matter_density"). Units can be specified by passing in *center*
+            as a tuple containing a coordinate and string unit name or by passing
+            in a YTArray. If a list or unitless array is supplied, code units are
+            assumed.
+        width : float, tuple, or YTQuantity.
+            The width of the projection. A float will assume the width is in code units.
+            A (value, unit) tuple or YTQuantity allows for the units of the width to be
+            specified.
         dims : tuple, optional
             A 3-tuple of dimensions (nx,ny,nv) for the cube.
         velocity_bounds : tuple, optional
@@ -163,6 +173,8 @@
         # Now fix the width
         if iterable(self.width):
             self.width = ds.quan(self.width[0], self.width[1])
+        elif isinstance(self.width, YTQuantity):
+            self.width = width
         else:
             self.width = ds.quan(self.width, "code_length")
 

diff -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -115,10 +115,19 @@
         ----------
         axis : integer or string
             The axis of the simulation domain along which to make the SZprojection.
-        center : array_like or string, optional
-            The center of the projection.
-        width : float or tuple
-            The width of the projection.
+        center : A sequence of floats, a string, or a tuple.
+            The coordinate of the center of the image. If set to 'c', 'center' or
+            left blank, the plot is centered on the middle of the domain. If set to
+            'max' or 'm', the center will be located at the maximum of the
+            ('gas', 'density') field. Centering on the max or min of a specific
+            field is supported by providing a tuple such as ("min","temperature") or
+            ("max","dark_matter_density"). Units can be specified by passing in *center*
+            as a tuple containing a coordinate and string unit name or by passing
+            in a YTArray. If a list or unitless array is supplied, code units are
+            assumed.
+        width : float, tuple, or YTQuantity.
+            The width of the projection. A float will assume the width is in code units.
+            A (value, unit) tuple or YTQuantity allows for the units of the width to be specified.
         nx : integer, optional
             The dimensions on a side of the projection image.
         source : yt.data_objects.data_containers.YTSelectionContainer, optional
@@ -129,13 +138,8 @@
         >>> szprj.on_axis("y", center="max", width=(1.0, "Mpc"), source=my_sphere)
         """
         axis = fix_axis(axis, self.ds)
-
-        if center == "c":
-            ctr = self.ds.domain_center
-        elif center == "max":
-            v, ctr = self.ds.find_max("density")
-        else:
-            ctr = center
+        ctr, dctr = self.ds.coordinates.sanitize_center(center, axis)
+        width = self.ds.coordinates.sanitize_width(axis, width, None)
 
         L = np.zeros((3))
         L[axis] = 1.0
@@ -177,10 +181,19 @@
         ----------
         L : array_like
             The normal vector of the projection.
-        center : array_like or string, optional
-            The center of the projection.
-        width : float or tuple
-            The width of the projection.
+        center : A sequence of floats, a string, or a tuple.
+            The coordinate of the center of the image. If set to 'c', 'center' or
+            left blank, the plot is centered on the middle of the domain. If set to
+            'max' or 'm', the center will be located at the maximum of the
+            ('gas', 'density') field. Centering on the max or min of a specific
+            field is supported by providing a tuple such as ("min","temperature") or
+            ("max","dark_matter_density"). Units can be specified by passing in *center*
+            as a tuple containing a coordinate and string unit name or by passing
+            in a YTArray. If a list or unitless array is supplied, code units are
+            assumed.
+        width : float, tuple, or YTQuantity.
+            The width of the projection. A float will assume the width is in code units.
+            A (value, unit) tuple or YTQuantity allows for the units of the width to be specified.
         nx : integer, optional
             The dimensions on a side of the projection image.
         source : yt.data_objects.data_containers.YTSelectionContainer, optional
@@ -198,12 +211,7 @@
             w = width.in_units("code_length").value
         else:
             w = width
-        if center == "c":
-            ctr = self.ds.domain_center
-        elif center == "max":
-            v, ctr = self.ds.find_max("density")
-        else:
-            ctr = center
+        ctr, dctr = self.ds.coordinates.sanitize_center(center, L)
 
         if source is not None:
             mylog.error("Source argument is not currently supported for off-axis S-Z projections.")

diff -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab yt/data_objects/construction_data_containers.py
--- a/yt/data_objects/construction_data_containers.py
+++ b/yt/data_objects/construction_data_containers.py
@@ -191,6 +191,8 @@
         "integrate" : integration along the axis
         "mip" : maximum intensity projection
         "sum" : same as "integrate", except that we don't multiply by the path length
+        WARNING: The "sum" option should only be used for uniform resolution grid
+        datasets, as other datasets may result in unphysical images.
     style : string, optional
         The same as the method keyword.  Deprecated as of version 3.0.2.  
         Please use method keyword instead.

diff -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -321,13 +321,15 @@
         The axis of the slice. One of "x","y","z", or 0,1,2.
     fields : string or list of strings
         The fields to slice
-    center : A sequence floats, a string, or a tuple.
-         The coordinate of the origin of the image. If set to 'c', 'center' or
+    center : A sequence of floats, a string, or a tuple.
+         The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -378,14 +380,16 @@
         The fields to project
     weight_field : string
         The field used to weight the projection.
-    center : A sequence floats, a string, or a tuple.
-        The coordinate of the origin of the image. If set to 'c', 'center' or
-        left blank, the plot is centered on the middle of the domain. If set to
-        'max' or 'm', the center will be located at the maximum of the
-        ('gas', 'density') field. Units can be specified by passing in center
-        as a tuple containing a coordinate and string unit name or by passing
-        in a YTArray.  If a list or unitless array is supplied, code units are
-        assumed.
+    center : A sequence of floats, a string, or a tuple.
+         The coordinate of the center of the image. If set to 'c', 'center' or
+         left blank, the plot is centered on the middle of the domain. If set to
+         'max' or 'm', the center will be located at the maximum of the
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
+         as a tuple containing a coordinate and string unit name or by passing
+         in a YTArray. If a list or unitless array is supplied, code units are
+         assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
          x and y widths.  They are:

diff -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -960,13 +960,15 @@
          or the axis name itself
     fields : string
          The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1081,13 +1083,15 @@
          or the axis name itself
     fields : string
          The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1164,7 +1168,9 @@
 
          "sum" : This method is the same as integrate, except that it does not 
          multiply by a path length when performing the integration, and is 
-         just a straight summation of the field along the given axis. 
+         just a straight summation of the field along the given axis. WARNING:
+         This should only be used for uniform resolution grid datasets, as other
+         datasets may result in unphysical images.
     proj_style : string
          The method of projection--same as method keyword.  Deprecated as of 
          version 3.0.2.  Please use method instead.
@@ -1238,13 +1244,15 @@
          The vector normal to the slicing plane.
     fields : string
          The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1349,13 +1357,15 @@
         The vector normal to the slicing plane.
     fields : string
         The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1407,7 +1417,9 @@
 
          "sum" : This method is the same as integrate, except that it does not
          multiply by a path length when performing the integration, and is
-         just a straight summation of the field along the given axis.
+         just a straight summation of the field along the given axis. WARNING:
+         This should only be used for uniform resolution grid datasets, as other
+         datasets may result in unphysical images.
     """
     _plot_type = 'OffAxisProjection'
     _frb_generator = OffAxisProjectionFixedResolutionBuffer
@@ -1767,9 +1779,11 @@
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable

diff -r 3d3bdd8cbf98314b60afe27a1159bfedd575a6f1 -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -2400,7 +2400,9 @@
 
          "sum" : This method is the same as integrate, except that it does not
          multiply by a path length when performing the integration, and is
-         just a straight summation of the field along the given axis.
+         just a straight summation of the field along the given axis. WARNING:
+         This should only be used for uniform resolution grid datasets, as other
+         datasets may result in unphysical images.
 
     Returns
     -------


https://bitbucket.org/yt_analysis/yt/commits/6c18ee5b5153/
Changeset:   6c18ee5b5153
Branch:      yt
User:        jzuhone
Date:        2014-11-04 01:44:45+00:00
Summary:     Make sure plots can pick up latex prefixes
Affected #:  1 file

diff -r 2030b40ee37eb48bc0383a6e0cc844a97b5e0eab -r 6c18ee5b51532cb947ba0914db7445a6f135a4c2 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -50,6 +50,8 @@
     Unit
 from yt.units.unit_registry import \
     UnitParseError
+from yt.units.unit_lookup_table import \
+    prefixable_units, latex_prefixes
 from yt.units.yt_array import \
     YTArray, YTQuantity
 from yt.utilities.png_writer import \
@@ -795,6 +797,11 @@
                     raise RuntimeError
                 if un in formatted_length_unit_names:
                     un = formatted_length_unit_names[un]
+                pp = un[0]
+                if pp in latex_prefixes:
+                    symbol_wo_prefix = un[1:]
+                    if symbol_wo_prefix in prefixable_units:
+                        un = un.replace(pp, "{"+latex_prefixes[pp]+"}", 1)
                 if un not in ['1', 'u', 'unitary']:
                     if hinv:
                         un = un + '\,h^{-1}'


https://bitbucket.org/yt_analysis/yt/commits/a020d4fa702d/
Changeset:   a020d4fa702d
Branch:      yt
User:        jzuhone
Date:        2014-11-04 02:01:31+00:00
Summary:     Make sure we check for the projection method here.
Affected #:  1 file

diff -r 6c18ee5b51532cb947ba0914db7445a6f135a4c2 -r a020d4fa702dee264c18d270153426546aab003e yt/visualization/fixed_resolution.py
--- a/yt/visualization/fixed_resolution.py
+++ b/yt/visualization/fixed_resolution.py
@@ -420,7 +420,7 @@
                                    no_ghost=dd.no_ghost, interpolated=dd.interpolated,
                                    north_vector=dd.north_vector, method=dd.method)
         units = Unit(dd.ds.field_info[item].units, registry=dd.ds.unit_registry)
-        if dd.weight_field is None:
+        if dd.weight_field is None and dd.method == "integrate":
             units *= Unit('cm', registry=dd.ds.unit_registry)
         ia = ImageArray(buff.swapaxes(0,1), input_units=units, info=self._get_info(item))
         self[item] = ia


https://bitbucket.org/yt_analysis/yt/commits/e99afa8b1ded/
Changeset:   e99afa8b1ded
Branch:      yt
User:        jzuhone
Date:        2014-11-04 02:05:48+00:00
Summary:     Make a warning here about how to use the sum projection method
Affected #:  1 file

diff -r a020d4fa702dee264c18d270153426546aab003e -r e99afa8b1ded63ffe8878b15f51ea418beb9e9cd doc/source/visualizing/plots.rst
--- a/doc/source/visualizing/plots.rst
+++ b/doc/source/visualizing/plots.rst
@@ -278,7 +278,8 @@
     straight summation of the field along the given axis. The units of the 
     projected field will be the same as those of the unprojected field. This 
     method is typically only useful for datasets such as 3D FITS cubes where 
-    the third axis of the dataset is something like velocity or frequency.
+    the third axis of the dataset is something like velocity or frequency, and
+    should _only_ be used with fixed-resolution grid-based datasets. 
 
 .. _off-axis-projections:
 


https://bitbucket.org/yt_analysis/yt/commits/61710bdf11d6/
Changeset:   61710bdf11d6
Branch:      yt
User:        jzuhone
Date:        2014-11-04 02:13:06+00:00
Summary:     Include information about the new centering options.
Affected #:  1 file

diff -r e99afa8b1ded63ffe8878b15f51ea418beb9e9cd -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 doc/source/visualizing/plots.rst
--- a/doc/source/visualizing/plots.rst
+++ b/doc/source/visualizing/plots.rst
@@ -124,11 +124,16 @@
 slice.  To instead use the coordinates as defined in the dataset, use
 the optional argument: ``origin="native"``
 
-If supplied
-without units, the center is assumed by in code units.  Optionally, you can
-supply 'c' or 'm' for the center.  These two choices will center the plot on the
-center of the simulation box and the coordinate of the maximum density cell,
-respectively.
+If supplied without units, the center is assumed by in code units.  There are also
+the following alternative options for the `center` keyword:
+
+* `"center"`, "c"`: the domain center
+* `"max"`, `"m"`: the position of the maximum density
+* `("min", field)`: the position of the minimum of `field`
+* `("max", field)`: the position of the maximum of `field`
+
+where for the last two objects any spatial field, such as `"density"`, `"velocity_z"`,
+etc., may be used, e.g. `center=("min","temperature")`
 
 Here is an example that combines all of the options we just discussed.
 
@@ -279,7 +284,7 @@
     projected field will be the same as those of the unprojected field. This 
     method is typically only useful for datasets such as 3D FITS cubes where 
     the third axis of the dataset is something like velocity or frequency, and
-    should _only_ be used with fixed-resolution grid-based datasets. 
+    should _only_ be used with fixed-resolution grid-based datasets.
 
 .. _off-axis-projections:
 


https://bitbucket.org/yt_analysis/yt/commits/9ea4e1e6ac41/
Changeset:   9ea4e1e6ac41
Branch:      yt
User:        jzuhone
Date:        2014-11-04 14:27:41+00:00
Summary:     Merge
Affected #:  6 files

diff -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 -r 9ea4e1e6ac41f14af6216052141a2f364f506112 doc/source/analyzing/objects.rst
--- a/doc/source/analyzing/objects.rst
+++ b/doc/source/analyzing/objects.rst
@@ -96,7 +96,7 @@
 
 **Point** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTPointBase`    
-    | Usage: ``point(coord, ds=None, field_parameters=None)``
+    | Usage: ``point(coord, ds=None, field_parameters=None, data_source=None)``
     | A point defined by a single cell at specified coordinates.
 
 1D Objects
@@ -104,14 +104,14 @@
 
 **Ray (Axis-Aligned)** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTOrthoRayBase`
-    | Usage: ``ortho_ray(axis, coord, ds=None, field_parameters=None)``
+    | Usage: ``ortho_ray(axis, coord, ds=None, field_parameters=None, data_source=None)``
     | A line (of data cells) stretching through the full domain 
       aligned with one of the x,y,z axes.  Defined by an axis and a point
       to be intersected.
 
 **Ray (Arbitrarily-Aligned)** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTRayBase`
-    | Usage: ``ray(start_coord, end_coord, ds=None, field_parameters=None)``
+    | Usage: ``ray(start_coord, end_coord, ds=None, field_parameters=None, data_source=None)``
     | A line (of data cells) defined by arbitrary start and end coordinates. 
 
 2D Objects
@@ -119,13 +119,13 @@
 
 **Slice (Axis-Aligned)** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTSliceBase`
-    | Usage: ``slice(axis, coord, center=None, ds=None, field_parameters=None)``
+    | Usage: ``slice(axis, coord, center=None, ds=None, field_parameters=None, data_source=None)``
     | A plane normal to one of the axes and intersecting a particular 
       coordinate.
 
 **Slice (Arbitrarily-Aligned)** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTCuttingPlaneBase`
-    | Usage: ``cutting(normal, coord, north_vector=None, ds=None, field_parameters=None)``
+    | Usage: ``cutting(normal, coord, north_vector=None, ds=None, field_parameters=None, data_source=None)``
     | A plane normal to a specified vector and intersecting a particular 
       coordinate.
 
@@ -141,8 +141,8 @@
 
 **Box Region** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTRegionBase`
-    | Usage: ``region(center, left_edge, right_edge, fields=None, ds=None, field_parameters=None)``
-    | Alternatively: ``box(left_edge, right_edge, fields=None, ds=None, field_parameters=None)``
+    | Usage: ``region(center, left_edge, right_edge, fields=None, ds=None, field_parameters=None, data_source=None)``
+    | Alternatively: ``box(left_edge, right_edge, fields=None, ds=None, field_parameters=None, data_source=None)``
     | A box-like region aligned with the grid axis orientation.  It is 
       defined by a left_edge, a right_edge, and a center.  The left_edge
       and right_edge are the minimum and maximum bounds in the three axes
@@ -152,14 +152,14 @@
 
 **Disk/Cylinder** 
     | Class: :class:`~yt.data_objects.selection_data_containers.YTDiskBase`
-    | Usage: ``disk(center, normal, radius, height, fields=None, ds=None, field_parameters=None)``
+    | Usage: ``disk(center, normal, radius, height, fields=None, ds=None, field_parameters=None, data_source=None)``
     | A cylinder defined by a point at the center of one of the circular bases,
       a normal vector to it defining the orientation of the length of the
       cylinder, and radius and height values for the cylinder's dimensions.
 
 **Ellipsoid** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTEllipsoidBase`
-    | Usage: ``ellipsoid(center, semi_major_axis_length, semi_medium_axis_length, semi_minor_axis_length, semi_major_vector, tilt, fields=None, ds=None, field_parameters=None)``
+    | Usage: ``ellipsoid(center, semi_major_axis_length, semi_medium_axis_length, semi_minor_axis_length, semi_major_vector, tilt, fields=None, ds=None, field_parameters=None, data_source=None)``
     | An ellipsoid with axis magnitudes set by semi_major_axis_length, 
      semi_medium_axis_length, and semi_minor_axis_length.  semi_major_vector 
      sets the direction of the semi_major_axis.  tilt defines the orientation 
@@ -167,7 +167,7 @@
 
 **Sphere** 
     | Class :class:`~yt.data_objects.selection_data_containers.YTSphereBase`
-    | Usage: ``sphere(center, radius, ds=None, field_parameters=None)``
+    | Usage: ``sphere(center, radius, ds=None, field_parameters=None, data_source=None)``
     | A sphere defined by a central coordinate and a radius.
 
 
@@ -176,6 +176,12 @@
 
 See also the section on :ref:`filtering-data`.
 
+**Intersecting Regions**
+    | Most Region objects provide a data_source parameter, which allows you to subselect
+    | one region from another (in the coordinate system of the DataSet). Note, this can
+    | easily lead to empty data for non-intersecting regions.
+    | Usage: ``slice(axis, coord, ds, data_source=sph)``
+
 **Boolean Regions** 
     | **Note: not yet implemented in yt 3.0**
     | Usage: ``boolean()``

diff -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 -r 9ea4e1e6ac41f14af6216052141a2f364f506112 yt/data_objects/construction_data_containers.py
--- a/yt/data_objects/construction_data_containers.py
+++ b/yt/data_objects/construction_data_containers.py
@@ -42,7 +42,7 @@
 from yt.utilities.minimal_representation import \
     MinimalProjectionData
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
-    parallel_objects, parallel_root_only, ParallelAnalysisInterface
+    parallel_objects, parallel_root_only 
 from yt.units.unit_object import Unit
 import yt.geometry.particle_deposit as particle_deposit
 from yt.utilities.grid_data_format.writer import write_to_gdf
@@ -835,7 +835,7 @@
             new_fields.append(output_field)
         level_state.fields = new_fields
 
-class YTSurfaceBase(YTSelectionContainer3D, ParallelAnalysisInterface):
+class YTSurfaceBase(YTSelectionContainer3D):
     r"""This surface object identifies isocontours on a cell-by-cell basis,
     with no consideration of global connectedness, and returns the vertices
     of the Triangles in that isocontour.
@@ -888,7 +888,6 @@
                          ("index", "z"))
     vertices = None
     def __init__(self, data_source, surface_field, field_value):
-        ParallelAnalysisInterface.__init__(self)
         self.data_source = data_source
         self.surface_field = surface_field
         self.field_value = field_value

diff -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 -r 9ea4e1e6ac41f14af6216052141a2f364f506112 yt/data_objects/data_containers.py
--- a/yt/data_objects/data_containers.py
+++ b/yt/data_objects/data_containers.py
@@ -41,6 +41,8 @@
 from yt.fields.derived_field import \
     ValidateSpatial
 import yt.geometry.selection_routines
+from yt.geometry.selection_routines import \
+    compose_selector
 from yt.extern.six import add_metaclass
 
 def force_array(item, shape):
@@ -101,8 +103,15 @@
         sets its initial set of fields, and the remainder of the arguments
         are passed as field_parameters.
         """
-        if ds != None:
+        # ds is typically set in the new object type created in Dataset._add_object_class
+        # but it can also be passed as a parameter to the constructor, in which case it will 
+        # override the default. This code ensures it is never not set.
+        if ds is not None:
             self.ds = ds
+        else:
+            if not hasattr(self, "ds"):
+                raise RuntimeError("Error: ds must be set either through class type or parameter to the constructor")
+
         self._current_particle_type = "all"
         self._current_fluid_type = self.ds.default_fluid_type
         self.ds.objects.append(weakref.proxy(self))
@@ -542,10 +551,22 @@
     _sort_by = None
     _selector = None
     _current_chunk = None
+    _data_source = None
+    _dimensionality = None
 
-    def __init__(self, *args, **kwargs):
-        super(YTSelectionContainer, self).__init__(*args, **kwargs)
-
+    def __init__(self, ds, field_parameters, data_source=None):
+        ParallelAnalysisInterface.__init__(self)
+        super(YTSelectionContainer, self).__init__(ds, field_parameters)
+        self._data_source = data_source
+        if data_source is not None:
+            if data_source.ds is not self.ds:
+                raise RuntimeError("Attempted to construct a DataContainer with a data_source from a different DataSet", ds, data_source.ds)
+            else:
+                print "DataSets: ", self.ds, data_source.ds
+            if data_source._dimensionality < self._dimensionality:
+                raise RuntimeError("Attempted to construct a DataContainer with a data_source of lower dimensionality (%u vs %u)" %
+                                    (data_source._dimensionality, self._dimensionality))
+ 
     @property
     def selector(self):
         if self._selector is not None: return self._selector
@@ -555,7 +576,11 @@
                          "%s_selector" % self._type_name, None)
         if sclass is None:
             raise YTDataSelectorNotImplemented(self._type_name)
-        self._selector = sclass(self)
+
+        if self._data_source is not None:
+            self._selector = compose_selector(self, self._data_source.selector, sclass(self))
+        else:
+            self._selector = sclass(self)
         return self._selector
 
     def chunks(self, fields, chunking_style, **kwargs):
@@ -765,30 +790,32 @@
 
 class YTSelectionContainer0D(YTSelectionContainer):
     _spatial = False
-    def __init__(self, ds, field_parameters):
+    _dimensionality = 0
+    def __init__(self, ds, field_parameters = None, data_source = None):
         super(YTSelectionContainer0D, self).__init__(
-            ds, field_parameters)
+            ds, field_parameters, data_source)
 
 class YTSelectionContainer1D(YTSelectionContainer):
     _spatial = False
-    def __init__(self, ds, field_parameters):
+    _dimensionality = 1
+    def __init__(self, ds, field_parameters = None, data_source = None):
         super(YTSelectionContainer1D, self).__init__(
-            ds, field_parameters)
+            ds, field_parameters, data_source)
         self._grids = None
         self._sortkey = None
         self._sorted = {}
 
 class YTSelectionContainer2D(YTSelectionContainer):
     _key_fields = ['px','py','pdx','pdy']
+    _dimensionality = 2
     """
     Prepares the YTSelectionContainer2D, normal to *axis*.  If *axis* is 4, we are not
     aligned with any axis.
     """
     _spatial = False
-    def __init__(self, axis, ds, field_parameters):
-        ParallelAnalysisInterface.__init__(self)
+    def __init__(self, axis, ds, field_parameters = None, data_source = None):
         super(YTSelectionContainer2D, self).__init__(
-            ds, field_parameters)
+            ds, field_parameters, data_source)
         # We need the ds, which will exist by now, for fix_axis.
         self.axis = fix_axis(axis, self.ds)
         self.set_field_parameter("axis", axis)
@@ -910,9 +937,9 @@
     _key_fields = ['x','y','z','dx','dy','dz']
     _spatial = False
     _num_ghost_zones = 0
-    def __init__(self, center, ds = None, field_parameters = None):
-        ParallelAnalysisInterface.__init__(self)
-        super(YTSelectionContainer3D, self).__init__(ds, field_parameters)
+    _dimensionality = 3
+    def __init__(self, center, ds, field_parameters = None, data_source = None):
+        super(YTSelectionContainer3D, self).__init__(ds, field_parameters, data_source)
         self._set_center(center)
         self.coords = None
         self._grids = None
@@ -1273,9 +1300,9 @@
     """
     _type_name = "boolean"
     _con_args = ("regions",)
-    def __init__(self, regions, fields = None, ds = None, **kwargs):
+    def __init__(self, regions, fields = None, ds = None, field_parameters = None, data_source = None):
         # Center is meaningless, but we'll define it all the same.
-        YTSelectionContainer3D.__init__(self, [0.5]*3, fields, ds, **kwargs)
+        YTSelectionContainer3D.__init__(self, [0.5]*3, fields, ds, field_parameters, data_source)
         self.regions = regions
         self._all_regions = []
         self._some_overlap = []

diff -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 -r 9ea4e1e6ac41f14af6216052141a2f364f506112 yt/data_objects/selection_data_containers.py
--- a/yt/data_objects/selection_data_containers.py
+++ b/yt/data_objects/selection_data_containers.py
@@ -51,8 +51,11 @@
     ds: Dataset, optional
         An optional dataset to use rather than self.ds
     field_parameters : dictionary
-         A dictionary of field parameters than can be accessed by derived
-         fields.
+        A dictionary of field parameters than can be accessed by derived
+        fields.
+    data_source: optional
+        Draw the selection from the provided data source rather than
+        all data associated with the data_set
 
     Examples
     --------
@@ -64,8 +67,8 @@
     """
     _type_name = "point"
     _con_args = ('p',)
-    def __init__(self, p, ds = None, field_parameters = None):
-        super(YTPointBase, self).__init__(ds, field_parameters)
+    def __init__(self, p, ds=None, field_parameters=None, data_source=None):
+        super(YTPointBase, self).__init__(ds, field_parameters, data_source)
         self.p = p
 
 class YTOrthoRayBase(YTSelectionContainer1D):
@@ -92,6 +95,9 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    data_source: optional
+        Draw the selection from the provided data source rather than
+        all data associated with the data_set
 
     Examples
     --------
@@ -104,8 +110,9 @@
     _key_fields = ['x','y','z','dx','dy','dz']
     _type_name = "ortho_ray"
     _con_args = ('axis', 'coords')
-    def __init__(self, axis, coords, ds=None, field_parameters=None):
-        super(YTOrthoRayBase, self).__init__(ds, field_parameters)
+    def __init__(self, axis, coords, ds=None, 
+                 field_parameters=None, data_source=None):
+        super(YTOrthoRayBase, self).__init__(ds, field_parameters, data_source)
         self.axis = axis
         xax = self.ds.coordinates.x_axis[self.axis]
         yax = self.ds.coordinates.y_axis[self.axis]
@@ -144,6 +151,9 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    data_source: optional
+        Draw the selection from the provided data source rather than
+        all data associated with the data_set
 
     Examples
     --------
@@ -156,8 +166,9 @@
     _type_name = "ray"
     _con_args = ('start_point', 'end_point')
     _container_fields = ("t", "dts")
-    def __init__(self, start_point, end_point, ds=None, field_parameters=None):
-        super(YTRayBase, self).__init__(ds, field_parameters)
+    def __init__(self, start_point, end_point, ds=None,
+                 field_parameters=None, data_source=None):
+        super(YTRayBase, self).__init__(ds, field_parameters, data_source)
         self.start_point = self.ds.arr(start_point,
                             'code_length', dtype='float64')
         self.end_point = self.ds.arr(end_point,
@@ -204,6 +215,9 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    data_source: optional
+        Draw the selection from the provided data source rather than
+        all data associated with the data_set
 
     Examples
     --------
@@ -217,10 +231,10 @@
     _type_name = "slice"
     _con_args = ('axis', 'coord')
     _container_fields = ("px", "py", "pdx", "pdy")
-
     def __init__(self, axis, coord, center=None, ds=None,
-                 field_parameters = None):
-        YTSelectionContainer2D.__init__(self, axis, ds, field_parameters)
+                 field_parameters=None, data_source=None):
+        YTSelectionContainer2D.__init__(self, axis, ds,
+                                        field_parameters, data_source)
         self._set_center(center)
         self.coord = coord
 
@@ -285,6 +299,9 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    data_source: optional
+        Draw the selection from the provided data source rather than
+        all data associated with the data_set
 
     Notes
     -----
@@ -308,10 +325,10 @@
     _type_name = "cutting"
     _con_args = ('normal', 'center')
     _container_fields = ("px", "py", "pz", "pdx", "pdy", "pdz")
-
-    def __init__(self, normal, center, north_vector = None, 
-                 ds = None, field_parameters = None):
-        YTSelectionContainer2D.__init__(self, 4, ds, field_parameters)
+    def __init__(self, normal, center, north_vector=None,
+                 ds=None, field_parameters=None, data_source=None):
+        YTSelectionContainer2D.__init__(self, 4, ds,
+                                        field_parameters, data_source)
         self._set_center(center)
         self.set_field_parameter('center',center)
         # Let's set up our plane equation
@@ -465,7 +482,7 @@
 
     Parameters
     ----------
-    center : array_like 
+    center : array_like
         coordinate to which the normal, radius, and height all reference
     normal : array_like
         the normal vector defining the direction of lengthwise part of the 
@@ -482,6 +499,9 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    data_source: optional
+        Draw the selection from the provided data source rather than
+        all data associated with the data_set
 
     Examples
     --------
@@ -494,8 +514,9 @@
     _type_name = "disk"
     _con_args = ('center', '_norm_vec', 'radius', 'height')
     def __init__(self, center, normal, radius, height, fields=None,
-                 ds=None, **kwargs):
-        YTSelectionContainer3D.__init__(self, center, fields, ds, **kwargs)
+                 ds=None, field_parameters=None, data_source=None):
+        YTSelectionContainer3D.__init__(self, center, ds,
+                                        field_parameters, data_source)
         self._norm_vec = np.array(normal)/np.sqrt(np.dot(normal,normal))
         self.set_field_parameter("normal", self._norm_vec)
         self.set_field_parameter("center", self.center)
@@ -523,9 +544,10 @@
     """
     _type_name = "region"
     _con_args = ('center', 'left_edge', 'right_edge')
-    def __init__(self, center, left_edge, right_edge, fields = None,
-                 ds = None, **kwargs):
-        YTSelectionContainer3D.__init__(self, center, ds, **kwargs)
+    def __init__(self, center, left_edge, right_edge, fields=None,
+                 ds=None, field_parameters=None, data_source=None):
+        YTSelectionContainer3D.__init__(self, center, ds,
+                                        field_parameters, data_source)
         if not isinstance(left_edge, YTArray):
             self.left_edge = self.ds.arr(left_edge, 'code_length')
         else:
@@ -542,8 +564,10 @@
     """
     _type_name = "data_collection"
     _con_args = ("_obj_list",)
-    def __init__(self, obj_list, ds=None, field_parameters=None, center=None):
-        YTSelectionContainer3D.__init__(self, center, ds, field_parameters)
+    def __init__(self, obj_list, ds=None, field_parameters=None,
+                 data_source=None, center=None):
+        YTSelectionContainer3D.__init__(self, center, ds,
+                                        field_parameters, data_source)
         self._obj_ids = np.array([o.id - o._id_offset for o in obj_list],
                                 dtype="int64")
         self._obj_list = obj_list
@@ -569,8 +593,10 @@
     """
     _type_name = "sphere"
     _con_args = ('center', 'radius')
-    def __init__(self, center, radius, ds = None, field_parameters = None):
-        super(YTSphereBase, self).__init__(center, ds, field_parameters)
+    def __init__(self, center, radius, ds=None,
+                 field_parameters=None, data_source=None):
+        super(YTSphereBase, self).__init__(center, ds,
+                                           field_parameters, data_source)
         # Unpack the radius, if necessary
         radius = fix_length(radius, self.ds)
         if radius < self.index.get_smallest_dx():
@@ -615,8 +641,9 @@
     _type_name = "ellipsoid"
     _con_args = ('center', '_A', '_B', '_C', '_e0', '_tilt')
     def __init__(self, center, A, B, C, e0, tilt, fields=None,
-                 ds=None, field_parameters = None):
-        YTSelectionContainer3D.__init__(self, center, ds, field_parameters)
+                 ds=None, field_parameters=None, data_source=None):
+        YTSelectionContainer3D.__init__(self, center, ds,
+                                        field_parameters, data_source)
         # make sure the magnitudes of semi-major axes are in order
         if A<B or B<C:
             raise YTEllipsoidOrdering(ds, A, B, C)
@@ -625,10 +652,10 @@
         self._B = self.ds.quan(B, 'code_length')
         self._C = self.ds.quan(C, 'code_length')
         if self._C < self.index.get_smallest_dx():
-            raise YTSphereTooSmall(ds, self._C, self.index.get_smallest_dx())
+            raise YTSphereTooSmall(self.ds, self._C, self.index.get_smallest_dx())
         self._e0 = e0 = e0 / (e0**2.0).sum()**0.5
         self._tilt = tilt
-        
+ 
         # find the t1 angle needed to rotate about z axis to align e0 to x
         t1 = np.arctan(e0[1] / e0[0])
         # rotate e0 by -t1
@@ -684,9 +711,10 @@
     """
     _type_name = "cut_region"
     _con_args = ("base_object", "conditionals")
-    def __init__(self, base_object, conditionals, ds = None,
-                 field_parameters = None):
-        super(YTCutRegionBase, self).__init__(base_object.center, ds, field_parameters)
+    def __init__(self, base_object, conditionals, ds=None,
+                 field_parameters=None, data_source=None):
+        super(YTCutRegionBase, self).__init__(base_object.center, ds,
+                                              field_parameters, data_source)
         self.conditionals = ensure_list(conditionals)
         self.base_object = base_object
         self._selector = None
@@ -762,4 +790,3 @@
     @property
     def fwidth(self):
         return self.base_object.fwidth[self._cond_ind,:]
-

diff -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 -r 9ea4e1e6ac41f14af6216052141a2f364f506112 yt/data_objects/tests/test_compose.py
--- /dev/null
+++ b/yt/data_objects/tests/test_compose.py
@@ -0,0 +1,146 @@
+from yt.testing import *
+from yt.fields.local_fields import add_field
+from yt.units.yt_array import YTArray, uintersect1d
+
+def setup():
+    from yt.config import ytcfg
+    ytcfg["yt","__withintesting"] = "True"
+
+# Copied from test_boolean for computing a unique identifier for
+# each cell from cell positions
+def _IDFIELD(field, data):
+    width = data.ds.domain_right_edge - data.ds.domain_left_edge
+    min_dx = YTArray(1.0/8192, input_units='code_length',
+                     registry=data.ds.unit_registry)
+    delta = width / min_dx
+    x = data['x'] - min_dx / 2.
+    y = data['y'] - min_dx / 2.
+    z = data['z'] - min_dx / 2.
+    xi = x / min_dx
+    yi = y / min_dx
+    zi = z / min_dx
+    index = xi + delta[0] * (yi + delta[1] * zi)
+    index = index.astype('int64')
+    return index
+
+def test_compose_no_overlap():
+    r"""Test to make sure that composed data objects that don't
+    overlap behave the way we expect (return empty collections)
+    """
+    empty = np.array([])
+    for n in [1, 2, 4, 8]:
+        ds = fake_random_ds(64, nprocs=n)
+        ds.add_field("ID", function=_IDFIELD)
+
+        # position parameters for initial region
+        center = [0.25]*3
+        left_edge = [0.1]*3
+        right_edge = [0.4]*3
+        normal = [1, 0, 0]
+        radius = height = 0.15
+
+        # initial 3D regions
+        sources = [ds.sphere(center, radius),
+                   ds.region(center, left_edge, right_edge),
+                   ds.disk(center, normal, radius, height)]
+
+        # position parameters for non-overlapping regions
+        center = [0.75]*3
+        left_edge = [0.6]*3
+        right_edge = [0.9]*3
+
+        # subselect non-overlapping 0, 1, 2, 3D regions
+        for data1 in sources:
+            data2 = ds.sphere(center, radius, data_source=data1)
+            yield assert_array_equal, data2['ID'], empty
+
+            data2 = ds.region(center, left_edge, right_edge, data_source=data1)
+            yield assert_array_equal, data2['ID'], empty  
+
+            data2 = ds.disk(center, normal, radius, height, data_source=data1)
+            yield assert_array_equal, data2['ID'], empty
+
+            for d in range(3):
+                data2 = ds.slice(d, center[d], data_source=data1)
+                yield assert_array_equal, data2['ID'], empty
+
+            for d in range(3):
+                data2 = ds.ortho_ray(d, center[0:d] + center[d+1:], data_source=data1)
+                yield assert_array_equal, data2['ID'], empty
+
+            data2 = ds.point(center, data_source=data1)
+            yield assert_array_equal, data2['ID'], empty
+
+def test_compose_overlap():
+    r"""Test to make sure that composed data objects that do
+    overlap behave the way we expect 
+    """
+    empty = np.array([])
+    for n in [1, 2, 4, 8]:
+        ds = fake_random_ds(64, nprocs=n)
+        ds.add_field("ID", function=_IDFIELD)
+
+        # position parameters for initial region
+        center = [0.4, 0.5, 0.5]
+        left_edge = [0.1]*3
+        right_edge = [0.7]*3
+        normal = [1, 0, 0]
+        radius = height = 0.15
+
+        # initial 3D regions
+        sources = [ds.sphere(center, radius),
+                   ds.region(center, left_edge, right_edge),
+                   ds.disk(center, normal, radius, height)]
+
+        # position parameters for overlapping regions
+        center = [0.6, 0.5, 0.5]
+        left_edge = [0.3]*3
+        right_edge = [0.9]*3
+
+        # subselect non-overlapping 0, 1, 2, 3D regions
+        for data1 in sources:
+            id1 = data1['ID']
+
+            data2 = ds.sphere(center, radius)
+            data3 = ds.sphere(center, radius, data_source=data1)
+            id2 = data2['ID']
+            id3 = data3['ID']
+            id3.sort()
+            yield assert_array_equal, uintersect1d(id1, id2), id3
+
+            data2 = ds.region(center, left_edge, right_edge)
+            data3 = ds.region(center, left_edge, right_edge, data_source=data1)
+            id2 = data2['ID']
+            id3 = data3['ID']
+            id3.sort()
+            yield assert_array_equal, uintersect1d(id1, id2), id3
+
+            data2 = ds.disk(center, normal, radius, height)
+            data3 = ds.disk(center, normal, radius, height, data_source=data1)
+            id2 = data2['ID']
+            id3 = data3['ID']
+            id3.sort()
+            yield assert_array_equal, uintersect1d(id1, id2), id3
+
+            for d in range(3):
+                data2 = ds.slice(d, center[d])
+                data3 = ds.slice(d, center[d], data_source=data1)
+                id2 = data2['ID']
+                id3 = data3['ID']
+                id3.sort()
+                yield assert_array_equal, uintersect1d(id1, id2), id3
+
+            for d in range(3):
+                data2 = ds.ortho_ray(d, center[0:d] + center[d+1:])
+                data3 = ds.ortho_ray(d, center[0:d] + center[d+1:], data_source=data1)
+                id2 = data2['ID']
+                id3 = data3['ID']
+                id3.sort()
+                yield assert_array_equal, uintersect1d(id1, id2), id3
+
+            data2 = ds.point(center)
+            data3 = ds.point(center, data_source=data1)
+            id2 = data2['ID']
+            id3 = data3['ID']
+            id3.sort()
+            yield assert_array_equal, uintersect1d(id1, id2), id3

diff -r 61710bdf11d68dd5695300fa0170bd3b2327e1b5 -r 9ea4e1e6ac41f14af6216052141a2f364f506112 yt/geometry/selection_routines.pyx
--- a/yt/geometry/selection_routines.pyx
+++ b/yt/geometry/selection_routines.pyx
@@ -112,7 +112,7 @@
 
 cdef class SelectorObject:
 
-    def __cinit__(self, dobj):
+    def __cinit__(self, dobj, *args):
         self.min_level = getattr(dobj, "min_level", 0)
         self.max_level = getattr(dobj, "max_level", 99)
         self.overlap_cells = 0
@@ -1726,6 +1726,65 @@
 
 always_selector = AlwaysSelector
 
+cdef class ComposeSelector(SelectorObject):
+    cdef SelectorObject selector1
+    cdef SelectorObject selector2
+
+    def __init__(self, dobj, selector1, selector2):
+        self.selector1 = selector1
+        self.selector2 = selector2
+
+    def select_grids(self,
+                     np.ndarray[np.float64_t, ndim=2] left_edges,
+                     np.ndarray[np.float64_t, ndim=2] right_edges,
+                     np.ndarray[np.int32_t, ndim=2] levels):
+        return np.logical_or(
+                    self.selector1.select_grids(left_edges, right_edges, levels),
+                    self.selector2.select_grids(left_edges, right_edges, levels))
+
+    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) nogil:
+        if self.selector1.select_cell(pos, dds) and \
+                self.selector2.select_cell(pos, dds):
+            return 1
+        else:
+            return 0
+
+    cdef int select_grid(self, np.float64_t left_edge[3],
+                         np.float64_t right_edge[3], np.int32_t level,
+                         Oct *o = NULL) nogil:
+        if self.selector1.select_grid(left_edge, right_edge, level, o) or \
+                self.selector2.select_grid(left_edge, right_edge, level, o):
+            return 1
+        else:
+            return 0
+        
+    cdef int select_point(self, np.float64_t pos[3]) nogil:
+        if self.selector1.select_point(pos) and \
+                self.selector2.select_point(pos):
+            return 1
+        else:
+            return 0
+
+    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) nogil:
+        if self.selector1.select_sphere(pos, radius) and \
+                self.selector2.select_sphere(pos, radius):
+            return 1
+        else:
+            return 0
+
+    cdef int select_bbox(self, np.float64_t left_edge[3],
+                               np.float64_t right_edge[3]) nogil:
+        if self.selector1.select_bbox(left_edge, right_edge) and \
+                self.selector2.select_bbox(left_edge, right_edge):
+            return 1
+        else:
+            return 0
+
+    def _hash_vals(self):
+        return (hash(self.selector1), hash(self.selector2))
+
+compose_selector = ComposeSelector
+
 cdef class HaloParticlesSelector(SelectorObject):
     cdef public object base_source
     cdef SelectorObject base_selector


https://bitbucket.org/yt_analysis/yt/commits/52a7220cf325/
Changeset:   52a7220cf325
Branch:      yt
User:        jzuhone
Date:        2014-11-05 01:46:24+00:00
Summary:     Bugfix
Affected #:  1 file

diff -r 9ea4e1e6ac41f14af6216052141a2f364f506112 -r 52a7220cf325116928457893a2ee1810407331e0 yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -148,7 +148,7 @@
         self.ds.add_field(("gas","beta_par"), function=beta_par, units="g/cm**3")
         setup_sunyaev_zeldovich_fields(self.ds)
         proj = self.ds.proj("density", axis, center=ctr, data_source=source)
-        frb = proj.to_frb(width, nx)
+        frb = proj.to_frb(width[0], nx, height=width[1])
         dens = frb["density"]
         Te = frb["t_sz"]/dens
         bpar = frb["beta_par"]/dens


https://bitbucket.org/yt_analysis/yt/commits/e433a9791f5d/
Changeset:   e433a9791f5d
Branch:      yt
User:        MatthewTurk
Date:        2014-11-06 15:33:31+00:00
Summary:     Merged in jzuhone/yt (pull request #1280)

Improved FITS support, with a number of small additional refinements
Affected #:  20 files

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de doc/source/analyzing/analysis_modules/PPVCube.ipynb
--- a/doc/source/analyzing/analysis_modules/PPVCube.ipynb
+++ b/doc/source/analyzing/analysis_modules/PPVCube.ipynb
@@ -1,7 +1,7 @@
 {
  "metadata": {
   "name": "",
-  "signature": "sha256:56a8d72735e3cc428ff04b241d4b2ce6f653019818c6fc7a4148840d99030c85"
+  "signature": "sha256:b83e125278c2e58da4d99ac9d2ca2a136d01f1094e1b83497925e0f9b9b056c2"
  },
  "nbformat": 3,
  "nbformat_minor": 0,
@@ -32,7 +32,7 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "To demonstrate this functionality, we'll create a simple unigrid dataset from scratch of a rotating disk galaxy. We create a thin disk in the x-y midplane of the domain of three cells in height in either direction, and a radius of 10 kpc. The density and azimuthal velocity profiles of the disk as a function of radius will be given by the following functions:"
+      "To demonstrate this functionality, we'll create a simple unigrid dataset from scratch of a rotating disk. We create a thin disk in the x-y midplane of the domain of three cells in height in either direction, and a radius of 10 kpc. The density and azimuthal velocity profiles of the disk as a function of radius will be given by the following functions:"
      ]
     },
     {
@@ -84,7 +84,7 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Second, we'll construct the data arrays for the density and the velocity of the disk. Since we have the tangential velocity profile, we have to use the polar coordinates we derived earlier to compute `velx` and `vely`. Everywhere outside the disk, all fields are set to zero.  "
+      "Second, we'll construct the data arrays for the density, temperature, and velocity of the disk. Since we have the tangential velocity profile, we have to use the polar coordinates we derived earlier to compute `velx` and `vely`. Everywhere outside the disk, all fields are set to zero.  "
      ]
     },
     {
@@ -93,12 +93,15 @@
      "input": [
       "dens = np.zeros((nx,ny,nz))\n",
       "dens[:,:,nz/2-3:nz/2+3] = (r**alpha).reshape(nx,ny,1) # the density profile of the disk\n",
-      "vel_theta = r/(1.+(r/r_0)**beta) # the azimuthal velocity profile of the disk\n",
+      "temp = np.zeros((nx,ny,nz))\n",
+      "temp[:,:,nz/2-3:nz/2+3] = 1.0e5 # Isothermal\n",
+      "vel_theta = 100.*r/(1.+(r/r_0)**beta) # the azimuthal velocity profile of the disk\n",
       "velx = np.zeros((nx,ny,nz))\n",
       "vely = np.zeros((nx,ny,nz))\n",
       "velx[:,:,nz/2-3:nz/2+3] = (-vel_theta*np.sin(theta)).reshape(nx,ny,1) # convert polar to cartesian\n",
       "vely[:,:,nz/2-3:nz/2+3] = (vel_theta*np.cos(theta)).reshape(nx,ny,1) # convert polar to cartesian\n",
       "dens[r > R] = 0.0\n",
+      "temp[r > R] = 0.0\n",
       "velx[r > R] = 0.0\n",
       "vely[r > R] = 0.0"
      ],
@@ -119,6 +122,7 @@
      "input": [
       "data = {}\n",
       "data[\"density\"] = (dens,\"g/cm**3\")\n",
+      "data[\"temperature\"] = (temp, \"K\")\n",
       "data[\"velocity_x\"] = (velx, \"km/s\")\n",
       "data[\"velocity_y\"] = (vely, \"km/s\")\n",
       "data[\"velocity_z\"] = (np.zeros((nx,ny,nz)), \"km/s\") # zero velocity in the z-direction\n",
@@ -189,7 +193,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "cube = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-1.5,1.5,\"km/s\"))"
+      "cube = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-150.,150.,\"km/s\"))"
      ],
      "language": "python",
      "metadata": {},
@@ -199,14 +203,33 @@
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Following this, we can now write this cube to a FITS file:"
+      "Following this, we can now write this cube to a FITS file. The x and y axes of the file can be in length units, which can be optionally specified by `length_unit`:"
      ]
     },
     {
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "cube.write_fits(\"cube.fits\", clobber=True, length_unit=(5.0,\"deg\"))"
+      "cube.write_fits(\"cube.fits\", clobber=True, length_unit=\"kpc\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Or one can use the `sky_scale` and `sky_center` keywords to set up the coordinates in RA and Dec:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "sky_scale = (1.0, \"arcsec/kpc\")\n",
+      "sky_center = (30., 45.) # RA, Dec in degrees\n",
+      "cube.write_fits(\"cube_sky.fits\", clobber=True, sky_scale=sky_scale, sky_center=sky_center)"
      ],
      "language": "python",
      "metadata": {},
@@ -223,7 +246,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "ds = yt.load(\"cube.fits\")"
+      "ds_cube = yt.load(\"cube.fits\")"
      ],
      "language": "python",
      "metadata": {},
@@ -234,7 +257,7 @@
      "collapsed": false,
      "input": [
       "# Specifying no center gives us the center slice\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"])\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"])\n",
       "slc.show()"
      ],
      "language": "python",
@@ -247,9 +270,9 @@
      "input": [
       "import yt.units as u\n",
       "# Picking different velocities for the slices\n",
-      "new_center = ds.domain_center\n",
-      "new_center[2] = ds.spec2pixel(-1.0*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center = ds_cube.domain_center\n",
+      "new_center[2] = ds_cube.spec2pixel(-100.*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -260,8 +283,8 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "new_center[2] = ds.spec2pixel(0.7*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center[2] = ds_cube.spec2pixel(70.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -272,8 +295,8 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "new_center[2] = ds.spec2pixel(-0.3*u.km/u.s)\n",
-      "slc = yt.SlicePlot(ds, \"z\", [\"density\"], center=new_center)\n",
+      "new_center[2] = ds_cube.spec2pixel(-30.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube, \"z\", [\"density\"], center=new_center)\n",
       "slc.show()"
      ],
      "language": "python",
@@ -291,7 +314,7 @@
      "cell_type": "code",
      "collapsed": false,
      "input": [
-      "prj = yt.ProjectionPlot(ds, \"z\", [\"density\"], method=\"sum\")\n",
+      "prj = yt.ProjectionPlot(ds_cube, \"z\", [\"density\"], method=\"sum\")\n",
       "prj.set_log(\"density\", True)\n",
       "prj.set_zlim(\"density\", 1.0e-3, 0.2)\n",
       "prj.show()"
@@ -299,9 +322,100 @@
      "language": "python",
      "metadata": {},
      "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The `thermal_broad` keyword allows one to simulate thermal line broadening based on the temperature, and the `atomic_weight` argument is used to specify the atomic weight of the particle that is doing the emitting."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube2 = PPVCube(ds, L, \"density\", dims=(200,100,50), velocity_bounds=(-150,150,\"km/s\"), thermal_broad=True, \n",
+      "                atomic_weight=12.0)\n",
+      "cube2.write_fits(\"cube2.fits\", clobber=True, length_unit=\"kpc\")"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Taking a slice of this cube shows:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "ds_cube2 = yt.load(\"cube2.fits\")\n",
+      "new_center = ds_cube2.domain_center\n",
+      "new_center[2] = ds_cube2.spec2pixel(70.0*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube2, \"z\", [\"density\"], center=new_center)\n",
+      "slc.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "new_center[2] = ds_cube2.spec2pixel(-100.*u.km/u.s)\n",
+      "slc = yt.SlicePlot(ds_cube2, \"z\", [\"density\"], center=new_center)\n",
+      "slc.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "where we can see the emission has been smeared into this velocity slice from neighboring slices due to the thermal broadening. \n",
+      "\n",
+      "Finally, the \"velocity\" or \"spectral\" axis of the cube can be changed to a different unit, such as wavelength, frequency, or energy: "
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "print cube2.vbins[0], cube2.vbins[-1]\n",
+      "cube2.transform_spectral_axis(400.0,\"nm\")\n",
+      "print cube2.vbins[0], cube2.vbins[-1]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "If a FITS file is now written from the cube, the spectral axis will be in the new units. To reset the spectral axis back to the original velocity units:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube2.reset_spectral_axis()\n",
+      "print cube2.vbins[0], cube2.vbins[-1]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
     }
    ],
    "metadata": {}
   }
  ]
-}
+}
\ No newline at end of file

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de doc/source/visualizing/plots.rst
--- a/doc/source/visualizing/plots.rst
+++ b/doc/source/visualizing/plots.rst
@@ -124,11 +124,16 @@
 slice.  To instead use the coordinates as defined in the dataset, use
 the optional argument: ``origin="native"``
 
-If supplied
-without units, the center is assumed by in code units.  Optionally, you can
-supply 'c' or 'm' for the center.  These two choices will center the plot on the
-center of the simulation box and the coordinate of the maximum density cell,
-respectively.
+If supplied without units, the center is assumed by in code units.  There are also
+the following alternative options for the `center` keyword:
+
+* `"center"`, "c"`: the domain center
+* `"max"`, `"m"`: the position of the maximum density
+* `("min", field)`: the position of the minimum of `field`
+* `("max", field)`: the position of the maximum of `field`
+
+where for the last two objects any spatial field, such as `"density"`, `"velocity_z"`,
+etc., may be used, e.g. `center=("min","temperature")`
 
 Here is an example that combines all of the options we just discussed.
 
@@ -278,7 +283,8 @@
     straight summation of the field along the given axis. The units of the 
     projected field will be the same as those of the unprojected field. This 
     method is typically only useful for datasets such as 3D FITS cubes where 
-    the third axis of the dataset is something like velocity or frequency.
+    the third axis of the dataset is something like velocity or frequency, and
+    should _only_ be used with fixed-resolution grid-based datasets.
 
 .. _off-axis-projections:
 

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -13,40 +13,81 @@
 import numpy as np
 from yt.utilities.on_demand_imports import _astropy
 from yt.utilities.orientation import Orientation
-from yt.utilities.fits_image import FITSImageBuffer
+from yt.utilities.fits_image import FITSImageBuffer, sanitize_fits_unit
 from yt.visualization.volume_rendering.camera import off_axis_projection
 from yt.funcs import get_pbar
+from yt.utilities.physical_constants import clight, mh
+import yt.units.dimensions as ytdims
+from yt.units.yt_array import YTQuantity
+from yt.funcs import iterable
+from yt.utilities.parallel_tools.parallel_analysis_interface import \
+    parallel_root_only, parallel_objects
+import re
+import ppv_utils
+from yt.funcs import is_root
 
-def create_vlos(z_hat):
-    def _v_los(field, data):
-        vz = data["velocity_x"]*z_hat[0] + \
-             data["velocity_y"]*z_hat[1] + \
-             data["velocity_z"]*z_hat[2]
-        return -vz
+def create_vlos(normal):
+    if isinstance(normal, basestring):
+        def _v_los(field, data): 
+            return -data["velocity_%s" % normal]
+    else:
+        orient = Orientation(normal)
+        los_vec = orient.unit_vectors[2]
+        def _v_los(field, data):
+            vz = data["velocity_x"]*los_vec[0] + \
+                data["velocity_y"]*los_vec[1] + \
+                data["velocity_z"]*los_vec[2]
+            return -vz
     return _v_los
 
+fits_info = {"velocity":("m/s","VELOCITY","v"),
+             "frequency":("Hz","FREQUENCY","f"),
+             "energy":("eV","ENERGY","E"),
+             "wavelength":("angstrom","WAVELENG","lambda")}
+
 class PPVCube(object):
-    def __init__(self, ds, normal, field, width=(1.0,"unitary"),
-                 dims=(100,100,100), velocity_bounds=None):
+    def __init__(self, ds, normal, field, center="c", width=(1.0,"unitary"),
+                 dims=(100,100,100), velocity_bounds=None, thermal_broad=False,
+                 atomic_weight=56., method="integrate"):
         r""" Initialize a PPVCube object.
 
         Parameters
         ----------
         ds : dataset
             The dataset.
-        normal : array_like
-            The normal vector along with to make the projections.
+        normal : array_like or string
+            The normal vector along with to make the projections. If an array, it
+            will be normalized. If a string, it will be assumed to be along one of the
+            principal axes of the domain ("x","y", or "z").
         field : string
             The field to project.
-        width : float or tuple, optional
-            The width of the projection in length units. Specify a float
-            for code_length units or a tuple (value, units).
+        center : A sequence of floats, a string, or a tuple.
+            The coordinate of the center of the image. If set to 'c', 'center' or
+            left blank, the plot is centered on the middle of the domain. If set to
+            'max' or 'm', the center will be located at the maximum of the
+            ('gas', 'density') field. Centering on the max or min of a specific
+            field is supported by providing a tuple such as ("min","temperature") or
+            ("max","dark_matter_density"). Units can be specified by passing in *center*
+            as a tuple containing a coordinate and string unit name or by passing
+            in a YTArray. If a list or unitless array is supplied, code units are
+            assumed.
+        width : float, tuple, or YTQuantity.
+            The width of the projection. A float will assume the width is in code units.
+            A (value, unit) tuple or YTQuantity allows for the units of the width to be
+            specified.
         dims : tuple, optional
             A 3-tuple of dimensions (nx,ny,nv) for the cube.
         velocity_bounds : tuple, optional
             A 3-tuple of (vmin, vmax, units) for the velocity bounds to
             integrate over. If None, the largest velocity of the
             dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())
+        atomic_weight : float, optional
+            Set this value to the atomic weight of the particle that is emitting the line
+            if *thermal_broad* is True. Defaults to 56 (Fe).
+        method : string, optional
+            Set the projection method to be used.
+            "integrate" : line of sight integration over the line element.
+            "sum" : straight summation over the line of sight.
 
         Examples
         --------
@@ -55,21 +96,22 @@
         >>> cube = PPVCube(ds, L, "density", width=(10.,"kpc"),
         ...                velocity_bounds=(-5.,4.,"km/s"))
         """
+
         self.ds = ds
         self.field = field
         self.width = width
+        self.particle_mass = atomic_weight*mh
+        self.thermal_broad = thermal_broad
+
+        self.center = ds.coordinates.sanitize_center(center, normal)[0]
 
         self.nx = dims[0]
         self.ny = dims[1]
         self.nv = dims[2]
 
-        normal = np.array(normal)
-        normal /= np.sqrt(np.dot(normal, normal))
-        vecs = np.identity(3)
-        t = np.cross(normal, vecs).sum(axis=1)
-        ax = t.argmax()
-        north = np.cross(normal, vecs[ax,:]).ravel()
-        orient = Orientation(normal, north_vector=north)
+        if method not in ["integrate","sum"]:
+            raise RuntimeError("Only the 'integrate' and 'sum' projection +"
+                               "methods are supported in PPVCube.")
 
         dd = ds.all_data()
 
@@ -85,27 +127,104 @@
                           ds.quan(velocity_bounds[1], velocity_bounds[2]))
 
         self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv+1)
+        self._vbins = self.vbins.copy()
         self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
-        self.dv = (self.v_bnd[1]-self.v_bnd[0])/self.nv
+        self.vmid_cgs = self.vmid.in_cgs().v
+        self.dv = self.vbins[1]-self.vbins[0]
+        self.dv_cgs = self.dv.in_cgs().v
 
-        _vlos = create_vlos(orient.unit_vectors[2])
-        ds.field_info.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+        self.current_v = 0.0
 
-        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.field_units)
+        _vlos = create_vlos(normal)
+        self.ds.add_field(("gas","v_los"), function=_vlos, units="cm/s")
+
+        _intensity = self.create_intensity()
+        self.ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
+
+        if method == "integrate":
+            self.proj_units = str(ds.quan(1.0, self.field_units+"*cm").units)
+        elif method == "sum":
+            self.proj_units = self.field_units
+
+        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
+        storage = {}
         pbar = get_pbar("Generating cube.", self.nv)
-        for i in xrange(self.nv):
-            _intensity = self._create_intensity(i)
-            ds.add_field(("gas","intensity"), function=_intensity, units=self.field_units)
-            prj = off_axis_projection(ds, ds.domain_center, normal, width,
-                                      (self.nx, self.ny), "intensity")
-            self.data[:,:,i] = prj[:,:]
-            ds.field_info.pop(("gas","intensity"))
+        for sto, i in parallel_objects(xrange(self.nv), storage=storage):
+            self.current_v = self.vmid_cgs[i]
+            if isinstance(normal, basestring):
+                prj = ds.proj("intensity", ds.coordinates.axis_id[normal], method=method)
+                buf = prj.to_frb(width, self.nx, center=self.center)["intensity"]
+            else:
+                buf = off_axis_projection(ds, self.center, normal, width,
+                                          (self.nx, self.ny), "intensity",
+                                          no_ghost=True, method=method)[::-1]
+            sto.result_id = i
+            sto.result = buf
             pbar.update(i)
-
         pbar.finish()
 
-    def write_fits(self, filename, clobber=True, length_unit=(10.0, "kpc"),
-                   sky_center=(30.,45.)):
+        self.data = ds.arr(np.zeros((self.nx,self.ny,self.nv)), self.proj_units)
+        if is_root():
+            for i, buf in sorted(storage.items()):
+                self.data[:,:,i] = buf[:,:]
+
+        self.axis_type = "velocity"
+
+        # Now fix the width
+        if iterable(self.width):
+            self.width = ds.quan(self.width[0], self.width[1])
+        elif isinstance(self.width, YTQuantity):
+            self.width = width
+        else:
+            self.width = ds.quan(self.width, "code_length")
+
+    def create_intensity(self):
+        def _intensity(field, data):
+            v = self.current_v-data["v_los"].v
+            T = data["temperature"].v
+            w = ppv_utils.compute_weight(self.thermal_broad, self.dv_cgs,
+                                         self.particle_mass, v.flatten(), T.flatten())
+            w[np.isnan(w)] = 0.0                                                                                                                        
+            return data[self.field]*w.reshape(v.shape)                                                                                                  
+        return _intensity
+
+    def transform_spectral_axis(self, rest_value, units):
+        """
+        Change the units of the spectral axis to some equivalent unit, such
+        as energy, wavelength, or frequency, by providing a *rest_value* and the
+        *units* of the new spectral axis. This corresponds to the Doppler-shifting
+        of lines due to gas motions and thermal broadening.
+        """
+        if self.axis_type != "velocity":
+            self.reset_spectral_axis()
+        x0 = self.ds.quan(rest_value, units)
+        if x0.units.dimensions == ytdims.rate or x0.units.dimensions == ytdims.energy:
+            self.vbins = x0*(1.-self.vbins.in_cgs()/clight)
+        elif x0.units.dimensions == ytdims.length:
+            self.vbins = x0/(1.-self.vbins.in_cgs()/clight)
+        self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.dv = self.vbins[1]-self.vbins[0]
+        dims = self.dv.units.dimensions
+        if dims == ytdims.rate:
+            self.axis_type = "frequency"
+        elif dims == ytdims.length:
+            self.axis_type = "wavelength"
+        elif dims == ytdims.energy:
+            self.axis_type = "energy"
+        elif dims == ytdims.velocity:
+            self.axis_type = "velocity"
+
+    def reset_spectral_axis(self):
+        """
+        Reset the spectral axis to the original velocity range and units.
+        """
+        self.vbins = self._vbins.copy()
+        self.vmid = 0.5*(self.vbins[1:]+self.vbins[:-1])
+        self.dv = self.vbins[1]-self.vbins[0]
+
+    @parallel_root_only
+    def write_fits(self, filename, clobber=True, length_unit=None,
+                   sky_scale=None, sky_center=None):
         r""" Write the PPVCube to a FITS file.
 
         Parameters
@@ -114,51 +233,71 @@
             The name of the file to write.
         clobber : boolean
             Whether or not to clobber an existing file with the same name.
-        length_unit : tuple, optional
-            The length that corresponds to the width of the projection in
-            (value, unit) form. Accepts a length unit or 'deg'.
+        length_unit : string
+            The units to convert the coordinates to in the file.
+        sky_scale : tuple or YTQuantity
+            Conversion between an angle unit and a length unit, if sky
+            coordinates are desired.
+            Examples: (1.0, "arcsec/kpc"), YTQuantity(0.001, "deg/kpc")
         sky_center : tuple, optional
             The (RA, Dec) coordinate in degrees of the central pixel if
-            *length_unit* is 'deg'.
+            *sky_scale* has been specified.
 
         Examples
         --------
-        >>> cube.write_fits("my_cube.fits", clobber=False, length_unit=(5,"deg"))
+        >>> cube.write_fits("my_cube.fits", clobber=False, sky_scale=(1.0,"arcsec/kpc"))
         """
-        if length_unit[1] == "deg":
-            center = sky_center
-            types = ["RA---SIN","DEC--SIN"]
+        if sky_scale is None:
+            center = (0.0,0.0)
+            types = ["LINEAR","LINEAR"]
         else:
-            center = [0.0,0.0]
-            types = ["LINEAR","LINEAR"]
+            if iterable(sky_scale):
+                sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
+            if sky_center is None:
+                center = (30.,45.)
+            else:
+                center = sky_center
+            types = ["RA---TAN","DEC--TAN"]
 
-        v_center = 0.5*(self.v_bnd[0]+self.v_bnd[1]).in_units("m/s").value
+        vunit = fits_info[self.axis_type][0]
+        vtype = fits_info[self.axis_type][1]
 
-        dx = length_unit[0]/self.nx
-        dy = length_unit[0]/self.ny
-        dv = self.dv.in_units("m/s").value
+        v_center = 0.5*(self.vbins[0]+self.vbins[-1]).in_units(vunit).value
 
-        if length_unit[1] == "deg":
+        if sky_scale:
+            dx = (self.width*sky_scale).in_units("deg").v/self.nx
+            units = "deg"
+        else:
+            if length_unit is None:
+                units = str(self.ds.get_smallest_appropriate_unit(self.width))
+            else:
+                units = length_unit
+        units = sanitize_fits_unit(units)
+        dx = self.width.in_units(units).v/self.nx
+        dy = dx
+        dv = self.dv.in_units(vunit).v
+
+        if sky_scale:
             dx *= -1.
 
         w = _astropy.pywcs.WCS(naxis=3)
         w.wcs.crpix = [0.5*(self.nx+1), 0.5*(self.ny+1), 0.5*(self.nv+1)]
         w.wcs.cdelt = [dx,dy,dv]
-        w.wcs.crval = [center[0], center[1], v_center]
-        w.wcs.cunit = [length_unit[1],length_unit[1],"m/s"]
-        w.wcs.ctype = [types[0],types[1],"VELO-LSR"]
+        w.wcs.crval = [center[0],center[1],v_center]
+        w.wcs.cunit = [units,units,vunit]
+        w.wcs.ctype = [types[0],types[1],vtype]
 
         fib = FITSImageBuffer(self.data.transpose(), fields=self.field, wcs=w)
-        fib[0].header["bunit"] = self.field_units
+        fib[0].header["bunit"] = re.sub('()', '', str(self.proj_units))
         fib[0].header["btype"] = self.field
 
         fib.writeto(filename, clobber=clobber)
 
-    def _create_intensity(self, i):
-        def _intensity(field, data):
-            vlos = data["v_los"]
-            w = np.abs(vlos-self.vmid[i])/self.dv.in_units(vlos.units)
-            w = 1.-w
-            w[w < 0.0] = 0.0
-            return data[self.field]*w
-        return _intensity
+    def __repr__(self):
+        return "PPVCube [%d %d %d] (%s < %s < %s)" % (self.nx, self.ny, self.nv,
+                                                      self.vbins[0],
+                                                      fits_info[self.axis_type][2],
+                                                      self.vbins[-1])
+
+    def __getitem__(self, item):
+        return self.data[item]

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/analysis_modules/ppv_cube/ppv_utils.pyx
--- /dev/null
+++ b/yt/analysis_modules/ppv_cube/ppv_utils.pyx
@@ -0,0 +1,40 @@
+import numpy as np
+cimport numpy as np
+cimport cython
+from yt.utilities.physical_constants import kboltz
+
+cdef extern from "math.h":
+    double exp(double x) nogil
+    double fabs(double x) nogil
+    double sqrt(double x) nogil
+
+cdef double kb = kboltz.v
+cdef double pi = np.pi
+
+ at cython.cdivision(True)
+ at cython.boundscheck(False)
+ at cython.wraparound(False)
+def compute_weight(np.uint8_t thermal_broad,
+    	           double dv,
+                   double m_part,
+	               np.ndarray[np.float64_t, ndim=1] v,
+    		       np.ndarray[np.float64_t, ndim=1] T):
+
+    cdef int i, n
+    cdef double v2_th, x
+    cdef np.ndarray[np.float64_t, ndim=1] w
+
+    n = v.shape[0]
+    w = np.zeros(n)
+
+    for i in range(n):
+        if thermal_broad:
+            if T[i] > 0.0:
+                v2_th = 2.*kb*T[i]/m_part
+                w[i] = dv*exp(-v[i]*v[i]/v2_th)/sqrt(v2_th*pi)
+        else:
+            x = 1.-fabs(v[i])/dv
+            if x > 0.0:
+                w[i] = x
+                
+    return w

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/analysis_modules/ppv_cube/setup.py
--- a/yt/analysis_modules/ppv_cube/setup.py
+++ b/yt/analysis_modules/ppv_cube/setup.py
@@ -8,7 +8,10 @@
 def configuration(parent_package='', top_path=None):
     from numpy.distutils.misc_util import Configuration
     config = Configuration('ppv_cube', parent_package, top_path)
-    #config.add_subpackage("tests")
+    config.add_extension("ppv_utils", 
+                         ["yt/analysis_modules/ppv_cube/ppv_utils.pyx"],
+                         libraries=["m"])
+    config.add_subpackage("tests")
     config.make_config_py()  # installs __config__.py
     #config.make_svn_version_py()
     return config

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/analysis_modules/ppv_cube/tests/test_ppv.py
--- /dev/null
+++ b/yt/analysis_modules/ppv_cube/tests/test_ppv.py
@@ -0,0 +1,62 @@
+"""
+Unit test the sunyaev_zeldovich analysis module.
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, 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.
+#-----------------------------------------------------------------------------
+
+from yt.frontends.stream.api import load_uniform_grid
+from yt.analysis_modules.ppv_cube.api import PPVCube
+import yt.units as u
+from yt.utilities.physical_constants import kboltz, mh, clight
+import numpy as np
+from yt.testing import *
+
+def setup():
+    """Test specific setup."""
+    from yt.config import ytcfg
+    ytcfg["yt", "__withintesting"] = "True"
+
+def test_ppv():
+
+    np.random.seed(seed=0x4d3d3d3)
+
+    dims = (8,8,1024)
+    v_shift = 1.0e7*u.cm/u.s
+    sigma_v = 2.0e7*u.cm/u.s
+    T_0 = 1.0e8*u.Kelvin
+    data = {"density":(np.ones(dims),"g/cm**3"),
+            "temperature":(T_0.v*np.ones(dims), "K"),
+            "velocity_x":(np.zeros(dims),"cm/s"),
+            "velocity_y":(np.zeros(dims),"cm/s"),
+            "velocity_z":(np.random.normal(loc=v_shift.v,scale=sigma_v.v,size=dims), "cm/s")}
+
+    ds = load_uniform_grid(data, dims)
+
+    cube = PPVCube(ds, "z", "density", dims=dims,
+                   velocity_bounds=(-300., 300., "km/s"),
+                   thermal_broad=True)
+
+    dv = cube.dv
+    v_th = np.sqrt(2.*kboltz*T_0/(56.*mh) + 2.*sigma_v**2).in_units("km/s")
+    a = cube.data.mean(axis=(0,1)).v
+    b = dv*np.exp(-((cube.vmid+v_shift)/v_th)**2)/(np.sqrt(np.pi)*v_th)
+
+    yield assert_allclose, a, b, 1.0e-2
+
+    E_0 = 6.8*u.keV
+
+    cube.transform_spectral_axis(E_0.v, str(E_0.units))
+
+    dE = -cube.dv
+    delta_E = E_0*v_th.in_cgs()/clight
+    E_shift = E_0*(1.+v_shift/clight)
+
+    c = dE*np.exp(-((cube.vmid-E_shift)/delta_E)**2)/(np.sqrt(np.pi)*delta_E)
+
+    yield assert_allclose, a, c, 1.0e-2

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -25,6 +25,7 @@
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
      communication_system, parallel_root_only
 from yt import units
+from yt.utilities.on_demand_imports import _astropy
 
 import numpy as np
 
@@ -114,10 +115,19 @@
         ----------
         axis : integer or string
             The axis of the simulation domain along which to make the SZprojection.
-        center : array_like or string, optional
-            The center of the projection.
-        width : float or tuple
-            The width of the projection.
+        center : A sequence of floats, a string, or a tuple.
+            The coordinate of the center of the image. If set to 'c', 'center' or
+            left blank, the plot is centered on the middle of the domain. If set to
+            'max' or 'm', the center will be located at the maximum of the
+            ('gas', 'density') field. Centering on the max or min of a specific
+            field is supported by providing a tuple such as ("min","temperature") or
+            ("max","dark_matter_density"). Units can be specified by passing in *center*
+            as a tuple containing a coordinate and string unit name or by passing
+            in a YTArray. If a list or unitless array is supplied, code units are
+            assumed.
+        width : float, tuple, or YTQuantity.
+            The width of the projection. A float will assume the width is in code units.
+            A (value, unit) tuple or YTQuantity allows for the units of the width to be specified.
         nx : integer, optional
             The dimensions on a side of the projection image.
         source : yt.data_objects.data_containers.YTSelectionContainer, optional
@@ -128,13 +138,8 @@
         >>> szprj.on_axis("y", center="max", width=(1.0, "Mpc"), source=my_sphere)
         """
         axis = fix_axis(axis, self.ds)
-
-        if center == "c":
-            ctr = self.ds.domain_center
-        elif center == "max":
-            v, ctr = self.ds.find_max("density")
-        else:
-            ctr = center
+        ctr, dctr = self.ds.coordinates.sanitize_center(center, axis)
+        width = self.ds.coordinates.sanitize_width(axis, width, None)
 
         L = np.zeros((3))
         L[axis] = 1.0
@@ -143,7 +148,7 @@
         self.ds.add_field(("gas","beta_par"), function=beta_par, units="g/cm**3")
         setup_sunyaev_zeldovich_fields(self.ds)
         proj = self.ds.proj("density", axis, center=ctr, data_source=source)
-        frb = proj.to_frb(width, nx)
+        frb = proj.to_frb(width[0], nx, height=width[1])
         dens = frb["density"]
         Te = frb["t_sz"]/dens
         bpar = frb["beta_par"]/dens
@@ -176,10 +181,19 @@
         ----------
         L : array_like
             The normal vector of the projection.
-        center : array_like or string, optional
-            The center of the projection.
-        width : float or tuple
-            The width of the projection.
+        center : A sequence of floats, a string, or a tuple.
+            The coordinate of the center of the image. If set to 'c', 'center' or
+            left blank, the plot is centered on the middle of the domain. If set to
+            'max' or 'm', the center will be located at the maximum of the
+            ('gas', 'density') field. Centering on the max or min of a specific
+            field is supported by providing a tuple such as ("min","temperature") or
+            ("max","dark_matter_density"). Units can be specified by passing in *center*
+            as a tuple containing a coordinate and string unit name or by passing
+            in a YTArray. If a list or unitless array is supplied, code units are
+            assumed.
+        width : float, tuple, or YTQuantity.
+            The width of the projection. A float will assume the width is in code units.
+            A (value, unit) tuple or YTQuantity allows for the units of the width to be specified.
         nx : integer, optional
             The dimensions on a side of the projection image.
         source : yt.data_objects.data_containers.YTSelectionContainer, optional
@@ -197,12 +211,7 @@
             w = width.in_units("code_length").value
         else:
             w = width
-        if center == "c":
-            ctr = self.ds.domain_center
-        elif center == "max":
-            v, ctr = self.ds.find_max("density")
-        else:
-            ctr = center
+        ctr, dctr = self.ds.coordinates.sanitize_center(center, L)
 
         if source is not None:
             mylog.error("Source argument is not currently supported for off-axis S-Z projections.")
@@ -279,8 +288,7 @@
         self.data["TeSZ"] = self.ds.arr(Te, "keV")
 
     @parallel_root_only
-    def write_fits(self, filename, units="kpc", sky_center=None, sky_scale=None,
-                   time_units="Gyr", clobber=True):
+    def write_fits(self, filename, sky_scale=None, sky_center=None, clobber=True):
         r""" Export images to a FITS file. Writes the SZ distortion in all
         specified frequencies as well as the mass-weighted temperature and the
         optical depth. Distance units are in kpc, unless *sky_center*
@@ -290,12 +298,13 @@
         ----------
         filename : string
             The name of the FITS file to be written. 
-        sky_center : tuple of floats, optional
-            The center of the observation in (RA, Dec) in degrees. Only used if
-            converting to sky coordinates.          
-        sky_scale : float, optional
-            Scale between degrees and kpc. Only used if
-            converting to sky coordinates.
+        sky_scale : tuple or YTQuantity
+            Conversion between an angle unit and a length unit, if sky
+            coordinates are desired.
+            Examples: (1.0, "arcsec/kpc"), YTQuantity(0.001, "deg/kpc")
+        sky_center : tuple, optional
+            The (RA, Dec) coordinate in degrees of the central pixel if
+            *sky_scale* has been specified.
         clobber : boolean, optional
             If the file already exists, do we overwrite?
 
@@ -304,27 +313,43 @@
         >>> # This example just writes out a FITS file with kpc coords
         >>> szprj.write_fits("SZbullet.fits", clobber=False)
         >>> # This example uses sky coords
-        >>> sky_scale = 1./3600. # One arcsec per kpc
+        >>> sky_scale = (1., "arcsec/kpc") # One arcsec per kpc
         >>> sky_center = (30., 45.) # In degrees
         >>> szprj.write_fits("SZbullet.fits", sky_center=sky_center, sky_scale=sky_scale)
         """
+        from yt.utilities.fits_image import FITSImageBuffer, sanitize_fits_unit
 
-        deltas = np.array([self.dx.in_units(units),
-                           self.dy.in_units(units)])
+        if sky_scale is None:
+            center = (0.0,0.0)
+            types = ["LINEAR","LINEAR"]
+        else:
+            if iterable(sky_scale):
+                sky_scale = self.ds.quan(sky_scale[0], sky_scale[1])
+            if sky_center is None:
+                center = (30.,45.)
+            else:
+                center = sky_center
+            types = ["RA---TAN","DEC--TAN"]
 
-        if sky_center is None:
-            center = [0.0]*2
-        else:
-            center = sky_center
+        units = self.ds.get_smallest_appropriate_unit(self.width)
+        units = sanitize_fits_unit(units)
+        # Hack because FITS is stupid and doesn't understand case
+        dx = self.dx.in_units(units)
+        if sky_scale:
+            dx = (dx*sky_scale).in_units("deg")
             units = "deg"
-            deltas *= sky_scale
-            deltas[0] *= -1.
+        dy = dx
+        if sky_scale:
+            dx *= -1.
 
-        from yt.utilities.fits_image import FITSImageBuffer
-        fib = FITSImageBuffer(self.data, fields=self.data.keys(),
-                              center=center, units=units,
-                              scale=deltas)
-        fib.update_all_headers("Time", float(self.ds.current_time.in_units(time_units).value))
+        w = _astropy.pywcs.WCS(naxis=2)
+        w.wcs.crpix = [0.5*(self.nx+1)]*2
+        w.wcs.cdelt = [dx.v,dy.v]
+        w.wcs.crval = center
+        w.wcs.cunit = [units]*2
+        w.wcs.ctype = types
+
+        fib = FITSImageBuffer(self.data, fields=self.data.keys(), wcs=w)
         fib.writeto(filename, clobber=clobber)
         
     @parallel_root_only

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/data_objects/construction_data_containers.py
--- a/yt/data_objects/construction_data_containers.py
+++ b/yt/data_objects/construction_data_containers.py
@@ -191,6 +191,8 @@
         "integrate" : integration along the axis
         "mip" : maximum intensity projection
         "sum" : same as "integrate", except that we don't multiply by the path length
+        WARNING: The "sum" option should only be used for uniform resolution grid
+        datasets, as other datasets may result in unphysical images.
     style : string, optional
         The same as the method keyword.  Deprecated as of version 3.0.2.  
         Please use method keyword instead.

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/frontends/fits/data_structures.py
--- a/yt/frontends/fits/data_structures.py
+++ b/yt/frontends/fits/data_structures.py
@@ -42,11 +42,11 @@
     unit_prefixes
 from yt.units import dimensions
 from yt.units.yt_array import YTQuantity
-from yt.utilities.on_demand_imports import _astropy
+from yt.utilities.on_demand_imports import _astropy, NotAModule
 
-lon_prefixes = ["X","RA","GLON"]
-lat_prefixes = ["Y","DEC","GLAT"]
-delimiters = ["*", "/", "-", "^"]
+lon_prefixes = ["X","RA","GLON","LINEAR"]
+lat_prefixes = ["Y","DEC","GLAT","LINEAR"]
+delimiters = ["*", "/", "-", "^", "(", ")"]
 delimiters += [str(i) for i in xrange(10)]
 regex_pattern = '|'.join(re.escape(_) for _ in delimiters)
 
@@ -552,7 +552,7 @@
         x = 0
         for p in lon_prefixes+lat_prefixes+spec_names.keys():
             y = np_char.startswith(self.axis_names[:self.dimensionality], p)
-            x += y.sum()
+            x += np.any(y)
         if x == self.dimensionality: self._setup_spec_cube()
 
     def _setup_spec_cube(self):
@@ -582,6 +582,12 @@
         self.lon_axis = np.where(self.lon_axis)[0][0]
         self.lon_name = ctypes[self.lon_axis].split("-")[0].lower()
 
+        if self.lat_axis == self.lon_axis and self.lat_name == self.lon_name:
+            self.lat_axis = 1
+            self.lon_axis = 0
+            self.lat_name = "Y"
+            self.lon_name = "X"
+
         if self.wcs.naxis > 2:
 
             self.spec_axis = np.zeros((end-1), dtype="bool")
@@ -658,6 +664,8 @@
             ext = args[0].rsplit(".", 1)[0].rsplit(".", 1)[-1]
         if ext.upper() not in ("FITS", "FTS"):
             return False
+        elif isinstance(_astropy.pyfits, NotAModule):
+            raise RuntimeError("This appears to be a FITS file, but AstroPy is not installed.")
         try:
             with warnings.catch_warnings():
                 warnings.filterwarnings('ignore', category=UserWarning, append=True)

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/geometry/coordinates/coordinate_handler.py
--- a/yt/geometry/coordinates/coordinate_handler.py
+++ b/yt/geometry/coordinates/coordinate_handler.py
@@ -183,7 +183,15 @@
         elif isinstance(center, YTArray):
             return self.ds.arr(center), self.convert_to_cartesian(center)
         elif iterable(center):
-            if iterable(center[0]) and isinstance(center[1], basestring):
+            if isinstance(center[0], basestring) and isinstance(center[1], basestring):
+                if center[0].lower() == "min":
+                    v, center = self.ds.find_min(center[1])
+                elif center[0].lower() == "max":
+                    v, center = self.ds.find_max(center[1])
+                else:
+                    raise RuntimeError("center keyword \"%s\" not recognized" % center)
+                center = self.ds.arr(center, 'code_length')
+            elif iterable(center[0]) and isinstance(center[1], basestring):
                 center = self.ds.arr(center[0], center[1])
             else:
                 center = self.ds.arr(center, 'code_length')

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/geometry/coordinates/spec_cube_coordinates.py
--- a/yt/geometry/coordinates/spec_cube_coordinates.py
+++ b/yt/geometry/coordinates/spec_cube_coordinates.py
@@ -26,8 +26,19 @@
         self.axis_name = {}
         self.axis_id = {}
 
-        for axis, axis_name in zip([ds.lon_axis, ds.lat_axis, ds.spec_axis],
-                                   ["Image\ x", "Image\ y", ds.spec_name]):
+        self.default_unit_label = {}
+        if ds.lon_name == "X" and ds.lat_name == "Y":
+            names = ["x","y"]
+        else:
+            names = ["Image\ x", "Image\ y"]
+            self.default_unit_label[ds.lon_axis] = "pixel"
+            self.default_unit_label[ds.lat_axis] = "pixel"
+        names.append(ds.spec_name)
+        axes = [ds.lon_axis, ds.lat_axis, ds.spec_axis]
+        self.default_unit_label[ds.spec_axis] = ds.spec_unit
+
+        for axis, axis_name in zip(axes, names):
+
             lower_ax = "xyz"[axis]
             upper_ax = lower_ax.upper()
 
@@ -40,11 +51,6 @@
             self.axis_id[axis] = axis
             self.axis_id[axis_name] = axis
 
-        self.default_unit_label = {}
-        self.default_unit_label[ds.lon_axis] = "pixel"
-        self.default_unit_label[ds.lat_axis] = "pixel"
-        self.default_unit_label[ds.spec_axis] = ds.spec_unit
-
         def _spec_axis(ax, x, y):
             p = (x,y)[ax]
             return [self.ds.pixel2spec(pp).v for pp in p]

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/units/unit_lookup_table.py
--- a/yt/units/unit_lookup_table.py
+++ b/yt/units/unit_lookup_table.py
@@ -172,6 +172,10 @@
     'y': 1e-24,  # yocto
 }
 
+latex_prefixes = {
+    "u" : "\\mu",
+    }
+
 prefixable_units = (
     "m",
     "pc",

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/units/unit_object.py
--- a/yt/units/unit_object.py
+++ b/yt/units/unit_object.py
@@ -26,7 +26,8 @@
 from yt.units.unit_lookup_table import \
     latex_symbol_lut, unit_prefixes, \
     prefixable_units, cgs_base_units, \
-    mks_base_units, cgs_conversions
+    mks_base_units, latex_prefixes, \
+    cgs_conversions
 from yt.units.unit_registry import UnitRegistry
 
 import copy
@@ -544,9 +545,14 @@
             prefix_value = unit_prefixes[possible_prefix]
 
             if symbol_str not in latex_symbol_lut:
+                if possible_prefix in latex_prefixes:
+                    sstr = symbol_str.replace(possible_prefix, 
+                                              '{'+latex_prefixes[possible_prefix]+'}')
+                else:
+                    sstr = symbol_str
                 latex_symbol_lut[symbol_str] = \
                     latex_symbol_lut[symbol_wo_prefix].replace(
-                                   '{'+symbol_wo_prefix+'}', '{'+symbol_str+'}')
+                                   '{'+symbol_wo_prefix+'}', '{'+sstr+'}')
 
             # don't forget to account for the prefix value!
             return (unit_data[0] * prefix_value, unit_data[1])

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/utilities/fits_image.py
--- a/yt/utilities/fits_image.py
+++ b/yt/utilities/fits_image.py
@@ -15,7 +15,8 @@
 from yt.visualization.fixed_resolution import FixedResolutionBuffer
 from yt.data_objects.construction_data_containers import YTCoveringGridBase
 from yt.utilities.on_demand_imports import _astropy
-from yt.units.yt_array import YTQuantity
+from yt.units.yt_array import YTQuantity, YTArray
+import re
 
 pyfits = _astropy.pyfits
 pywcs = _astropy.pywcs
@@ -27,8 +28,7 @@
 
 class FITSImageBuffer(HDUList):
 
-    def __init__(self, data, fields=None, units="cm",
-                 center=None, scale=None, wcs=None):
+    def __init__(self, data, fields=None, units="cm", wcs=None):
         r""" Initialize a FITSImageBuffer object.
 
         FITSImageBuffer contains a list of FITS ImageHDU instances, and
@@ -49,29 +49,32 @@
             keys, it will use these for the fields. If *data* is just a
             single array one field name must be specified.
         units : string
-            The units of the WCS coordinates, default "cm". 
-        center : array_like, optional
-            The coordinates [xctr,yctr] of the images in units
-            *units*. If *units* is not specified, defaults to the origin. 
-        scale : tuple of floats, optional
-            Pixel scale in unit *units*. Will be ignored if *data* is
-            a FixedResolutionBuffer or a YTCoveringGrid. Must be
-            specified otherwise, or if *units* is "deg".
+            The units of the WCS coordinates. Only applies
+            to FixedResolutionBuffers or YTCoveringGrids. Defaults to "cm".
         wcs : `astropy.wcs.WCS` instance, optional
-            Supply an AstroPy WCS instance to override automatic WCS creation.
+            Supply an AstroPy WCS instance. Will override automatic WCS
+            creation from FixedResolutionBuffers and YTCoveringGrids.
 
         Examples
         --------
 
+        >>> # This example uses a FRB.
         >>> ds = load("sloshing_nomag2_hdf5_plt_cnt_0150")
         >>> prj = ds.proj(2, "kT", weight_field="density")
         >>> frb = prj.to_frb((0.5, "Mpc"), 800)
         >>> # This example just uses the FRB and puts the coords in kpc.
         >>> f_kpc = FITSImageBuffer(frb, fields="kT", units="kpc")
-        >>> # This example specifies sky coordinates.
-        >>> scale = [1./3600.]*2 # One arcsec per pixel
-        >>> f_deg = FITSImageBuffer(frb, fields="kT", units="deg",
-                                    scale=scale, center=(30., 45.))
+        >>> # This example specifies a specific WCS.
+        >>> from astropy.wcs import WCS
+        >>> w = WCS(naxis=self.dimensionality)
+        >>> w.wcs.crval = [30., 45.] # RA, Dec in degrees
+        >>> w.wcs.cunit = ["deg"]*2
+        >>> nx, ny = 800, 800
+        >>> w.wcs.crpix = [0.5*(nx+1), 0.5*(ny+1)]
+        >>> w.wcs.ctype = ["RA---TAN","DEC--TAN"]
+        >>> scale = 1./3600. # One arcsec per pixel
+        >>> w.wcs.cdelt = [-scale, scale]
+        >>> f_deg = FITSImageBuffer(frb, fields="kT", wcs=w)
         >>> f_deg.writeto("temp.fits")
         """
         
@@ -98,8 +101,14 @@
 
         first = True
 
+        self.field_units = {}
+
         for key in fields:
             if key not in exclude_fields:
+                if hasattr(img_data[key], "units"):
+                    self.field_units[key] = str(img_data[key].units)
+                else:
+                    self.field_units[key] = "dimensionless"
                 mylog.info("Making a FITS image of field %s" % (key))
                 if first:
                     hdu = pyfits.PrimaryHDU(np.array(img_data[key]))
@@ -109,7 +118,7 @@
                 hdu.name = key
                 hdu.header["btype"] = key
                 if hasattr(img_data[key], "units"):
-                    hdu.header["bunit"] = str(img_data[key].units)
+                    hdu.header["bunit"] = re.sub('()', '', str(img_data[key].units))
                 self.append(hdu)
 
         self.dimensionality = len(self[0].data.shape)
@@ -121,25 +130,11 @@
 
         has_coords = (isinstance(img_data, FixedResolutionBuffer) or
                       isinstance(img_data, YTCoveringGridBase))
-        
-        if center is None:
-            if units == "deg":
-                mylog.error("Please specify center=(RA, Dec) in degrees.")
-                raise ValueError
-            elif not has_coords:
-                mylog.warning("Setting center to the origin.")
-                center = [0.0]*self.dimensionality
-
-        if scale is None:
-            if units == "deg" or not has_coords and wcs is None:
-                mylog.error("Please specify scale=(dx,dy[,dz]) in %s." % (units))
-                raise ValueError
 
         if wcs is None:
             w = pywcs.WCS(header=self[0].header, naxis=self.dimensionality)
             w.wcs.crpix = 0.5*(np.array(self.shape)+1)
-            proj_type = ["linear"]*self.dimensionality
-            if isinstance(img_data, FixedResolutionBuffer) and units != "deg":
+            if isinstance(img_data, FixedResolutionBuffer):
                 # FRBs are a special case where we have coordinate
                 # information, so we take advantage of this and
                 # construct the WCS object
@@ -151,28 +146,20 @@
             elif isinstance(img_data, YTCoveringGridBase):
                 dx, dy, dz = img_data.dds.in_units(units)
                 center = 0.5*(img_data.left_edge+img_data.right_edge).in_units(units)
-            elif units == "deg" and self.dimensionality == 2:
-                dx = -scale[0]
-                dy = scale[1]
-                proj_type = ["RA---TAN","DEC--TAN"]
             else:
-                dx = scale[0]
-                dy = scale[1]
-                if self.dimensionality == 3: dz = scale[2]
-            
+                # We default to pixel coordinates if nothing is provided
+                dx, dy, dz = 1.0, 1.0, 1.0
+                center = 0.5*(np.array(self.shape)+1)
             w.wcs.crval = center
-            w.wcs.cunit = [units]*self.dimensionality
-            w.wcs.ctype = proj_type
-        
+            if has_coords:
+                w.wcs.cunit = [units]*self.dimensionality
             if self.dimensionality == 2:
                 w.wcs.cdelt = [dx,dy]
             elif self.dimensionality == 3:
                 w.wcs.cdelt = [dx,dy,dz]
-
+            w.wcs.ctype = ["linear"]*self.dimensionality
             self._set_wcs(w)
-
         else:
-
             self._set_wcs(wcs)
 
     def _set_wcs(self, wcs):
@@ -194,7 +181,7 @@
         for img in self: img.header[key] = value
             
     def keys(self):
-        return [f.name for f in self]
+        return [f.name.lower() for f in self]
 
     def has_key(self, key):
         return key in self.keys()
@@ -249,33 +236,71 @@
         import aplpy
         return aplpy.FITSFigure(self, **kwargs)
 
+    def get_data(self, field):
+        return YTArray(self[field].data, self.field_units[field])
+
+    def set_unit(self, field, units):
+        """
+        Set the units of *field* to *units*.
+        """
+        new_data = YTArray(self[field].data, self.field_units[field]).in_units(units)
+        self[field].data = new_data.v
+        self[field].header["bunit"] = units
+        self.field_units[field] = units
+        
 axis_wcs = [[1,2],[0,2],[0,1]]
 
-def construct_image(data_source, center=None):
+def sanitize_fits_unit(unit):
+    if unit == "Mpc":
+        mylog.info("Changing FITS file unit to kpc.")
+        unit = "kpc"
+    elif unit == "au":
+        unit = "AU"
+    return unit
+
+def construct_image(data_source, center=None, width=None, image_res=None):
     ds = data_source.ds
     axis = data_source.axis
+    if center is None or width is None:
+        center = ds.domain_center[axis_wcs[axis]]
+    if width is None:
+        width = ds.domain_width[axis_wcs[axis]]
+        mylog.info("Making an image of the entire domain, "+
+                   "so setting the center to the domain center.")
+    else:
+        width = ds.coordinates.sanitize_width(axis, width, None)
+    if image_res is None:
+        dd = ds.all_data()
+        dx, dy = [dd.quantities.extrema("d%s" % "xyz"[idx])[0]
+                  for idx in axis_wcs[axis]]
+        nx = int((width[0]/dx).in_units("dimensionless"))
+        ny = int((width[1]/dy).in_units("dimensionless"))
+    else:
+        if iterable(image_res):
+            nx, ny = image_res
+        else:
+            nx, ny = image_res, image_res
+        dx, dy = width[0]/nx, width[1]/ny
+    crpix = [0.5*(nx+1), 0.5*(ny+1)]
     if hasattr(ds, "wcs"):
-        # This is a FITS dataset
-        nx, ny = ds.domain_dimensions[axis_wcs[axis]]
-        crpix = [ds.wcs.wcs.crpix[idx] for idx in axis_wcs[axis]]
-        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
-        crval = [ds.wcs.wcs.crval[idx] for idx in axis_wcs[axis]]
+        # This is a FITS dataset, so we use it to construct the WCS
         cunit = [str(ds.wcs.wcs.cunit[idx]) for idx in axis_wcs[axis]]
         ctype = [ds.wcs.wcs.ctype[idx] for idx in axis_wcs[axis]]
+        cdelt = [ds.wcs.wcs.cdelt[idx] for idx in axis_wcs[axis]]
+        ctr_pix = center.in_units("code_length")[:self.dimensionality].v
+        crval = ds.wcs.wcs_pix2world(ctr_pix.reshape(1,self.dimensionality))[0]
+        crval = [crval[idx] for idx in axis_wcs[axis]]
     else:
-        # This is some other kind of dataset
-        unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
-        if center is None:
-            crval = [0.0,0.0]
-        else:
-            crval = [(ds.domain_center-center)[idx].in_units(unit) for idx in axis_wcs[axis]]
-        dx = ds.index.get_smallest_dx()
-        nx, ny = (ds.domain_width[axis_wcs[axis]]/dx).ndarray_view().astype("int")
-        crpix = [0.5*(nx+1), 0.5*(ny+1)]
-        cdelt = [dx.in_units(unit)]*2
+        # This is some other kind of dataset                                                                                    
+        unit = str(width[0].units)
+        if unit == "unitary":
+            unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())
+        unit = sanitize_fits_unit(unit)
         cunit = [unit]*2
         ctype = ["LINEAR"]*2
-    frb = data_source.to_frb((1.0,"unitary"), (nx,ny))
+        cdelt = [dx.in_units(unit)]*2
+        crval = [center[idx].in_units(unit) for idx in axis_wcs[axis]]
+    frb = data_source.to_frb(width[0], (nx,ny), center=center, height=width[1])
     w = pywcs.WCS(naxis=2)
     w.wcs.crpix = crpix
     w.wcs.cdelt = cdelt
@@ -296,21 +321,47 @@
         The axis of the slice. One of "x","y","z", or 0,1,2.
     fields : string or list of strings
         The fields to slice
-    center : A sequence floats, a string, or a tuple.
-         The coordinate of the origin of the image. If set to 'c', 'center' or
+    center : A sequence of floats, a string, or a tuple.
+         The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+    image_res : an int or 2-tuple of ints
+        Specify the resolution of the resulting image. If not provided, it will be
+        determined based on the minimum cell size of the dataset.
     """
-    def __init__(self, ds, axis, fields, center="c", **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         slc = ds.slice(axis, center[axis], **kwargs)
-        w, frb = construct_image(slc, center=center)
+        w, frb = construct_image(slc, center=dcenter, width=width,
+                                 image_res=image_res)
         super(FITSSlice, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)
@@ -329,21 +380,48 @@
         The fields to project
     weight_field : string
         The field used to weight the projection.
-    center : A sequence floats, a string, or a tuple.
-        The coordinate of the origin of the image. If set to 'c', 'center' or
-        left blank, the plot is centered on the middle of the domain. If set to
-        'max' or 'm', the center will be located at the maximum of the
-        ('gas', 'density') field. Units can be specified by passing in center
-        as a tuple containing a coordinate and string unit name or by passing
-        in a YTArray.  If a list or unitless array is supplied, code units are
-        assumed.
+    center : A sequence of floats, a string, or a tuple.
+         The coordinate of the center of the image. If set to 'c', 'center' or
+         left blank, the plot is centered on the middle of the domain. If set to
+         'max' or 'm', the center will be located at the maximum of the
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
+         as a tuple containing a coordinate and string unit name or by passing
+         in a YTArray. If a list or unitless array is supplied, code units are
+         assumed.
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+    image_res : an int or 2-tuple of ints
+        Specify the resolution of the resulting image. If not provided, it will be
+        determined based on the minimum cell size of the dataset.
     """
-    def __init__(self, ds, axis, fields, center="c", weight_field=None, **kwargs):
+    def __init__(self, ds, axis, fields, center="c", width=None, 
+                 weight_field=None, image_res=None, **kwargs):
         fields = ensure_list(fields)
         axis = fix_axis(axis, ds)
-        center = ds.coordinates.sanitize_center(center, axis)
+        center, dcenter = ds.coordinates.sanitize_center(center, axis)
         prj = ds.proj(fields[0], axis, weight_field=weight_field, **kwargs)
-        w, frb = construct_image(prj, center=center)
+        w, frb = construct_image(prj, center=dcenter, width=width,
+                                 image_res=image_res)
         super(FITSProjection, self).__init__(frb, fields=fields, wcs=w)
         for i, field in enumerate(fields):
             self[i].header["bunit"] = str(frb[field].units)

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/utilities/on_demand_imports.py
--- a/yt/utilities/on_demand_imports.py
+++ b/yt/utilities/on_demand_imports.py
@@ -132,4 +132,15 @@
             self._interpolate = interpolate
         return self._interpolate
 
+    _special = None
+    @property
+    def special(self):
+        if self._special is None:
+            try:
+                import scipy.special as special
+            except ImportError:
+                special = NotAModule(self._name)
+            self._special = special
+        return self._special
+
 _scipy = scipy_imports()
\ No newline at end of file

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/visualization/fixed_resolution.py
--- a/yt/visualization/fixed_resolution.py
+++ b/yt/visualization/fixed_resolution.py
@@ -418,9 +418,9 @@
                                    width, dd.resolution, item,
                                    weight=dd.weight_field, volume=dd.volume,
                                    no_ghost=dd.no_ghost, interpolated=dd.interpolated,
-                                   north_vector=dd.north_vector)
+                                   north_vector=dd.north_vector, method=dd.method)
         units = Unit(dd.ds.field_info[item].units, registry=dd.ds.unit_registry)
-        if dd.weight_field is None:
+        if dd.weight_field is None and dd.method == "integrate":
             units *= Unit('cm', registry=dd.ds.unit_registry)
         ia = ImageArray(buff.swapaxes(0,1), input_units=units, info=self._get_info(item))
         self[item] = ia

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -50,6 +50,8 @@
     Unit
 from yt.units.unit_registry import \
     UnitParseError
+from yt.units.unit_lookup_table import \
+    prefixable_units, latex_prefixes
 from yt.units.yt_array import \
     YTArray, YTQuantity
 from yt.utilities.png_writer import \
@@ -771,9 +773,10 @@
                 if hasattr(self.ds.coordinates, "default_unit_label"):
                     axax = getattr(self.ds.coordinates,
                                    "%s_axis" % ("xy"[i]))[axis_index]
-                    un = self.ds.coordinates.default_unit_label[axax]
-                    axes_unit_labels[i] = '\/\/\left('+un+'\right)'
-                    continue
+                    unn = self.ds.coordinates.default_unit_label.get(axax, "")
+                    if unn != "":
+                        axes_unit_labels[i] = '\/\/\left('+unn+'\right)'
+                        continue
                 # Use sympy to factor h out of the unit.  In this context 'un'
                 # is a string, so we call the Unit constructor.
                 expr = Unit(un, registry=self.ds.unit_registry).expr
@@ -794,6 +797,11 @@
                     raise RuntimeError
                 if un in formatted_length_unit_names:
                     un = formatted_length_unit_names[un]
+                pp = un[0]
+                if pp in latex_prefixes:
+                    symbol_wo_prefix = un[1:]
+                    if symbol_wo_prefix in prefixable_units:
+                        un = un.replace(pp, "{"+latex_prefixes[pp]+"}", 1)
                 if un not in ['1', 'u', 'unitary']:
                     if hinv:
                         un = un + '\,h^{-1}'
@@ -940,13 +948,15 @@
          or the axis name itself
     fields : string
          The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1061,13 +1071,15 @@
          or the axis name itself
     fields : string
          The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1144,7 +1156,9 @@
 
          "sum" : This method is the same as integrate, except that it does not 
          multiply by a path length when performing the integration, and is 
-         just a straight summation of the field along the given axis. 
+         just a straight summation of the field along the given axis. WARNING:
+         This should only be used for uniform resolution grid datasets, as other
+         datasets may result in unphysical images.
     proj_style : string
          The method of projection--same as method keyword.  Deprecated as of 
          version 3.0.2.  Please use method instead.
@@ -1218,13 +1232,15 @@
          The vector normal to the slicing plane.
     fields : string
          The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1284,12 +1300,11 @@
 
 class OffAxisProjectionDummyDataSource(object):
     _type_name = 'proj'
-    method = 'integrate'
     _key_fields = []
     def __init__(self, center, ds, normal_vector, width, fields,
                  interpolated, resolution = (800,800), weight=None,
                  volume=None, no_ghost=False, le=None, re=None,
-                 north_vector=None):
+                 north_vector=None, method="integrate"):
         self.center = center
         self.ds = ds
         self.axis = 4 # always true for oblique data objects
@@ -1306,6 +1321,7 @@
         self.le = le
         self.re = re
         self.north_vector = north_vector
+        self.method = method
 
     def _determine_fields(self, *args):
         return self.dd._determine_fields(*args)
@@ -1329,13 +1345,15 @@
         The vector normal to the slicing plane.
     fields : string
         The name of the field(s) to be plotted.
-    center : A sequence floats, a string, or a tuple.
+    center : A sequence of floats, a string, or a tuple.
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable
@@ -1376,7 +1394,20 @@
          set, an arbitrary grid-aligned north-vector is chosen.
     fontsize : integer
          The size of the fonts for the axis, colorbar, and tick labels.
+    method : string
+         The method of projection.  Valid methods are:
 
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not
+         multiply by a path length when performing the integration, and is
+         just a straight summation of the field along the given axis. WARNING:
+         This should only be used for uniform resolution grid datasets, as other
+         datasets may result in unphysical images.
     """
     _plot_type = 'OffAxisProjection'
     _frb_generator = OffAxisProjectionFixedResolutionBuffer
@@ -1384,7 +1415,7 @@
     def __init__(self, ds, normal, fields, center='c', width=None,
                  depth=(1, '1'), axes_unit=None, weight_field=None,
                  max_level=None, north_vector=None, volume=None, no_ghost=False,
-                 le=None, re=None, interpolated=False, fontsize=18):
+                 le=None, re=None, interpolated=False, fontsize=18, method="integrate"):
         (bounds, center_rot) = \
           get_oblique_window_parameters(normal,center,width,ds,depth=depth)
         fields = ensure_list(fields)[:]
@@ -1394,7 +1425,7 @@
         OffAxisProj = OffAxisProjectionDummyDataSource(
             center_rot, ds, normal, oap_width, fields, interpolated,
             weight=weight_field,  volume=volume, no_ghost=no_ghost,
-            le=le, re=re, north_vector=north_vector)
+            le=le, re=re, north_vector=north_vector, method=method)
         # If a non-weighted, integral projection, assure field-label reflects that
         if weight_field is None and OffAxisProj.method == "integrate":
             self.projected = True
@@ -1736,9 +1767,11 @@
          The coordinate of the center of the image. If set to 'c', 'center' or
          left blank, the plot is centered on the middle of the domain. If set to
          'max' or 'm', the center will be located at the maximum of the
-         ('gas', 'density') field. Units can be specified by passing in center
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
          as a tuple containing a coordinate and string unit name or by passing
-         in a YTArray.  If a list or unitless array is supplied, code units are
+         in a YTArray. If a list or unitless array is supplied, code units are
          assumed.
     width : tuple or a float.
          Width can have four different formats to support windows with variable

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/visualization/volume_rendering/camera.py
--- a/yt/visualization/volume_rendering/camera.py
+++ b/yt/visualization/volume_rendering/camera.py
@@ -2171,7 +2171,8 @@
 class ProjectionCamera(Camera):
     def __init__(self, center, normal_vector, width, resolution,
             field, weight=None, volume=None, no_ghost = False, 
-            north_vector=None, ds=None, interpolated=False):
+            north_vector=None, ds=None, interpolated=False,
+            method="integrate"):
 
         if not interpolated:
             volume = 1
@@ -2180,6 +2181,7 @@
         self.field = field
         self.weight = weight
         self.resolution = resolution
+        self.method = method
 
         fields = [field]
         if self.weight is not None:
@@ -2250,13 +2252,14 @@
         dd = ds.all_data()
         field = dd._determine_fields([self.field])[0]
         finfo = ds._get_field_info(*field)
-        if self.weight is None:
-            dl = self.width[2]
-            image *= dl
-        else:
-            image[:,:,0] /= image[:,:,1]
+        dl = 1.0
+        if self.method == "integrate":
+            if self.weight is None:
+                dl = self.width[2].in_units("cm")
+            else:
+                image[:,:,0] /= image[:,:,1]
 
-        return image[:,:,0]
+        return ImageArray(image[:,:,0], finfo.units)*dl
 
 
     def _render(self, double_check, num_threads, image, sampler):
@@ -2342,7 +2345,7 @@
 def off_axis_projection(ds, center, normal_vector, width, resolution,
                         field, weight = None, 
                         volume = None, no_ghost = False, interpolated = False,
-                        north_vector = None):
+                        north_vector = None, method = "integrate"):
     r"""Project through a dataset, off-axis, and return the image plane.
 
     This function will accept the necessary items to integrate through a volume
@@ -2386,6 +2389,20 @@
         If True, the data is first interpolated to vertex-centered data, 
         then tri-linearly interpolated along the ray. Not suggested for 
         quantitative studies.
+    method : string
+         The method of projection.  Valid methods are:
+
+         "integrate" with no weight_field specified : integrate the requested
+         field along the line of sight.
+
+         "integrate" with a weight_field specified : weight the requested
+         field by the weighting field and integrate along the line of sight.
+
+         "sum" : This method is the same as integrate, except that it does not
+         multiply by a path length when performing the integration, and is
+         just a straight summation of the field along the given axis. WARNING:
+         This should only be used for uniform resolution grid datasets, as other
+         datasets may result in unphysical images.
 
     Returns
     -------
@@ -2403,7 +2420,7 @@
     projcam = ProjectionCamera(center, normal_vector, width, resolution,
                                field, weight=weight, ds=ds, volume=volume,
                                no_ghost=no_ghost, interpolated=interpolated, 
-                               north_vector=north_vector)
+                               north_vector=north_vector, method=method)
     image = projcam.snapshot()
     return image[:,:]
 

diff -r ab0c5ed83cc0eff0c8f72e3faca3cecfe9cc93e6 -r e433a9791f5dadcd5de80c5d9f801526335455de yt/visualization/volume_rendering/image_handling.py
--- a/yt/visualization/volume_rendering/image_handling.py
+++ b/yt/visualization/volume_rendering/image_handling.py
@@ -40,9 +40,7 @@
         data["b"] = image[:,:,2]
         data["a"] = image[:,:,3]
         nx, ny = data["r"].shape
-        fib = FITSImageBuffer(data, units="pixel",
-                              center=[0.5*(nx+1), 0.5*(ny+1)],
-                              scale=[1.]*2)
+        fib = FITSImageBuffer(data)
         fib.writeto('%s.fits'%fn,clobber=True)
 
 def import_rgba(name, h5=True):

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