diff --git a/docs/conf.py b/docs/conf.py
index 0e2f1bc59..261036949 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -70,6 +70,7 @@
"""
intersphinx_mapping['astroquery'] = ('http://astroquery.readthedocs.org/en/latest/', None)
+intersphinx_mapping['regions'] = ('http://astropy-regions.readthedocs.org/en/latest/', None)
intersphinx_mapping['radio_beam'] = ('https://radio-beam.readthedocs.io/en/latest/', None)
# -- Project information ------------------------------------------------------
diff --git a/docs/installing.rst b/docs/installing.rst
index ce81ae0da..e657d3066 100644
--- a/docs/installing.rst
+++ b/docs/installing.rst
@@ -13,6 +13,9 @@ This package has the following dependencies:
reading in spectral cubes that use the BMAJ/BMIN convention for specifying the beam size.
* `Bottleneck `_, optional (speeds
up median and percentile operations on cubes with missing data)
+* `Regions `_ >=0.3dev, optional
+ (Serialises/Deserialises DS9/CRTF region files and handles them. Used when
+ extracting a subcube from region)
Installation
------------
diff --git a/docs/manipulating.rst b/docs/manipulating.rst
index c230de10b..412ef5a54 100644
--- a/docs/manipulating.rst
+++ b/docs/manipulating.rst
@@ -79,31 +79,69 @@ Numpy slicing notation::
This returns a new :class:`~spectral_cube.SpectralCube` object
with updated WCS information.
-Extracting a subcube from a ds9 region
---------------------------------------
+.. _reg:
-Starting with spectral_cube v0.2, you can use ds9 regions to extract subcubes.
-The minimal enclosing subcube will be extracted with a two-dimensional mask
-corresponding to the ds9 region. `pyregion
-`_ is required for region parsing::
+Extracting a subcube from a DS9/CRTF region
+-------------------------------------------
- >>> region_list = pyregion.open('file.reg') # doctest: +SKIP
- >>> sub_cube = cube.subcube_from_ds9region(region_list) # doctest: +SKIP
+You can use `DS9
+`_/`CRTF
+`_ regions to extract
+subcubes. The minimal enclosing subcube will be extracted with a two-dimensional
+mask corresponding to the DS9/CRTF region. `Regions
+`_ is required for region
+parsing. CRTF regions may also contain spectral cutout information.
-If you want to loop over individual regions with a single region file, you need to convert the individual
-region to a shape list due to limitations in pyregion::
+This example shows extraction of a subcube from a ds9 region file ``file.reg``.
+`~regions.read_ds9` parses the ds9 file and converts it to a list of
+`~regions.Region` objects::
- >>> region_list = pyregion.open('file.reg') #doctest: +SKIP
- >>> for region in region_list: #doctest: +SKIP
- >>> sub_cube = cube.subcube_from_ds9region(pyregion.ShapeList([region])) #doctest: +SKIP
-
-You can also create a region on the fly using ds9 region syntax. This extracts
-a 0.1 degree circle around the Galactic Center::
+ >>> import regions # doctest: +SKIP
+ >>> region_list = regions.read_ds9('file.reg') # doctest: +SKIP
+ >>> sub_cube = cube.subcube_from_regions(region_list) # doctest: +SKIP
+
+This one shows extraction of a subcube from a CRTF region file ``file.crtf``,
+parsed using `~regions.read_crtf`::
+
+ >>> import regions # doctest: +SKIP
+ >>> region_list = regions.read_crtf('file.reg') # doctest: +SKIP
+ >>> sub_cube = cube.subcube_from_regions(region_list) # doctest: +SKIP
- >>> region_list = pyregion.parse("galactic; circle(0,0,0.1)") # doctest: +SKIP
- >>> sub_cube = cube.subcube_from_ds9region(region_list) # doctest: +SKIP
+If you want to loop over individual regions with a single region file, you need
+to convert the individual regions to lists of that region::
+ >>> region_list = regions.read_ds9('file.reg') #doctest: +SKIP
+ >>> for region in region_list: #doctest: +SKIP
+ >>> sub_cube = cube.subcube_from_regions([region]) #doctest: +SKIP
+You can also directly use a ds9 region string. This example extracts a 0.1
+degree circle around the Galactic Center::
+
+ >>> region_str = "galactic; circle(0, 0, 0.1)" # doctest: +SKIP
+ >>> sub_cube = cube.subcube_from_ds9region(region_str) # doctest: +SKIP
+
+Similarly, you can also use a CRTF region string::
+
+ >>> region_str = "circle[[0deg, 0deg], 0.1deg], coord=galactic, range=[150km/s, 300km/s]" # doctest: +SKIP
+ >>> sub_cube = cube.subcube_from_crtfregion(region_str) # doctest: +SKIP
+
+CRTF regions that specify a subset in the spectral dimension can be used to
+produce full 3D cutouts. The ``meta`` attribute of a `regions.Region` object
+contains the spectral information for that region in the three special keywords
+``range``, ``restfreq``, and ``veltype``::
+
+ >>> import regions # doctest: +SKIP
+ >>> from astropy import units as u
+
+ >>> regpix = regions.RectanglePixelRegion(regions.PixCoord(0.5, 1), width=4, height=2) # doctest: +SKIP
+ >>> regpix.meta['range'] = [150 * u.km/u.s, 300 * u.km/u.s] # spectral range # doctest: +SKIP
+ >>> regpix.meta['restfreq'] = [100 * u.GHz] # rest frequency # doctest: +SKIP
+ >>> regpix.meta['veltype'] = 'OPTICAL' # velocity convention # doctest: +SKIP
+ >>> subcube = cube.subcube_from_regions([regpix]) # doctest: +SKIP
+
+If ``range`` is specified, but the other two keywords are not, the code will
+likely crash.
+
Extract the minimal valid subcube
---------------------------------
diff --git a/docs/spectral_extraction.rst b/docs/spectral_extraction.rst
index a7e9d3d55..d9560e75a 100644
--- a/docs/spectral_extraction.rst
+++ b/docs/spectral_extraction.rst
@@ -40,22 +40,34 @@ mask from scratch and apply it to the data.::
>>> maskedsubcube = subcube.with_mask(mask) # doctest: +SKIP
>>> spectrum = maskedsubcube.mean(axis=(1,2)) # doctest: +SKIP
-Aperture extraction using regions
----------------------------------
+Aperture and spectral extraction using regions
+----------------------------------------------
-Spectral-cube supports ds9 regions, so you can use the ds9 region to create a
-mask. The ds9 region support relies on `pyregion
-`_, which supports most shapes in
-ds9, so you are not limited to circular apertures.
+Spectral-cube supports ds9 and crtf regions, so you can use them to create a
+mask. The ds9/crtf region support relies on `regions
+`, which supports most
+shapes in ds9 and crtf, so you are not limited to circular apertures.
-In this example, we'll create a region "from scratch", but you can also use a
-predefined region file using `pyregion.open
-`_.::
+In this example, we'll extract a subcube from ds9 region string using
+`~spectral_cube.spectral_cube.SpectralCube.subcube_from_ds9region`::
- >>> shapelist = pyregion.parse("fk5; circle(19:23:43.907,+14:30:34.66, 3\")") # doctest: +SKIP
- >>> subcube = cube.subcube_from_ds9region(shapelist) # doctest: +SKIP
- >>> spectrum = subcube.mean(axis=(1,2)) # doctest: +SKIP
+ >>> ds9_str = 'fk5; circle(19:23:43.907, +14:30:34.66, 3")' # doctest: +SKIP
+ >>> subcube = cube.subcube_from_ds9region(ds9_str) # doctest: +SKIP
+ >>> spectrum = subcube.mean(axis=(1, 2)) # doctest: +SKIP
+
+Similarly, one can extract a subcube from a crtf region string using
+`~spectral_cube.spectral_cube.SpectralCube.subcube_from_crtfregion`::
+
+ >>> crtf_str = 'circle[[19:23:43.907, +14:30:34.66], 3"], coord=fk5, range=[150km/s, 300km/s]' # doctest: +SKIP
+ >>> subcube = cube.subcube_from_crtfregion(crtf_str) # doctest: +SKIP
+ >>> spectrum = subcube.mean(axis=(1, 2)) # doctest: +SKIP
+
+You can also use a _list_ of `~regions.Region` objects to extract a subcube using
+`~spectral_cube.spectral_cube.SpectralCube.subcube_from_regions`::
+
+ >>> import regions # doctest: +SKIP
+ >>> regpix = regions.RectanglePixelRegion(regions.PixCoord(0.5, 1), width=4, height=2) # doctest: +SKIP
+ >>> subcube = cube.subcube_from_regions([regpix]) # doctest: +SKIP
+ >>> spectrum = subcube.mean(axis=(1, 2)) # doctest: +SKIP
-Eventually, we hope to change the region support from pyregion to `astropy
-regions `_, so the
-above example may become obsolete.
+To learn more, go to :ref:`reg`.
diff --git a/spectral_cube/spectral_cube.py b/spectral_cube/spectral_cube.py
index 8aced3c5f..55bea3a05 100644
--- a/spectral_cube/spectral_cube.py
+++ b/spectral_cube/spectral_cube.py
@@ -1842,7 +1842,7 @@ def subcube_from_ds9region(self, ds9_region, allow_empty=False):
return self.subcube_from_regions(region_list, allow_empty)
- def subcube_from_crtf(self, crtf_region, allow_empty=False):
+ def subcube_from_crtfregion(self, crtf_region, allow_empty=False):
"""
Extract a masked subcube from a CRTF region.
diff --git a/spectral_cube/tests/test_subcubes.py b/spectral_cube/tests/test_subcubes.py
index c58eae82d..a9f36e2b0 100644
--- a/spectral_cube/tests/test_subcubes.py
+++ b/spectral_cube/tests/test_subcubes.py
@@ -1,6 +1,7 @@
from __future__ import print_function, absolute_import, division
import pytest
+from distutils.version import LooseVersion
from astropy import units as u
from astropy import wcs
@@ -14,8 +15,9 @@
try:
import regions
regionsOK = True
+ REGIONS_GT_03 = LooseVersion(regions.__version__) >= LooseVersion('0.3')
except ImportError:
- regionsOK = False
+ regionsOK = REGIONS_GT_03 = False
try:
import scipy
@@ -55,6 +57,7 @@ def test_subcube():
@pytest.mark.skipif('not scipyOK', reason='Could not import scipy')
@pytest.mark.skipif('not regionsOK', reason='Could not import regions')
+@pytest.mark.skipif('not REGIONS_GT_03', reason='regions version should be >= 0.3')
@pytest.mark.parametrize('regfile',
('255-fk5.reg', '255-pixel.reg'),
)
@@ -71,6 +74,7 @@ def test_ds9region_255(regfile):
@pytest.mark.skipif('not scipyOK', reason='Could not import scipy')
@pytest.mark.skipif('not regionsOK', reason='Could not import regions')
+@pytest.mark.skipif('not REGIONS_GT_03', reason='regions version should be >= 0.3')
@pytest.mark.parametrize(('regfile', 'result'),
(('fk5.reg', [slice(None), 1, 1]),
('image.reg', [slice(None), 1, slice(None)]),
@@ -108,6 +112,7 @@ def test_ds9region_new(regfile, result):
@pytest.mark.skipif('not scipyOK', reason='Could not import scipy')
@pytest.mark.skipif('not regionsOK', reason='Could not import regions')
+@pytest.mark.skipif('not REGIONS_GT_03', reason='regions version should be >= 0.3')
def test_regions_spectral():
cube, data = cube_and_raw('adv.fits')
rf_cube = get_rest_value_from_wcs(cube.wcs).to("GHz",