OSOceanAcoustics / echoshader

Interactive visualization of ocean sonar data
https://echoshader.readthedocs.io
MIT License
9 stars 8 forks source link

Syntax for plotting different types of echograms and linkage to associated widgets #107

Closed leewujung closed 1 year ago

leewujung commented 1 year ago

Note 2023-07-26 See the 2nd comment text for update; this initial comment is preserved here just as a reference


Right now to plot a multi-frequency echogram from a dataset (ds_MVBS), one can use ds_MVBS.eshader.echogram_multiple_frequency, with the layout argument being "single_frequency", "multiple_frequency", or "composite". But then there is another method echogram_single_frequency that plots a single frequency echogram, and only this one is linked to the Sv slider and colormap selector.

Based on our discussion notes, I think we can consolidate these to use a single call:

ds_MVBS.eshader.echogram(layout, channel)

Another set of changes would be to link the associated widgets Sv_range_slider, cmap_selector (note the names) with all types of echograms

Let me know if the above makes sense. Thanks!

leewujung commented 1 year ago

Update on 2023-07-26:

Regular echogram + slider

IMG_7954

Tricolor (composite) echogram

IMG_7956

valentina-s commented 1 year ago

@leewujung Based on a follow-up discussion with @ldr426, turned out the proposed approach would not work to dynamically update the widgets after that, one need to proceed as follows if they want to both to set parameters and dynamically link widgets after that:

# we do not assign this to an object 
# this sets the channel in the Echoshader object
ds.eshader.echogram(channel=["38kHz"])
ds.eshader.Sv_range_slider(["38kHz"])
# pass the functions to panel: this allows for dynamic updates
panel.row(ds.eshader.echogram, ds.eshader.Sv_range_slider)

If we just run as discussed

echogram_widget = ds.eshader.echogram(channel=["38kHz"])
Sv_range_slider_widget = ds.eshader.Sv_range_slider(["38kHz"])
# pass the functions to panel: this allows for dynamic updates
panel.row(echogram_widget, Sv_range_slider_widget)

one would not be able to update the plots dynamically, as the widgets are static objects.

With the first scenario, we worry that even if we write in the documentation that they should pass the function, they will set the function call to an object and will pass it to panel and will be puzzled why it is not updating.

An alternative to mitigate this problem is to actually make echogram return a function instead of the holoviews object. To the user that might be a bit invisible.

# echogram_widget is a function
echogram_widget = ds.eshader.echogram(channel=["38kHz"])

# the function then can be passed to panel
panel.row(echogram_widget)

panel will accept the function and the echogram will plot.

The function can be created by some pseudo code like that

def echogram(MVBS_ds, ch, cmap):
    return(hv.Image(MVBS_ds, ch, map))

channel_select = panel.select()
cmap_input = panel.Input()

echogram_bind  = panel.bind(echogram, ch=channel_select, cmap=cmap_select)

def eg(MVBS_Sv, ch, cmap, echogram_bind):
    ch_sel.value = ch
    cmap_sel.value = cmap
    return (echogram_bind)

Of course, if the user just runs echogram_widget in a cell it will show that this is a function. To display the plot, one needs to run echogram_widget().

Yet another alternative is that they always create a changing widget if they want to change a value of the parameters, but this will result in too many widgets that we may not want to update.

@ldr426 can you verify that the above is correct.

ldr426 commented 1 year ago

@valentina-s Thanks for illustration. yes, that is the situation is

leewujung commented 1 year ago

Would something like the following work?

from echogram import plot_echogram
from widgets import create_range_slider

@xarray.register_dataset_accessor("eshader")
class Echoshader:
   def __init__():   
      # self.ds_MVBS points the accessed xr dataset

      # initiate Sv range slider for all channels so we always have them for potential linkage
      self.Sv_range_slider = create_range_slider(...)
      return self.Sv_range_slider  # probably needs to be a list with elements being each channel

   def Sv_range_slider(CHANNEL_SPECIFICTION, ...):
      # something to select channel from self.Sv_range_slider
      # return channel slider with correct channel selection

   @pn.depends(Sv_range=self.Sv_range_slider)
   def echogram(CHANNEL_SPECIFICTION, ...):
      # something to select vmin and vmax of the slider of the correct channel
      return plot_echogram(..., vmin=..., vmax=...)

The plot_echogram function content could be very similar to what you have in https://github.com/OSOceanAcoustics/echoshader/blob/3f075a115ed20e74201d4d27023a776d627cf4a3/echoshader/new_version/echogram.py#L213C11-L213C11

This is not completely thought out (for example I did not completely specify the mechanism to make sure the returned slider and echogram works on the same channel) but I just took a quick look at this page to see if how things could be linked, so this is my pattern matching result.

ldr426 commented 1 year ago

@leewujung Hey wu-jung, Thanks for the suggestion. This is what I used similarly in my latest commit. But this is not the issue @valentina-s trying to explain. I mentioned it in the thread previously.

ldr426 commented 1 year ago

New designing pattern has been built in https://github.com/OSOceanAcoustics/echoshader/pull/131