Test Failed
Push — master ( cd34bd...574a84 )
by Chad
03:13
created

diff_classifier.features.minboundrect()   B

Complexity

Conditions 5

Size

Total Lines 139
Code Lines 54

Duplication

Lines 139
Ratio 100 %

Importance

Changes 0
Metric Value
cc 5
eloc 54
nop 1
dl 139
loc 139
rs 8.0387
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
"""Functions to calculate trajectory features from input trajectory data
2
3
This module provides functions to calculate trajectory features based off the
4
ImageJ plugin TrajClassifer by Thorsten Wagner. See details at
5
https://imagej.net/TraJClassifier.
6
7
"""
8
9
import math
10
import struct
11
12
import pandas as pd
13
import numpy as np
14
import numpy.linalg as LA
15
import numpy.ma as ma
16
from scipy.optimize import curve_fit
17
import matplotlib.pyplot as plt
18
import diff_classifier.msd as msd
19
20
21 View Code Duplication
def unmask_track(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
22
    """Removes empty frames from inpute trajectory datset.
23
24
    Parameters
25
    ----------
26
    track : pandas.core.frame.DataFrame
27
        At a minimum, must contain a Frame, Track_ID, X, Y, MSDs, and
28
        Gauss column.
29
30
    Returns
31
    -------
32
    comp_track : pandas.core.frame.DataFrame
33
        Similar to track, but has all masked components removed.
34
35
    """
36
    xpos = ma.masked_invalid(track['X'])
37
    msds = ma.masked_invalid(track['MSDs'])
38
    x_mask = ma.getmask(xpos)
39
    msd_mask = ma.getmask(msds)
40
    comp_frame = ma.compressed(ma.masked_where(msd_mask, track['Frame']))
41
    compid = ma.compressed(ma.masked_where(msd_mask, track['Track_ID']))
42
    comp_x = ma.compressed(ma.masked_where(x_mask, track['X']))
43
    comp_y = ma.compressed(ma.masked_where(x_mask, track['Y']))
44
    comp_msd = ma.compressed(ma.masked_where(msd_mask, track['MSDs']))
45
    comp_gauss = ma.compressed(ma.masked_where(msd_mask, track['Gauss']))
46
    comp_qual = ma.compressed(ma.masked_where(x_mask, track['Quality']))
47
    comp_snr = ma.compressed(ma.masked_where(x_mask, track['SN_Ratio']))
48
    comp_meani = ma.compressed(ma.masked_where(x_mask,
49
                                               track['Mean_Intensity']))
50
51
    data1 = {'Frame': comp_frame,
52
             'Track_ID': compid,
53
             'X': comp_x,
54
             'Y': comp_y,
55
             'MSDs': comp_msd,
56
             'Gauss': comp_gauss,
57
             'Quality': comp_qual,
58
             'SN_Ratio': comp_snr,
59
             'Mean_Intensity': comp_meani
60
             }
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (remove 1 space).
Loading history...
61
    comp_track = pd.DataFrame(data=data1)
62
    return comp_track
63
64
65 View Code Duplication
def alpha_calc(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
66
    """Calculates alpha, the exponential fit parameter for MSD data
67
68
    Parameters
69
    ----------
70
    track : pandas.core.frame.DataFrame
71
        At a minimum, must contain a Frames and a MSDs column.  The function
72
        msd_calc can be used to generate the correctly formatted pd dataframe.
73
74
    Returns
75
    -------
76
    alph : numpy.float64
77
        The anomalous exponent derived by fitting MSD values to the function,
78
        <rad**2(n)> = 4*dcoef*(n*delt)**alph
79
    dcoef : numpy.float64
80
        The fitted diffusion coefficient derived by fitting MSD values to the
81
        function above.
82
83
    Examples
84
    --------
85
    >>> frames = 5
86
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
87
    ...          'X': np.linspace(1, frames, frames)+5,
88
    ...          'Y': np.linspace(1, frames, frames)+3}
89
    >>> dframe = pd.DataFrame(data=data1)
90
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
91
    >>> alpha_calc(dframe)
92
    (2.0000000000000004, 0.4999999999999999)
93
94
    >>> frames = 10
95
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
96
    ...          'X': np.sin(np.linspace(1, frames, frames)+3),
97
    ...          'Y': np.cos(np.linspace(1, frames, frames)+3)}
98
    >>> dframe = pd.DataFrame(data=data1)
99
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
100
    >>> alpha_calc(dframe)
101
    (0.023690002018364065, 0.5144436515510022)
102
103
    """
104
105
    ypos = track['MSDs']
106
    xpos = track['Frame']
107
108
    def msd_alpha(xpos, alph, dcoef):
109
        return 4*dcoef*(xpos**alph)
110
111
    try:
112
        popt, pcov = curve_fit(msd_alpha, xpos, ypos)
113
        alph = popt[0]
114
        dcoef = popt[1]
115
    except RuntimeError:
116
        print('Optimal parameters not found. Print NaN instead.')
117
        alph = np.nan
118
        dcoef = np.nan
119
    return alph, dcoef
120
121
122 View Code Duplication
def gyration_tensor(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
123
    """Calculates the eigenvalues and eigenvectors of the gyration tensor of the
124
    input trajectory.
125
126
    Parameters
127
    ----------
128
    track : pandas DataFrame
129
        At a minimum, must contain an X and Y column.  The function
130
        msd_calc can be used to generate the correctly formatted pd dataframe.
131
132
    Returns
133
    -------
134
    eig1 : numpy.float64
135
        Dominant eigenvalue of the gyration tensor.
136
    eig2 : numpy.float64
137
        Secondary eigenvalue of the gyration tensor.
138
    eigv1 : numpy.ndarray
139
        Dominant eigenvector of the gyration tensor.
140
    eigv2 : numpy.ndarray
141
        Secondary eigenvector of the gyration tensor.
142
143
    Examples
144
    --------
145
    >>> frames = 5
146
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
147
    ...          'X': np.linspace(1, frames, frames)+5,
148
    ...          'Y': np.linspace(1, frames, frames)+3}
149
    >>> dframe = pd.DataFrame(data=data1)
150
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
151
    >>> gyration_tensor(dframe)
152
    (4.0,
153
    4.4408920985006262e-16,
154
    array([ 0.70710678, -0.70710678]),
155
    array([ 0.70710678,  0.70710678]))
156
157
    >>> frames = 10
158
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
159
    ...          'X': np.sin(np.linspace(1, frames, frames)+3),
160
    ...          'Y': np.cos(np.linspace(1, frames, frames)+3)}
161
    >>> dframe = pd.DataFrame(data=data1)
162
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
163
    >>> gyration_tensor(dframe)
164
    (0.53232560128104522,
165
    0.42766829138901619,
166
    array([ 0.6020119 , -0.79848711]),
167
    array([-0.79848711, -0.6020119 ]))
168
169
    """
170
171
    dframe = track
172
    assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\
173
     dataframe."
174
    assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\
175
     X column."
176
    assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\
177
     Y column."
178
    assert dframe.shape[0] > 0, "track must not be empty."
179
180
    matrixa = np.sum((dframe['X'] - np.mean(
181
                     dframe['X']))**2)/dframe['X'].shape[0]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 13 spaces).
Loading history...
182
    matrixb = np.sum((dframe['Y'] - np.mean(
183
                     dframe['Y']))**2)/dframe['Y'].shape[0]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 13 spaces).
Loading history...
184
    matrixab = np.sum((dframe['X'] - np.mean(
185
                      dframe['X']))*(dframe['Y'] - np.mean(
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 14 spaces).
Loading history...
186
                                     dframe['Y'])))/dframe['X'].shape[0]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 11 spaces).
