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

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Sat Jul 16 09:25:29 PDT 2016


19 new commits in yt:

https://bitbucket.org/yt_analysis/yt/commits/4592d652b5f2/
Changeset:   4592d652b5f2
Branch:      yt
User:        ngoldbaum
Date:        2016-07-08 15:49:41+00:00
Summary:     Make VolumeSource.transfer_function a cached property that gets generated on demand
Affected #:  1 file

diff -r b264176ae1527a32714dbd782c7f5a3e79dc993a -r 4592d652b5f27b9644cad647863586c9021b4665 yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -146,17 +146,34 @@
         assert(self.data_source is not None)
 
         # In the future these will merge
-        self.transfer_function = None
-        self.tfh = None
+        self.tfh = TransferFunctionHelper(self.data_source.pf)
+        self.tfh.set_field(self.field)
+        self._transfer_function = None
         if auto:
             self.build_defaults()
 
+    @property
+    def transfer_function(self):
+        """The transfer function associated with this VolumeSource"""
+        if self._transfer_function is not None:
+            return self._transfer_function
+
+        if self.tfh.tf is not None:
+            self._transfer_function = self.tfh.tf
+            return self._transfer_function
+
+        mylog.info("Creating default transfer function")
+        self.tfh.set_field(self.field)
+        self.tfh.build_transfer_function()
+        self.tfh.setup_default()
+        self._transfer_function = self.tfh.tf
+
+        return self._transfer_function
+
     def build_defaults(self):
         """Sets a default volume and transfer function"""
         mylog.info("Creating default volume")
         self.build_default_volume()
-        mylog.info("Creating default transfer function")
-        self.build_default_transfer_function()
 
     def set_transfer_function(self, transfer_function):
         """Set transfer function for this source"""
@@ -183,15 +200,6 @@
         if self.transfer_function is None:
             raise RuntimeError("Transfer Function not Supplied")
 
