Skip to content

Measure360

Bacteria360 #

A class to store all the properties of a single bacterium. It requires an image and a mask to be initialised, which are used to calculate the properties.

Parameters:

Name Type Description Default
img np.array

The image of the bacterium.

required
centroid tuple

The centroid of the bacterium.

required
options dict

Dictionary of options to pass to the measure360 function.

dict()

Attributes:

Name Type Description
centroid tuple

The centroid of the bacterium.

width_data np.array

The width data of the bacterium.

width float

The minimum width of the bacterium.

length float

The maximum width of the bacterium.

area float

The area of the bacterium.

label int

The label of the bacterium.

bbox tuple

The bounding box of the bacterium.

slice int

The slice of the stack the bacterium is from.

n_lines int

The number of lines to measure the width along.

magnitude float

The length of the lines.

px_size float

The pixel size of the image.

psfFWHM float

The FWHM of the PSF.

xc float

The x-coordinate of the centroid.

yc float

The y-coordinate of the centroid.

xc_nm float

The x-coordinate of the centroid in nm.

yc_nm float

The y-coordinate of the centroid in nm.

Source code in src/micromorph/measure360/measure360.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
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
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
172
173
class Bacteria360:
    """
    A class to store all the properties of a single bacterium.
    It requires an image and a mask to be initialised, which are used to calculate the properties.

    Parameters
    ----------
    img : np.array
        The image of the bacterium.
    centroid : tuple
        The centroid of the bacterium.
    options : dict
        Dictionary of options to pass to the measure360 function.

    Attributes
    ----------
    centroid : tuple
        The centroid of the bacterium.
    width_data : np.array
        The width data of the bacterium.
    width : float
        The minimum width of the bacterium.
    length : float
        The maximum width of the bacterium.
    area : float
        The area of the bacterium.
    label : int
        The label of the bacterium.
    bbox : tuple
        The bounding box of the bacterium.
    slice : int
        The slice of the stack the bacterium is from.
    n_lines : int
        The number of lines to measure the width along.
    magnitude : float
        The length of the lines.
    px_size : float
        The pixel size of the image.
    psfFWHM : float
        The FWHM of the PSF.
    xc : float
        The x-coordinate of the centroid.
    yc : float
        The y-coordinate of the centroid.
    xc_nm : float
        The x-coordinate of the centroid in nm.
    yc_nm : float
        The y-coordinate of the centroid in nm.

    """
    def __init__(self, img, centroid, options=dict()):

        n_lines = options.get('n_lines', 50)
        magnitude = options.get('magnitude', 100)
        pool = options.get('pool', None)
        area = options.get('area', 0)
        label = options.get('label', None)
        bbox = options.get('bbox', None)

        width_data = np.array(measure360(img, centroid, options=options))

        # remove any rows containing nan values
        width_data = width_data[~np.isnan(width_data).any(axis=1)]

        self.centroid = centroid
        self.width_data = width_data
        self.width = np.min(self.width_data[:, 1])
        self.length = np.max(self.width_data[:, 1])
        self.area = area
        self.label = label
        self.bbox = bbox
        self.slice = 0  # this is by default 0, but gets overwritten when data is generated from a stack

        self.n_lines = n_lines
        self.magnitude = magnitude
        self.px_size = options.get('px_size', None)
        self.psfFWHM = options.get('psfFWHM', None)

        self.xc = centroid[0]
        self.yc = centroid[1]

        self.xc_nm = self.xc * options.get('px_size', 1)
        self.yc_nm = self.yc * options.get('px_size', 1)

filter_measure360(bacteria, filter_type, filter_settings) #

Function to filter the width data of a bacterium.

Parameters:

Name Type Description Default
bacteria Bacteria360

The Bacteria360 object to be filtered.

required
filter_type str

The type of filter to apply. Options are 'stdev', 'derivative' or 'sav-gol'.

required
filter_settings list

The settings for the filter. For 'stdev' and 'derivative' this is a single value, for 'sav-gol' it is a list of two values [window, order].

required

Returns:

Name Type Description
bacteria Bacteria360

The Bacteria360 object with the filtered width data.

Source code in src/micromorph/measure360/measure360.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
def filter_measure360(bacteria, filter_type, filter_settings):
    """
    Function to filter the width data of a bacterium.

    Parameters
    ----------
    bacteria : Bacteria360
        The Bacteria360 object to be filtered.
    filter_type : str
        The type of filter to apply. Options are 'stdev', 'derivative' or 'sav-gol'.
    filter_settings : list
        The settings for the filter. For 'stdev' and 'derivative' this is a single value, for 'sav-gol' it is a list
        of two values [window, order].

    Returns
    -------
    bacteria : Bacteria360
        The Bacteria360 object with the filtered width data.
    """
    width_data = np.copy(bacteria.width_data[:, 1])

    if filter_type == 'stdev':
        # Filter the width
        idx = abs(width_data - np.median(width_data)) < filter_settings[0] * np.std(width_data)

        bacteria.width_data = bacteria.width_data[idx, :]
    elif filter_type == 'derivative':
        delta = np.diff(width_data)
        delta = np.insert(delta, 0, 0)
        idx = abs(delta) < filter_settings[0]

        bacteria.width_data = bacteria.width_data[idx, :]
    elif filter_type == 'sav-gol':
        window = filter_settings[0]
        order = filter_settings[1]
        width_data = savgol_filter(width_data, window, order)
        bacteria.width_data[:, 1] = width_data

    try:
        bacteria.width = np.min(bacteria.width_data[:, 1])
        bacteria.length = np.max(bacteria.width_data[:, 1])
    except:
        print("filtering didn't work")

    return bacteria