Loading history...
187
188
    eigvals, eigvecs = LA.eig(np.array([[matrixa, matrixab],
189
                                       [matrixab, matrixb]]))
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 1 space).
Loading history...
190
    dom = np.argmax(np.abs(eigvals))
191
    rec = np.argmin(np.abs(eigvals))
192
    eig1 = eigvals[dom]
193
    eig2 = eigvals[rec]
194
    eigv1 = eigvecs[dom]
195
    eigv2 = eigvecs[rec]
196
    return eig1, eig2, eigv1, eigv2
197
198
199 View Code Duplication
def kurtosis(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
200
    """Calculates the kurtosis of input track.
201
202
    Parameters
203
    ----------
204
    track : pandas.core.frame.DataFrame
205
        At a minimum, must contain an X and Y column.  The function
206
        msd_calc can be used to generate the correctly formatted pd dataframe.
207
208
    Returns
209
    -------
210
    kurt : numpy.float64
211
        Kurtosis of the input track.  Calculation based on projected 2D
212
        positions on the dominant eigenvector of the radius of gyration tensor.
213
214
    Examples
215
    --------
216
    >>> frames = 5
217
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
218
    ...          'X': np.linspace(1, frames, frames)+5,
219
    ...          'Y': np.linspace(1, frames, frames)+3}
220
    >>> dframe = pd.DataFrame(data=data1)
221
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
222
    >>> kurtosis(dframe)
223
    2.5147928994082829
224
225
    >>> frames = 10
226
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
227
    ...          'X': np.sin(np.linspace(1, frames, frames)+3),
228
    ...          'Y': np.cos(np.linspace(1, frames, frames)+3)}
229
    >>> dframe = pd.DataFrame(data=data1)
230
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
231
    >>> kurtosis(dframe)
232
    1.8515139698652476
233
234
    """
235
236
    dframe = track
237
    assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\
238
     dataframe."
239
    assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\
240
     X column."
241
    assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\
242
     Y column."
243
    assert dframe.shape[0] > 0, "track must not be empty."
244
245
    eig1, eig2, eigv1, eigv2 = gyration_tensor(dframe)
246
    projection = dframe['X']*eigv1[0] + dframe['Y']*eigv1[1]
247
248
    kurt = np.mean((projection - np.mean(
249
                   projection))**4/(np.std(projection)**4))
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 11 spaces).
Loading history...
250
251
    return kurt