-    def build_default_transfer_function(self):
-        """Sets up a transfer function"""
-        self.tfh = \
-            TransferFunctionHelper(self.data_source.pf)
-        self.tfh.set_field(self.field)
-        self.tfh.build_transfer_function()
-        self.tfh.setup_default()
-        self.transfer_function = self.tfh.tf
-
     def build_default_volume(self):
         """Sets up an AMRKDTree based on the VolumeSource's field"""
         self.volume = AMRKDTree(self.data_source.pf,


https://bitbucket.org/yt_analysis/yt/commits/97c4021ed000/
Changeset:   97c4021ed000
Branch:      yt
User:        ngoldbaum
Date:        2016-07-08 15:50:30+00:00
Summary:     Making the logging message about the TranferFunctionHelper bounds more explicit about where it comes from
Affected #:  1 file

diff -r 4592d652b5f27b9644cad647863586c9021b4665 -r 97c4021ed000876334ef479f4794bd358dc04885 yt/visualization/volume_rendering/transfer_function_helper.py
--- a/yt/visualization/volume_rendering/transfer_function_helper.py
+++ b/yt/visualization/volume_rendering/transfer_function_helper.py
@@ -118,7 +118,7 @@
         """
         if self.bounds is None:
             mylog.info('Calculating data bounds. This may take a while.' +
-                       '  Set the .bounds to avoid this.')
+                       '  Set the TranferFunctionHelper.bounds to avoid this.')
             self.set_bounds()
 
         if self.log:


https://bitbucket.org/yt_analysis/yt/commits/17157a4ed245/
Changeset:   17157a4ed245
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 01:57:08+00:00
Summary:     Only reset transfer_function_helper logarithm option if selecting a new field
Affected #:  1 file

diff -r 97c4021ed000876334ef479f4794bd358dc04885 -r 17157a4ed24511553ab64420f10c1690e4040ef9 yt/visualization/volume_rendering/transfer_function_helper.py
--- a/yt/visualization/volume_rendering/transfer_function_helper.py
+++ b/yt/visualization/volume_rendering/transfer_function_helper.py
@@ -83,8 +83,9 @@
         field: string
             The field to be rendered.
         """
+        if field != self.field:
+            self.log = self.ds._get_field_info(field).take_log
         self.field = field
-        self.log = self.ds._get_field_info(self.field).take_log
 
     def set_log(self, log):
         """


https://bitbucket.org/yt_analysis/yt/commits/bb4cd6d1b36c/
Changeset:   bb4cd6d1b36c
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 01:57:26+00:00
Summary:     Don't override global take_log option in transfer function helper
Affected #:  1 file

diff -r 17157a4ed24511553ab64420f10c1690e4040ef9 -r bb4cd6d1b36c3d6fe536d1d6bae4d6af0f54e89a yt/visualization/volume_rendering/transfer_function_helper.py
--- a/yt/visualization/volume_rendering/transfer_function_helper.py
+++ b/yt/visualization/volume_rendering/transfer_function_helper.py
@@ -99,8 +99,6 @@
             Sets whether the transfer function should use log or linear space.
         """
         self.log = log
-        self.ds.index
-        self.ds._get_field_info(self.field).take_log = log
 
     def build_transfer_function(self):
         """


https://bitbucket.org/yt_analysis/yt/commits/2ae5f8a7b1c0/
Changeset:   2ae5f8a7b1c0
Branch:      yt
User:        ngoldbaum
Date:        2016-07-08 15:58:08+00:00
Summary:     Removing outdated comment
Affected #:  1 file

diff -r bb4cd6d1b36c3d6fe536d1d6bae4d6af0f54e89a -r 2ae5f8a7b1c05094e650b87ee53b31554fbd84ed yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -145,7 +145,6 @@
         assert(self.field is not None)
         assert(self.data_source is not None)
 
-        # In the future these will merge
         self.tfh = TransferFunctionHelper(self.data_source.pf)
         self.tfh.set_field(self.field)
         self._transfer_function = None


https://bitbucket.org/yt_analysis/yt/commits/04533a054830/
Changeset:   04533a054830
Branch:      yt
User:        ngoldbaum
Date:        2016-07-09 06:16:11+00:00
Summary:     Ensure log_fields is a list in the AMRKDtree
Affected #:  1 file

diff -r 2ae5f8a7b1c05094e650b87ee53b31554fbd84ed -r 04533a054830bdab943a2c1ee33ca66b9ced82d9 yt/utilities/amr_kdtree/amr_kdtree.py
--- a/yt/utilities/amr_kdtree/amr_kdtree.py
+++ b/yt/utilities/amr_kdtree/amr_kdtree.py
@@ -17,7 +17,9 @@
 import operator
 import numpy as np
 
-from yt.funcs import mylog
+from yt.funcs import \
+    iterable, \
+    mylog
 from yt.utilities.on_demand_imports import _h5py as h5py
 from yt.utilities.amr_kdtree.amr_kdtools import \
     receive_and_reduce, \
@@ -174,14 +176,17 @@
         regenerate_data = self.fields is None or \
                           len(self.fields) != len(new_fields) or \
                           self.fields != new_fields or force
+        if not iterable(log_fields):
+            log_fields = [log_fields]
+        new_log_fields = list(log_fields)
         self.tree.trunk.set_dirty(regenerate_data)
         self.fields = new_fields
 
         if self.log_fields is not None and not regenerate_data:
-            flip_log = list(map(operator.ne, self.log_fields, log_fields))
+            flip_log = list(map(operator.ne, self.log_fields, new_log_fields))
         else:
-            flip_log = [False] * len(log_fields)
-        self.log_fields = log_fields
+            flip_log = [False] * len(new_log_fields)
+        self.log_fields = new_log_fields
 
         self.no_ghost = no_ghost
         del self.bricks, self.brick_dimensions
@@ -189,7 +194,7 @@
         bricks = []
 
         for b in self.traverse():
-            list(map(_apply_log, b.my_data, flip_log, log_fields))
+            list(map(_apply_log, b.my_data, flip_log, self.log_fields))
             bricks.append(b)
         self.bricks = np.array(bricks)
         self.brick_dimensions = np.array(self.brick_dimensions)


https://bitbucket.org/yt_analysis/yt/commits/9cc63aa0ed6c/
Changeset:   9cc63aa0ed6c
Branch:      yt
User:        ngoldbaum
Date:        2016-07-08 21:33:17+00:00
Summary:     Ensure the weight field of an off-axis projection is always a field tuple or None
Affected #:  1 file

diff -r 04533a054830bdab943a2c1ee33ca66b9ced82d9 -r 9cc63aa0ed6c58883e409237d767fa697084bfa6 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -1533,6 +1533,8 @@
         self.fields = fields
         self.interpolated = interpolated
         self.resolution = resolution
+        if weight is not None:
+            weight = self.dd._determine_fields(weight)[0]
         self.weight_field = weight
         self.volume = volume
         self.no_ghost = no_ghost


https://bitbucket.org/yt_analysis/yt/commits/415dd9fc1d24/
Changeset:   415dd9fc1d24
Branch:      yt
User:        ngoldbaum
Date:        2016-07-08 21:38:26+00:00
Summary:     Raise an error message if data_source_or_all is passed something that isn't a dataset, data container, or None
Affected #:  1 file

diff -r 9cc63aa0ed6c58883e409237d767fa697084bfa6 -r 415dd9fc1d24a3dc3a8c5cdad8c7aa1a4f0ee682 yt/visualization/volume_rendering/utils.py
--- a/yt/visualization/volume_rendering/utils.py
+++ b/yt/visualization/volume_rendering/utils.py
@@ -1,4 +1,6 @@
 import numpy as np
+from yt.data_objects.data_containers import \
+    YTSelectionContainer3D
 from yt.data_objects.static_output import Dataset
 from yt.utilities.lib import bounding_volume_hierarchy
 from yt.utilities.lib.grid_traversal import \
@@ -13,6 +15,11 @@
 def data_source_or_all(data_source):
     if isinstance(data_source, Dataset):
         data_source = data_source.all_data()
+    if not isinstance(data_source, (YTSelectionContainer3D, type(None))):
+        raise RuntimeError(
+            "The data_source is not a valid 3D data container.\n"
+            "Expected an ojbect of type YTSelectionContainer3D but received "
+            "an object of type %s." % type(data_source))
     return data_source
 
 


https://bitbucket.org/yt_analysis/yt/commits/4a65278f5a64/
Changeset:   4a65278f5a64
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 04:23:52+00:00
Summary:     Refactor VolumeSource so state is handled with properties, removing auto=True behavior.

If any stateful attribute gets updated, this ensures that the volume is updated appropriately immediately. This also makes it so the transfer function and volume are generated lazily.
Affected #:  4 files

diff -r 415dd9fc1d24a3dc3a8c5cdad8c7aa1a4f0ee682 -r 4a65278f5a64f085410db96e8eaa6222d8a325fe doc/source/cookbook/amrkdtree_downsampling.py
--- a/doc/source/cookbook/amrkdtree_downsampling.py
+++ b/doc/source/cookbook/amrkdtree_downsampling.py
@@ -38,7 +38,7 @@
 # again.
 
 render_source.set_volume(kd_low_res)
-render_source.set_fields('density')
+render_source.set_field('density')
 sc.render()
 sc.save("v1.png", sigma_clip=6.0)
 

diff -r 415dd9fc1d24a3dc3a8c5cdad8c7aa1a4f0ee682 -r 4a65278f5a64f085410db96e8eaa6222d8a325fe yt/visualization/volume_rendering/off_axis_projection.py
--- a/yt/visualization/volume_rendering/off_axis_projection.py
+++ b/yt/visualization/volume_rendering/off_axis_projection.py
@@ -127,10 +127,8 @@
     funits = data_source.ds._get_field_info(item).units
 
     vol = VolumeSource(data_source, item)
-    ptf = ProjectionTransferFunction()
-    vol.set_transfer_function(ptf)
     if weight is None:
-        vol.set_fields([item])
+        vol.set_field(item)
     else:
         # This is a temporary field, which we will remove at the end.
         weightfield = ("index", "temp_weightfield")
@@ -147,7 +145,10 @@
         deps, _ = data_source.ds.field_info.check_derived_fields([weightfield])
         data_source.ds.field_dependencies.update(deps)
         fields = [weightfield, weight]
-        vol.set_fields(fields)
+        vol.set_field(weightfield)
+        vol.set_weight_field(weight)
+    ptf = ProjectionTransferFunction()
+    vol.set_transfer_function(ptf)
     camera = sc.add_camera(data_source)
     camera.set_width(width)
     if not iterable(resolution):
@@ -184,7 +185,9 @@
                 if np.any(np.isnan(data)):
                     raise RuntimeError
 
-    fields = vol.field
+    fields = [vol.field]
+    if vol.weight_field is not None:
+        fields.append(vol.weight_field)
 
     for i, (grid, mask) in enumerate(data_source.blocks):
         data = [(grid[f] * mask).astype("float64") for f in fields]

diff -r 415dd9fc1d24a3dc3a8c5cdad8c7aa1a4f0ee682 -r 4a65278f5a64f085410db96e8eaa6222d8a325fe yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -12,6 +12,7 @@
 # -----------------------------------------------------------------------------
 
 import numpy as np
+from functools import wraps
 from yt.config import \
     ytcfg
 from yt.funcs import mylog, ensure_numpy_array
@@ -44,6 +45,37 @@
     ytcfg["yt", "ray_tracing_engine"] = "yt"
 
 
+def invalidate_volume(f):
+    @wraps(f)
+    def wrapper(*args, **kwargs):
+        ret = f(*args, **kwargs)
+        obj = args[0]
+        if isinstance(obj._transfer_function, ProjectionTransferFunction):
+            obj.sampler_type = 'projection'
+            obj._log_field = False
+            obj._use_ghost_zones = False
+        del obj.volume
+        obj._volume_valid = False
+        return ret
+    return wrapper
+
+def validate_volume(f):
+    @wraps(f)
+    def wrapper(*args, **kwargs):
+        obj = args[0]
+        fields = [obj.field]
+        log_fields = [obj.log_field]
+        if obj.weight_field is not None:
+            fields.append(obj.weight_field)
+            log_fields.append(obj.log_field)
+        if obj._volume_valid is False:
+            obj.volume.set_fields(fields, log_fields,
+                                  no_ghost=(not obj.use_ghost_zones))
+        obj._volume_valid = True
+        return f(*args, **kwargs)
+    return wrapper
+
+
 class RenderSource(ParallelAnalysisInterface):
 
     """Base Class for Render Sources.
@@ -94,9 +126,6 @@
         data object or dataset.
     fields : string
         The name of the field(s) to be rendered.
-    auto: bool, optional
-        If True, will build a default AMRKDTree and transfer function based
-        on the data.
 
     Examples
     --------
@@ -128,28 +157,29 @@
     _image = None
     data_source = None
 
-    def __init__(self, data_source, field, auto=True):
+    def __init__(self, data_source, field):
         r"""Initialize a new volumetric source for rendering."""
         super(VolumeSource, self).__init__()
         self.data_source = data_source_or_all(data_source)
         field = self.data_source._determine_fields(field)[0]
-        self.field = field
-        self.volume = None
         self.current_image = None
         self.check_nans = False
         self.num_threads = 0
         self.num_samples = 10
         self.sampler_type = 'volume-render'
 
-        # Error checking
-        assert(self.field is not None)
-        assert(self.data_source is not None)
+        self._volume_valid = False
+
+        # these are caches for properties, defined below
+        self._volume = None
+        self._transfer_function = None
+        self._field = field
+        self._log_field = self.data_source.ds.field_info[field].take_log
+        self._use_ghost_zones = False
+        self._weight_field = None
 
         self.tfh = TransferFunctionHelper(self.data_source.pf)
         self.tfh.set_field(self.field)
-        self._transfer_function = None
-        if auto:
-            self.build_defaults()
 
     @property
     def transfer_function(self):
@@ -161,7 +191,7 @@
             self._transfer_function = self.tfh.tf
             return self._transfer_function
 
-        mylog.info("Creating default transfer function")
+        mylog.info("Creating transfer function")
         self.tfh.set_field(self.field)
         self.tfh.build_transfer_function()
         self.tfh.setup_default()
@@ -169,22 +199,104 @@
 
         return self._transfer_function
 
-    def build_defaults(self):
-        """Sets a default volume and transfer function"""
-        mylog.info("Creating default volume")
-        self.build_default_volume()
+    @transfer_function.setter
+    def transfer_function(self, value):
+        valid_types = (TransferFunction, ColorTransferFunction,
+                       ProjectionTransferFunction, type(None))
+        if not isinstance(value, valid_types):
+            raise RuntimeError("transfer_function not a valid type, "
+                               "received object of type %s" % type(value))
+        if isinstance(value, ProjectionTransferFunction):
+            self.sampler_type = 'projection'
+            if self._volume is not None:
+                fields = [self.field]
+                if self.weight_field is not None:
+                    fields.append(self.weight_field)
+                self._volume_valid = False
+        self._transfer_function = value
+
+    @property
+    def volume(self):
+        """The abstract volume associated with this VolumeSource
+
+        This object does the heavy lifting to access data in an efficient manner
+        using a KDTree
+        """
+        if self._volume is None:
+            mylog.info("Creating volume")
+            volume = AMRKDTree(self.data_source.ds, data_source=self.data_source)
+            self._volume = volume
+
+        return self._volume
+
+    @volume.setter
+    def volume(self, value):
+        assert(isinstance(value, AMRKDTree))
+        del self._volume
+        self._field = value.fields
+        self._log_field = value.log_fields
+        self._volume = value
+        self._volume_valid is True
+
+    @volume.deleter
+    def volume(self):
+        del self._volume
+        self._volume = None
+
+    @property
+    def field(self):
+        """The field to be rendered"""
+        return self._field
+
+    @field.setter
+    @invalidate_volume
+    def field(self, value):
+        field = self.data_source._determine_fields(value)
+        if len(field) > 1:
+            raise RuntimeError(
+                "VolumeSource.field can only be a single field but received "
+                "multiple fields: %s") % field
+        field = field[0]
+        log_field = self.data_source.ds.field_info[field].take_log
+        self._log_field = log_field
+        self._field = value
+
+    @property
+    def log_field(self):
+        """Whether or not the field rendering is computed in log space"""
+        return self._log_field
+
+    @log_field.setter
+    @invalidate_volume
+    def log_field(self, value):
+        self._log_field = value
+
+    @property
+    def use_ghost_zones(self):
+        """Whether or not ghost zones are used to estimate vertex-centered data
+        values at grid boundaries"""
+        return self._use_ghost_zones
+
+    @use_ghost_zones.setter
+    @invalidate_volume
+    def use_ghost_zones(self, value):
+        self._use_ghost_zones = value
+
+    @property
+    def weight_field(self):
+        """The weight field for the rendering
+
+        Currently this is only used for off-axis projections.
+        """
+        return self._weight_field
+
+    @weight_field.setter
+    @invalidate_volume
+    def weight_field(self, value):
+        self._weight_field = value
 
     def set_transfer_function(self, transfer_function):
         """Set transfer function for this source"""
-        if not isinstance(transfer_function,
-                          (TransferFunction, ColorTransferFunction,
-                           ProjectionTransferFunction)):
-            raise RuntimeError("transfer_function not of correct type")
-        if isinstance(transfer_function, ProjectionTransferFunction):
-            self.sampler_type = 'projection'
-            self.volume.set_fields([self.field], log_fields=[False], 
-                                   no_ghost=True, force=True)
-
         self.transfer_function = transfer_function
         return self
 
@@ -193,44 +305,71 @@
         if self.data_source is None:
             raise RuntimeError("Data source not initialized")
 
-        if self.volume is None:
-            raise RuntimeError("Volume not initialized")
-
-        if self.transfer_function is None:
-            raise RuntimeError("Transfer Function not Supplied")
-
-    def build_default_volume(self):
-        """Sets up an AMRKDTree based on the VolumeSource's field"""
-        self.volume = AMRKDTree(self.data_source.pf,
-                                data_source=self.data_source)
-        log_fields = [self.data_source.pf.field_info[self.field].take_log]
-        mylog.debug('Log Fields:' + str(log_fields))
-        self.volume.set_fields([self.field], log_fields, True)
-
     def set_volume(self, volume):
         """Associates an AMRKDTree with the VolumeSource"""
-        assert(isinstance(volume, AMRKDTree))
-        del self.volume
         self.volume = volume
+        return self
 
-    def set_fields(self, fields, no_ghost=True):
-        """Set the source's fields to render
+    def set_field(self, field):
+        """Set the source's field to render
 
         Parameters
         ----------
 
-        fields: field name or list of field names
-            The field or fields to render
-        no_ghost: boolean
-            If False, the AMRKDTree estimates vertex centered data using ghost
+        field: field name
+            The field to render
+        """
+        self.field = field
+        return self
+
+    def set_log(self, log_field):
+        """Set whether the rendering of the source's field is done in log space
+
+        Generally volume renderings of data whose values span a large dynamic
+        range should be done on log space and volume renderings of data with
+        small dynamic range should be done in linear space.
+
+        Parameters
+        ----------
+
+        log_field: boolean
+            If True, the volume rendering will be done in log space, and if False
+            will be done in linear space.
+        """
+        self.log_field = log_field
+        return self
+
+    def set_weight_field(self, weight_field):
+        """Set the source's weight field
+
+        .. note::
+
+          This is currently only used for renderings using the
+          ProjectionTransferFunction
+
+        Parameters
+        ----------
+
+        weight_field: field name
+            The weight field to use in the rendering
+        """
+        self.weight_field = weight_field
+        return self
+
+    def set_use_ghost_zones(self, use_ghost_zones):
+        """Set whether or not interpolation at grid edges uses ghost zones
+
+        Parameters
+        ----------
+
+        use_ghost_zones: boolean
+            If True, the AMRKDTree estimates vertex centered data using ghost
             zones, which can eliminate seams in the resulting volume rendering.
-            Defaults to True for performance reasons.
+            Defaults to False for performance reasons.
+
         """
-        fields = self.data_source._determine_fields(fields)
-        log_fields = [self.data_source.ds.field_info[f].take_log
-                      for f in fields]
-        self.volume.set_fields(fields, log_fields, no_ghost)
-        self.field = fields
+        self.use_ghost_zones = use_ghost_zones
+        return self
 
     def set_sampler(self, camera, interpolated=True):
         """Sets a volume render sampler
@@ -240,10 +379,10 @@
         sampler types are supported.
 
         The 'interpolated' argument is only meaningful for projections. If True,
-        the data is first interpolated to the cell vertices, and then tri-linearly
-        interpolated to the ray sampling positions. If False, then the cell-centered
-        data is simply accumulated along the ray. Interpolation is always performed
-        for volume renderings.
+        the data is first interpolated to the cell vertices, and then
+        tri-linearly interpolated to the ray sampling positions. If False, then
+        the cell-centered data is simply accumulated along the
+        ray. Interpolation is always performed for volume renderings.
 
         """
         if self.sampler_type == 'volume-render':
@@ -257,6 +396,7 @@
         self.sampler = sampler
         assert(self.sampler is not None)
 
+    @validate_volume
     def render(self, camera, zbuffer=None):
         """Renders an image using the provided camera
 
@@ -327,7 +467,7 @@
 
     def __repr__(self):
         disp = "<Volume Source>:%s " % str(self.data_source)
-        disp += "transfer_function:%s" % str(self.transfer_function)
+        disp += "transfer_function:%s" % str(self._transfer_function)
         return disp
 
 

diff -r 415dd9fc1d24a3dc3a8c5cdad8c7aa1a4f0ee682 -r 4a65278f5a64f085410db96e8eaa6222d8a325fe yt/visualization/volume_rendering/tests/test_varia.py
--- a/yt/visualization/volume_rendering/tests/test_varia.py
+++ b/yt/visualization/volume_rendering/tests/test_varia.py
@@ -62,7 +62,8 @@
         im, sc = yt.volume_render(self.ds)
 
         volume_source = sc.get_source(0)
-        volume_source.set_fields([('gas', 'velocity_x'), ('gas', 'density')])
+        volume_source.set_field(('gas', 'velocity_x'))
+        volume_source.set_weight_field(('gas', 'density'))
         sc.render()
 
     def test_rotation_volume_rendering(self):


https://bitbucket.org/yt_analysis/yt/commits/b2ed8e636d03/
Changeset:   b2ed8e636d03
Branch:      yt
User:        ngoldbaum
Date:        2016-07-08 22:07:33+00:00
Summary:     Optimize off-axis projections, since they don't need a KDTree
Affected #:  2 files

diff -r 4a65278f5a64f085410db96e8eaa6222d8a325fe -r b2ed8e636d038f2a5938259fe6cfa2f002c63ef1 yt/visualization/volume_rendering/off_axis_projection.py
--- a/yt/visualization/volume_rendering/off_axis_projection.py
+++ b/yt/visualization/volume_rendering/off_axis_projection.py
@@ -177,18 +177,12 @@
     vol.set_sampler(camera, interpolated=False)
     assert (vol.sampler is not None)
 
-    mylog.debug("Casting rays")
-    double_check = False
-    if double_check:
-        for brick in vol.volume.bricks:
-            for data in brick.my_data:
-                if np.any(np.isnan(data)):
-                    raise RuntimeError
-
     fields = [vol.field]
     if vol.weight_field is not None:
         fields.append(vol.weight_field)
 
+    mylog.debug("Casting rays")
+
     for i, (grid, mask) in enumerate(data_source.blocks):
         data = [(grid[f] * mask).astype("float64") for f in fields]
         pg = PartitionedGrid(

diff -r 4a65278f5a64f085410db96e8eaa6222d8a325fe -r b2ed8e636d038f2a5938259fe6cfa2f002c63ef1 yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -455,7 +455,8 @@
             Whether or not this is being called from a higher level in the VR
             interface. Used to set the correct orientation.
         """
-        image = self.volume.reduce_tree_images(image, camera.lens.viewpoint)
+        if self._volume is not None:
+            image = self.volume.reduce_tree_images(image, camera.lens.viewpoint)
         image.shape = camera.resolution[0], camera.resolution[1], 4
         # If the call is from VR, the image is rotated by 180 to get correct
         # up direction


https://bitbucket.org/yt_analysis/yt/commits/cd7248fb50ac/
Changeset:   cd7248fb50ac
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 01:58:54+00:00
Summary:     Invalidate the source's transfer function if the field or log scale changes
Affected #:  1 file

diff -r b2ed8e636d038f2a5938259fe6cfa2f002c63ef1 -r cd7248fb50ac9d70c41e4e8b7da3ec8527907bae yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -193,6 +193,7 @@
 
         mylog.info("Creating transfer function")
         self.tfh.set_field(self.field)
+        self.tfh.set_log(self.log_field)
         self.tfh.build_transfer_function()
         self.tfh.setup_default()
         self._transfer_function = self.tfh.tf
@@ -201,6 +202,7 @@
 
     @transfer_function.setter
     def transfer_function(self, value):
+        self.tfh.tf = None
         valid_types = (TransferFunction, ColorTransferFunction,
                        ProjectionTransferFunction, type(None))
         if not isinstance(value, valid_types):
@@ -257,9 +259,16 @@
                 "VolumeSource.field can only be a single field but received "
                 "multiple fields: %s") % field
         field = field[0]
-        log_field = self.data_source.ds.field_info[field].take_log
+        if self._field != field:
+            log_field = self.data_source.ds.field_info[field].take_log
+            self.tfh.bounds = None
+        else:
+            log_field = self._log_field
         self._log_field = log_field
         self._field = value
+        self.transfer_function = None
+        self.tfh.set_field(value)
+        self.tfh.set_log(log_field)
 
     @property
     def log_field(self):
@@ -269,6 +278,8 @@
     @log_field.setter
     @invalidate_volume
     def log_field(self, value):
+        self.transfer_function = None
+        self.tfh.set_log(value)
         self._log_field = value
 
     @property


https://bitbucket.org/yt_analysis/yt/commits/41a879b3d7ed/
Changeset:   41a879b3d7ed
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 02:29:46+00:00
Summary:     Add a test for the lazy construction of VolumeSource data
Affected #:  1 file

diff -r cd7248fb50ac9d70c41e4e8b7da3ec8527907bae -r 41a879b3d7eddf709de870e32f2d217dfbb19cda yt/visualization/volume_rendering/tests/test_varia.py
--- a/yt/visualization/volume_rendering/tests/test_varia.py
+++ b/yt/visualization/volume_rendering/tests/test_varia.py
@@ -18,6 +18,8 @@
 import yt
 from yt.testing import \
     fake_random_ds
+from yt.visualization.volume_rendering.render_source import VolumeSource
+from yt.visualization.volume_rendering.scene import Scene
 from unittest import TestCase
 
 def setup():
@@ -77,3 +79,70 @@
 
     def test_simple_volume_rendering(self):
         im, sc = yt.volume_render(self.ds, sigma_clip=4.0)
+
+    def test_lazy_volume_source_construction(self):
+        sc = Scene()
+        source = VolumeSource(self.ds.all_data(), 'density')
+
+        assert source._volume is None
+        assert source._transfer_function is None
+
+        source.tfh.bounds = (0.1, 1)
+
+        source.set_log(False)
+
+        assert source.log_field is False
+        assert source.transfer_function.x_bounds == [0.1, 1]
+        assert source._volume is None
+
+        source.set_log(True)
+
+        assert source.log_field is True
+        assert source.transfer_function.x_bounds == [-1, 0]
+        assert source._volume is None
+
+        source.transfer_function = None
+        source.tfh.bounds = None
+
+        ad = self.ds.all_data()
+
+        assert source.transfer_function.x_bounds == \
+            list(np.log10(ad.quantities.extrema('density')))
+        assert source.tfh.log == source.log_field
+
+        source.set_field('velocity_x')
+        source.set_log(False)
+
+        assert source.transfer_function.x_bounds == \
+            list(ad.quantities.extrema('velocity_x'))
+        assert source._volume is None
+
+        source.set_field('density')
+
+        assert source.volume is not None
+        assert source.volume._initialized is False
+        assert source.volume.fields is None
+
+        del source.volume
+        assert source._volume is None
+
+        sc.add_source(source)
+
+        sc.add_camera()
+
+        sc.render()
+
+        assert source.volume is not None
+        assert source.volume._initialized is True
+        assert source.volume.fields == [('gas', 'density')]
+        assert source.volume.log_fields == [True]
+
+        source.set_field('velocity_x')
+        source.set_log(False)
+
+        sc.render()
+
+        assert source.volume is not None
+        assert source.volume._initialized is True
+        assert source.volume.fields == [('gas', 'velocity_x')]
+        assert source.volume.log_fields == [False]


https://bitbucket.org/yt_analysis/yt/commits/52f618e909aa/
Changeset:   52f618e909aa
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 21:16:39+00:00
Summary:     Fix typo in map_to_colormap docstrings
Affected #:  1 file

diff -r 41a879b3d7eddf709de870e32f2d217dfbb19cda -r 52f618e909aa63af05dbf8940a6f1f8f2bd4d02a yt/visualization/volume_rendering/transfer_functions.py
--- a/yt/visualization/volume_rendering/transfer_functions.py
+++ b/yt/visualization/volume_rendering/transfer_functions.py
@@ -708,7 +708,7 @@
         --------
 
         >>> def linramp(vals, minval, maxval):
-        ...     return (vals - vals.min())/(vals.(max) - vals.min())
+        ...     return (vals - vals.min())/(vals.max() - vals.min())
         >>> tf = ColorTransferFunction( (-10.0, -5.0) )
         >>> tf.map_to_colormap(-8.0, -6.0, scale=10.0, colormap='algae')
         >>> tf.map_to_colormap(-6.0, -5.0, scale=10.0, colormap='algae',


https://bitbucket.org/yt_analysis/yt/commits/cb432a37b94f/
Changeset:   cb432a37b94f
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 21:17:25+00:00
Summary:     Expand docstring for TransferFunctionHelper.setup_default
Affected #:  1 file

diff -r 52f618e909aa63af05dbf8940a6f1f8f2bd4d02a -r cb432a37b94fad73526936ffa1da953e793ca685 yt/visualization/volume_rendering/transfer_function_helper.py
--- a/yt/visualization/volume_rendering/transfer_function_helper.py
+++ b/yt/visualization/volume_rendering/transfer_function_helper.py
@@ -131,11 +131,13 @@
         return self.tf
 
     def setup_default(self):
-        """docstring for setup_default"""
-        if self.log:
-            mi, ma = np.log10(self.bounds[0]), np.log10(self.bounds[1])
-        else:
-            mi, ma = self.bounds
+        """Setup a default colormap
+
+        Creates a ColorTransferFunction including 10 gaussian layers whose
+        colors smaple the 'spectral' colormap. Also attempts to scale the
+        transfer function to produce a natural contrast ratio.
+
+        """
         self.tf.add_layers(10, colormap='spectral')
         factor = self.tf.funcs[-1].y.size / self.tf.funcs[-1].y.sum()
         self.tf.funcs[-1].y *= 2*factor


https://bitbucket.org/yt_analysis/yt/commits/941ff485ccee/
Changeset:   941ff485ccee
Branch:      yt
User:        ngoldbaum
Date:        2016-07-11 21:17:49+00:00
Summary:     Ensure transfer function generated by TransferFunctionHelper.plot() is valid
Affected #:  1 file

diff -r cb432a37b94fad73526936ffa1da953e793ca685 -r 941ff485cceed17566e24800cf259cb88324a163 yt/visualization/volume_rendering/transfer_function_helper.py
--- a/yt/visualization/volume_rendering/transfer_function_helper.py
+++ b/yt/visualization/volume_rendering/transfer_function_helper.py
@@ -163,6 +163,7 @@
         from matplotlib.figure import Figure
         if self.tf is None:
             self.build_transfer_function()
+            self.setup_default()
         tf = self.tf
         if self.log:
             xfunc = np.logspace


https://bitbucket.org/yt_analysis/yt/commits/b7073ff9763a/
Changeset:   b7073ff9763a
Branch:      yt
User:        ngoldbaum
Date:        2016-07-12 01:06:25+00:00
Summary:     Expand documentation for volume rendering with more worked examples
Affected #:  1 file

diff -r 941ff485cceed17566e24800cf259cb88324a163 -r b7073ff9763ab99d7e7b0667076e641ab434d941 doc/source/visualizing/volume_rendering.rst
--- a/doc/source/visualizing/volume_rendering.rst
+++ b/doc/source/visualizing/volume_rendering.rst
@@ -13,6 +13,11 @@
 hardware. Optimized versions implemented with OpenGL and utilizing graphics
 processors are being actively developed.
 
+.. note::
+
+   There is a Jupyter notebook containing a volume rendering tutorial available
+   at :ref:`volume-rendering-tutorial`.
+
 Volume Rendering Introduction
 -----------------------------
 
@@ -166,8 +171,70 @@
 Because good transfer functions can be difficult to generate, the
 :class:`~yt.visualization.volume_rendering.transfer_function_helper.TransferFunctionHelper`
 exists in order to help create and modify transfer functions with smart
-defaults for your datasets.  To see a full example on how to use this
-interface, follow the annotated :ref:`transfer-function-helper-tutorial`.
+defaults for your datasets.
+
+To ease constructing transfer functions, each ``VolumeSource`` instance has a
+``TransferFunctionHelper`` instance associated with it. This is the easiest way
+to construct and customize a ``ColorTransferFunction`` for a volume rendering.
+
+In the following example, we make use of the ``TransferFunctionHelper``
+associated with a scene's ``VolumeSource`` to create an appealing transfer
+function between a physically motivated range of densities in a cosmological
+simulation:
+
+.. python-script::
+
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   # Get a reference to the VolumeSource associated with this scene
+   # It is the first source associated with the scene, so we can refer to it
+   # using index 0.
+   source = sc[0]
+
+   # Set the bounds of the transfer function
+   source.tfh.set_bounds((3e-31, 5e-27))
+
+   # set that the transfer function should be evaluated in log space
+   source.tfh.set_log(True)
+
+   # Make underdense regions appear opaque
+   source.tfh.grey_opacity = True
+
+   # Plot the transfer function, along with the CDF of the density field to
+   # see how the transfer function corresponds to structure in the CDF
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   # save the image, flooring especially bright pixels for better contrast
+   sc.save('rendering.png', sigma_clip=6.0)
+
+For fun, let's make the same volume_rendering, but this time setting
+``grey_opacity=False``, which will make overdense regions stand out more:
+
+.. python-script::
+
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   # Set transfer function properties
+   source.tfh.set_bounds((3e-31, 5e-27))
+   source.tfh.set_log(True)
+   source.tfh.grey_opacity = False
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=4.0)
+
+To see a full example on how to use the ``TransferFunctionHelper`` interface,
+follow the annotated :ref:`transfer-function-helper-tutorial`.
 
 Color Transfer Functions
 ++++++++++++++++++++++++
@@ -177,26 +244,168 @@
 and opacities in the rendered rays.  One can add discrete features to the
 transfer function, which will render isocontours in the field data and
 works well for visualizing nested structures in a simulation.  Alternatively,
-one can add continuous features to the transfer function, which tends to
-produce better results for most datasets.
+one can also add continuous features to the transfer function.
 
-In order to modify a
+See :ref:`cookbook-custom-transfer-function` for an annotated, runnable tutorial
+explaining usage of the ColorTransferFunction.
+
+There are several methods to create a
 :class:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction`
-use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_layers`,
-which will add evenly spaced isocontours along the transfer
-function; use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.sample_colormap`,
-which will sample a colormap at a given value;
-use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_gaussian`,
-which will allow you to specify the colors directly on the transfer function,
-and use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.map_to_colormap`,
-where you can map a segment of the transfer function space to an entire
-colormap at a single alpha value.
+for a volume rendering. We will describe the low-level interface for
+constructing color transfer functions here, and provide examples for each
+option.
 
-See :ref:`cookbook-custom-transfer-function` for an example usage.
+add_layers
+""""""""""
+
+The easiest way to create a ColorTransferFunction is to use the
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_layers` function,
+which will add evenly spaced isocontours along the transfer function, sampling a
+colormap to determine the colors of the layers.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   tf.add_layers(5, colormap='arbre')
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)
+
+sample_colormap
+"""""""""""""""
+
+To add a single gaussian layer with a color determined by a colormap value, use
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.sample_colormap`.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   tf.sample_colormap(np.log10(1e-30), w=.01, colormap='arbre')
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)
+   
+
+add_gaussian
+""""""""""""
+
+If you would like to add a gaussian with a customized color or no color, use
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_gaussian`.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   tf.add_gaussian(np.log10(1e-29), width=.005, height=[0.753, 1.0, 0.933, 1.0])
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)
+
+
+map_to_colormap
+"""""""""""""""
+
+Finally, to map a colormap directly to a range in densities use
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.map_to_colormap`. This
+makes it possible to map a segment of the transfer function space to a colormap
+at a single alpha value. Where the above options produced layered volume
+renderings, this allows all of the density values in a dataset to contribute to
+the volume rendering.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   def linramp(vals, minval, maxval):
+       return (vals - vals.min())/(vals.max() - vals.min())
+
+   tf.map_to_colormap(np.log10(3e-31), np.log10(5e-27), colormap='arbre', 
+                      scale_func=linramp)
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)   
 
 Projection Transfer Function
 ++++++++++++++++++++++++++++