fit_multiprocessing(img, angle, x_1, y_1, x_2, y_2, params) #

Function to fit the width of the bacterium at a single angle.

Parameters:

Name Type Description Default
img np.array

The image of the bacterium.

required
angle float

The angle corresponding to the line. It is stores in the results array but has no bearing on the width calculation.

required
x_1 float

The x-coordinate of the start of the line.

required
y_1 float

The y-coordinate of the start of the line.

required
x_2 float

The x-coordinate of the end of the line.

required
y_2 float

The y-coordinate of the end of the line.

required
params list

List of parameters [psfFWHM, pxsize, fit_type].

required

Returns:

Name Type Description
data np.array

Array of data [angle, width, x_1, y_1, x_2, y_2].

Source code in src/micromorph/measure360/measure360.py
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def fit_multiprocessing(img, angle, x_1, y_1, x_2, y_2, params):
    """
    Function to fit the width of the bacterium at a single angle.

    Parameters
    ----------
    img : np.array
        The image of the bacterium.
    angle : float
        The angle corresponding to the line. It is stores in the results array but has no bearing on the width
        calculation.
    x_1 : float
        The x-coordinate of the start of the line.
    y_1 : float
        The y-coordinate of the start of the line.
    x_2 : float
        The x-coordinate of the end of the line.
    y_2 : float
        The y-coordinate of the end of the line.
    params : list
        List of parameters [psfFWHM, pxsize, fit_type].

    Returns
    -------
    data : np.array
        Array of data [angle, width, x_1, y_1, x_2, y_2].
    """

    psfFWHM = params[0]
    pxsize = params[1]
    fit_type = params[2]

    # Set up array to collect data
    current_profile = profile_line(img, (y_1, x_1), (y_2, x_2))

    x = np.arange(0, len(current_profile)) * pxsize

    try:
        if fit_type == 'fluorescence':
            result = fit_ring_profile(x, current_profile, psfFWHM)
            width = result.params['R'] * 2
        elif fit_type == 'phase':
            result = fit_phase_contrast_profile(x, current_profile)
            width = result.params['width'].value
        elif fit_type == 'tophat':
            result = fit_top_hat_profile(x, current_profile)
            width = result.params['width'].value
        else:
            raise ValueError('Invalid fit type - please choose "fluorescence" or "phase".')
    except:
        width = np.nan

    if width < 0:
        width = np.nan

    data = np.array([angle, width, x_1, y_1, x_2, y_2])

    return data

get_points_on_circle(centroid, magnitude, angle_range) #

Function to get the points on a circle of a given radius around a centroid.

Parameters:

Name Type Description Default
centroid tuple

The centroid of the bacterium.

required
magnitude float

The radius of the circle.

required
angle_range np.array

The range of angles to calculate the points.

required

Returns:

Name Type Description
x_1 np.array

The x-coordinates of the start of the lines.

y_1 np.array

The y-coordinates of the start of the lines.

x_2 np.array

The x-coordinates of the end of the lines.

y_2 np.array

The y-coordinates of the end of the lines.

Source code in src/micromorph/measure360/measure360.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def get_points_on_circle(centroid, magnitude, angle_range):
    """
    Function to get the points on a circle of a given radius around a centroid.

    Parameters
    ----------
    centroid : tuple
        The centroid of the bacterium.
    magnitude : float
        The radius of the circle.
    angle_range : np.array
        The range of angles to calculate the points.

    Returns
    -------
    x_1 : np.array
        The x-coordinates of the start of the lines.
    y_1 : np.array
        The y-coordinates of the start of the lines.
    x_2 : np.array
        The x-coordinates of the end of the lines.
    y_2 : np.array
        The y-coordinates of the end of the lines.
    """
    x_1 = centroid[1] + magnitude*np.cos(angle_range)
    y_1 = centroid[0] + magnitude*np.sin(angle_range)

    x_2 = centroid[1] - magnitude*np.cos(angle_range)
    y_2 = centroid[0] - magnitude*np.sin(angle_range)

    return x_1, y_1, x_2, y_2

measure360(img, centroid, options=dict()) #

This function measures the width of a bacterium at 360 degrees around its centroid.

Parameters:

Name Type Description Default
img np.array

The image of the bacterium.

required
centroid tuple

The centroid of the bacterium.

required
options dict

Dictionary of options to pass to the measure360 function.

dict()

Returns:

Name Type Description
data list

List of data [angle, width, x_1, y_1, x_2, y_2].

