Simple walkthrough

The heart of the DDM code (found in the ddm_calc.py file) is the computation of the image structure function. This is found by taking the average of the Fourier transforms of all image differences. By “image differences,” I mean the result of subtracting two images separated by a given lag time \(\Delta t\).

To describe the process mathematically, we find the difference between images separated by some lag time \(\Delta t\):

\[\Delta I = I(x,y;t) - I(x,y;t + \Delta t)\]

For a given \(\Delta t\) all such image differences are calculated. We then Fourier transform each \(\Delta I\) and average all of the same \(\Delta t\).

This results in the image structure function \(D(q_x,q_y,\Delta t)\).

Importing the necessary modules

Modules for plotting

We use matplotlib for creating figures and plots. Note that we use:

%matplotlib inline

This sets the backend of matplotlib to inline which means the plots are included in the notebook. If you want the plots to also be interactive (e.g., having the ability to zoom, scroll, and save) then use:

%matplotlib notebook
[1]:
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt

Modules for numerical work

Here, we import numpy and xarray.

Note that xarray might not have come with your Anaconda Python distribution (or whichever other distribution you installed). If that is the case, you’ll need to install this package.

[2]:
import numpy as np #numerical python used for working with arrays, mathematical operations
import xarray as xr #package for labeling and adding metadata to multi-dimensional arrays

Import the PyDDM package

Make sure you append to sys.path the directory containing the PyDDM code.

[3]:
import sys
sys.path.append("../../PyDDM") #must point to the PyDDM folder
import ddm_analysis_and_fitting as ddm

Initializing DDM_Analysis class and computing the DDM matrix

