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)