@@ -351,8 +560,8 @@
              information can be hard.  We've provided information about best
              practices and tried to make the interface easy to develop nice
              visualizations, but getting them *just right* is often
-             time-consuming.  It's usually best to start out simple with the
-             built-in helper interface, and expand on that as you need.
+             time-consuming.  It's usually best to start out simple and expand 
+             and tweak as needed.
 
 The scene interface provides a modular interface for creating renderings
 of arbitrary data sources. As such, manual composition of a scene can require
@@ -412,6 +621,12 @@
   # sc is an instance of a Scene object, which allows you to further refine
   # your renderings and later save them.
 
+  # Let's zoom in and take a closer look
+  sc.camera.width = (300, 'kpc')
+  sc.camera.switch_orientation()
+
+  # Save the zoomed in rendering
+  sc.save('zoomed_rendering.png')
 
 Alternatively, if you don't want to immediately generate an image of your
 volume rendering, and you just want access to the default scene object,
@@ -423,12 +638,39 @@
 
 .. python-script::
 
-  import yt
-  ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
-  sc = yt.create_scene(ds, 'density')
+    import numpy as np
+    import yt
+  
 
+    ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
+    sc = yt.create_scene(ds, 'density')
 
-For a more in-depth tutorial on how to create a Scene and modify its contents,
+    source = sc[0]
+
+    source.transfer_function = yt.ColorTransferFunction(
+        np.log10((1e-30, 1e-23)), grey_opacity=True)
+
+    def linramp(vals, minval, maxval):
+        return (vals - vals.min())/(vals.max() - vals.min())
+
+    source.transfer_function.map_to_colormap(
+        np.log10(1e-25), np.log10(8e-24), colormap='arbre', scale_func=linramp)
+
+    # For this low resolution dataset it's very important to use interpolated
+    # vertex centered data to avoid artifacts. For high resolution data this
+    # setting may cause a substantial slowdown for marginal visual improvement.
+    source.set_use_ghost_zones(True)
+
+    cam = sc.camera
+
+    cam.width = 15*yt.units.kpc
+    cam.focus = ds.domain_center
+    cam.normal_vector = [-0.3, -0.3, 1]
+    cam.switch_orientation()
+
+    sc.save('rendering.png')
+
+For an in-depth tutorial on how to create a Scene and modify its contents,
 see this annotated :ref:`volume-rendering-tutorial`.
 
 