252
253
254 View Code Duplication
def asymmetry(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
255
    """Calculates the asymmetry of the trajectory.
256
257
    Parameters
258
    ----------
259
    track : pandas DataFrame
260
        At a minimum, must contain an X and Y column.  The function
261
        msd_calc can be used to generate the correctly formatted pd dataframe.
262
263
    Returns
264
    -------
265
    eig1 : numpy.float64
266
        Dominant eigenvalue of the gyration tensor.
267
    eig2 : numpy.float64
268
        Secondary eigenvalue of the gyration tensor.
269
    asym1 : numpy.float64
270
        asymmetry of the input track.  Equal to 0 for circularly symmetric
271
        tracks, and 1 for linear tracks.
272
    asym2 : numpy.float64
273
        alternate definition of asymmetry.  Equal to 1 for circularly
274
        symmetric tracks, and 0 for linear tracks.
275
    asym3 : numpy.float64
276
        alternate definition of asymmetry.
277
278
    Examples
279
    --------
280
    >>> frames = 10
281
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
282
    ...          'X': np.linspace(1, frames, frames)+5,
283
    ...          'Y': np.linspace(1, frames, frames)+3}
284
    >>> dframe = pd.DataFrame(data=data1)
285
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
286
    >>> asymmetry(dframe)
287
    (16.5, 0.0, 1.0, 0.0, 0.69314718055994529)
288
289
    >>> frames = 10
290
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
291
    ...          'X': np.sin(np.linspace(1, frames, frames)+3),
292
    ...          'Y': np.cos(np.linspace(1, frames, frames)+3)}
293
    >>> dframe = pd.DataFrame(data=data1)
294
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
295
    >>> asymmetry(dframe)
296
    (0.53232560128104522,
297
    0.42766829138901619,
298
    0.046430119259539708,
299
    0.80339606128247354,
300
    0.0059602683290953052)
301
302
    """
303
    dframe = track
304
    assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\
305
     dataframe."
306
    assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\
307
     X column."
308
    assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\
309
     Y column."
310
    assert dframe.shape[0] > 0, "track must not be empty."
311
312
    eig1, eig2, eigv1, eigv2 = gyration_tensor(track)
313
    asym1 = (eig1**2 - eig2**2)**2/(eig1**2 + eig2**2)**2
314
    asym2 = eig2/eig1
315
    asym3 = -np.log(1-((eig1-eig2)**2)/(2*(eig1+eig2)**2))
316
317
    return eig1, eig2, asym1, asym2, asym3
318
319
320 View Code Duplication
def minboundrect(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
321
    """Calculates the minimum bounding rectangle of an input trajectory.
322
323
    Parameters
324
    ----------
325
    dframe : pandas.core.frame.DataFrame
326
        At a minimum, must contain an X and Y column.  The function
327
        msd_calc can be used to generate the correctly formatted pd dataframe.
328
329
    Returns
330
    -------
331
    rot_angle : numpy.float64
332
        Angle of rotation of the bounding box.
333
    area : numpy.float64
334
        Area of the bounding box.
335
    width : numpy.float64
336
        Width of the bounding box.
337
    height : numpy.float64
338
        Height of the bounding box.
339
    center_point : numpy.ndarray
340
        Center point of the bounding box.
341
    corner_pts : numpy.ndarray
342
        Corner points of the bounding box.
343
344
    Examples
345
    --------
346
    >>> frames = 10
347
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
348
    ...          'X': np.linspace(1, frames, frames)+5,
349
    ...          'Y': np.linspace(1, frames, frames)+3}
350
    >>> dframe = pd.DataFrame(data=data1)
351
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
352
    >>> minboundrect(dframe)
353
    (-2.3561944901923448,
354
    2.8261664256307952e-14,
355
    12.727922061357855,
356
    2.2204460492503131e-15,
357
    array([ 10.5,   8.5]),
358
    array([[  6.,   4.],
359
           [ 15.,  13.],
360
           [ 15.,  13.],
361
           [  6.,   4.]]))
362
363
    >>> frames = 10
364
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
365
    ...          'X': np.sin(np.linspace(1, frames, frames))+3,
366
    ...          'Y': np.cos(np.linspace(1, frames, frames))+3}
367
    >>> dframe = pd.DataFrame(data=data1)
368
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
369
    >>> minboundrect(dframe)
370
    (0.78318530717958657,
371
    3.6189901131223992,
372
    1.9949899732081091,
373
    1.8140392491811692,
374
    array([ 3.02076903,  2.97913884]),
375
    array([[ 4.3676025 ,  3.04013439],
376
           [ 2.95381341,  1.63258851],
377
           [ 1.67393557,  2.9181433 ],
378
           [ 3.08772466,  4.32568917]]))
379
380
    Notes
381
    -----
382
    Based off of code from the following repo:
383
    https://github.com/dbworth/minimum-area-bounding-rectangle/blob/master/
384
    python/min_bounding_rect.py
385
386
    """
387
388
    dframe = track
389
    assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\
390
     dataframe."
391
    assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\
392
     X column."
393
    assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\
394
     Y column."
395
    assert dframe.shape[0] > 0, "track must not be empty."
396
397
    df2 = np.zeros((dframe.shape[0]+1, 2))
398
    df2[:-1, :] = dframe[['X', 'Y']].values
399
    df2[-1, :] = dframe[['X', 'Y']].values[0, :]
400
    hull_points_2d = df2
401
402
    edges = np.zeros((len(hull_points_2d)-1, 2))
403
404
    for i in range(len(edges)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable range does not seem to be defined.
Loading history...
405
        edge_x = hull_points_2d[i+1, 0] - hull_points_2d[i, 0]
406
        edge_y = hull_points_2d[i+1, 1] - hull_points_2d[i, 1]
407
        edges[i] = [edge_x, edge_y]
408
409
    edge_angles = np.zeros((len(edges)))
410
411
    for i in range(len(edge_angles)):
412
        edge_angles[i] = math.atan2(edges[i, 1], edges[i, 0])
413
    edge_angles = np.unique(edge_angles)
414
415
    start_area = 2 ** (struct.Struct('i').size * 8 - 1) - 1
416
    min_bbox = (0, start_area, 0, 0, 0, 0, 0, 0)
417
    for i in range(len(edge_angles)):
418
        rads = np.array([[math.cos(edge_angles[i]),
419
                          math.cos(edge_angles[i]-(math.pi/2))],
420
                        [math.cos(edge_angles[i]+(math.pi/2)),
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 1 space).
Loading history...
421
                         math.cos(edge_angles[i])]])
422
423
        rot_points = np.dot(rads, np.transpose(hull_points_2d))
424
425
        min_x = np.nanmin(rot_points[0], axis=0)
426
        max_x = np.nanmax(rot_points[0], axis=0)
427
        min_y = np.nanmin(rot_points[1], axis=0)
428
        max_y = np.nanmax(rot_points[1], axis=0)
429
430
        width = max_x - min_x
431
        height = max_y - min_y
432
        area = width*height
433
434
        if area < min_bbox[1]:
435
            min_bbox = (edge_angles[i], area, width, height,
436
                        min_x, max_x, min_y, max_y)
437
438
    angle = min_bbox[0]
439
    rads = np.array([[math.cos(angle), math.cos(angle-(math.pi/2))],
440
                     [math.cos(angle+(math.pi/2)), math.cos(angle)]])
441
442
    min_x = min_bbox[4]
443
    max_x = min_bbox[5]
444
    min_y = min_bbox[6]
445
    max_y = min_bbox[7]
446
447
    center_x = (min_x + max_x)/2
448
    center_y = (min_y + max_y)/2
449
    center_point = np.dot([center_x, center_y], rads)
450
451
    corner_pts = np.zeros((4, 2))
452
    corner_pts[0] = np.dot([max_x, min_y], rads)
453
    corner_pts[1] = np.dot([min_x, min_y], rads)
454
    corner_pts[2] = np.dot([min_x, max_y], rads)
455
    corner_pts[3] = np.dot([max_x, max_y], rads)
456
457
    return (angle, min_bbox[1], min_bbox[2], min_bbox[3],
458
            center_point, corner_pts)
459
460
461 View Code Duplication
def aspectratio(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
462
    """Calculates the aspect ratio of the rectangle containing the input track.
463
464
    Parameters
465
    ----------
466
    track : pandas.core.frame.DataFrame
467
        At a minimum, must contain an X and Y column.  The function
468
        msd_calc can be used to generate the correctly formatted pd dataframe.
469
470
    Returns
471
    -------
472
    aspratio : numpy.float64
473
        aspect ratio of the trajectory.  Always >= 1.
474
    elong : numpy.float64
475
        elongation of the trajectory.  A transformation of the aspect ratio
476
        given by 1 - aspratio**-1.
477
478
    Examples
479
    --------
480
    >>> frames = 10
481
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
482
    ...          'X': np.linspace(1, frames, frames)+5,
483
    ...          'Y': np.linspace(1, frames, frames)+3}
484
    >>> dframe = pd.DataFrame(data=data1)
485
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
486
    >>> aspectratio(dframe)
487
    (5732146505273195.0, 0.99999999999999978)
488
489
    >>> frames = 10
490
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
491
    ...          'X': np.sin(np.linspace(1, frames, frames))+3,
492
    ...          'Y': np.cos(np.linspace(1, frames, frames))+3}
493
    >>> dframe = pd.DataFrame(data=data1)
494
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
495
    >>> aspectratio(dframe)
496
    (1.0997501702946164, 0.090702573174318291)
497
498
    """
499
500
    dframe = track
501
    assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\
502
     dataframe."
503
    assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\
504
     X column."
505
    assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\
506
     Y column."
507
    assert dframe.shape[0] > 0, "track must not be empty."
508
509
    rangle, area, width, height, center_point, corner_pts = minboundrect(track)
510
    aspratio = width/height
511
    if aspratio > 1:
512
        aspratio = aspratio
513
    else:
514
        aspratio = 1/aspratio
515
    elong = 1 - (1/aspratio)
516
517
    return aspratio, elong, center_point
518
519
520 View Code Duplication
def boundedness(track, framerate=1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
521
    """Calculates the boundedness, fractal dimension, and trappedness of the
522
    input track.
523
524
    Parameters
525
    ----------
526
    track : pandas.core.frame.DataFrame
527
        At a minimum, must contain a Frames and a MSDs column.  The function
528
        msd_calc can be used to generate the correctly formatted pd dataframe.
529
    framerate : framrate of the video being analyzed.  Actually cancels out. So
530
        why did I include this. Default is 1.
531
532
    Returns
533
    -------
534
    bound : float
535
        Boundedness of the input track.  Quantifies how much a particle with
536
        diffusion coefficient dcoef is restricted by a circular confinement of
537
        radius rad when it diffuses for a time duration N*delt.  Defined as
538
        bound = dcoef*N*delt/rad**2. For this case, dcoef is the short time
539
        diffusion coefficient (after 2 frames), and rad is half the maximum
540
        distance between any two positions.
541
    fractd : float
542
        The fractal path dimension defined as fractd = log(N)/log(N*data1*l**-1)
543
        where netdisp is the total length (sum over all steplengths), N is the
544
        number of steps, and data1 is the largest distance between any two
545
        positions.
546
    probf : float
547
        The probability that a particle with diffusion coefficient dcoef and
548
        traced for a period of time N*delt is trapped in region r0.  Given by
549
        pt = 1 - exp(0.2048 - 0.25117*(dcoef*N*delt/r0**2)). For this case,
550
        dcoef is the short time diffusion coefficient, and r0 is half the
551
        maximum distance between any two positions.
552
553
    Examples
554
    --------
555
    >>> frames = 10
556
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
557
    ...          'X': np.linspace(1, frames, frames)+5,
558
    ...          'Y': np.linspace(1, frames, frames)+3}
559
    >>> dframe = pd.DataFrame(data=data1)
560
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
561
    >>> boundedness(dframe)
562
    (1.0, 1.0000000000000002, 0.045311337970735499)
563
564
    >>> frames = 10
565
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
566
    ...          'X': np.sin(np.linspace(1, frames, frames)+3),
567
    ...          'Y': np.cos(np.linspace(1, frames, frames)+3)}
568
    >>> dframe = pd.DataFrame(data=data1)
569
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
570
    >>> boundedness(dframe)
571
    (0.96037058689895005, 2.9989749477908401, 0.03576118370932313)
572
573
    """
574
575
    dframe = track
576
    assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\
577
     dataframe."
578
    assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\
579
     X column."
580
    assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\
581
     Y column."
582
    assert dframe.shape[0] > 0, "track must not be empty."
583
584
    dframe = track
585
586
    if dframe.shape[0] > 2:
587
        length = dframe.shape[0]
588
        distance = np.zeros((length, length))
589
590
        for frame in range(0, length-1):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable range does not seem to be defined.
Loading history...
591
            distance[frame, 0:length-frame-1] =\
592
             (np.sqrt(msd.nth_diff(dframe['X'], frame+1)**2 +
593
              msd.nth_diff(dframe['Y'], frame+1)**2).values)
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 8 spaces).
Loading history...
594
595
        netdisp = np.sum((np.sqrt(msd.nth_diff(dframe['X'], 1)**2 +
596
                                  msd.nth_diff(dframe['Y'], 1)**2).values))
597
        rad = np.max(distance)/2
598
        N = dframe['Frame'][dframe['Frame'].shape[0]-1]
599
        fram = N*framerate
600
        dcoef = dframe['MSDs'][2]/(4*fram)
601
602
        bound = dcoef*fram/(rad**2)
603
        fractd = np.log(N)/np.log(N*2*rad/netdisp)
604
        probf = 1 - np.exp(0.2048 - 0.25117*(dcoef*fram/(rad**2)))
605
    else:
606
        bound = np.nan
607
        fractd = np.nan
608
        probf = np.nan
609
610
    return bound, fractd, probf
611
612
613 View Code Duplication
def efficiency(track):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
614
    """Calculates the efficiency and straitness of the input track
615
616
    Parameters
617
    ----------
618
    track : pandas.core.frame.DataFrame
619
        At a minimum, must contain a Frames and a MSDs column.  The function
620
        msd_calc can be used to generate the correctly formatted pd dataframe.
621
622
    Returns
623
    -------
624
    eff : float
625
        Efficiency of the input track.  Relates the sum of squared step
626
        lengths.  Based on Helmuth et al. (2007) and defined as:
627
        E = |xpos(N-1)-xpos(0)|**2/SUM(|xpos(i) - xpos(i-1)|**2
628
    strait : float
629
        Relates the net displacement netdisp to the sum of step lengths and is
630
        defined as:
631
        S = |xpos(N-1)-xpos(0)|/SUM(|xpos(i) - xpos(i-1)|
632
633
    Examples
634
    --------
635
    >>> frames = 10
636
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
637
    ...          'X': np.linspace(1, frames, frames)+5,
638
    ...          'Y': np.linspace(1, frames, frames)+3}
639
    >>> dframe = pd.DataFrame(data=data1)
640
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
641
    >>> ft.efficiency(dframe)
642
    (9.0, 0.9999999999999999)
643
644
    >>> frames = 10
645
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
646
    ...          'X': np.sin(np.linspace(1, frames, frames))+3,
647
    ...          'Y': np.cos(np.linspace(1, frames, frames))+3}
648
    >>> dframe = pd.DataFrame(data=data1)
649
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
650
    >>> ft.efficiency(dframe)
651
    (0.46192924086141945, 0.22655125514290225)
652
653
    """
654
655
    dframe = track
656
    length = dframe.shape[0]
657
    num = (msd.nth_diff(dframe['X'],
658
                        length-1)**2 + msd.nth_diff(dframe['Y'],
659
                                                    length-1)**2)[0]
660
    num2 = np.sqrt(num)
661
662
    den = np.sum(msd.nth_diff(dframe['X'],
663
                              1)**2 + msd.nth_diff(dframe['Y'], 1)**2)
664
    den2 = np.sum(np.sqrt(msd.nth_diff(dframe['X'],
665
                          1)**2 + msd.nth_diff(dframe['Y'], 1)**2))
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 13 spaces).
Loading history...
666
667
    eff = num/den
668
    strait = num2/den2
669
    return eff, strait
670
671
672 View Code Duplication
def msd_ratio(track, fram1=3, fram2=100):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
673
    """Calculates the MSD ratio of the input track at the specified frames.
674
675
    Parameters
676
    ----------
677
    track : pandas.core.frame.DataFrame
678
        At a minimum, must contain a Frames and a MSDs column.  The function
679
        msd_calc can be used to generate the correctly formatted pd dataframe.
680
    fram1 : int
681
        First frame at which to calculate the MSD ratio.
682
    fram2 : int
683
        Last frame at which to calculate the MSD ratio.
684
685
    Returns
686
    -------
687
    ratio: numpy.float64
688
        MSD ratio as defined by
689
        [MSD(fram1)/MSD(fram2)] - [fram1/fram2]
690
        where fram1 < fram2.  For Brownian motion, it is 0; for restricted
691
        motion it is < 0.  For directed motion it is > 0.
692
693
    Examples
694
    --------
695
    >>> frames = 10
696
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
697
    ...          'X': np.linspace(1, frames, frames)+5,
698
    ...          'Y': np.linspace(1, frames, frames)+3}
699
    >>> dframe = pd.DataFrame(data=data1)
700
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
701
    >>> ft.msd_ratio(dframe, 1, 9)
702
    -0.18765432098765433
703
704
    >>> frames = 10
705
    >>> data1 = {'Frame': np.linspace(1, frames, frames),
706
    ...          'X': np.sin(np.linspace(1, frames, frames))+3,
707
    ...          'Y': np.cos(np.linspace(1, frames, frames))+3}
708
    >>> dframe = pd.DataFrame(data=data1)
709
    >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe)
710
    >>> ft.msd_ratio(dframe, 1, 9)
711
    0.04053708075268797
712
713
    """
714
715
    dframe = track
716
    assert fram1 < fram2, "fram1 must be less than fram2"
717
    ratio = (dframe['MSDs'][fram1]/dframe['MSDs'][fram2]) - (
718
             dframe['Frame'][fram1]/dframe['Frame'][fram2])
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 5 spaces).
Loading history...
719
    return ratio
720
721
722 View Code Duplication
def calculate_features(dframe, framerate=1, frame=(10, 100), mean_values=True):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
723
    """test test test Calculates multiple features from input MSD dataset and stores in pandas
724
    dataframe.
725
726
    Parameters
727
    ----------
728
    dframe : pandas.core.frame.DataFrame
729
        Output from msd.all_msds2.  Must have at a minimum the following
730
        columns:
731
        Track_ID, Frame, X, Y, and MSDs.
732
    framerate : int or float
733
        Framerate of the input videos from which trajectories were calculated.
734
        Required for accurate calculation of some features.  Default is 1.
735
        Possibly not required. Ignore if performing all calcuations without
736
        units.
737
    frame : int
738
        Frame at which to calculate Deff
739
740
    Returns
741
    -------
742
    datai: pandas.core.frame.DataFrame
743
        Contains a row for each trajectory in dframe.  Holds the following
744
        features of each trajetory: Track_ID, alpha, D_fit, kurtosis,
745
        asymmetry1, asymmetry2, asymmetry3, aspect ratio (AR), elongation,
746
        boundedness, fractal dimension (fractal_dim), trappedness, efficiency,
747
        straightness, MSD ratio, frames, X, and Y.
748
749
    Examples
750
    --------
751
    See example outputs from individual feature functions.
752
753
    """
754
755
    # Skeleton of Trajectory features metadata table.
756
    # Builds entry for each unique Track ID.
757
    holder = dframe.Track_ID.unique().astype(float)
758
    die = {'Track_ID': holder,
759
           'alpha': holder,
760
           'D_fit': holder,
761
           'kurtosis': holder,
762
           'asymmetry1': holder,
763
           'asymmetry2': holder,
764
           'asymmetry3': holder,
765
           'AR': holder,
766
           'elongation': holder,
767
           'boundedness': holder,
768
           'fractal_dim': holder,
769
           'trappedness': holder,
770
           'efficiency': holder,
771
           'straightness': holder,
772
           'MSD_ratio': holder,
773
           'frames': holder,
774
           'X': holder,
775
           'Y': holder,
776
           'Quality': holder,
777
           'Mean_Intensity': holder,
778
           'SN_Ratio': holder,
779
           'Deff1': holder,
780
           'Deff2': holder}
781
782
    datai = pd.DataFrame(data=die)
783
784
    trackids = dframe.Track_ID.unique()
785
    partcount = trackids.shape[0]
786
787
    for particle in range(0, partcount):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable range does not seem to be defined.
Loading history...
788
        single_track_masked =\
789
         dframe.loc[dframe['Track_ID'] ==
790
                    trackids[particle]].sort_values(['Track_ID', 'Frame'],
791
                                                    ascending=[
792
                                                    1,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (add 4 spaces).
Loading history...
793
                                                    1]).reset_index(drop=True)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (add 4 spaces).
Loading history...
794
        single_track = unmask_track(single_track_masked)
795
        (datai['alpha'][particle],
796
         datai['D_fit'][particle]) = alpha_calc(single_track)
797
        datai['kurtosis'][particle] = kurtosis(single_track)
798
        (eig1, eig2, datai['asymmetry1'][particle],
799
         datai['asymmetry2'][particle],
800
         datai['asymmetry3'][particle]) = asymmetry(single_track)
801
        (datai['AR'][particle], datai['elongation'][particle],
802
         (datai['X'][particle],
803
          datai['Y'][particle])) = aspectratio(single_track)
804
        (datai['boundedness'][particle], datai['fractal_dim'][particle],
805
         datai['trappedness'][particle]) = boundedness(single_track, framerate)
806
        (datai['efficiency'][particle],
807
         datai['straightness'][particle]) = efficiency(single_track)
808
        datai['frames'][particle] = single_track.shape[0]
809
        if single_track['Frame'][single_track.shape[0]-2] > 2:
810
            datai['MSD_ratio'][particle] = msd_ratio(single_track, 2,
811
                                                     single_track['Frame'][
812
                                                      single_track.shape[0]-2])
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (add 3 spaces).
Loading history...
813
        else:
814
            datai['MSD_ratio'][particle] = np.nan
815
816
        try:
817
            datai['Deff1'][particle] = single_track['MSDs'][frame[0]] / (4*frame[0])
818
        except:
819
            datai['Deff1'][particle] = np.nan
820
821
        try:
822
            datai['Deff2'][particle] = single_track['MSDs'][frame[1]] / (4*frame[1])
823
        except:
824
            datai['Deff2'][particle] = np.nan
825
826
        datai['Mean_Intensity'][particle] = np.nanmean(single_track[
827
              'Mean_Intensity'].replace([np.inf, -np.inf], np.nan).dropna(how="all").values)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 2 spaces).
