Skip to content

User Manual for Signal Manipulation with FMKit

This user manual demonstrates the basic usage of the FMKit code library on signal manipulation.

Requirement

The FMKit code library requires the following software packages

  • Python 3 (tested with Python 3.6.9)
  • NumPy (tested with NumPy 1.19.5)
  • Matplotlib (tested with 3.1.2)

Installation

The code and data are released on GitHub. Please download the code and the data and unzip them to maintain the folowing folder structure.

workspace_fmkit
    ├── code_fmkit
    │   ├── fmsignal.py
    │   ├── fmsingal_vis.py
    │   ├── fmsignal_demo.py
    │   ├── ...(other code)...
    │   ├── my_code.py
    │   └── ...
    └── data_demo
        ├── glove_pp
        │   ├── alice_FMKit_0.csv
        │   ├── alice_FMKit_0.npy
        │   └── ...
        ├── glove_raw
        │   ├── alice_FMKit_0.txt
        │   └── ...
        ├── leap_pp
        │   ├── alice_FMKit_0.csv
        │   ├── alice_FMKit_0.npy
        │   └── ...
        └── leap_raw
            ├── alice_FMKit_0.txt
            └── ...

All example signals used in this manual are in the "data_demo" folder. These files are packaged in a single zip file on the GitHub repository and they must be unzipped in this folder structure.

The following three python modules are demonstrated in this tutorial.

To use this code library, please download the Python modules under the "code_fmkit" folder and incorporate into your project. A simple way to try the code in this manual is creating a file "my_code.py" under the folder "code_fmkit", import everything as follows, and run it.

from fmsignal import *
from fmsignal_vis import *
from fmsignal_demo import *

# Put the demo code in this manual here.

Note that the signal files for the same user writing the same content have the same file names, regardless of the data capture devices or whether they are raw signals or preprocessed signals. They are only differentiated by the folders, and hence, maintaining the correct folder structure is important for the demo.

Signal I/O

This section demonstrates preprocessed signal input/output, raw signal input/ouput, and raw signal preprocessing.

Preprocessed Signal I/O

A preprocessed signal can be constructed from a file as follows.

fn = '../date_demo/leap_pp/alice_FMKit_0'
signal = FMSignal.construct_from_file(fn, mode='csv')
This load the file "alice_FMKit_0.csv" under the folder "date_demo/leap_pp".

A signal can also be constructed using a different file format as follows.

fn = '../date_demo/leap_pp/alice_FMKit_0'
signal = FMSignal.construct_from_file(fn, mode='npy')
This load the file "alice_FMKit_0.npy" under the folder "date_demo/leap_pp".

These two signals are identical in memory. These files can have two different "mode", i.e., "csv" for the Comma Separated Value format, and "npy" for the NumPy binary format. The "csv" format is human readable and programming language agnostic, while the "npy" format is only designed to work with Python and NumPy. Loading the files in the "npy" format is in general faster than those in the "csv" format. Note that the file name does not need to specify the ".csv" or ".npy" extension. They are attached automatically by specify the "mode".

A signal has the three attributes "user", "cid", and "seq" to indicate the user who create this signal, the content ID, and the sequence number. They can be set when a signal is constructed from a file as follows.

fn = '../date_demo/leap_pp/alice_FMKit_0'
signal = FMSignal.construct_from_file(fn, mode='csv', user='alice', cid='FMKit', seq=0)
If these attributes are not specified at loading, by default, "user" is an empty string, "cid" is also an empty string, and "seq" is zero.

A signal can also be saved to a file as follows.

fn = '../date_demo/leap_pp/temp/alice_FMKit_0'
signal.save_to_file(fn, mode='csv')
This creates the file "alice_FMKit_0.csv" containing the signal data under the folder "date_demo/leap_pp/temp".

A signal can also be saved using a different file format as follows.

fn = '../date_demo/leap_pp/temp/alice_FMKit_0'
signal.save_to_file(fn, mode='npy')
This creates the file "alice_FMKit_0.csv" containing the signal data under the folder "date_demo/leap_pp/temp".

