Skip to content

Width Fitting#

napari-bactmeasure provides two fitting methods to calculate the width of a given bacterium, which have been developed to work with either fluorescence or phase contrast microscopy images.

Fluorescence microscopy fitting method#

fit_ring_profile(x: np.array, y: np.array, psfFWHM: float) #

Fit the ring profile using a tilted circle model.

Parameters:

Name Type Description Default
x np.array

values at which the model is evaluated

required
y np.array

measured ring profile

required
psfFWHM float

the FWHM of the microscope PSF, in the unit of x_values

required

Returns:

Name Type Description
result lmfit.model.ModelResult

the result of the fitting

Source code in src/micromorph/bacteria/fluorescence_fitting.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def fit_ring_profile(x: np.array, y: np.array, psfFWHM: float):
    """
    Fit the ring profile using a tilted circle model.

    Parameters
    ----------
    x: np.array
        values at which the model is evaluated
    y: np.array
        measured ring profile
    psfFWHM: float
        the FWHM of the microscope PSF, in the unit of x_values

    Returns
    -------
    result: lmfit.model.ModelResult
        the result of the fitting
    """
    # Prepare the data
    y = prepare_data(y)

    # upscale the data - helps with ring fitting
    n = 10
    x_upscaled = np.linspace(np.min(x), np.max(x), len(x) * n)
    y_upscaled = np.interp(x_upscaled, x, y)

    # Get initial guess for the parameters
    initial_guess = get_initial_guess(x_upscaled, y_upscaled)

    # Create the model
    gmodel = Model(ring_profile)

    # Set the parameters
    params = gmodel.make_params(x0=initial_guess['x0'],
                                R=initial_guess['R'],
                                offset=initial_guess['offset'],
                                amp=initial_guess['amp'],
                                psfFWHM=psfFWHM)

    # Fix the psfFWHM
    params['psfFWHM'].vary = False

    # Fit the model
    result = gmodel.fit(y_upscaled, params, x=x_upscaled)
    return result

gaussian(params: tuple or list, x: np.array) -> np.array #

Gaussian function with 1 peak.

Parameters:

Name Type Description Default
params tuple or list

parameters of the gaussian function [centre, sigma]

required
x np.array

values at which the model is evaluated

required

Returns:

Name Type Description
values np.array

value(s) of the gaussian at x

Source code in src/micromorph/bacteria/fluorescence_fitting.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def gaussian(params: tuple or list, x: np.array) -> np.array:
    """
    Gaussian function with 1 peak.

    Parameters
    ----------
    params: tuple or list
        parameters of the gaussian function [centre, sigma]
    x: np.array
        values at which the model is evaluated

    Returns
    -------
    values: np.array
        value(s) of the gaussian at x
    """

    a0 = params[0]
    a1 = params[1]

    # values = np.exp(-(x - a0) ** 2 / (a1 ** 2)) / (a1 * np.sqrt(2 * np.pi))
    values = np.exp(-(x - a0) ** 2 / (a1 ** 2))
    return values

get_initial_guess(x: np.array, y: np.array) #

Get initial guess for ring profile model.

Parameters:

Name Type Description Default
x np.array

values at which the model is evaluated

required
y np.array

measured ring profile

required

Returns:

Name Type Description
initial_guess dict

initial guess for the parameters, in a dictionary