@@ -574,14 +816,14 @@
 ^^^^^^^
 
 There are currently two models for opacity when rendering a volume, which are
-controlled in the ColorTransferFunction with the keyword
-grey_opacity=False(default)/True. The first (default) will act such for each of
-the r,g,b channels, each channel is only opaque to itself.  This means that if
-a ray that has some amount of red then encounters material that emits blue, the
-red will still exist and in the end that pixel will be a combination of blue
-and red.  However, if the ColorTransferFunction is set up with
-grey_opacity=True, then blue will be opaque to red, and only the blue emission
-will remain.
+controlled in the ``ColorTransferFunction`` with the keyword
+``grey_opacity=False`` or ``True`` (the default). The first will act such for
+each of the red, green, and blue channels, each channel is only opaque to
+itself.  This means that if a ray that has some amount of red then encounters
+material that emits blue, the red will still exist and in the end that pixel
+will be a combination of blue and red.  However, if the ColorTransferFunction is
+set up with grey_opacity=True, then blue will be opaque to red, and only the
+blue emission will remain.
 
 For an in-depth example, please see the cookbook example on opaque renders here:
 :ref:`cookbook-opaque_rendering`.


https://bitbucket.org/yt_analysis/yt/commits/be97d4b4bfee/
Changeset:   be97d4b4bfee
Branch:      yt
User:        ngoldbaum
Date:        2016-07-12 01:06:32+00:00
Summary:     Removing the _last_render caching from scene.render() and scene.save_annotated()
Affected #:  1 file