The signals are writtent to the folder "date_demo/leap_pp/temp" to avoid overwriting the original signal files. Note that the file name does not need to specify the ".csv" or ".npy" extension. They are attached automatically by specify the "mode".

As explained in "The FMKit Architecture" document, preprocessed signals are device agnostic and they have a unified format. Although the previous demo only shows those signals generated using the Leap Motion controller device, they can be easily modified to show those signals generated using the data glove device, as follows.

fn_load = '../date_demo/glove_pp/alice_FMKit_0'
signal = FMSignal.construct_from_file(fn_load, mode='csv')
fn_save = '../date_demo/glove_pp/temp/alice_FMKit_0'
signal.save_to_file(fn_save, mode='npy')
This loads the file "alice_FMKit_0.csv" under the folder "date_demo/glove_pp" and saves it to the file "alice_FMKit_0.npy" under the folder "date_demo/glove_pp/temp"

Raw Signal I/O

A raw signal generated using the Leap Motion controller can also be constructed in a similar way as preprocessed signals.

fn = '../date_demo/leap_raw/alice_FMKit_0'
raw_signal_leap = FMSignalLeap.construct_from_file(fn, mode='raw_csv')
This load the file "alice_FMKit_0.csv" under the folder "date_demo/leap_raw".

Similarly, a raw signal generated using the Leap Motion controller can also be constructed using a different file format as follows.

fn = '../date_demo/leap_raw/alice_FMKit_0'
raw_signal_leap = FMSignalLeap.construct_from_file(fn, mode='raw_npy')
This load the file "alice_FMKit_0.npy" under the folder "date_demo/leap_raw".

Note that the two file "modes" are "raw_csv" and "raw_npy" but the actual file extensions are just ".csv" and ".npy". This is intentional to avoid mistakes of using raw signals as preprocessed signals. The file extensions are attached automatically, similar to the preprocessed signals.

Besides these two formats, there is another "mode" named "raw_internal", which has the file extension ".txt". Files in this format are generated directly by our client software. There are several versions of the format with small variations, which complicates the code. Hence, they are generally only for internal usage. All released datasets only contains ".csv" or ".npy" files. Still, a raw signal generated using the Leap Motion controller can be constructed using the "raw_internal" mode as follows.

fn = '../date_demo/leap_raw/alice_FMKit_0'
raw_signal_leap = FMSignalLeap.construct_from_file(fn, mode='raw_interal')
This load the file "alice_FMKit_0.txt" under the folder "date_demo/leap_raw".

These signals are identical in memory. They are only different in the formats when saved in files.

A raw signal generated using the Leap Motion controller can be saved to a file as follows.

fn = '../date_demo/leap_raw/temp/alice_FMKit_0'
raw_signal_leap.save_to_file(fn, mode='raw_csv')
This creates the file "alice_FMKit_0.csv" containing the signal data under the folder "date_demo/leap_raw/temp".

A raw signal generated using the Leap Motion controller can also be saved using a different file format as follows.

fn = '../date_demo/leap_raw/temp/alice_FMKit_0'
raw_signal_leap.save_to_file(fn, mode='raw_npy')
This creates the file "alice_FMKit_0.csv" containing the signal data under the folder "date_demo/leap_raw/temp".

Note that a raw signal cannot be saved in the "raw_internal" format.

Besides the Leap Motion controller, raw signals can also generated using our data glove device. This kind of signals can be load in a similar way as follows

fn = '../date_demo/glove_raw/alice_FMKit_0'
raw_signal_glove = FMSignalGlove.construct_from_file(fn, mode='raw_interal')
This load the file "alice_FMKit_0.txt" under the folder "date_demo/glove_raw". The "mode" can be "raw_csv", "raw_npy", or "raw_internal", which is the same as the signals generated by the Leap Motion controller.

Similarly, they can be saved to files as follows.

fn = '../date_demo/glove_raw/temp/alice_FMKit_0'
raw_signal_glove.save_to_file(fn, mode='raw_csv')
This creates the file "alice_FMKit_0.csv" containing the signal data under the folder "date_demo/glove_raw/temp". The "mode" can also be "raw_npy".

Raw Signal Preprocessing

