Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sonification plugin tweaks #3377

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ New Features
Cubeviz
^^^^^^^

- Enhancements for the cube sonification plugin. [#3377]

Imviz
^^^^^

Expand Down
10 changes: 5 additions & 5 deletions jdaviz/configs/cubeviz/plugins/cube_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we mixing number and string in this tuple?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is strauss syntax where strings are used for percentiles while numeric represents absolute values. In this case the maximum of the spectrum is the 100th %ile (i.e. max) in flux but using 0 flux for the minimum, rather than the 0th %ile in flux (min). I'll add a comment to clarify this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is kinda confusing. Why not be more explicit in the string specs like '100%' or '100pct'?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that could be an improvement - I will add accepting 'XX%' as a percentile format in the next batch of strauss changes - though would this be ok for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the limitation, sure. The comment is helpful enough for now. Thanks!


# set up source
sources = Events(data.keys())
Expand All @@ -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
Expand All @@ -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

Expand Down
29 changes: 23 additions & 6 deletions jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -93,12 +95,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']
Expand All @@ -108,6 +113,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'])
Expand Down
56 changes: 28 additions & 28 deletions jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,23 @@
:scroll_to.sync="scroll_to"
:disabled_msg="disabled_msg">

<j-plugin-section-header>Sonify Cube</j-plugin-section-header>
<j-plugin-section-header>Cube Pre-Sonification Options</j-plugin-section-header>
<v-alert v-if="!has_strauss" type="warning" style="margin-left: -12px; margin-right: -12px">
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.
</v-alert>

<v-row>
<j-docs-link>Choose the input cube, spectral subset and any advanced sonification options.</j-docs-link>
</v-row>
<plugin-dataset-select
:items="dataset_items"
:selected.sync="dataset_selected"
:show_if_single_entry="false"
label="Data"
api_hint="plg.dataset ="
:api_hints_enabled="api_hints_enabled"
hint="Select the data set."
/>
<plugin-subset-select
:items="spectral_subset_items"
:selected.sync="spectral_subset_selected"
Expand All @@ -24,34 +35,13 @@
:api_hints_enabled="api_hints_enabled"
hint="Select spectral region that defines the wavelength range."
/>

<v-row>
<v-expansion-panels accordion>
<v-expansion-panel>
<v-expansion-panel-header v-slot="{ open }">
<span style="padding: 6px">Advanced Sound Options</span>
</v-expansion-panel-header>
<v-expansion-panel-content class="plugin-expansion-panel-content">
<v-row>
<v-text-field
ref="sample_rate"
type="number"
label="Sample Rate"
v-model.number="sample_rate"
hint="The desired sample rate."
persistent-hint
></v-text-field>
</v-row>
<v-row>
<v-text-field
ref="buffer_size"
type="number"
label="Buffer Size"
v-model.number="buffer_size"
hint="The desired buffer size."
persistent-hint
></v-text-field>
</v-row>
<v-row>
<v-text-field
ref="audfrqmin"
Expand Down Expand Up @@ -92,13 +82,21 @@
persistent-hint
></v-text-field>
</v-row>
<v-row>
<v-row>
<v-switch
v-model="use_pccut"
label="Use Flux Percentile Cut?"
hint="Whether to only sonify flux above a min. percentile (else use absolute values)"
persistent-hint
></v-switch>
</v-row>
<v-row v-if="use_pccut">
<v-text-field
ref="pccut"
type="number"
label="Flux Percentile Cut"
label="Flux Percentile Cut Value"
v-model.number="pccut"
hint="The minimum flux percentile to be heard."
hint="The minimum percentile to be heard."
persistent-hint
></v-text-field>
</v-row>
Expand Down Expand Up @@ -133,20 +131,22 @@
Stop stream
</plugin-action-button>
</v-row>
<j-plugin-section-header>Live Sound Options</j-plugin-section-header>
<v-row>
<v-select
:menu-props="{ left: true }"
attach
:items="sound_devices_items"
v-model="sound_devices_selected"
label="Sound device"
hint="Device which sound will be output from. Must be selected BEFORE cube is sonified."
hint="Device which sound will be output from."
persistent-hint
></v-select>
</v-row>
<v-row>
Volume
<glue-throttled-slider label="Volume" wait="300" max="100" step="1" :value.sync="volume" hide-details class="no-hint" />
</v-row>
</v-row>
</j-tray-plugin>
</template>
</template>
31 changes: 17 additions & 14 deletions jdaviz/configs/cubeviz/plugins/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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]

Expand All @@ -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)
Expand Down
Loading