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 plug-in. [#3377]
james-trayford marked this conversation as resolved.
Show resolved Hide resolved

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 @@

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')}

Check warning on line 48 in jdaviz/configs/cubeviz/plugins/cube_listener.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/cube_listener.py#L48

Added line #L48 was not covered by tests
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 @@

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 @@
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 @@
"""
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 @@
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)

Check warning on line 98 in jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py#L98

Added line #L98 was not covered by tests

# Ensure the current spectral region bounds are up-to-date at render time
self.update_wavelength_range(None)

Check warning on line 101 in jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py#L101

Added line #L101 was not covered by tests
# 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 @@
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

Check warning on line 120 in jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py#L120

Added line #L120 was not covered by tests
# is this spectral selection or the entire spectrum?
if hasattr(self.spectral_subset.selected_obj, "subregions"):
wlranges = self.spectral_subset.selected_obj.subregions

Check warning on line 123 in jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py#L122-L123

Added lines #L122 - L123 were not covered by tests
else:
wlranges = None
self.flux_viewer.update_listener_wls(wlranges, display_unit)

Check warning on line 126 in jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/sonify_data/sonify_data.py#L125-L126

Added lines #L125 - L126 were not covered by tests

@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 @@

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 @@
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

Check warning on line 118 in jdaviz/configs/cubeviz/plugins/viewers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/viewers.py#L118

Added line #L118 was not covered by tests
self.sonification_wl_unit = wunit

def update_sound_device(self, device_index):
Expand All @@ -133,18 +134,20 @@
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:

Check warning on line 146 in jdaviz/configs/cubeviz/plugins/viewers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/viewers.py#L144-L146

Added lines #L144 - L146 were not covered by tests
# index just the spectral subregion
wdx = np.logical_or(wdx,

Check warning on line 148 in jdaviz/configs/cubeviz/plugins/viewers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/viewers.py#L148

Added line #L148 was not covered by tests
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 @@
# 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:

Check warning on line 162 in jdaviz/configs/cubeviz/plugins/viewers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/viewers.py#L162

Added line #L162 was not covered by tests
# subtract any percentile cut
clipped_arr -= np.expand_dims(pc_cube, axis=2)

Check warning on line 164 in jdaviz/configs/cubeviz/plugins/viewers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/viewers.py#L164

Added line #L164 was not covered by tests

# and re-clip
clipped_arr = np.clip(clipped_arr, 0, np.inf)
# and re-clip
clipped_arr = np.clip(clipped_arr, 0, np.inf)

Check warning on line 167 in jdaviz/configs/cubeviz/plugins/viewers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/plugins/viewers.py#L167

Added line #L167 was not covered by tests

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