Although the raw signals from two different devices are fundamentally different, both types of signals can be preprocessed to generate the signals in the same format, as explained in "The FMKit Architecture" document.

A raw signal obtained using the Leap Motion controller device can be preprocessed as follows.

signal_leap_pp = raw_signal_leap.preprocess(point='tip')
Here the "point" parameter indicates which point on the hand is used, either the tip of the index finger (i.e., "tip") or the center of the hand (i.e., "center"). After preprocessing, a new signal is returned, and the original raw signal is also modified.

For a raw signal obtained using the data glove, the preprocessing is similar as follows. A raw signal obtained using the Leap Motion controller device can be preprocessed as follows.

signal_glove_pp = raw_signal_glove.preprocess(point='tip')
Here the "point" parameter has the same meaning and it can be either "tip" or "center".

Currently, the only usage of a raw signal (i.e., an instance of FMSignalLeap or FMSignalGlove) is to run the preprocessing procedure to obtain a preprocessed signal (i.e, an instance of FMSignal). There are other parameters can be set to control the behavior of the preprocessing procedure. See the code reference (FMSignalLeap.preprocess() or FMSignalGlove.preprocess()) for further details.

Helper Functions For Demo

Since signal I/O is frequently used in the demo, a few helper functions are provided to make the demo code short and succinct. These helper functions are defined in the "fmsignal_demo.py". They also assume the file name has the format of "user_cid_seq.csv" or "user_cid_seq.npy", and the folder structures has the format specified at the beginning of this tutorial.

The following global variables are defined for convenience, and they are used to specify the default values for the parameters of these helper functions.

FOLDER = '../data_demo'
USER = "alice"
#USER = "bob"
CID = 'FMKit'
#CID = '123456'
PP_MODE_LOAD = 'npy'
PP_MODE_SAVE = 'npy'
RAW_MODE_LOAD = 'raw_internal'
RAW_MODE_SAVE = 'raw_npy'

LEAP_PP_SUBFOLDER = 'leap_pp'
GLOVE_PP_SUBFOLDER = 'glove_pp'
LEAP_TEMPLATE_SUBFOLDER = 'leap_template'
GLOVE_TEMPLATE_SUBFOLDER = 'glove_template'
LEAP_RAW_SUBFOLDER = 'leap_raw'
GLOVE_RAW_SUBFOLDER = 'glove_raw'

DEVICE = 'leap'
#DEVICE = 'glove'

A single preprocessed signal for demo can be loaded as follows.

signal = load_one_demo_signal_pp()
This loads the file "alice_FMKit_0.npy" from the folder "date_demo/leap_pp" or "date_demo/glove_pp" depending on the "device" parameter, which can be either "leap" or "glove".

A specific signal can be loaded by setting the parameter to something different from the default values as follows.

signal = load_one_demo_signal_pp(device='glove', user='bob', cid='123456', seq=5, mode='csv')
This loads the file "bob_123456_5.csv" from the folder "data_demo/glove_pp". By default, the "device" is "leap", the "user" parameter is "alice", the "cid" parameter is "FMKit", the "seq" parameter is 0, the "mode" parameter is "npy". This function constructs the file name according to these parameters.

A collection of signals for demo can be loaded as follows.

signals = load_demo_signals_pp()
This loads ten files, from "alice_FMKit_0.npy" to "alice_FMKit_9.npy", all in the folder "data_demo/leap_pp", and it returns a list of ten signals.

A specific collection of signals can be loaded by setting the parameters to something different from the default values as follows.

signals = load_demo_signals_pp(device='glove', user='bob', cid='123456', sequences=[5, 6], mode='csv')
This loads two files, i.e., "bob_123456_5.csv" and "bob_123456_6.csv", all in the folder "data_demo/glove_pp", and returns a list of two signals.

A signal can be saved at a certain place as follows.

save_one_demo_signal_pp(signal)
This save the signal in the folder "data_demo/leap_pp/temp" and it automatically generates the file name as "user_cid_seq.csv" using the signal attributes. The parameter "device" can be specified to change the target folder. Note that this function can not distinguish the signals from different devices. Users are required to pay attention to save the signal at the correct folder. For example, if the signal is obtained from the data glove device, use save_one_demo_signal_pp(signal, device='glove') instead.