diff -r b7073ff9763ab99d7e7b0667076e641ab434d941 -r be97d4b4bfee0264f787609c44029c0ce5bba1ca yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -292,8 +292,7 @@
             suffix = '.png'
             fname = '%s%s' % (fname, suffix)
 
-        if self._last_render is None:
-            self.render()
+        self.render()
 
         mylog.info("Saving render %s", fname)
         self._last_render.write_png(fname, sigma_clip=sigma_clip)
@@ -380,8 +379,7 @@
             suffix = '.png'
             fname = '%s%s' % (fname, suffix)
 
-        if self._last_render is None:
-            self.render()
+        self.render()
 
         # which transfer function?
         rs = rensources[0]


https://bitbucket.org/yt_analysis/yt/commits/3eeedb1db582/
Changeset:   3eeedb1db582
Branch:      yt
User:        ngoldbaum
Date:        2016-07-12 05:32:39+00:00
Summary:     Remove reference to build_default_transfer_function, which is no longer necessary
Affected #:  1 file

diff -r be97d4b4bfee0264f787609c44029c0ce5bba1ca -r 3eeedb1db5824f655513fe69809df675eb616202 doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
--- a/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
+++ b/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
@@ -257,10 +257,6 @@
     "sc.camera.set_position(ds.domain_left_edge)\n",
     "sc.camera.switch_orientation()\n",
     "\n",
-    "# reset the transfer function to the default\n",
-    "render_source = sc.get_source(0)\n",
-    "render_source.build_default_transfer_function()\n",
-    "\n",
     "# add an opaque source to the scene\n",
     "sc.annotate_axes()\n",
     "\n",


https://bitbucket.org/yt_analysis/yt/commits/e7aabcd61165/
Changeset:   e7aabcd61165
Branch:      yt
User:        xarthisius
Date:        2016-07-16 16:25:00+00:00
Summary:     Merged in ngoldbaum/yt (pull request #2266)

Volume Source refactor
Affected #:  12 files

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 doc/source/cookbook/amrkdtree_downsampling.py
--- a/doc/source/cookbook/amrkdtree_downsampling.py
+++ b/doc/source/cookbook/amrkdtree_downsampling.py
@@ -38,7 +38,7 @@
 # again.
 
 render_source.set_volume(kd_low_res)
-render_source.set_fields('density')
+render_source.set_field('density')
 sc.render()
 sc.save("v1.png", sigma_clip=6.0)
 

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
--- a/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
+++ b/doc/source/visualizing/Volume_Rendering_Tutorial.ipynb
@@ -257,10 +257,6 @@
     "sc.camera.set_position(ds.domain_left_edge)\n",
     "sc.camera.switch_orientation()\n",
     "\n",
-    "# reset the transfer function to the default\n",
-    "render_source = sc.get_source(0)\n",
-    "render_source.build_default_transfer_function()\n",
-    "\n",
     "# add an opaque source to the scene\n",
     "sc.annotate_axes()\n",
     "\n",

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 doc/source/visualizing/volume_rendering.rst
--- a/doc/source/visualizing/volume_rendering.rst
+++ b/doc/source/visualizing/volume_rendering.rst
@@ -13,6 +13,11 @@
 hardware. Optimized versions implemented with OpenGL and utilizing graphics
 processors are being actively developed.
 
+.. note::
+
+   There is a Jupyter notebook containing a volume rendering tutorial available
+   at :ref:`volume-rendering-tutorial`.
+
 Volume Rendering Introduction
 -----------------------------
 
@@ -166,8 +171,70 @@
 Because good transfer functions can be difficult to generate, the
 :class:`~yt.visualization.volume_rendering.transfer_function_helper.TransferFunctionHelper`
 exists in order to help create and modify transfer functions with smart
-defaults for your datasets.  To see a full example on how to use this
-interface, follow the annotated :ref:`transfer-function-helper-tutorial`.
+defaults for your datasets.
+
+To ease constructing transfer functions, each ``VolumeSource`` instance has a
+``TransferFunctionHelper`` instance associated with it. This is the easiest way
+to construct and customize a ``ColorTransferFunction`` for a volume rendering.
+
+In the following example, we make use of the ``TransferFunctionHelper``
+associated with a scene's ``VolumeSource`` to create an appealing transfer
+function between a physically motivated range of densities in a cosmological
+simulation:
+
+.. python-script::
+
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   # Get a reference to the VolumeSource associated with this scene
+   # It is the first source associated with the scene, so we can refer to it
+   # using index 0.
+   source = sc[0]
+
+   # Set the bounds of the transfer function
+   source.tfh.set_bounds((3e-31, 5e-27))
+
+   # set that the transfer function should be evaluated in log space
+   source.tfh.set_log(True)
+
+   # Make underdense regions appear opaque
+   source.tfh.grey_opacity = True
+
+   # Plot the transfer function, along with the CDF of the density field to
+   # see how the transfer function corresponds to structure in the CDF
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   # save the image, flooring especially bright pixels for better contrast
+   sc.save('rendering.png', sigma_clip=6.0)
+
+For fun, let's make the same volume_rendering, but this time setting
+``grey_opacity=False``, which will make overdense regions stand out more:
+
+.. python-script::
+
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   # Set transfer function properties
+   source.tfh.set_bounds((3e-31, 5e-27))
+   source.tfh.set_log(True)
+   source.tfh.grey_opacity = False
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=4.0)
+
+To see a full example on how to use the ``TransferFunctionHelper`` interface,
+follow the annotated :ref:`transfer-function-helper-tutorial`.
 
 Color Transfer Functions
 ++++++++++++++++++++++++