Loading history...
828
        datai['Quality'][particle] = np.nanmean(single_track[
829
              'Quality'].replace([np.inf, -np.inf], np.nan).dropna(how="all").values)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 2 spaces).
Loading history...
830
        datai['SN_Ratio'][particle] = np.nanmean(single_track[
831
              'SN_Ratio'].replace([np.inf, -np.inf], np.nan).dropna(how="all").values)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (remove 2 spaces).
Loading history...
832
833
    if mean_values:
834
        nonnum = ['Track_ID']
835
        for col in datai.columns:
836
            if col not in nonnum:
837
                datai['Mean ' + col] = np.nan
838
                datai['Std ' + col] = np.nan
839
840
        for xrange in range(0, 16):
841
            for yrange in range(0, 16):
842
                bitesize = datai[(datai['X'] >= 128*xrange) & (datai['X'] < 128*(xrange+1)) &
843
                                 (datai['Y'] >= 128*yrange) & (datai['Y'] < 128*(yrange+1))]
844
                bitesize.replace([np.inf, -np.inf], np.nan)
845
                print(bitesize.shape)
846
                for col in bitesize.columns:
847
                    if col not in nonnum and 'Mean' not in col and 'Std' not in col:
848
                        datai['Mean '+ col][bitesize.index] = np.nanmean(bitesize[col])