A collection of signals can be saved at a certain place as follows.

save_demo_signals_pp(signals)
This saves each signal in the folder "data_demo/leap_pp/temp" and it automatically generates the file name for each signal. The parameter "device" can be specified to change the target folder.

For raw signal I/O, there are similar helper functions as follows.

signal_raw = load_one_demo_signal_raw()
This loads the file "alice_FMKit_0.txt" from the folder "date_demo/leap_raw". The parameter "device" can be specified to change the folder accordingly. By default it uses the "raw_internal" mode.

signals_raw = load_demo_signals_raw()
This loads ten files, from "alice_FMKit_0.txt" to "alice_FMKit_9.txt", all in the folder "date_demo/leap_raw", and returns a list of ten signals.

save_one_demo_signal_raw(signal)
This save the signal in the folder "data_demo/leap_pp/temp" and it automatically generates the file name as "user_cid_seq.csv" using the signal attributes. The parameter "device" can be specified to change the folder accordingly.

A collection of signals can be saved at a certain place as follows.

save_demo_signals_raw(signals)
This saves each signal in the folder "data_demo/leap_pp/temp" and it automatically generates the file name for each signal. The parameter "device" can be specified to change the folder accordingly.

Note that different signals from different devices are only distinguished by the corresponding folders. Similarly, preprocessed signals and raw signals are only distinguished by the corresponding folders. Users need to pay attention to specify the correct folder when loading and saving signals.

Signal Visualization

This section demonstrates visualization of preprocessed signals. All functions related to visualization are provided in the "fmsignal_vis.py" module. Note that only preprocessed signals can be visualized using these methods (raw signals do not follow the format of 18 sensor axes, and hence, they cannot be visualized in this way). Also, preprocessed signals are sensor agnostic, i.e., the visualization functions does care whether they are obtained from raw signals obtain by the Leap Motion controller device or the data glove device.

Visualization of One Signal

A signal can be visualized as follows.

signal = load_one_demo_signal_pp(device)
signal_vis(signal)
This plots the signal data for all 18 sensor axes, as shown in the following figure.

Signal visualization.

The edges of each plot are colored to indicate the sensor axes groups as follows.

  • black - position
  • red - velocity
  • green - linear acceleration
  • blue - orientation
  • cyan - angular speed
  • magenta - angular acceleration

Within each color group, the three axes are in the order x-y-z.

If there is no need to plot all sensor axes, a subset can be visualized as follows.

signal = load_one_demo_signal_pp(device='leap')
signal_vis(signal, start_col=9, nr_cols=3)
This only plots the three axes of orientation, as shown in the following figure.

Signal visualization with selected axes.

The "start_col" means the starting index of the sensor axis (i.e., columns). Note that both "start_col" and "nr_cols" must be multiples of three, i.e., the plot always bundles three axes of one specific type of physical states in the x-y-z directions together. The plot is also colored accordingly.

The signal data for all sensor axes can also be visualized in a compact way as follows.

signal = load_one_demo_signal_pp(device='leap')
signal_vis_compact(signal)
This plots the signal data in bundles of three axes, as shown in the following figure.

Signal visualization in a compact way.

The edges of each plot are colored accordingly. Within a plot, the RGB colors of the lines map to the sensor axes x-y-z.

Signal Comparison

Two signals can be visualized in the same plot for comparison as follows.

signal_0 = load_one_demo_signal_pp(device='leap', seq=0)
signal_1 = load_one_demo_signal_pp(device='leap', seq=1)
signal_vis_comparison([signal_0, signal_1], start_col=9, nr_cols=3)
This compares the data of two signals in the orientation axes. The result is shown in the following figure.

Signal comparison.

Here, the blue lines are the "signal_0" and the orange lines are the "signal_1". Since they are generated by the same person (i.e., "alice") writing the same content (i.e., "FMKit"), the two signals are similar in the visualized sensor axes. The plot is also colored according to the sensor axes groups.

Similarly, multiple signals can be compared together as follows.