@@ -177,26 +244,168 @@
 and opacities in the rendered rays.  One can add discrete features to the
 transfer function, which will render isocontours in the field data and
 works well for visualizing nested structures in a simulation.  Alternatively,
-one can add continuous features to the transfer function, which tends to
-produce better results for most datasets.
+one can also add continuous features to the transfer function.
 
-In order to modify a
+See :ref:`cookbook-custom-transfer-function` for an annotated, runnable tutorial
+explaining usage of the ColorTransferFunction.
+
+There are several methods to create a
 :class:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction`
-use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_layers`,
-which will add evenly spaced isocontours along the transfer
-function; use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.sample_colormap`,
-which will sample a colormap at a given value;
-use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_gaussian`,
-which will allow you to specify the colors directly on the transfer function,
-and use
-:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.map_to_colormap`,
-where you can map a segment of the transfer function space to an entire
-colormap at a single alpha value.
+for a volume rendering. We will describe the low-level interface for
+constructing color transfer functions here, and provide examples for each
+option.
 
-See :ref:`cookbook-custom-transfer-function` for an example usage.
+add_layers
+""""""""""
+
+The easiest way to create a ColorTransferFunction is to use the
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_layers` function,
+which will add evenly spaced isocontours along the transfer function, sampling a
+colormap to determine the colors of the layers.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   tf.add_layers(5, colormap='arbre')
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)
+
+sample_colormap
+"""""""""""""""
+
+To add a single gaussian layer with a color determined by a colormap value, use
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.sample_colormap`.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   tf.sample_colormap(np.log10(1e-30), w=.01, colormap='arbre')
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)
+   
+
+add_gaussian
+""""""""""""
+
+If you would like to add a gaussian with a customized color or no color, use
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_gaussian`.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   tf.add_gaussian(np.log10(1e-29), width=.005, height=[0.753, 1.0, 0.933, 1.0])
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)
+
+
+map_to_colormap
+"""""""""""""""
+
+Finally, to map a colormap directly to a range in densities use
+:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.map_to_colormap`. This
+makes it possible to map a segment of the transfer function space to a colormap
+at a single alpha value. Where the above options produced layered volume
+renderings, this allows all of the density values in a dataset to contribute to
+the volume rendering.
+
+.. python-script::
+
+   import numpy as np
+   import yt
+
+   ds = yt.load('Enzo_64/DD0043/data0043')
+
+   sc = yt.create_scene(ds, lens_type='perspective')
+
+   source = sc[0]
+
+   source.set_field('density')
+   source.set_log(True)
+
+   bounds = (3e-31, 5e-27)
+
+   # Since this rendering is done in log space, the transfer function needs
+   # to be specified in log space.
+   tf = yt.ColorTransferFunction(np.log10(bounds))
+
+   def linramp(vals, minval, maxval):
+       return (vals - vals.min())/(vals.max() - vals.min())
+
+   tf.map_to_colormap(np.log10(3e-31), np.log10(5e-27), colormap='arbre', 
+                      scale_func=linramp)
+
+   source.tfh.tf = tf
+   source.tfh.bounds = bounds
+
+   source.tfh.plot('transfer_function.png', profile_field='density')
+
+   sc.save('rendering.png', sigma_clip=6)   
 
 Projection Transfer Function
 ++++++++++++++++++++++++++++
@@ -351,8 +560,8 @@
              information can be hard.  We've provided information about best
              practices and tried to make the interface easy to develop nice
              visualizations, but getting them *just right* is often
-             time-consuming.  It's usually best to start out simple with the
-             built-in helper interface, and expand on that as you need.
+             time-consuming.  It's usually best to start out simple and expand 
+             and tweak as needed.
 
 The scene interface provides a modular interface for creating renderings
 of arbitrary data sources. As such, manual composition of a scene can require
@@ -412,6 +621,12 @@
   # sc is an instance of a Scene object, which allows you to further refine
   # your renderings and later save them.
 
+  # Let's zoom in and take a closer look
+  sc.camera.width = (300, 'kpc')
+  sc.camera.switch_orientation()
+
+  # Save the zoomed in rendering
+  sc.save('zoomed_rendering.png')
 
 Alternatively, if you don't want to immediately generate an image of your
 volume rendering, and you just want access to the default scene object,
@@ -423,12 +638,39 @@
 
 .. python-script::
 
-  import yt
-  ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
-  sc = yt.create_scene(ds, 'density')
+    import numpy as np
+    import yt
+  
 
+    ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
+    sc = yt.create_scene(ds, 'density')
 
-For a more in-depth tutorial on how to create a Scene and modify its contents,
+    source = sc[0]
+
+    source.transfer_function = yt.ColorTransferFunction(
+        np.log10((1e-30, 1e-23)), grey_opacity=True)
+
+    def linramp(vals, minval, maxval):
+        return (vals - vals.min())/(vals.max() - vals.min())
+
+    source.transfer_function.map_to_colormap(
+        np.log10(1e-25), np.log10(8e-24), colormap='arbre', scale_func=linramp)
+
+    # For this low resolution dataset it's very important to use interpolated
+    # vertex centered data to avoid artifacts. For high resolution data this
+    # setting may cause a substantial slowdown for marginal visual improvement.
+    source.set_use_ghost_zones(True)
+
+    cam = sc.camera
+
+    cam.width = 15*yt.units.kpc
+    cam.focus = ds.domain_center
+    cam.normal_vector = [-0.3, -0.3, 1]
+    cam.switch_orientation()
+
+    sc.save('rendering.png')
+
+For an in-depth tutorial on how to create a Scene and modify its contents,
 see this annotated :ref:`volume-rendering-tutorial`.
 
 
@@ -574,14 +816,14 @@
 ^^^^^^^
 
 There are currently two models for opacity when rendering a volume, which are
-controlled in the ColorTransferFunction with the keyword
-grey_opacity=False(default)/True. The first (default) will act such for each of
-the r,g,b channels, each channel is only opaque to itself.  This means that if
-a ray that has some amount of red then encounters material that emits blue, the
-red will still exist and in the end that pixel will be a combination of blue
-and red.  However, if the ColorTransferFunction is set up with
-grey_opacity=True, then blue will be opaque to red, and only the blue emission
-will remain.
+controlled in the ``ColorTransferFunction`` with the keyword
+``grey_opacity=False`` or ``True`` (the default). The first will act such for
+each of the red, green, and blue channels, each channel is only opaque to
+itself.  This means that if a ray that has some amount of red then encounters
+material that emits blue, the red will still exist and in the end that pixel
+will be a combination of blue and red.  However, if the ColorTransferFunction is
+set up with grey_opacity=True, then blue will be opaque to red, and only the
+blue emission will remain.
 
 For an in-depth example, please see the cookbook example on opaque renders here:
 :ref:`cookbook-opaque_rendering`.

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/utilities/amr_kdtree/amr_kdtree.py
--- a/yt/utilities/amr_kdtree/amr_kdtree.py
+++ b/yt/utilities/amr_kdtree/amr_kdtree.py
@@ -17,7 +17,9 @@
 import operator
 import numpy as np
 
-from yt.funcs import mylog
+from yt.funcs import \
+    iterable, \
+    mylog
 from yt.utilities.on_demand_imports import _h5py as h5py
 from yt.utilities.amr_kdtree.amr_kdtools import \
     receive_and_reduce, \
@@ -174,14 +176,17 @@
         regenerate_data = self.fields is None or \
                           len(self.fields) != len(new_fields) or \
                           self.fields != new_fields or force
+        if not iterable(log_fields):
+            log_fields = [log_fields]
+        new_log_fields = list(log_fields)
         self.tree.trunk.set_dirty(regenerate_data)
         self.fields = new_fields
 
         if self.log_fields is not None and not regenerate_data:
-            flip_log = list(map(operator.ne, self.log_fields, log_fields))
+            flip_log = list(map(operator.ne, self.log_fields, new_log_fields))
         else:
-            flip_log = [False] * len(log_fields)
-        self.log_fields = log_fields
+            flip_log = [False] * len(new_log_fields)
+        self.log_fields = new_log_fields
 
         self.no_ghost = no_ghost
         del self.bricks, self.brick_dimensions
@@ -189,7 +194,7 @@
         bricks = []
 
         for b in self.traverse():
-            list(map(_apply_log, b.my_data, flip_log, log_fields))
+            list(map(_apply_log, b.my_data, flip_log, self.log_fields))
             bricks.append(b)
         self.bricks = np.array(bricks)
         self.brick_dimensions = np.array(self.brick_dimensions)

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -1533,6 +1533,8 @@
         self.fields = fields
         self.interpolated = interpolated
         self.resolution = resolution
+        if weight is not None:
+            weight = self.dd._determine_fields(weight)[0]
         self.weight_field = weight
         self.volume = volume
         self.no_ghost = no_ghost

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/off_axis_projection.py
--- a/yt/visualization/volume_rendering/off_axis_projection.py
+++ b/yt/visualization/volume_rendering/off_axis_projection.py
@@ -127,10 +127,8 @@
     funits = data_source.ds._get_field_info(item).units
 
     vol = VolumeSource(data_source, item)
-    ptf = ProjectionTransferFunction()
-    vol.set_transfer_function(ptf)
     if weight is None:
-        vol.set_fields([item])
+        vol.set_field(item)
     else:
         # This is a temporary field, which we will remove at the end.
         weightfield = ("index", "temp_weightfield")
@@ -147,7 +145,10 @@
         deps, _ = data_source.ds.field_info.check_derived_fields([weightfield])
         data_source.ds.field_dependencies.update(deps)
         fields = [weightfield, weight]
-        vol.set_fields(fields)
+        vol.set_field(weightfield)
+        vol.set_weight_field(weight)
+    ptf = ProjectionTransferFunction()
+    vol.set_transfer_function(ptf)
     camera = sc.add_camera(data_source)
     camera.set_width(width)
     if not iterable(resolution):
@@ -176,15 +177,11 @@
     vol.set_sampler(camera, interpolated=False)
     assert (vol.sampler is not None)
 
+    fields = [vol.field]
+    if vol.weight_field is not None:
+        fields.append(vol.weight_field)
+
     mylog.debug("Casting rays")
-    double_check = False
-    if double_check:
-        for brick in vol.volume.bricks:
-            for data in brick.my_data:
-                if np.any(np.isnan(data)):
-                    raise RuntimeError
-
-    fields = vol.field
 
     for i, (grid, mask) in enumerate(data_source.blocks):
         data = [(grid[f] * mask).astype("float64") for f in fields]

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/render_source.py
--- a/yt/visualization/volume_rendering/render_source.py
+++ b/yt/visualization/volume_rendering/render_source.py
@@ -12,6 +12,7 @@
 # -----------------------------------------------------------------------------
 
 import numpy as np
+from functools import wraps
 from yt.config import \
     ytcfg
 from yt.funcs import mylog, ensure_numpy_array
@@ -44,6 +45,37 @@
     ytcfg["yt", "ray_tracing_engine"] = "yt"
 
 
+def invalidate_volume(f):
+    @wraps(f)
+    def wrapper(*args, **kwargs):
+        ret = f(*args, **kwargs)
+        obj = args[0]
+        if isinstance(obj._transfer_function, ProjectionTransferFunction):
+            obj.sampler_type = 'projection'
+            obj._log_field = False
+            obj._use_ghost_zones = False
+        del obj.volume
+        obj._volume_valid = False
+        return ret
+    return wrapper
+
+def validate_volume(f):
+    @wraps(f)
+    def wrapper(*args, **kwargs):
+        obj = args[0]
+        fields = [obj.field]
+        log_fields = [obj.log_field]
+        if obj.weight_field is not None:
+            fields.append(obj.weight_field)
+            log_fields.append(obj.log_field)
+        if obj._volume_valid is False:
+            obj.volume.set_fields(fields, log_fields,
+                                  no_ghost=(not obj.use_ghost_zones))
+        obj._volume_valid = True
+        return f(*args, **kwargs)
+    return wrapper
+
+
 class RenderSource(ParallelAnalysisInterface):
 
     """Base Class for Render Sources.
@@ -94,9 +126,6 @@
         data object or dataset.
     fields : string
         The name of the field(s) to be rendered.
-    auto: bool, optional
-        If True, will build a default AMRKDTree and transfer function based
-        on the data.
 
     Examples
     --------
@@ -128,47 +157,157 @@
     _image = None
     data_source = None
 
-    def __init__(self, data_source, field, auto=True):
+    def __init__(self, data_source, field):
         r"""Initialize a new volumetric source for rendering."""
         super(VolumeSource, self).__init__()
         self.data_source = data_source_or_all(data_source)
         field = self.data_source._determine_fields(field)[0]
-        self.field = field
-        self.volume = None
         self.current_image = None
         self.check_nans = False
         self.num_threads = 0
         self.num_samples = 10
         self.sampler_type = 'volume-render'
 
-        # Error checking
-        assert(self.field is not None)
-        assert(self.data_source is not None)
+        self._volume_valid = False
 
-        # In the future these will merge
+        # these are caches for properties, defined below
+        self._volume = None
+        self._transfer_function = None
+        self._field = field
+        self._log_field = self.data_source.ds.field_info[field].take_log
+        self._use_ghost_zones = False
+        self._weight_field = None
+
+        self.tfh = TransferFunctionHelper(self.data_source.pf)
+        self.tfh.set_field(self.field)
+
+    @property
+    def transfer_function(self):
+        """The transfer function associated with this VolumeSource"""
+        if self._transfer_function is not None:
+            return self._transfer_function
+
+        if self.tfh.tf is not None:
+            self._transfer_function = self.tfh.tf
+            return self._transfer_function
+
+        mylog.info("Creating transfer function")
+        self.tfh.set_field(self.field)
+        self.tfh.set_log(self.log_field)
+        self.tfh.build_transfer_function()
+        self.tfh.setup_default()
+        self._transfer_function = self.tfh.tf
+
+        return self._transfer_function
+
+    @transfer_function.setter
+    def transfer_function(self, value):
+        self.tfh.tf = None
+        valid_types = (TransferFunction, ColorTransferFunction,
+                       ProjectionTransferFunction, type(None))
+        if not isinstance(value, valid_types):
+            raise RuntimeError("transfer_function not a valid type, "
+                               "received object of type %s" % type(value))
+        if isinstance(value, ProjectionTransferFunction):
+            self.sampler_type = 'projection'
+            if self._volume is not None:
+                fields = [self.field]
+                if self.weight_field is not None:
+                    fields.append(self.weight_field)
+                self._volume_valid = False
+        self._transfer_function = value
+
+    @property
+    def volume(self):
+        """The abstract volume associated with this VolumeSource
+
+        This object does the heavy lifting to access data in an efficient manner
+        using a KDTree
+        """
+        if self._volume is None:
+            mylog.info("Creating volume")
+            volume = AMRKDTree(self.data_source.ds, data_source=self.data_source)
+            self._volume = volume
+
+        return self._volume
+
+    @volume.setter
+    def volume(self, value):
+        assert(isinstance(value, AMRKDTree))
+        del self._volume
+        self._field = value.fields
+        self._log_field = value.log_fields
+        self._volume = value
+        self._volume_valid is True
+
+    @volume.deleter
+    def volume(self):
+        del self._volume
+        self._volume = None
+
+    @property
+    def field(self):
+        """The field to be rendered"""
+        return self._field
+
+    @field.setter
+    @invalidate_volume
+    def field(self, value):
+        field = self.data_source._determine_fields(value)
+        if len(field) > 1:
+            raise RuntimeError(
+                "VolumeSource.field can only be a single field but received "
+                "multiple fields: %s") % field
+        field = field[0]
+        if self._field != field:
+            log_field = self.data_source.ds.field_info[field].take_log
+            self.tfh.bounds = None
+        else:
+            log_field = self._log_field
+        self._log_field = log_field
+        self._field = value
         self.transfer_function = None
-        self.tfh = None
-        if auto:
-            self.build_defaults()
+        self.tfh.set_field(value)
+        self.tfh.set_log(log_field)
 
-    def build_defaults(self):
-        """Sets a default volume and transfer function"""
-        mylog.info("Creating default volume")
-        self.build_default_volume()
-        mylog.info("Creating default transfer function")
-        self.build_default_transfer_function()
+    @property
+    def log_field(self):
+        """Whether or not the field rendering is computed in log space"""
+        return self._log_field
+
+    @log_field.setter
+    @invalidate_volume
+    def log_field(self, value):
+        self.transfer_function = None
+        self.tfh.set_log(value)
+        self._log_field = value
+
+    @property
+    def use_ghost_zones(self):
+        """Whether or not ghost zones are used to estimate vertex-centered data
+        values at grid boundaries"""
+        return self._use_ghost_zones
+
+    @use_ghost_zones.setter
+    @invalidate_volume
+    def use_ghost_zones(self, value):
+        self._use_ghost_zones = value
+
+    @property
+    def weight_field(self):
+        """The weight field for the rendering
+
+        Currently this is only used for off-axis projections.
+        """
+        return self._weight_field
+
+    @weight_field.setter
+    @invalidate_volume
+    def weight_field(self, value):
+        self._weight_field = value
 
     def set_transfer_function(self, transfer_function):
         """Set transfer function for this source"""
-        if not isinstance(transfer_function,
-                          (TransferFunction, ColorTransferFunction,
-                           ProjectionTransferFunction)):
-            raise RuntimeError("transfer_function not of correct type")
-        if isinstance(transfer_function, ProjectionTransferFunction):
-            self.sampler_type = 'projection'
-            self.volume.set_fields([self.field], log_fields=[False], 
-                                   no_ghost=True, force=True)
-
         self.transfer_function = transfer_function
         return self
 
@@ -177,53 +316,71 @@
         if self.data_source is None:
             raise RuntimeError("Data source not initialized")
 
-        if self.volume is None:
-            raise RuntimeError("Volume not initialized")
-
-        if self.transfer_function is None:
-            raise RuntimeError("Transfer Function not Supplied")
-
-    def build_default_transfer_function(self):
-        """Sets up a transfer function"""
-        self.tfh = \
-            TransferFunctionHelper(self.data_source.pf)
-        self.tfh.set_field(self.field)
-        self.tfh.build_transfer_function()
-        self.tfh.setup_default()
-        self.transfer_function = self.tfh.tf
-
-    def build_default_volume(self):
-        """Sets up an AMRKDTree based on the VolumeSource's field"""
-        self.volume = AMRKDTree(self.data_source.pf,
-                                data_source=self.data_source)
-        log_fields = [self.data_source.pf.field_info[self.field].take_log]
-        mylog.debug('Log Fields:' + str(log_fields))
-        self.volume.set_fields([self.field], log_fields, True)
-
     def set_volume(self, volume):
         """Associates an AMRKDTree with the VolumeSource"""
-        assert(isinstance(volume, AMRKDTree))
-        del self.volume
         self.volume = volume
+        return self
 
-    def set_fields(self, fields, no_ghost=True):
-        """Set the source's fields to render
+    def set_field(self, field):
+        """Set the source's field to render
 
         Parameters
         ----------
 
-        fields: field name or list of field names
-            The field or fields to render
-        no_ghost: boolean
-            If False, the AMRKDTree estimates vertex centered data using ghost
+        field: field name
+            The field to render
+        """
+        self.field = field
+        return self
+
+    def set_log(self, log_field):
+        """Set whether the rendering of the source's field is done in log space
+
+        Generally volume renderings of data whose values span a large dynamic
+        range should be done on log space and volume renderings of data with
+        small dynamic range should be done in linear space.
+
+        Parameters
+        ----------
+
+        log_field: boolean
+            If True, the volume rendering will be done in log space, and if False
+            will be done in linear space.
+        """
+        self.log_field = log_field
+        return self
+
+    def set_weight_field(self, weight_field):
+        """Set the source's weight field
+
+        .. note::
+
+          This is currently only used for renderings using the
+          ProjectionTransferFunction
+
+        Parameters
+        ----------
+
+        weight_field: field name
+            The weight field to use in the rendering
+        """
+        self.weight_field = weight_field
+        return self
+
+    def set_use_ghost_zones(self, use_ghost_zones):
+        """Set whether or not interpolation at grid edges uses ghost zones
+
+        Parameters
+        ----------
+
+        use_ghost_zones: boolean
+            If True, the AMRKDTree estimates vertex centered data using ghost
             zones, which can eliminate seams in the resulting volume rendering.
-            Defaults to True for performance reasons.
+            Defaults to False for performance reasons.
+
         """
-        fields = self.data_source._determine_fields(fields)
-        log_fields = [self.data_source.ds.field_info[f].take_log
-                      for f in fields]
-        self.volume.set_fields(fields, log_fields, no_ghost)
-        self.field = fields
+        self.use_ghost_zones = use_ghost_zones
+        return self
 
     def set_sampler(self, camera, interpolated=True):
         """Sets a volume render sampler
@@ -233,10 +390,10 @@
         sampler types are supported.
 
         The 'interpolated' argument is only meaningful for projections. If True,
-        the data is first interpolated to the cell vertices, and then tri-linearly
-        interpolated to the ray sampling positions. If False, then the cell-centered
-        data is simply accumulated along the ray. Interpolation is always performed
-        for volume renderings.
+        the data is first interpolated to the cell vertices, and then
+        tri-linearly interpolated to the ray sampling positions. If False, then
+        the cell-centered data is simply accumulated along the
+        ray. Interpolation is always performed for volume renderings.
 
         """
         if self.sampler_type == 'volume-render':
@@ -250,6 +407,7 @@
         self.sampler = sampler
         assert(self.sampler is not None)
 
+    @validate_volume
     def render(self, camera, zbuffer=None):
         """Renders an image using the provided camera
 
@@ -308,7 +466,8 @@
             Whether or not this is being called from a higher level in the VR
             interface. Used to set the correct orientation.
         """
-        image = self.volume.reduce_tree_images(image, camera.lens.viewpoint)
+        if self._volume is not None:
+            image = self.volume.reduce_tree_images(image, camera.lens.viewpoint)
         image.shape = camera.resolution[0], camera.resolution[1], 4
         # If the call is from VR, the image is rotated by 180 to get correct
         # up direction
@@ -320,7 +479,7 @@
 
     def __repr__(self):
         disp = "<Volume Source>:%s " % str(self.data_source)
-        disp += "transfer_function:%s" % str(self.transfer_function)
+        disp += "transfer_function:%s" % str(self._transfer_function)
         return disp
 
 

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -292,8 +292,7 @@
             suffix = '.png'
             fname = '%s%s' % (fname, suffix)
 
-        if self._last_render is None:
-            self.render()
+        self.render()
 
         mylog.info("Saving render %s", fname)
         self._last_render.write_png(fname, sigma_clip=sigma_clip)
@@ -380,8 +379,7 @@
             suffix = '.png'
             fname = '%s%s' % (fname, suffix)
 
-        if self._last_render is None:
-            self.render()
+        self.render()
 
         # which transfer function?
         rs = rensources[0]

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/tests/test_varia.py
--- a/yt/visualization/volume_rendering/tests/test_varia.py
+++ b/yt/visualization/volume_rendering/tests/test_varia.py
@@ -18,6 +18,8 @@
 import yt
 from yt.testing import \
     fake_random_ds
+from yt.visualization.volume_rendering.render_source import VolumeSource
+from yt.visualization.volume_rendering.scene import Scene
 from unittest import TestCase
 
 def setup():
@@ -62,7 +64,8 @@
         im, sc = yt.volume_render(self.ds)
 
         volume_source = sc.get_source(0)
-        volume_source.set_fields([('gas', 'velocity_x'), ('gas', 'density')])
+        volume_source.set_field(('gas', 'velocity_x'))
+        volume_source.set_weight_field(('gas', 'density'))
         sc.render()
 
     def test_rotation_volume_rendering(self):
@@ -76,3 +79,70 @@
 
     def test_simple_volume_rendering(self):
         im, sc = yt.volume_render(self.ds, sigma_clip=4.0)
+
+    def test_lazy_volume_source_construction(self):
+        sc = Scene()
+        source = VolumeSource(self.ds.all_data(), 'density')
+
+        assert source._volume is None
+        assert source._transfer_function is None
+
+        source.tfh.bounds = (0.1, 1)
+
+        source.set_log(False)
+
+        assert source.log_field is False
+        assert source.transfer_function.x_bounds == [0.1, 1]
+        assert source._volume is None
+
+        source.set_log(True)
+
+        assert source.log_field is True
+        assert source.transfer_function.x_bounds == [-1, 0]
+        assert source._volume is None
+
+        source.transfer_function = None
+        source.tfh.bounds = None
+
+        ad = self.ds.all_data()
+
+        assert source.transfer_function.x_bounds == \
+            list(np.log10(ad.quantities.extrema('density')))
+        assert source.tfh.log == source.log_field
+
+        source.set_field('velocity_x')
+        source.set_log(False)
+
+        assert source.transfer_function.x_bounds == \
+            list(ad.quantities.extrema('velocity_x'))
+        assert source._volume is None
+
+        source.set_field('density')
+
+        assert source.volume is not None
+        assert source.volume._initialized is False
+        assert source.volume.fields is None
+
+        del source.volume
+        assert source._volume is None
+
+        sc.add_source(source)
+
+        sc.add_camera()
+
+        sc.render()
+
+        assert source.volume is not None
+        assert source.volume._initialized is True
+        assert source.volume.fields == [('gas', 'density')]
+        assert source.volume.log_fields == [True]
+
+        source.set_field('velocity_x')
+        source.set_log(False)
+
+        sc.render()
+
+        assert source.volume is not None
+        assert source.volume._initialized is True
+        assert source.volume.fields == [('gas', 'velocity_x')]
+        assert source.volume.log_fields == [False]

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/transfer_function_helper.py
--- a/yt/visualization/volume_rendering/transfer_function_helper.py
+++ b/yt/visualization/volume_rendering/transfer_function_helper.py
@@ -83,8 +83,9 @@
         field: string
             The field to be rendered.
         """
+        if field != self.field:
+            self.log = self.ds._get_field_info(field).take_log
         self.field = field
-        self.log = self.ds._get_field_info(self.field).take_log
 
     def set_log(self, log):
         """
@@ -98,8 +99,6 @@
             Sets whether the transfer function should use log or linear space.
         """
         self.log = log
-        self.ds.index
-        self.ds._get_field_info(self.field).take_log = log
 
     def build_transfer_function(self):
         """
@@ -118,7 +117,7 @@
         """
         if self.bounds is None:
             mylog.info('Calculating data bounds. This may take a while.' +
-                       '  Set the .bounds to avoid this.')
+                       '  Set the TranferFunctionHelper.bounds to avoid this.')
             self.set_bounds()
 
         if self.log:
@@ -132,11 +131,13 @@
         return self.tf
 
     def setup_default(self):
-        """docstring for setup_default"""
-        if self.log:
-            mi, ma = np.log10(self.bounds[0]), np.log10(self.bounds[1])
-        else:
-            mi, ma = self.bounds
+        """Setup a default colormap
+
+        Creates a ColorTransferFunction including 10 gaussian layers whose
+        colors smaple the 'spectral' colormap. Also attempts to scale the
+        transfer function to produce a natural contrast ratio.
+
+        """
         self.tf.add_layers(10, colormap='spectral')
         factor = self.tf.funcs[-1].y.size / self.tf.funcs[-1].y.sum()
         self.tf.funcs[-1].y *= 2*factor
@@ -162,6 +163,7 @@
         from matplotlib.figure import Figure
         if self.tf is None:
             self.build_transfer_function()
+            self.setup_default()
         tf = self.tf
         if self.log:
             xfunc = np.logspace

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/transfer_functions.py
--- a/yt/visualization/volume_rendering/transfer_functions.py
+++ b/yt/visualization/volume_rendering/transfer_functions.py
@@ -708,7 +708,7 @@
         --------
 
         >>> def linramp(vals, minval, maxval):
-        ...     return (vals - vals.min())/(vals.(max) - vals.min())
+        ...     return (vals - vals.min())/(vals.max() - vals.min())
         >>> tf = ColorTransferFunction( (-10.0, -5.0) )
         >>> tf.map_to_colormap(-8.0, -6.0, scale=10.0, colormap='arbre')
         >>> tf.map_to_colormap(-6.0, -5.0, scale=10.0, colormap='arbre',

diff -r b58837b96e9d93e200ec564f11db913ec4c515e0 -r e7aabcd611651728be2dca5e817883f99c75a4a5 yt/visualization/volume_rendering/utils.py
--- a/yt/visualization/volume_rendering/utils.py
+++ b/yt/visualization/volume_rendering/utils.py
@@ -1,4 +1,6 @@
 import numpy as np
+from yt.data_objects.data_containers import \
+    YTSelectionContainer3D
 from yt.data_objects.static_output import Dataset
 from yt.utilities.lib import bounding_volume_hierarchy
 from yt.utilities.lib.grid_traversal import \
@@ -13,6 +15,11 @@
 def data_source_or_all(data_source):
     if isinstance(data_source, Dataset):
         data_source = data_source.all_data()
+    if not isinstance(data_source, (YTSelectionContainer3D, type(None))):
+        raise RuntimeError(
+            "The data_source is not a valid 3D data container.\n"
+            "Expected an ojbect of type YTSelectionContainer3D but received "
+            "an object of type %s." % type(data_source))
     return data_source

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