The instance of the DDM_Analysis class we create will need, when initialized, metadata about the images to analyze and the analysis and fitting parameters. This can be done by using a yaml file as shown in the following cell of code (there, the metadata is saved in the file “example_data_silica_beads.yml”. But one can also initialize DDM_Analysis with a dictionary containing the necessary metadata. One way to create such a dictionary and then using it to initialize DDM_Analysis is shown below.

import yaml
ddm_analysis_parameters_str = """
DataDirectory: 'C:/Users/rmcgorty/Documents/GitHub/DDM-at-USD/ExampleData/'
FileName: 'images_nobin_40x_128x128_8bit.tif'
Metadata:
  pixel_size: 0.242 # size of pixel in um
  frame_rate: 41.7 #frames per second
Analysis_parameters:
  number_lag_times: 40
  last_lag_time: 600
  binning: no
Fitting_parameters:
  model: 'DDM Matrix - Single Exponential'
  Tau: [1.0, 0.001, 10]
  StretchingExp: [1.0, 0.5, 1.1]
  Amplitude: [1e2, 1, 1e6]
  Background: [2.5e4, 0, 1e7]
  Good_q_range: [5, 20]
  Auto_update_good_q_range: True
"""
parameters_as_dictionary = yaml.safe_load(ddm_analysis_parameters_str)
ddm_calc = ddm.DDM_Analysis(parameters_as_dictionary)
[4]:
#The yaml file `example_data_silica_beads.yml` contains the metadata and parameters above
ddm_calc = ddm.DDM_Analysis("../../Examples/example_data_silica_beads.yml")
Provided metadata: {'pixel_size': 0.242, 'frame_rate': 41.7}
Image shape: 3000-by-128-by-128
Number of frames to use for analysis: 3000
Maximum lag time (in frames): 600
Number of lag times to compute DDM matrix: 40

Below, with the method calculate_DDM_matrix, we compute the DDM matrix and some associated data. The data will be stored as an xarray Dataset as an attribute to ddm_calc called ddm_dataset.

Note: There are a few optional arguments we can pass to calculate_DDM_matrix. There is an optional argument quiet (True or False, False by default). Then we have some optional keyword arguments (all of which could also be specified in the YAML file). These are: overlap_method which sets the degree of overlap between image pairs when finding all image differences for a each lag time and is either 0, 1, 2, or 3, background_method which sets how to estimate the background parameter B and is either 0, 1, 2, or 3, and number_lag_times. If any of these three keyword arguments are set here, the value specified in the YAML file will be overwritten.

[5]:
#If a file is found that matches what, by default, the program will save the computed DDM matrix as,
#then you will be prompted as to whether or not you want to proceed with this calculation. If you
#do not, then enter 'n' and the program will read from the existing file to load the DDM dataset
#into memory. If you enter 'y', then the DDM matrix will be calculated.

ddm_calc.calculate_DDM_matrix()
2022-02-10 13:11:37,747 - DDM Calculations - Running dt = 1...
2022-02-10 13:11:44,311 - DDM Calculations - Running dt = 5...
2022-02-10 13:11:48,710 - DDM Calculations - Running dt = 9...
2022-02-10 13:11:52,546 - DDM Calculations - Running dt = 16...
2022-02-10 13:11:55,853 - DDM Calculations - Running dt = 27...
2022-02-10 13:11:59,025 - DDM Calculations - Running dt = 47...
2022-02-10 13:12:02,002 - DDM Calculations - Running dt = 81...
2022-02-10 13:12:04,828 - DDM Calculations - Running dt = 138...
2022-02-10 13:12:07,484 - DDM Calculations - Running dt = 236...
2022-02-10 13:12:09,998 - DDM Calculations - Running dt = 402...
DDM matrix took 34.66797375679016 seconds to compute.
 Background estimate ± std is 211.17 ± 1.49
[5]:
<xarray.Dataset>
Dimensions:           (lagtime: 40, q_y: 128, q_x: 128, q: 64, y: 128, x: 128, frames: 40)
Coordinates:
  * lagtime           (lagtime) float64 0.02398 0.04796 0.07194 ... 12.59 14.36
    framelag          (frames) int32 1 2 3 4 5 6 7 ... 308 352 402 459 525 599
  * q_y               (q_y) float64 -12.98 -12.78 -12.58 ... 12.37 12.58 12.78
  * q_x               (q_x) float64 -12.98 -12.78 -12.58 ... 12.37 12.58 12.78
  * q                 (q) float64 0.0 0.2028 0.4057 0.6085 ... 12.37 12.58 12.78
  * y                 (y) int32 0 1 2 3 4 5 6 7 ... 121 122 123 124 125 126 127
  * x                 (x) int32 0 1 2 3 4 5 6 7 ... 121 122 123 124 125 126 127
Dimensions without coordinates: frames
Data variables:
    ddm_matrix_full   (lagtime, q_y, q_x) float64 194.5 183.5 ... 192.0 196.8
    ddm_matrix        (lagtime, q) float64 0.0 294.2 321.4 ... 207.8 201.1 200.4
    first_image       (y, x) float64 128.0 149.0 173.0 ... 175.0 229.0 215.0
    avg_image_ft      (q) float64 0.0 1.293e+05 5.225e+03 ... 105.3 104.7 105.3
    num_pairs_per_dt  (lagtime) int32 2999 2998 2997 1498 1498 ... 20 17 15 13
    B                 float64 211.2
    B_std             float64 1.491
    Amplitude         (q) float64 -211.2 2.585e+05 1.024e+04 ... -1.699 -0.52
    ISF               (lagtime, q) float64 0.0 0.9997 0.9892 ... -4.952 -19.73
Attributes: (12/24)
    units:                   Intensity
    lagtime:                 sec
    q:                       μm$^{-1}$
    x:                       pixels
    y:                       pixels
    info:                    ddm_matrix is the averages of FFT difference ima...
    ...                      ...
    split_into_4_rois:       no
    use_windowing_function:  no
    binning:                 no
    bin_size:                2
    central_angle:           no
    angle_range:             no
_images/Walkthrough_-_simple_12_3.png
_images/Walkthrough_-_simple_12_4.png
_images/Walkthrough_-_simple_12_5.png
_images/Walkthrough_-_simple_12_6.png

Initiazing DDM_Fit class and fitting our data to a model

Next, we initialize the DDM_Fit class which will help us fit the calculated DDM matrix to a model of our choosing.

When the DDM_Fit class is initialized, it will display a table of the parameters, with the initial guesses and bounds, that were in the parameters of the yaml file. It will also indicate if, for the chosen model, initial guesses for parameters are missing.

Note: The intial guesses and bounds for the parameters are given in the yaml file. But you may realize that you need to adjust the bounds. You could change the yaml file and reload it. But you can also change the initial guesses and bounds for the parameters as shown in this code snippet:

ddm_fit.set_parameter_initial_guess('Tau', 0.5)
ddm_fit.set_parameter_bounds('Tau', [0.001, 1000])

Also, note that to initialize the DDM_Fit class, we need to pass it the YAML file which contains the metadata and parameters. Below, we use the fact that the path to the YAML file is stored in the variable ddm_calc.data_yaml. But we could also use the path to the YAML file just like we did when initializing the DDM_Analysis class. That is, this would work as well:

ddm_fit = ddm.DDM_Fit("../../Examples/example_data_silica_beads.yml")
[6]:
ddm_fit = ddm.DDM_Fit(ddm_calc.data_yaml)
Initial guess Minimum Maximum
Amplitude 100.0 1.000 1000000.0
Tau 1.0 0.001 10.0
Background 25000.0 0.000 10000000.0
StretchingExp 1.0 0.500 1.1
Loading file C:/Users/rmcgorty/Documents/GitHub/PyDDM/Examples/images_nobin_40x_128x128_8bit_ddmmatrix.nc ...

Below, the fit is performed and saved into an xarray Dataset. We also have a table of the parameters for each q. Since this table may be long (especially if you have a large image size), you might want to not display it. If that’s the case, then run this command with the display_table parmeter set to False, i.e.:

fit01 = ddm_fit.fit(name_fit = 'fit01', display_table=False)
[7]:
#Note that the method `fit` has many optional parameters, but here we
# allow those optional parameter to take their default values

fit01 = ddm_fit.fit(name_fit = 'fit01')
In function 'get_tau_vs_q_fit', using new tau...
Fit is saved in fittings dictionary with key 'fit01'.
q Amplitude Tau Background StretchingExp
0 0.000000 1.000000 10.000000 0.000000 1.100000
1 0.202840 5009.160959 10.000000 125.265537 0.500000
2 0.405681 8482.808495 10.000000 0.000000 0.525256
3 0.608521 10442.590957 3.868067 110.613248 0.710762
4 0.811362 16595.852548 2.556070 83.269636 0.811578
5 1.014202 18774.162108 1.424364 101.316228 0.909133
6 1.217043 20126.206549 1.035536 37.943122 0.911751
7 1.419883 20269.935277 0.742219 30.165661 0.952913
8 1.622723 19408.933976 0.552100 0.000000 0.955882
9 1.825564 21442.103307 0.465083 0.000000 0.977973
10 2.028404 23388.120235 0.396199 0.000000 0.968451
11 2.231245 27490.759703 0.321218 0.000000 1.008676
12 2.434085 33464.835149 0.290591 0.000000 1.014272
13 2.636926 39525.290291 0.248451 0.000000 1.040113
14 2.839766 41793.934306 0.213868 0.000000 1.030231
15 3.042607 42078.124470 0.192926 0.000000 1.030781
16 3.245447 40768.461703 0.172733 0.000000 1.045874
17 3.448287 35721.127412 0.155423 0.000000 1.043916
18 3.651128 28431.332255 0.142240 0.000000 1.043399
19 3.853968 20619.257623 0.128071 0.000000 1.054034
20 4.056809 14370.154840 0.121052 0.000000 1.035008
21 4.259649 9293.652992 0.111708 0.000000 1.025896
22 4.462490 5662.007475 0.102243 0.000000 1.008149
23 4.665330 3487.994425 0.097775 0.000000 0.971809
24 4.868170 2077.865135 0.090379 0.000000 0.913350
25 5.071011 1223.115625 0.089518 83.952238 0.911770
26 5.273851 876.927434 0.083446 84.578516 0.860156
27 5.476692 690.124583 0.081228 107.731737 0.859201
28 5.679532 559.962532 0.074982 89.841197 0.757985
29 5.882373 417.404173 0.077395 113.007189 0.732307
30 6.085213 339.585926 0.074093 121.429878 0.702539
31 6.288053 328.928162 0.063312 104.741958 0.648010
32 6.490894 273.021363 0.065949 123.344201 0.657741
33 6.693734 210.366431 0.069732 138.341989 0.630490
34 6.896575 168.823091 0.078894 151.432022 0.624047
35 7.099415 120.146768 0.096006 168.399637 0.643459
36 7.302256 90.471468 0.114569 179.212366 0.696849
37 7.505096 73.280303 0.143625 183.974447 0.707400
38 7.707937 63.315036 0.145241 186.298290 0.743781
39 7.910777 54.490768 0.146684 188.785276 0.771679
40 8.113617 48.703834 0.161850 189.738060 0.784964
41 8.316458 46.274244 0.157325 189.710146 0.805431
42 8.519298 42.999737 0.150650 188.305785 0.739396
43 8.722139 41.307921 0.160369 188.695165 0.715729
44 8.924979 37.804395 0.155632 188.636134 0.761223
45 9.127820 35.459070 0.163358 188.442035 0.690826
46 9.330660 31.309845 0.158481 191.127615 0.879080
47 9.533500 27.495879 0.159557 190.705808 0.826357
48 9.736341 29.578351 0.164955 189.489710 0.747781
49 9.939181 27.750185 0.173154 190.573335 0.817289
50 10.142022 26.022042 0.169798 190.445764 0.694703
51 10.344862 25.817798 0.149565 188.926426 0.677988
52 10.547703 20.964509 0.171498 192.258742 0.865291
53 10.750543 22.212659 0.186064 191.315356 0.803059
54 10.953383 20.296560 0.175117 191.111831 0.815027
55 11.156224 17.733487 0.177052 191.985542 0.975116
56 11.359064 20.012534 0.174850 190.372072 0.764394
57 11.561905 17.740871 0.167563 190.855691 0.796393
58 11.764745 19.164617 0.177914 190.699793 0.827180
59 11.967586 17.974918 0.176198 190.556119 0.771980
60 12.170426 15.801139 0.197665 191.322769 0.965799
61 12.373267 15.561198 0.167777 190.627761 0.941686
62 12.576107 14.880392 0.194683 191.407404 0.907700
63 12.778947 15.539401 0.183432 190.803455 0.990262

Inspecting the outcome of the fit

We can now take a look at the best fit parameters determined by the fitting code. We can generate a set of plots and have the output saved as PDF with the function fit_report. This takes a few arguments, including: * the result of the fit in an xarray Dataset (here fit01) * q_indices: the DDM matrix or the ISF (whichever was used in the fitting) will be plotted at these \(q\)-values specified by their index in the array of \(q\)-values * forced_qs: range of \(q\)-values (specified, again, by the index) for which to extract power law relationship between the decay time \(\tau\) and \(q\)

[8]:
ddm.fit_report(fit01, q_indices=[3,6,9,22], forced_qs=[4,16], use_new_tau=True, show=True)
In function 'get_tau_vs_q_fit', using new tau...
In hf.plot_one_tau_vs_q function, using new tau...
[8]:
<xarray.Dataset>
Dimensions:          (parameter: 4, q: 64, lagtime: 40)
Coordinates:
  * parameter        (parameter) <U13 'Amplitude' 'Tau' ... 'StretchingExp'
  * q                (q) float64 0.0 0.2028 0.4057 0.6085 ... 12.37 12.58 12.78
  * lagtime          (lagtime) float64 0.02398 0.04796 0.07194 ... 12.59 14.36
Data variables:
    parameters       (parameter, q) float64 1.0 5.009e+03 ... 0.9077 0.9903
    theory           (lagtime, q) float64 0.001311 364.7 349.3 ... 206.3 206.3
    isf_data         (lagtime, q) float64 0.0 0.9997 0.9892 ... -4.952 -19.73
    ddm_matrix_data  (lagtime, q) float64 0.0 294.2 321.4 ... 207.8 201.1 200.4
    A                (q) float64 -211.2 2.585e+05 1.024e+04 ... -1.699 -0.52
    B                float64 211.2
Attributes: (12/18)
    model:                          DDM Matrix - Single Exponential
    data_to_use:                    DDM Matrix
    initial_params_dict:            ["{'n': 0, 'value': 100.0, 'limits': [1.0...
    effective_diffusion_coeff:      0.616760567419975
    tau_vs_q_slope:                 [-1.9799845]
    msd_alpha:                      [1.01010892]
    ...                             ...
    DataDirectory:                  C:/Users/rmcgorty/Documents/GitHub/PyDDM/...
    FileName:                       images_nobin_40x_128x128_8bit.tif
    pixel_size:                     0.242
    frame_rate:                     41.7
    BackgroundMethod:               0
    OverlapMethod:                  2
_images/Walkthrough_-_simple_19_2.png
_images/Walkthrough_-_simple_19_3.png
_images/Walkthrough_-_simple_19_4.png
_images/Walkthrough_-_simple_19_5.png
_images/Walkthrough_-_simple_19_6.png
_images/Walkthrough_-_simple_19_7.png

Trying a different mathematical model

Above, we fit the DDM matrix to a model where the intermediate scattering function (ISF or \(f(q,\Delta t)\)) is an exponential. That is, we fit our data to:

\[D(q,\Delta t) = A(q) \left[ 1 - \exp \left(-\Delta t / \tau(q) \right) ^{s(q)} \right] + B(q)\]

where \(A\) is the amplitude, \(B\) is the background, \(\tau\) is the decay time, and \(s\) is the stretching exponent.

An alternative is to use other information to determine \(A(q)\) and \(B\) and then fit only the ISF. We will try this below.

[9]:
ddm_fit.reload_fit_model_by_name("ISF - Single Exponential")
Initial guess Minimum Maximum
Tau 1.0 0.001 10.0
StretchingExp 1.0 0.500 1.1
[10]:
fit02 = ddm_fit.fit(name_fit = 'fit02', display_table=False)
In function 'get_tau_vs_q_fit', using new tau...
Fit is saved in fittings dictionary with key 'fit02'.
[11]:
ddm.fit_report(fit02, q_indices=[3,6,9,22], forced_qs=[4,16], use_new_tau=True, show=True)
In function 'get_tau_vs_q_fit', using new tau...
In hf.plot_one_tau_vs_q function, using new tau...
[11]:
<xarray.Dataset>
Dimensions:          (parameter: 2, q: 64, lagtime: 40)
Coordinates:
  * parameter        (parameter) <U13 'Tau' 'StretchingExp'
  * q                (q) float64 0.0 0.2028 0.4057 0.6085 ... 12.37 12.58 12.78
  * lagtime          (lagtime) float64 0.02398 0.04796 0.07194 ... 12.59 14.36
Data variables:
    parameters       (parameter, q) float64 0.001886 10.0 10.0 ... 1.1 1.093
    theory           (lagtime, q) float64 4.761e-07 0.9987 0.9812 ... 0.0 0.0
    isf_data         (lagtime, q) float64 0.0 0.9997 0.9892 ... -4.952 -19.73
    ddm_matrix_data  (lagtime, q) float64 0.0 294.2 321.4 ... 207.8 201.1 200.4
    A                (q) float64 -211.2 2.585e+05 1.024e+04 ... -1.699 -0.52
    B                float64 211.2
Attributes: (12/18)
    model:                          ISF - Single Exponential
    data_to_use:                    ISF
    initial_params_dict:            ["{'n': 0, 'value': 1.0, 'limits': [0.001...
    effective_diffusion_coeff:      0.6044361168424172
    tau_vs_q_slope:                 [-1.97401294]
    msd_alpha:                      [1.01316459]
    ...                             ...
    DataDirectory:                  C:/Users/rmcgorty/Documents/GitHub/PyDDM/...
    FileName:                       images_nobin_40x_128x128_8bit.tif
    pixel_size:                     0.242
    frame_rate:                     41.7
    BackgroundMethod:               0
    OverlapMethod:                  2
_images/Walkthrough_-_simple_23_2.png
_images/Walkthrough_-_simple_23_3.png
_images/Walkthrough_-_simple_23_4.png
_images/Walkthrough_-_simple_23_5.png
_images/Walkthrough_-_simple_23_6.png
_images/Walkthrough_-_simple_23_7.png

Interactive with matplotlib

Using the Browse_DDM_Fits class as shown below, one can interactively inspect the fits (to either the DDM matrix or the ISF) by selecting the appropriate point on the \(\tau\) vs \(q\) plot or by pressing ‘N’ or ‘P’ for next or previous.

[12]:
%matplotlib notebook
fig, (ax, ax2) = plt.subplots(2, 1, figsize=(10,10/1.618))
browser = ddm.Browse_DDM_Fits(fig, ax, ax2, fit02)

fig.canvas.mpl_connect('pick_event', browser.on_pick)
fig.canvas.mpl_connect('key_press_event', browser.on_press)

ax.set_title('Decay time vs wavevector')
ax.set_xlabel("q")
ax.set_ylabel("tau (s)")
Click on a point in the tau vs q plot to see a fit.
Or press 'N' or 'P' to display next or previous fit.
[12]:
Text(0, 0.5, 'tau (s)')
[ ]:

Saving the results

We have a few options for saving the results of the fit. We can save the information contained in the xarray Dataset fit01 as an Excel file. Or we can save it as a netCDF file using the xarray function `to_netcdf <https://xarray.pydata.org/en/stable/generated/xarray.Dataset.to_netcdf.html>`__. If we have the fit results as a netCDF file, then we can reload it easily with teh xarray function `open_dataset <https://xarray.pydata.org/en/stable/generated/xarray.open_dataset.html>`__.

[13]:
ddm.save_fit_results_to_excel(fit01)
[14]:
fit01.to_netcdf("fit_of_ddmmatrix.nc")