signals = load_demo_signals_pp(device='leap')
signal_vis_comparison(signals, start_col=9, nr_cols=3)
This compares ten signals in the orientation axes. The result is shown in the following figure.

Comparison of ten signals.

Here, each signal has its own color. They are also generated by the same person (i.e., "alice") writing the same content (i.e., "FMKit"). This plot shows the inherent "fuzziness" in the in-air-handwriting, i.e., even if the same person write the same content multiple times, there are variations in the writing speed and intensity of each stroke. Hence, alignment is needed (see the section "Signal Alignment and Template")

Trajectory Visualization

This section demonstrates visualization of trajectories for preprocessed signals. This function works for both devices. It also works for both raw signals and preprocessed signals. However, the trajectory of a signal obtained by the data glove device is generally illegible.

Visualization of One Trajectory

The trajectory of one signal can be visualized as follows.

signal = load_one_demo_signal_pp(device='leap')
trajectory_vis(signal)
This shows the trajectory of a point on the hand (either "tip" or "center", depending on the preprocessing). The result is shown in the following figure.

Trajectory visualization of a signal in 3D (Leap Motion device).

The view angles are kept moving intentionally to show the 3D structure in this figure. This signal is obtained by the user "alice" writing the string "FMKit" using the Leap Motion controller device. Note that this user intentionally wrote it in a legible way to facilitate this visualization. Also, the Leap Motion controller device directly records the 3D position of the joints on the hand with an infrared stereo camera.

The orientation of the finger can also be shown together with the trajectory as follows.

signal = load_one_demo_signal_pp(device='leap')
trajectory_vis(signal, show_local_axes=True, interval=10)
The result is shown in the following figure.

Trajectory visualization of a signal in 3D with local axes (Leap Motion device).

The line segments indicates the axes of the local finger orientation where the RGB colors correspond to x-y-z. The "interval=10" parameter means for every 10 samples of the time series, the local axes are plot once.

Trajectory Comparison

Two trajectories from two signals can be visualized together for comparison as follows.

signal_0 = load_one_demo_signal_pp(device='leap', seq=0)
signal_1 = load_one_demo_signal_pp(device='leap', seq=1)
trajectorie_vis_comparison([signal_0, signal_1])
The result is shown in the following figure.

Trajectory comparison.

Since these two signals are generated by the same person (i.e., "alice") writing the same content (i.e., "FMKit"), the trajectories are close to each other.

Similarly, more trajectories can be visualized together as follows.

signals = load_demo_signals_pp(device='leap')
trajectorie_vis_comparison(signals)
The result is shown in the following figure.

Trajectory comparison with 10 signals.

Each trajectory has its own color. Since they are all generated by the same person (i.e., "alice") writing the same content (i.e., "FMKit"), the trajectories are all similar in shape. Note that the preprocessing procedure normalize the pose of the in-air-handwriting, and hence, the relative pose between the in-air-handwriting and the sensor does not matter.

Trajectory Animation

As the in-air-handwriting signal is a time series of physical states of the hand, the process of generating the trajectory can be reproduced using the signal data as follows.

signal = load_one_demo_signal_pp(device='leap')
trajectory_animation(signal, seg_length=30)
This generates the following animation.

Trajectory animation.

Essentially, this function plot the trajectory of a signal segment in a sliding-forward window. The parameter "seg_length" controls the length of the signal segment. If it is less or equal to zero, the whole signal is plotted (by default it is zero). This is designed to facilitate the visual confirmation of the signal content for those users who write all letters in the same place.

Trajectory and Hand Geometry Animation

If a signal is obtained using the Leap Motion controller device, the raw signal contains the position of each joint on the hand, and the hand geometry can be animated together with the trajectory as follows.

raw_signal = load_one_demo_signal_raw(device='leap')
raw_signal.preprocess(point='tip')
trajectory_animation(raw_signal, seg_length=30)
Note that the raw signal still needs preprocessing for pose normalization and filtering (which is essential to generate the "trajectory" attribute and the "joints" attribute). The hand geometry plot works only for raw signals from the Leap Motion controller device. It does not work for preprocessed signals or raw signals from the data glove (because they do not have the "joints" attribute). This generates the following animation.