Source code in src/micromorph/measure360/measure360.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def measure360(img, centroid, options=dict()):
    """
    This function measures the width of a bacterium at 360 degrees around its centroid.

    Parameters
    ----------
    img : np.array
        The image of the bacterium.
    centroid : tuple
        The centroid of the bacterium.
    options : dict
        Dictionary of options to pass to the measure360 function.

    Returns
    -------
    data : list
        List of data [angle, width, x_1, y_1, x_2, y_2].
    """
    n_lines = options.get('n_lines', 100)
    magnitude = options.get('magnitude', 100)
    psfFWHM = options.get('psfFWHM', 0.25)
    px_size = options.get('pxsize', 0.65)
    fit_type = options.get('fit_type', 'fluorescence')
    pool = options.get('pool', None)
    params = [psfFWHM, px_size, fit_type]

    # # Initialise multi processing pool
    # if pool is None:
    #     # Number of processes to run in parallel
    #     num_processes = multiprocessing.cpu_count()
    #     # Create a pool of worker processes
    #     pool = multiprocessing.Pool(processes=num_processes)

    # Define angle_range based on required number of lines
    angle_range = np.linspace(0, np.pi, n_lines)

    # Get points on circle
    x_1, y_1, x_2, y_2 = get_points_on_circle(centroid, magnitude/2, angle_range)

    # Check if any x or y is below 0 or above the image size
    x_1 = np.clip(x_1, 0, img.shape[1])
    y_1 = np.clip(y_1, 0, img.shape[0])
    x_2 = np.clip(x_2, 0, img.shape[1])
    y_2 = np.clip(y_2, 0, img.shape[0])

    data = []

    start = time.time()

    # possible way to run multiprocessing?
#     with multiprocessing.Pool() as pool:
#        data = pool.map(fit_multiprocessing, [(img, angle_range[i], x_1[i], y_1[i], x_2[i], y_2[i], params) for i in range(len(x_1))])
    # data = pool.starmap(fit_multiprocessing, [(img, angle_range[i], x_1[i], y_1[i], x_2[i], y_2[i], params) for i in range(len(x_1))])

    for i in range(len(x_1)):
        # logging.debug(f'Processing line {i+1} of {len(x_1)}')
        data.append(fit_multiprocessing(img, angle_range[i], x_1[i], y_1[i], x_2[i], y_2[i], params))

    if options.get('verbose', False):
        logging.debug('Time taken: ', time.time() - start)

    return data

run_measure360(img: np.array, mask: np.array, options: dict = dict(), pool=None, close_pool=True) #

Runs the measure360 function on a single image or a stack of images.

Parameters:

Name Type Description Default
img np.array

Single frame (or stack), either phase contrast or fluorescence.

required
mask np.array

Labelled mask from the image.

required
options dict

Dictionary of options to pass to the measure360 function.

dict()

Returns:

Name Type Description
all_bacteria list

List of Bacteria360 objects.

Source code in src/micromorph/measure360/measure360.py
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
def run_measure360(img: np.array, mask: np.array, options: dict = dict(), pool=None, close_pool=True):
    """
    Runs the measure360 function on a single image or a stack of images.

    Parameters
    ----------
    img : np.array
        Single frame (or stack), either phase contrast or fluorescence.
    mask : np.array
        Labelled mask from the image.
    options : dict, optional
        Dictionary of options to pass to the measure360 function.

    Returns
    -------
    all_bacteria : list
        List of Bacteria360 objects.
    """
    if pool is None:
        logging.info("Making a pool for multithreading - this only happens once!")
        pool = ThreadPoolExecutor()  # Create executor outside a with block
        logging.info("Pool created!")
    else:
        logging.info("Using existing pool for multithreading")

    all_bacteria = []
    if len(img.shape) == 2:
        # Get bacteria properties
        props = regionprops(mask)

        args = [(img, mask, i, bact, options) for i, bact in enumerate(props)]

        futures = [pool.submit(process_single_bacterium, arg) for arg in args]
        with tqdm(total=len(args)) as pbar:
            for future in as_completed(futures):
                result = future.result()
                all_bacteria.append(result)
                pbar.update(1)
    else:
        n_frames = img.shape[0]
        for i in range(n_frames):
            # Get ith image and mask
            img_current = np.copy(img[i])
            mask_current = np.copy(mask[i])

            current_data = run_measure360(img_current, mask_current, options=options, pool=pool, close_pool=False)

            for bact in current_data:
                bact.slice = i

            all_bacteria.extend(current_data)

    if close_pool:
        if pool:
            pool.shutdown(wait=True)

    return all_bacteria

width_distribution(all_data) #

Convenience function to plot the distribution of the widths of all the bacteria.

Parameters:

Name Type Description Default
all_data list

List of Bacteria360 objects.

required

Returns:

Type Description
None
Source code in src/micromorph/measure360/measure360.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def width_distribution(all_data):
    """
    Convenience function to plot the distribution of the widths of all the bacteria.

    Parameters
    ----------
    all_data : list
        List of Bacteria360 objects.

    Returns
    -------
    None
    """

    all_widths = []

    for data in all_data:
        width = data.width
        all_widths.append(width)

    plt.hist(all_widths)
    plt.show()
    print(len(all_widths))