849
                        datai['Std '+ col][bitesize.index] = np.nanstd(bitesize[col])
850
851
    return datai
852
853
854 View Code Duplication
def feature_violin(tgroups, feature='boundedness',
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
855
                   labels=['sample 1', 'sample 2', 'sample 3'],
856
                   points=40, ylim=[0, 1], nticks=11):
857
    '''Plots violin plots of features in comparison groups
858
859
    Parameters
860
    ----------
861
    tgroups : dict of pandas.core.frames.DataFrame
862
        Dictionary containing pandas dataframes containing trajectory
863
        features of subgroups to be plotted
864
    feature : string
865
        Feature to be compared
866
    labels : list of strings
867
        Labels of subgroups to be plotted.
868
    points : int
869
        Determines resolution of violin plot
870
    ylim : list of int
871
        Y range of output plot
872
873
    '''
874
875
    majorticks = np.linspace(ylim[0], ylim[1], nticks)
876
    to_graph = []
877
    pos = []
878
    counter = 1
879
    for key in tgroups:
880
        to_graph.append(tgroups[key][feature][tgroups[key][feature] < 10000].replace([np.inf, -np.inf], np.nan).dropna().values)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (128/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
881
        pos.append(counter)
882
        counter = counter + 1
883
884
    def set_axis_style(ax, labels):
885
        ax.get_xaxis().set_tick_params(direction='out')
886
        ax.xaxis.set_ticks_position('bottom')
887
        ax.set_xticks(np.arange(1, len(labels) + 1))
888
        ax.set_xticklabels(labels)
889
        ax.set_xlim(0.25, len(labels) + 0.75)
890
891
    fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(6, 6))
892
893
    axes.violinplot(to_graph, pos, points=points, widths=0.9,
894
                    showmeans=True, showextrema=False)
895
    set_axis_style(axes, labels)
896
    axes.tick_params(axis='both', which='major',
897
                     labelsize=16)
898
    axes.set_ylim(ylim)
899
    axes.set_yticks(majorticks)
900
901
    plt.show()
902