diff --git a/CHANGES.rst b/CHANGES.rst
index 7bd1688be0..da857bfaa5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -18,7 +18,7 @@ New Features
Cubeviz
^^^^^^^
-- Enhancements for the cube sonification plugin. [#3387]
+- Enhancements for the cube sonification plugin. [#3377, #3387]
Imviz
^^^^^
diff --git a/jdaviz/configs/cubeviz/plugins/cube_listener.py b/jdaviz/configs/cubeviz/plugins/cube_listener.py
index d862b78a8b..ab3b2e2c85 100644
--- a/jdaviz/configs/cubeviz/plugins/cube_listener.py
+++ b/jdaviz/configs/cubeviz/plugins/cube_listener.py
@@ -42,8 +42,10 @@ def sonify_spectrum(spec, duration, overlap=0.05, system='mono', srate=44100, fm
data = {'spectrum': [spec], 'pitch': [1]}
- # again, use maximal range for the mapped parameters
- lims = {'spectrum': ('0', '100')}
+ # set range in spectral flux representing the maximum and minimum sound frequency power:
+ # 0 (numeric): absolute 0 in flux units, such that any flux above 0 will sound.
+ # '100' (string): 100th percentile (i.e. maximum value) in spectral flux.
+ lims = {'spectrum': (0, '100')}
# set up source
sources = Events(data.keys())
@@ -60,8 +62,7 @@ def sonify_spectrum(spec, duration, overlap=0.05, system='mono', srate=44100, fm
class CubeListenerData:
def __init__(self, cube, wlens, samplerate=44100, duration=1, overlap=0.05, buffsize=1024,
- bdepth=16, wl_bounds=None, wl_unit=None, audfrqmin=50, audfrqmax=1500,
- eln=False, vol=None):
+ bdepth=16, wl_unit=None, audfrqmin=50, audfrqmax=1500, eln=False, vol=None):
self.siglen = int(samplerate*(duration-overlap))
self.cube = cube
self.dur = duration
@@ -75,7 +76,6 @@ def __init__(self, cube, wlens, samplerate=44100, duration=1, overlap=0.05, buff
else:
self.atten_level = int(np.clip((vol/100)**2, MINVOL, 1))
- self.wl_bounds = wl_bounds
self.wl_unit = wl_unit
self.wlens = wlens
diff --git a/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py b/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py
index c212b1d25f..3699d301fe 100644
--- a/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py
+++ b/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py
@@ -39,13 +39,15 @@ class SonifyData(PluginTemplateMixin, DatasetSelectMixin, SpectralSubsetSelectMi
"""
template_file = __file__, "sonify_data.vue"
- sample_rate = IntHandleEmpty(44100).tag(sync=True)
- buffer_size = IntHandleEmpty(2048).tag(sync=True)
+ # Removing UI option to vary these for now
+ sample_rate = 44100 # IntHandleEmpty(44100).tag(sync=True)
+ buffer_size = 2048 # IntHandleEmpty(2048).tag(sync=True)
assidx = FloatHandleEmpty(2.5).tag(sync=True)
ssvidx = FloatHandleEmpty(0.65).tag(sync=True)
- eln = Bool(False).tag(sync=True)
+ eln = Bool(True).tag(sync=True)
audfrqmin = FloatHandleEmpty(50).tag(sync=True)
- audfrqmax = FloatHandleEmpty(1500).tag(sync=True)
+ audfrqmax = FloatHandleEmpty(1000).tag(sync=True)
+ use_pccut = Bool(True).tag(sync=True)
pccut = IntHandleEmpty(20).tag(sync=True)
volume = IntHandleEmpty(100).tag(sync=True)
stream_active = Bool(True).tag(sync=True)
@@ -91,12 +93,15 @@ def vue_sonify_cube(self, *args):
display_unit = self.spec_viewer.state.x_display_unit
min_wavelength = self.spectral_subset.selected_obj.lower.to_value(u.Unit(display_unit))
max_wavelength = self.spectral_subset.selected_obj.upper.to_value(u.Unit(display_unit))
- self.flux_viewer.update_listener_wls(min_wavelength, max_wavelength, display_unit)
+ self.flux_viewer.update_listener_wls((min_wavelength, max_wavelength), display_unit)
+ # Ensure the current spectral region bounds are up-to-date at render time
+ self.update_wavelength_range(None)
+ # generate the sonified cube
self.flux_viewer.get_sonified_cube(self.sample_rate, self.buffer_size,
selected_device_index, self.assidx, self.ssvidx,
self.pccut, self.audfrqmin,
- self.audfrqmax, self.eln)
+ self.audfrqmax, self.eln, self.use_pccut)
# Automatically select spectrum-at-spaxel tool
spec_at_spaxel_tool = self.flux_viewer.toolbar.tools['jdaviz:spectrumperspaxel']
@@ -106,6 +111,18 @@ def vue_start_stop_stream(self, *args):
self.stream_active = not self.stream_active
self.flux_viewer.stream_active = not self.flux_viewer.stream_active
+ @observe('spectral_subset_selected')
+ def update_wavelength_range(self, event):
+ if not hasattr(self, 'spec_viewer'):
+ return
+ display_unit = self.spec_viewer.state.x_display_unit
+ # is this spectral selection or the entire spectrum?
+ if hasattr(self.spectral_subset.selected_obj, "subregions"):
+ wlranges = self.spectral_subset.selected_obj.subregions
+ else:
+ wlranges = None
+ self.flux_viewer.update_listener_wls(wlranges, display_unit)
+
@observe('volume')
def update_volume_level(self, event):
self.flux_viewer.update_volume_level(event['new'])
diff --git a/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.vue b/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.vue
index 7e08733c3e..2c53067f6c 100644
--- a/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.vue
+++ b/jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.vue
@@ -9,12 +9,23 @@
:scroll_to.sync="scroll_to"
:disabled_msg="disabled_msg">
- Sonify Cube
+ Cube Pre-Sonification Options
To use Sonify Data, install strauss and restart Jdaviz. You can do this by running pip install strauss
in the command line and then launching Jdaviz.
-
+
+ Choose the input cube, spectral subset and any advanced sonification options.
+
+
-
@@ -32,26 +42,6 @@
Advanced Sound Options
-
-
-
-
-
-
-
+
+
+
+
@@ -138,6 +136,7 @@
Refresh Device List
+ Live Sound Options
@@ -153,5 +152,6 @@
Volume
+
-
\ No newline at end of file
+
diff --git a/jdaviz/configs/cubeviz/plugins/viewers.py b/jdaviz/configs/cubeviz/plugins/viewers.py
index 7bba966582..10883da1de 100644
--- a/jdaviz/configs/cubeviz/plugins/viewers.py
+++ b/jdaviz/configs/cubeviz/plugins/viewers.py
@@ -50,7 +50,8 @@ def __init__(self, *args, **kwargs):
self.sonified_cube = None
self.stream = None
- self.sonification_wl_bounds = None
+
+ self.sonification_wl_ranges = None
self.sonification_wl_unit = None
self.volume_level = None
self.stream_active = True
@@ -113,8 +114,8 @@ def update_sonified_cube(self, x, y):
self.sonified_cube.newsig = self.sonified_cube.sigcube[x, y, :]
self.sonified_cube.cbuff = True
- def update_listener_wls(self, w1, w2, wunit):
- self.sonification_wl_bounds = (w1, w2)
+ def update_listener_wls(self, wranges, wunit):
+ self.sonification_wl_ranges = wranges
self.sonification_wl_unit = wunit
def update_sound_device(self, device_index):
@@ -133,18 +134,20 @@ def update_volume_level(self, level):
self.sonified_cube.atten_level = int(1/np.clip((level/100.)**2, MINVOL, 1))
def get_sonified_cube(self, sample_rate, buffer_size, device, assidx, ssvidx,
- pccut, audfrqmin, audfrqmax, eln):
+ pccut, audfrqmin, audfrqmax, eln, use_pccut):
spectrum = self.active_image_layer.layer.get_object(statistic=None)
wlens = spectrum.wavelength.to('m').value
flux = spectrum.flux.value
self.sample_rate = sample_rate
self.buffer_size = buffer_size
- if self.sonification_wl_bounds:
- wl_unit = getattr(u, self.sonification_wl_unit)
- si_wl_bounds = (self.sonification_wl_bounds * wl_unit).to('m')
- wdx = np.logical_and(wlens >= si_wl_bounds[0].value,
- wlens <= si_wl_bounds[1].value)
+ if self.sonification_wl_ranges:
+ wdx = np.zeros(wlens.size).astype(bool)
+ for r in self.sonification_wl_ranges:
+ # index just the spectral subregion
+ wdx = np.logical_or(wdx,
+ np.logical_and(wlens >= r[0].to_value(u.m),
+ wlens <= r[1].to_value(u.m)))
wlens = wlens[wdx]
flux = flux[:, :, wdx]
@@ -156,15 +159,15 @@ def get_sonified_cube(self, sample_rate, buffer_size, device, assidx, ssvidx,
# make a rough white-light image from the clipped array
whitelight = np.expand_dims(clipped_arr.sum(-1), axis=2)
- # subtract any percentile cut
- clipped_arr -= np.expand_dims(pc_cube, axis=2)
+ if use_pccut:
+ # subtract any percentile cut
+ clipped_arr -= np.expand_dims(pc_cube, axis=2)
- # and re-clip
- clipped_arr = np.clip(clipped_arr, 0, np.inf)
+ # and re-clip
+ clipped_arr = np.clip(clipped_arr, 0, np.inf)
self.sonified_cube = CubeListenerData(clipped_arr ** assidx, wlens, duration=0.8,
samplerate=sample_rate, buffsize=buffer_size,
- wl_bounds=self.sonification_wl_bounds,
wl_unit=self.sonification_wl_unit,
audfrqmin=audfrqmin, audfrqmax=audfrqmax,
eln=eln, vol=self.volume_level)