Trajectory animation with Hand geometry.

This plots the position of the joints for each data sample at a time and make an animation.

Orientation Animation

Since the data glove device can only obtain angular speed and linear acceleration directly, the trajectory of a signal obtained by the data glove device is derived indirectly (usually with some regularization to prevent the unbounded accumulation of errors in the dead reckoning procedure). This trajectory does not represent the actual trajectory of the hand, and this usually means that the visualization of such a "trajectory" is usually not legible.

To check the signals from the data glove in a human understandable way, the orientation can be animated as follows.

raw_signal = load_one_demo_signal_raw(device='glove', cid='123456')
raw_signal.preprocess(point='tip')
orientation_animation(raw_signal, seg_length=20)
The result is shown as follows.

Orientation animation with a signal from the data glove device.

This plots the x-y-z axes of each sample in an animation to show the orientation of the point on the hand. The animation also plots the trajectory of the end point of the x-axis. The signal is obtained by the user "alice" writing the string "123456". Clearly a person can visually confirm the writing content is "123456" with this animation. However, not every user write strings in this way. Some user will maintain the pointing direction of the finger while moving the arm as a whole in the air to write like on an invisible wall.

Although it is designed mainly for signals obtained from the data glove device, this function works for both devices. It also works for both raw signals and preprocessed signals.

Signal Alignment and Template

This section demostrates signal alignment with the Dynamic Time Warp (DTW) algorithm.

Signal Alignment Visualization

A preprocessed signal can be aligned to another preprocessed signal using DTW as follow.

signal_0 = load_one_demo_signal_pp(device='leap', seq=0)
signal_1 = load_one_demo_signal_pp(device='leap', seq=1)

signal_0.amplitude_normalize()
signal_1.amplitude_normalize()

signal_1_aligned = signal_1.align_to(signal_0)

aligned = [signal_0, signal_1_aligned]
unaligned = [signal_0, signal_1]

signal_vis_comparison_side_by_side(aligned, unaligned, start_col=0, nr_cols=9)
The difference before the alignment and after the alignment is shown in the following figure.

Signal alignment example.

These two signals are generated by the same user (i.e., "alice") writing the same content (i.e., "FMKit"). As mentioned previously in the "Signal Comparison" subsection, even for signals generated by the same user writing the same content, there are differences, mainly in the writing speed and the intensity of strokes. As the figure shows, these differences can be reduced by aligning one signal to the other signal along the time using DTW.

The function "signal_vis_comparison_side_by_side()" is designed to plot two sets of signals side by side on two columns for comparison.

Warping Path Visualization

The detailed warping path of the alignment of on signal to another signal (or a template) can be visualized as follows.

signal_0 = load_one_demo_signal_pp(device='leap', seq=0)
signal_1 = load_one_demo_signal_pp(device='leap', seq=1)

signal_0.amplitude_normalize()
signal_1.amplitude_normalize()

signal_1_aligned = signal_1.align_to(signal_0, keep_dist_matrix=True)

alignment_vis(signal_1, signal_0, signal_1_aligned, col=9)
The result is shown in the following figure.

Warping path example.

The subplot named "template" means the template signal (i.e., "signal_0" in this demo), and the subplot named "signal" is the signal that is aligned to the template signal (i.e., "signal_1" in this demo). This figure essentially shows the DTW distance matrix (i.e., the "dist_matrix" attribute of the "signal_1_aligned") together with the two signals. The specific sensor axis representing the signal and the template can be selected by setting the "col" parameter to the sensor axis index.

The warping path can also be visualized in 3D as follows.

signal_0 = load_one_demo_signal_pp(device='leap', seq=0)
signal_1 = load_one_demo_signal_pp(device='leap', seq=1)

signal_0.amplitude_normalize()
signal_1.amplitude_normalize()

signal_1_aligned = signal_1.align_to(signal_0, keep_dist_matrix=True)

alignment_vis(signal_1, signal_0, signal_1_aligned, plot_3d=True)
The result is shown in the following figure.

Warping path example.

This 3D visualization plot the DTW distance matrix in 3D.

