8. Notebook Widgets

Download this page as a Jupyter notebook

When analyzing notebooks, it can be helpful to make use of interactive widgets. For this, we provide some widgets to help you analyze your data. To enable such widgets, start the notebook with:

# enable this line if you are using jupyter lab
%matplotlib widget

# enable this line if you are using jupyter notebook
# %matplotlib notebook

We can download the data needed for this tutorial directly from Zenodo using Pylake. Since we don’t want it in our working folder, we’ll put it in a folder called "test_data":

kymo_filenames = lk.download_from_doi("10.5281/zenodo.7729525", "test_data")
stack_filenames = lk.download_from_doi("10.5281/zenodo.7729700", "test_data")
fd_filenames = lk.download_from_doi("10.5281/zenodo.7729929", "test_data")

8.1. Channel slicing

Let’s say we want to do some analyses on slices of channel data. It would be nice to just quickly visually select some regions using a widget. Let’s load the file and open an interactive plot using the range_selector() method:

file = lk.File("test_data/kymo.h5")
channel = file["Force HF"]["Force 1x"]

plt.figure()
selector = channel.range_selector()
../_images/slice_widget.png

This returns a SliceRangeSelectorWidget. You can use the left mouse button to select time ranges (by clicking the left and then the right boundary of the region you wish to select). The right mouse button can be used to remove previous selections. We can access the selected timestamps of the ranges we selected by accessing the ranges property:

>>> selector.ranges
array([[1638534508099192400, 1638534546751480400],
       [1638534592854827600, 1638534628712978000]], dtype=int64)

And the actual slices from slices. If we want to plot all of our selections in separate plots for instance, we can do the following:

plt.figure(figsize=(3, 6))
for idx, data_slice in enumerate(selector.slices):
    plt.subplot(len(selector.slices), 1, idx + 1)
    data_slice.plot()

plt.tight_layout()
../_images/slice_widget2.png

8.2. F,d selection

8.2.1. Range selection by time

Assume we have an F,d curve we want to analyze. We know that this file contains one F,d curve which should be split up into three segments that should be analyzed separately. Let’s open the FdRangeSelector and make a few selections:

file = lk.File("test_data/fd_multiple_Lc.h5")
fdcurves = file.fdcurves
selector = lk.FdRangeSelector(fdcurves)

This opens up a little widget, where you can use the left mouse button to select time ranges and the right mouse button to remove previous selections.

../_images/fd_widget.png

After making a few selections, the properties ranges and fdcurves contain the time ranges and force extension curves corrseponding to our selection. Once we’ve selected some time ranges, we can output the timestamps using the ranges property:

>>> selector.ranges

{'40': array([[1588263182203865800, 1588263189671475400], [1588263189981376200, 1588263190821107400], [1588263191131008200, 1588263195189709000]], dtype=int64)}

These timestamps can directly be used to extract the relevant data:

plt.figure(figsize=(8, 2.5))
for idx, (t_start, t_stop) in enumerate(selector.ranges["40"]):
    plt.subplot(1, len(selector.ranges["40"]), idx + 1)
    plt.scatter(
        fdcurves["40"].d[t_start:t_stop].data,
        fdcurves["40"].f[t_start:t_stop].data,
        s=2,  # Use a smaller marker size
    )
    plt.xlabel("Distance [$\mu$m]")
    plt.ylabel("Force [pN]")

plt.tight_layout()
../_images/fd_widget2.png

This produces a separate plot for each selection. There’s also a more direct way to get these plots, namely through fdcurves. This gives you an FdCurve for each section you selected:

plt.figure()
for fdcurve in selector.fdcurves["40"]:
    fdcurve.plot_scatter()
../_images/fd_widget3.png

8.2.2. Processing multiple files

Now let’s say our experiment is split up over multiple files, each containing a few F,d curves. We would like to load these curves all at once and make our selections. We can do this using automatically using glob. With glob.glob we grab a list of all .h5 files in the directory my_directory. We then iterate over this list and open each file. Then, for all those files, we add each individual curves to our variable fdcurves:

import glob

fdcurves = {}
for filename in glob.glob('test_data/*.h5'):
    file = lk.File(filename)
    for key, curve in file.fdcurves.items():
        fdcurves[key] = curve

Using this dictionary, we can open our widget and see all the data at once:

selector = lk.FdRangeSelector(fdcurves)
../_images/fd_widget4.png

Plotting the curves can be done similarly as before. Here .values() indicates that we want the values from the dictionary of curve sets, and not the keys (which in our case are the curve names):

for curve_set in selector.fdcurves.values():
    if curve_set:
        # Open a figure only if we selected regions in this dataset
        plt.figure()
    for fdcurve in curve_set:
        fdcurve.plot_scatter()
../_images/fd_widget5.png ../_images/fd_widget6.png

8.2.3. Range selection by distance

It is also possible to select a portion of an F,d curve based on distance using the FdDistanceRangeSelector:

selector = lk.FdDistanceRangeSelector(fdcurves)
../_images/fd_dist_widget.png