Source code in src/micromorph/bacteria/fluorescence_fitting.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def get_initial_guess(x: np.array, y: np.array):
    """
    Get initial guess for ring profile model.

    Parameters
    ----------
    x: np.array
        values at which the model is evaluated
    y: np.array
        measured ring profile

    Returns
    -------
    initial_guess: dict
        initial guess for the parameters, in a dictionary
    """
    # Determine initial guess for the parameters
    amplitude_guess = np.max(y)
    bg_guess = np.min(y)

    # x0 is the middle position
    x0_guess = x[len(x) // 2]

    # find location minimum in first half of the data
    min_loc = x[np.argmax(y[:len(y) // 2])]
    # find location of maximum in second half of the data
    max_loc = x[(np.argmax(y[len(y) // 2:]) + len(y) // 2)]

    # print(max_loc - min_loc)
    width_guess = (max_loc - min_loc) / 2  # this could possibly be determined from the FWHM of the peak

    initial_guess = {'x0': x0_guess, 'R': width_guess, 'offset': bg_guess, 'amp': amplitude_guess}

    return initial_guess

prepare_data(profile: np.array) -> np.array #

Prepare the data for fitting, normalising the profile.

Parameters:

Name Type Description Default
profile np.array

the profile to be prepared

required

Returns:

Name Type Description
profile np.array

the normalised profile

Source code in src/micromorph/bacteria/fluorescence_fitting.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def prepare_data(profile: np.array)-> np.array:
    """
    Prepare the data for fitting, normalising the profile.

    Parameters
    ----------
    profile: np.array
        the profile to be prepared

    Returns
    -------
    profile: np.array
        the normalised profile
    """
    # Normalise the profile
    profile = profile / np.max(profile)


    return profile

ring_profile(x: np.array, psfFWHM: int or float, R: int or float, x0: int or float, offset: int or float, amp: int or float) -> np.array #

This function produces a line profile of a septum using an explicit 'tilted circle' model. It is based off code written in MATLAB by Séamus Holden, described in Whitley et al. 2021 and hosted in

Parameters:

Name Type Description Default
x np.array

values at which the model is evaluated

required
x0 int or float

the centre of the ring

required
R int or float

the radius of the ring

required
psfFWHM int or float

the FWHM of the microscope PSF, in the unit of x_values

required
offset int or float

the background value

required
amp int or float

the amplitude of the ring profile

required

Returns:

Name Type Description
image_profile np.array

value(s) of the ring profile at x

Source code in src/micromorph/bacteria/fluorescence_fitting.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def ring_profile(x: np.array, 
                 psfFWHM: int or float, 
                 R: int or float, 
                 x0: int or float, 
                 offset: int or float,
                 amp: int or float) -> np.array:
    """
    This function produces a line profile of a septum using an explicit 'tilted circle' model.
    It is based off code written in MATLAB by Séamus Holden, described in [Whitley et al. 2021](
    https://www.nature.com/articles/s41467-021-22526-0) and hosted in 

    Parameters
    ----------
    x: np.array
        values at which the model is evaluated
    x0: float
        the centre of the ring
    R: float
        the radius of the ring
    psfFWHM: float
        the FWHM of the microscope PSF, in the unit of x_values
    offset: float
        the background value
    amp: float
        the amplitude of the ring profile

    Returns
    -------
    image_profile: np.array
        value(s) of the ring profile at x
    """
    # n = 10
    # x_original = np.copy(x) # new
    # x = np.linspace(np.min(x), np.max(x), len(x) * n) # new
    # Calculate the sigma (standard deviation) of the PSF
    sigma = psfFWHM / 2.35

    x_extended = np.insert(x, 0, 0)

    profile_integral = 2 * R * np.real(np.emath.arcsin((x_extended - x0) / R))

    image_profile = np.diff(profile_integral)

    # # add zero to the beginning of the array
    # image_profile = np.insert(image_profile, 0, 0)

    # Convolution with gaussian function
    gauss_profile = gaussian([0, sigma], profile_integral)
    # gauss_profile = np.insert(gauss_profile, 0, 0)
    image_profile = convolve(image_profile, gauss_profile, mode='same')

    # image_profile = image_profile / np.max(image_profile) # temp
    image_profile = image_profile * amp + offset

    # calculate center of mass of image_profile
    com_profile = np.average(x, weights=image_profile)

    index_x0 = np.argmin(np.abs(x - x0))
    index_com = np.argmin(np.abs(x - com_profile))

    # shift the profile to the center of mass
    image_profile = np.roll(image_profile, - index_x0 + index_com)

    # image_profile_original = np.copy(image_profile) # new
    # image_profile = image_profile[::n] # new
    # image_profile_interp = np.interp(x_original, x, image_profile)
    return image_profile

Phase contrast microscopy fitting method#

fit_phase_contrast_profile(x: np.array, y: np.array) #

Fit the phase contrast profile using a super gaussian (top-hat) model.

Parameters:

Name Type Description Default
x np.array

x (independent) values of the profile

required
y np.array

y values of the profile - this is what gets fitted

required

Returns:

Name Type Description
result lmfit object

the result of the fitting process, see lmfit documentation for more details

Source code in src/micromorph/bacteria/phase_contrast_fitting.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def fit_phase_contrast_profile(x: np.array, y: np.array):
    """
    Fit the phase contrast profile using a super gaussian (top-hat) model.

    Parameters
    ----------
    x: np.array
        x (independent) values of the profile

    y: np.array
        y values of the profile - this is what gets fitted

    Returns
    -------
    result : lmfit object
        the result of the fitting process, see lmfit documentation for more details
    """
    # Invert the profile
    y = invert_profile(y)

    # Get initial guess for the parameters
    initial_guess = get_initial_guess(x, y)

    # Create the model
    gmodel = Model(super_gaussian)

    # Set the parameters
    params = gmodel.make_params(center=initial_guess['center'],
                                width=initial_guess['width'],
                                amplitude=initial_guess['amplitude'],
                                order=4,
                                offset=initial_guess['offset'])

    # fix order
    params['order'].vary = False
    params['width'].min = 0


    # Fit the model
    result = gmodel.fit(y, params, x=x, nan_policy='raise')

    return result

fit_top_hat_profile(x: np.array, y: np.array) #

Fit the phase contrast profile using a super gaussian (top-hat) model.

Parameters:

Name Type Description Default
x np.array

x (independent) values of the profile

required
y np.array

y values of the profile - this is what gets fitted

required

Returns:

Name Type Description
result lmfit object

the result of the fitting process, see lmfit documentation for more details

Source code in src/micromorph/bacteria/phase_contrast_fitting.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def fit_top_hat_profile(x: np.array, y: np.array):
    """
    Fit the phase contrast profile using a super gaussian (top-hat) model.

    Parameters
    ----------
    x: np.array
        x (independent) values of the profile

    y: np.array
        y values of the profile - this is what gets fitted

    Returns
    -------
    result : lmfit object
        the result of the fitting process, see lmfit documentation for more details
    """
    # Invert the profile
    # y = invert_profile(y)

    # Get initial guess for the parameters
    initial_guess = get_initial_guess(x, y)

    # Create the model
    gmodel = Model(super_gaussian)

    # Set the parameters
    params = gmodel.make_params(center=initial_guess['center'],
                                width=initial_guess['width'],
                                amplitude=initial_guess['amplitude'],
                                order=4,
                                offset=initial_guess['offset'])

    # fix order
    params['order'].vary = False

    # Fit the model
    result = gmodel.fit(y, params, x=x, nan_policy='raise')
    return result

get_initial_guess(x: np.array, y: np.array) -> dict #

Get the initial guess for super gaussian model.

Parameters:

Name Type Description Default
x np.array

values at which the model is evaluated

required
y np.array

measured phase contrast profile

required

Returns:

Name Type Description
initial_guess dict

initial guess for the parameters, in a dictionary

Source code in src/micromorph/bacteria/phase_contrast_fitting.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def get_initial_guess(x: np.array, y: np.array) -> dict:
    """
    Get the initial guess for super gaussian model.

    Parameters
    ----------
    x: np.array
        values at which the model is evaluated
    y: np.array
        measured phase contrast profile

    Returns
    -------
    initial_guess: dict
        initial guess for the parameters, in a dictionary
    """
    # Determine initial guess for the parameters
    amplitude_guess = np.max(y)
    offset_guess = np.min(y)

    # find location minimum in first half of the data
    min_loc = x[np.argmin(y[:len(y) // 2])]
    # find location of minimum in second half of the data
    max_loc = x[(np.argmin(y[len(y) // 2:]) + len(y) // 2)]

    center_guess = (max_loc + min_loc) / 2

    width_guess = (max_loc - min_loc) / 2

    # Store all the initial guesses in a dictionary
    initial_guess = {'center': center_guess, 'width': width_guess, 'amplitude': amplitude_guess, 'offset': offset_guess}

    return initial_guess

invert_profile(profile: np.array) -> np.array #

Convenience function to invert the profile to make it more suitable for fitting.

Parameters:

Name Type Description Default
profile np.array

the profile to be inverted

required

Returns:

Name Type Description
profile np.array

the inverted profile

Source code in src/micromorph/bacteria/phase_contrast_fitting.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def invert_profile(profile: np.array) -> np.array:
    """
    Convenience function to invert the profile to make it more suitable for fitting.

    Parameters
    ----------
    profile: np.array
        the profile to be inverted

    Returns
    -------
    profile: np.array
        the inverted profile
    """
    # Invert the profile
    profile = np.max(profile) - profile

    return profile

super_gaussian(x: np.array, center: float, width: float, amplitude: float, order: float or int, offset: float or int) -> np.array #

Super Gaussian model (top hat) to be used for fitting phase contrast profiles.

Parameters:

Name Type Description Default
x np.array

values at which the model is evaluated

required
center float

center of the super gaussian

required
width float

width of the super gaussian

required
amplitude float

amplitude of the super gaussian

required
order float or int

order of the super gaussian (makes it more or less top-hat shaped)

required
offset float or int

offset of the super gaussian (background)

required

Returns:

Name Type Description
value np.array

value(s) of the super gaussian at x

Source code in src/micromorph/bacteria/phase_contrast_fitting.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def super_gaussian(x: np.array, center: float, width: float, amplitude: float, order: float or int, offset: float or
    int) -> np.array:
    """
    Super Gaussian model (top hat) to be used for fitting phase contrast profiles.

    Parameters
    ----------
    x: np.array
        values at which the model is evaluated
    center: float
        center of the super gaussian
    width: float
        width of the super gaussian
    amplitude: float
        amplitude of the super gaussian
    order: int
        order of the super gaussian (makes it more or less top-hat shaped)
    offset: float
        offset of the super gaussian (background)

    Returns
    -------
    value: np.array
        value(s) of the super gaussian at x
    """

    value = np.exp(-2*((x - center) / width) ** (order)) * amplitude + offset
    if np.isnan(value).any():
        print(f'nan value found for x={x}, center={center}, width={width}, order={1}, amplitude={amplitude}')
    return value