These two signals are generated by the same user (i.e., "alice") writing the same content (i.e., "FMKit"), and this user can maintain the same writing behavior very well across multiple repetitions. Hence, these two signals can be alsigned very well and the warping path is close to the diagonal line. In fact, any signal can be aligned to another signal using DTW regardless whether they are generated by the same user or writing the same content. The warping path can vary significantly based on the shapes of the two signals.

Template Visualization

For multiple repetitions of the in-air-handwriting of the same content by the same user, they can be all aligned to one signal and to construct a template by taking the average of them as follows.

signals = load_demo_signals_pp(device=device, user=user, cid=cid)

template = FMSignalTemplate.construct_from_signals(signals, template_index=0)

signals_alinged = template.signals_aligned

signal_vis_comparison_side_by_side(signals_alinged, signals, start_col=0, nr_cols=9)
signal_vis_comparison_side_by_side(signals_alinged, [template], start_col=0, nr_cols=9)
This will first show the comparison of ten aligned signals and the original ones as follows. The aligned signals are on the left and the original ones are on the right.

Ten signals aligned.

After closing the first plot, the aligned signals and the template are shown in the following figure.

Template.

The function "FMSignalTemplate.construct_from_signals()" does alignment and template construction. The "template_index" parameter indicate which signal in the set is used as the "base signal" so that other signals are aligned to it. A template, i.e., an instance of the "FMSignalTemplate", is essentially the same as a preprocessed signal, only with an additional attribute to keep the variations of the original signals.

A template can be saved to a file as follows, which is the same as saving a signal. The mode can be either "csv" or "npy".

fn = '../date_demo/leap_template/alice_FMKit_0'
template(fn, mode='csv')

A template can also be loaded from a file as follows, which is also the same as loading a signal.

fn = '../date_demo/leap_template/alice_FMKit_0'
template = FMSignalTemplate.construct_from_file(fn, mode='csv')

A template also has the "user" attribute, the "cid" attribute, and the "seq" attribute. If a template is constructed from a set of signals, its "user" and "cid" attributes are copied from the "base signal" indicated by the "template_index" parameter in "FMSignalTemplate.construct_from_signals()". The "seq" attribute is always set to 0.

The demo also provide two helper functions as follows, one for loading, and the other for saving.

template = load_one_demo_template()
save_one_demo_template(template)

Improving DTW Efficiency

The default DTW implementation is in Python, which is relatively inefficient because it will iterate through the two signal data and the n-by-m DTW distance matrix (which are all NumPy ndarrays). To improve the efficiency, a version implemented in C is provided under the "code_utilities" folder. It can be installed as follows.

# cd code_utilities
# python3 ./setup.py build
# sudo python3 setup.py install --record ./files.txt

If the build step cannot find numpy header files, run the following first.

# export CFLAGS="-I $HOME/.local/lib/python3.6/site-packages/numpy/core/include $CFLAGS"
Here the path after "-I" is the place where NumPy is installed.

This will install a package named "fmkit_utilities" which introduces a function "fmkit_utilities.dtw_c()". It will be shown as a "built-in" function because it is implemented in C code. The "fmsignal.py" module has a Python wrapper function "dtw_c()" that calls the C implementation. This wrapper function takes the exact same format of parameters as the default Python implementation.

The "fmsignal.py" module will use the C implementation automatically if the "fmkit_utilities" package is installed. This is made using the following code at the beginning of the module.

try:
    import fmkit_utilities
    DTW_METHOD = "c"
except ImportError:
    DTW_METHOD = "python"

The only method that invokes "dtw()" or "dtw_c()" is "FMSignal.align_to()", i.e., aligning on signal to another signal or template. It takes a parameter named "method", which is set to "DTW_METHOD" by default. Users can choose which implementations to use as follows.

signal_aligned = signal.align_to(template, method="python") # This uses the Python implementation.
Or
signal_aligned = signal.align_to(template, method="c") # This uses the C implementation.

Note that the C implementation is kind of "fragile", and hence, it is recommended to always use the wrapper "FMSignal.dtw_c()" instead of directly using the "fmkit_utilities.dtw_c()".