Again, we can retrieve the selected data from ranges and fdcurves just as with FdRangeSelector:

original = fdcurves["40"]
sliced = selector.fdcurves["40"][0]

plt.figure()

plt.subplot(2, 1, 1)
original.plot_scatter(label="original")
sliced.plot_scatter(label="sliced")
plt.legend()

plt.subplot(2, 1, 2)
original.f.plot()
sliced.f.plot(start=original.start)

plt.tight_layout()
../_images/fd_dist_widget2.png

The returned F,d curves correspond to the longest contiguous (in time) stretch of data that falls within the distance thresholds.

8.2.4. Range selection of single curve

The selector widgets can also be easily accessed from single FdCurve instances using range_selector():

plt.figure()
fdcurve = fdcurves["40"]
t_selector = fdcurve.range_selector()
../_images/single_curve_widget1.png

And for the distance-based selector we can use distance_range_selector():

plt.figure()
d_selector = fdcurve.distance_range_selector()
plt.show()
../_images/single_curve_widget2.png

8.3. Cropping and Rotating Image Stacks

You can interactively define the location of a tether for a ImageStack by using crop_and_rotate():

stack = lk.ImageStack("test_data/tether.tiff")
editor = stack.crop_and_rotate()
plt.show()

Simply left-click on the start of the tether

../_images/widget_stack_editor_1.png

and then on the end of the tether

../_images/widget_stack_editor_2.png

After a tether is defined, the view will update showing the location of the tether and the image rotated such that the tether is horizontal.

To crop an image, right-click and drag a rectangle around the region of interest. Once the rectangle is defined, you can edit the shape by right-clicking and dragging the various handles.

../_images/widget_stack_editor_3.png

You can also use the mouse wheel to scroll through the individual frames (if using Jupyter Lab, hold Shift while scrolling).

Note that crop_and_rotate() accepts all of the arguments that can be used for plot().

To obtain a copy of the edited ImageStack object, use:

plt.figure()
new_stack = editor.image
new_stack.plot()
new_stack.plot_tether()
../_images/widget_stack_editor_4.png

8.4. Kymotracking

Note

For details of the tracking algorithms and downstream analyses see the Kymotracking tutorial.

For tracking binding events on a kymograph, using the track_greedy() algorithm purely by function calls can be challenging if not all parts of the kymograph look the same or when the signal to noise ratio is somewhat low. To help with this, we included a kymotracking widget that can help you track subsections of the kymograph and iteratively tweak the algorithm parameters as you do so. You can open this widget by creating a KymoWidgetGreedy as follows:

file = lk.File("test_data/kymo.h5")
kymo = file.kymos["16"]
kymowidget = lk.KymoWidgetGreedy(kymo, "green", axis_aspect_ratio=2)
../_images/kymotracker.png

Here we see the optional axis_aspect_ratio argument that allows us to control the aspect ratio of the plot and how much data is visible at a given time. You can easily pan horizontally by clicking and dragging left or right.

You can optionally also pass algorithm parameters when opening the widget:

lk.KymoWidgetGreedy(kymo, "green", axis_aspect_ratio=2, min_length=4, pixel_threshold=9, window=6, sigma=0.2, correct_origin=True)
../_images/kymotracker2.png

Warning

When saving tracks to a file, the widget samples a region around the tracked lines to obtained a summed intensity. Prior to version 1.1.0 the method sample_from_image() had a bug that assumed the origin of a pixel to be at the edge rather than the center of the pixel. Consequently, the sampled window could frequently be off by one pixel. To get the correct behavior and silence this warning, specify correct_origin=True when opening the kymotracking widget. The old (incorrect) behavior is maintained until the next major release to ensure backward compatibility. To silence this warning use correct_origin=False.

You can also change the range of each of the algorithm parameter sliders. To do this, simply pass a dictionary where the key indicates the algorithm parameter and the value contains its desired range in the form (minimum bound, maximum bound). For example:

widget = lk.KymoWidgetGreedy(
    kymo,
    "green",
    axis_aspect_ratio=2,
    min_length=4,
    pixel_threshold=9,
    slider_ranges={"window": (0, 20)},
    window=20,
)
../_images/kymotracker3.png

You can perform tracking by clicking the Track all button.

../_images/kymotracker4.png

Detected tracks are accessible through the tracks property:

>>> print(kymowidget.tracks)
KymoTrackGroup(N=132)

For more information on its use, please see the example Analyzing Cas9 binding to DNA.

8.5. Migrating old track files

Prior to pylake 1.2.0, tracks saved from the widget or API did not store the minimum length in the CSV file. This means that certain downstream analyses are not possible. To update a file with tracks to the new format, load the file in the widget, then filter the tracks by length using the largest minimum length you have used while tracking. You can do this as follows:

filtered_tracks = lk.filter_tracks(widget.tracks, minimum_length=4)
filtered_tracks.save("fixed_file.csv")

where 4 should be replaced with a length relevant for your tracking settings.