celldetective 1.1.0__py3-none-any.whl → 1.1.1.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/__main__.py +5 -19
- celldetective/extra_properties.py +63 -53
- celldetective/filters.py +39 -11
- celldetective/gui/classifier_widget.py +56 -7
- celldetective/gui/control_panel.py +5 -0
- celldetective/gui/layouts.py +3 -2
- celldetective/gui/measurement_options.py +13 -109
- celldetective/gui/plot_signals_ui.py +1 -0
- celldetective/gui/process_block.py +1 -1
- celldetective/gui/survival_ui.py +7 -1
- celldetective/gui/tableUI.py +294 -28
- celldetective/gui/thresholds_gui.py +51 -10
- celldetective/gui/viewers.py +169 -22
- celldetective/io.py +41 -17
- celldetective/measure.py +13 -238
- celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
- celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
- celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
- celldetective/neighborhood.py +4 -1
- celldetective/preprocessing.py +483 -143
- celldetective/scripts/segment_cells.py +26 -7
- celldetective/scripts/train_segmentation_model.py +35 -34
- celldetective/segmentation.py +29 -20
- celldetective/signals.py +13 -231
- celldetective/tracking.py +2 -1
- celldetective/utils.py +440 -26
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/METADATA +1 -1
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/RECORD +34 -30
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/WHEEL +1 -1
- tests/test_preprocessing.py +37 -0
- tests/test_utils.py +48 -1
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/top_level.txt +0 -0
celldetective/measure.py
CHANGED
|
@@ -27,6 +27,7 @@ from skimage.draw import disk as dsk
|
|
|
27
27
|
from celldetective.filters import std_filter, gauss_filter
|
|
28
28
|
from celldetective.utils import rename_intensity_column, create_patch_mask, remove_redundant_features, \
|
|
29
29
|
remove_trajectory_measurements
|
|
30
|
+
from celldetective.preprocessing import field_correction
|
|
30
31
|
import celldetective.extra_properties as extra_properties
|
|
31
32
|
from celldetective.extra_properties import *
|
|
32
33
|
import cv2
|
|
@@ -771,7 +772,7 @@ def measure_isotropic_intensity(positions, # Dataframe of cell positions @ t
|
|
|
771
772
|
projection = np.multiply(crop_temp, expanded_mask)
|
|
772
773
|
|
|
773
774
|
projection[crop==epsilon] = epsilon
|
|
774
|
-
projection[expanded_mask==0
|
|
775
|
+
projection[expanded_mask[:,:,0]==0.,:] = epsilon
|
|
775
776
|
|
|
776
777
|
for op in operations:
|
|
777
778
|
func = eval('np.'+op)
|
|
@@ -861,7 +862,7 @@ def measure_at_position(pos, mode, return_measurements=False, threads=1):
|
|
|
861
862
|
return None
|
|
862
863
|
|
|
863
864
|
|
|
864
|
-
def local_normalisation(image, labels, background_intensity,
|
|
865
|
+
def local_normalisation(image, labels, background_intensity, measurement='intensity_median', operation='subtract', clip=False):
|
|
865
866
|
"""
|
|
866
867
|
Perform local normalization on an image based on labels.
|
|
867
868
|
|
|
@@ -907,10 +908,10 @@ def local_normalisation(image, labels, background_intensity, model='median', ope
|
|
|
907
908
|
continue
|
|
908
909
|
if operation == 'subtract':
|
|
909
910
|
image[np.where(labels == cell)] = image[np.where(labels == cell)].astype(float) - \
|
|
910
|
-
background_intensity[
|
|
911
|
+
background_intensity[measurement][index-1].astype(float)
|
|
911
912
|
elif operation == 'divide':
|
|
912
913
|
image[np.where(labels == cell)] = image[np.where(labels == cell)].astype(float) / \
|
|
913
|
-
background_intensity[
|
|
914
|
+
background_intensity[measurement][index-1].astype(float)
|
|
914
915
|
if clip:
|
|
915
916
|
image[image<=0.] = 0.
|
|
916
917
|
|
|
@@ -956,249 +957,23 @@ def normalise_by_cell(image, labels, distance=5, model='median', operation='subt
|
|
|
956
957
|
"""
|
|
957
958
|
border = contour_of_instance_segmentation(label=labels, distance=distance * (-1))
|
|
958
959
|
if model == 'mean':
|
|
960
|
+
measurement = 'intensity_nanmean'
|
|
961
|
+
extra_props = [getattr(extra_properties, measurement)]
|
|
959
962
|
background_intensity = regionprops_table(intensity_image=image, label_image=border,
|
|
960
|
-
|
|
963
|
+
extra_properties=extra_props)
|
|
961
964
|
elif model == 'median':
|
|
962
|
-
|
|
963
|
-
|
|
965
|
+
measurement = 'intensity_median'
|
|
966
|
+
extra_props = [getattr(extra_properties, measurement)]
|
|
964
967
|
background_intensity = regionprops_table(intensity_image=image, label_image=border,
|
|
965
|
-
extra_properties=
|
|
968
|
+
extra_properties=extra_props)
|
|
969
|
+
|
|
966
970
|
normalised_frame = local_normalisation(image=image.astype(float).copy(),
|
|
967
|
-
labels=labels, background_intensity=background_intensity,
|
|
971
|
+
labels=labels, background_intensity=background_intensity, measurement=measurement,
|
|
968
972
|
operation=operation, clip=clip)
|
|
969
973
|
|
|
970
974
|
return normalised_frame
|
|
971
975
|
|
|
972
976
|
|
|
973
|
-
def paraboloid(x, y, a, b, c, d, e, g):
|
|
974
|
-
return a * x ** 2 + b * y ** 2 + c * x * y + d * x + e * y + g
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
def plane(x, y, a, b, c):
|
|
978
|
-
return a * x + b * y + c
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
def fit_plane(image, cell_masks=None):
|
|
982
|
-
"""
|
|
983
|
-
Fit a plane to the given image data.
|
|
984
|
-
|
|
985
|
-
Parameters:
|
|
986
|
-
- image (numpy.ndarray): The input image data.
|
|
987
|
-
- cell_masks (numpy.ndarray, optional): An array specifying cell masks. If provided, areas covered by
|
|
988
|
-
cell masks will be excluded from the fitting process.
|
|
989
|
-
|
|
990
|
-
Returns:
|
|
991
|
-
- numpy.ndarray: The fitted plane.
|
|
992
|
-
|
|
993
|
-
This function fits a plane to the given image data using least squares regression. It constructs a mesh
|
|
994
|
-
grid based on the dimensions of the image and fits a plane model to the data points. If cell masks are
|
|
995
|
-
provided, areas covered by cell masks will be excluded from the fitting process.
|
|
996
|
-
|
|
997
|
-
Example:
|
|
998
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
999
|
-
>>> result = fit_plane(image)
|
|
1000
|
-
>>> print(result)
|
|
1001
|
-
[[1. 2. 3.]
|
|
1002
|
-
[4. 5. 6.]
|
|
1003
|
-
[7. 8. 9.]]
|
|
1004
|
-
|
|
1005
|
-
Note:
|
|
1006
|
-
- The 'cell_masks' parameter allows excluding areas covered by cell masks from the fitting process.
|
|
1007
|
-
"""
|
|
1008
|
-
data = np.empty(image.shape)
|
|
1009
|
-
x = np.arange(0, image.shape[1])
|
|
1010
|
-
y = np.arange(0, image.shape[0])
|
|
1011
|
-
xx, yy = np.meshgrid(x, y)
|
|
1012
|
-
params = Parameters()
|
|
1013
|
-
params.add('a', value=1)
|
|
1014
|
-
params.add('b', value=1)
|
|
1015
|
-
params.add('c', value=1)
|
|
1016
|
-
model = Model(plane, independent_vars=['x', 'y'])
|
|
1017
|
-
weights = np.ones_like(xx, dtype=float)
|
|
1018
|
-
weights[np.where(cell_masks > 0)] = 0.
|
|
1019
|
-
result = model.fit(image,
|
|
1020
|
-
x=xx,
|
|
1021
|
-
y=yy,
|
|
1022
|
-
weights=weights,
|
|
1023
|
-
params=params, max_nfev=3000)
|
|
1024
|
-
a = result.params['a'].value
|
|
1025
|
-
b = result.params['b'].value
|
|
1026
|
-
c = result.params['c'].value
|
|
1027
|
-
fitted_plane = plane(xx, yy, a, b, c)
|
|
1028
|
-
return fitted_plane
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
def fit_paraboloid(image, cell_masks=None):
|
|
1032
|
-
"""
|
|
1033
|
-
Fit a paraboloid to the given image data.
|
|
1034
|
-
|
|
1035
|
-
Parameters:
|
|
1036
|
-
- image (numpy.ndarray): The input image data.
|
|
1037
|
-
- cell_masks (numpy.ndarray, optional): An array specifying cell masks. If provided, areas covered by
|
|
1038
|
-
cell masks will be excluded from the fitting process.
|
|
1039
|
-
|
|
1040
|
-
Returns:
|
|
1041
|
-
- numpy.ndarray: The fitted paraboloid.
|
|
1042
|
-
|
|
1043
|
-
This function fits a paraboloid to the given image data using least squares regression. It constructs
|
|
1044
|
-
a mesh grid based on the dimensions of the image and fits a paraboloid model to the data points. If cell
|
|
1045
|
-
masks are provided, areas covered by cell masks will be excluded from the fitting process.
|
|
1046
|
-
|
|
1047
|
-
Example:
|
|
1048
|
-
>>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
1049
|
-
>>> result = fit_paraboloid(image)
|
|
1050
|
-
>>> print(result)
|
|
1051
|
-
[[1. 2. 3.]
|
|
1052
|
-
[4. 5. 6.]
|
|
1053
|
-
[7. 8. 9.]]
|
|
1054
|
-
|
|
1055
|
-
Note:
|
|
1056
|
-
- The 'cell_masks' parameter allows excluding areas covered by cell masks from the fitting process.
|
|
1057
|
-
"""
|
|
1058
|
-
data = np.empty(image.shape)
|
|
1059
|
-
x = np.arange(0, image.shape[1])
|
|
1060
|
-
y = np.arange(0, image.shape[0])
|
|
1061
|
-
xx, yy = np.meshgrid(x, y)
|
|
1062
|
-
params = Parameters()
|
|
1063
|
-
params.add('a', value=1)
|
|
1064
|
-
params.add('b', value=1)
|
|
1065
|
-
params.add('c', value=1)
|
|
1066
|
-
params.add('d', value=1)
|
|
1067
|
-
params.add('e', value=1)
|
|
1068
|
-
params.add('g', value=1)
|
|
1069
|
-
model = Model(paraboloid, independent_vars=['x', 'y'])
|
|
1070
|
-
weights = np.ones_like(xx, dtype=float)
|
|
1071
|
-
weights[np.where(cell_masks > 0)] = 0.
|
|
1072
|
-
|
|
1073
|
-
result = model.fit(image,
|
|
1074
|
-
x=xx,
|
|
1075
|
-
y=yy,
|
|
1076
|
-
weights=weights,
|
|
1077
|
-
params=params, max_nfev=3000)
|
|
1078
|
-
a = result.params['a'].value
|
|
1079
|
-
b = result.params['b'].value
|
|
1080
|
-
c = result.params['c'].value
|
|
1081
|
-
d = result.params['d'].value
|
|
1082
|
-
e = result.params['e'].value
|
|
1083
|
-
g = result.params['g'].value
|
|
1084
|
-
fitted_paraboloid = paraboloid(xx, yy, a, b, c, d, e, g)
|
|
1085
|
-
return fitted_paraboloid
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
def correct_image(img, cell_masks=None, normalisation_operation=None, clip=False, mode=None):
|
|
1089
|
-
"""
|
|
1090
|
-
Correct an image based on fitted models.
|
|
1091
|
-
|
|
1092
|
-
Parameters:
|
|
1093
|
-
- img (numpy.ndarray): The input image data.
|
|
1094
|
-
- cell_masks (numpy.ndarray, optional): An array specifying cell masks. If provided, areas covered by
|
|
1095
|
-
cell masks will be considered during correction.
|
|
1096
|
-
- normalisation_operation (str, optional): The normalisation operation ('Subtract' or 'Divide') to apply
|
|
1097
|
-
during correction.
|
|
1098
|
-
- clip (bool, optional): Whether to clip corrected values below zero to a minimum value of 0.00001.
|
|
1099
|
-
- mode (str, optional): The mode of correction ('Paraboloid' or 'Plane').
|
|
1100
|
-
|
|
1101
|
-
Returns:
|
|
1102
|
-
- tuple: A tuple containing the corrected image and the fitted model parameters.
|
|
1103
|
-
|
|
1104
|
-
This function corrects an image based on fitted models such as paraboloid or plane. It first fits a model
|
|
1105
|
-
to the image data based on the specified mode. Then, it performs correction by subtracting or dividing the
|
|
1106
|
-
image by the fitted model. Optionally, it clips corrected values below zero to a minimum value of 0.00001.
|
|
1107
|
-
|
|
1108
|
-
Example:
|
|
1109
|
-
>>> img = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
1110
|
-
>>> correction, para = correct_image(img, mode='Paraboloid', normalisation_operation='Subtract', clip=True)
|
|
1111
|
-
>>> print(correction)
|
|
1112
|
-
[[0. 0. 0.]
|
|
1113
|
-
[0. 0. 0.]
|
|
1114
|
-
[0. 0. 0.]]
|
|
1115
|
-
>>> print(para)
|
|
1116
|
-
[[1. 2. 3.]
|
|
1117
|
-
[4. 5. 6.]
|
|
1118
|
-
[7. 8. 9.]]
|
|
1119
|
-
|
|
1120
|
-
Note:
|
|
1121
|
-
- The 'cell_masks' parameter allows considering areas covered by cell masks during correction.
|
|
1122
|
-
- The 'normalisation_operation' parameter specifies whether to subtract or divide the fitted model from
|
|
1123
|
-
the image.
|
|
1124
|
-
- If 'clip' is set to True, corrected values below zero will be clipped to a minimum value of 0.00001.
|
|
1125
|
-
"""
|
|
1126
|
-
if mode == "Paraboloid":
|
|
1127
|
-
para = fit_paraboloid(img.astype(float), cell_masks=cell_masks).astype(float)
|
|
1128
|
-
elif mode == "Plane":
|
|
1129
|
-
para = fit_plane(img.astype(float), cell_masks=cell_masks).astype(float)
|
|
1130
|
-
|
|
1131
|
-
if para is not None:
|
|
1132
|
-
para = np.array(para)
|
|
1133
|
-
if normalisation_operation == 'Subtract':
|
|
1134
|
-
correction = img.astype(float) - para.astype(float) # + 1000.
|
|
1135
|
-
else:
|
|
1136
|
-
correction = img.astype(float) / para.astype(float) # + 1000.
|
|
1137
|
-
correction = np.array(correction, dtype=float)
|
|
1138
|
-
if clip:
|
|
1139
|
-
correction[correction <= 0] = 0.00001
|
|
1140
|
-
|
|
1141
|
-
else:
|
|
1142
|
-
correction = None
|
|
1143
|
-
|
|
1144
|
-
return correction, para
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
def field_normalisation(img, threshold, normalisation_operation, clip, mode):
|
|
1148
|
-
"""
|
|
1149
|
-
Perform field normalization on an image.
|
|
1150
|
-
|
|
1151
|
-
Parameters:
|
|
1152
|
-
- img (numpy.ndarray): The input image data.
|
|
1153
|
-
- threshold (float): The threshold value for determining regions of interest.
|
|
1154
|
-
- normalisation_operation (str): The normalization operation ('Subtract' or 'Divide') to apply during correction.
|
|
1155
|
-
- clip (bool): Whether to clip corrected values below zero to a minimum value of 0.00001.
|
|
1156
|
-
- mode (str): The mode of correction ('Paraboloid' or 'Plane').
|
|
1157
|
-
|
|
1158
|
-
Returns:
|
|
1159
|
-
- tuple: A tuple containing the normalized image and the fitted background model.
|
|
1160
|
-
|
|
1161
|
-
This function performs field normalization on an image based on regions of interest determined by the
|
|
1162
|
-
specified threshold. It identifies regions with standard deviation above the threshold and considers them
|
|
1163
|
-
as areas of interest. Then, it corrects the image using the 'correct_image' function based on the specified
|
|
1164
|
-
mode and normalization operation.
|
|
1165
|
-
|
|
1166
|
-
Example:
|
|
1167
|
-
>>> img = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
|
1168
|
-
>>> fluo_max, bg_fit = field_normalisation(img, threshold=0.5, normalisation_operation='Subtract', clip=True, mode='Paraboloid')
|
|
1169
|
-
>>> print(fluo_max)
|
|
1170
|
-
[[0. 0. 0.]
|
|
1171
|
-
[0. 0. 0.]
|
|
1172
|
-
[0. 0. 0.]]
|
|
1173
|
-
>>> print(bg_fit)
|
|
1174
|
-
[[1. 2. 3.]
|
|
1175
|
-
[4. 5. 6.]
|
|
1176
|
-
[7. 8. 9.]]
|
|
1177
|
-
|
|
1178
|
-
Note:
|
|
1179
|
-
- The 'threshold' parameter determines regions of interest based on standard deviation.
|
|
1180
|
-
- The 'normalisation_operation' parameter specifies whether to subtract or divide the fitted model from
|
|
1181
|
-
the image during correction.
|
|
1182
|
-
- If 'clip' is set to True, corrected values below zero will be clipped to a minimum value of 0.00001.
|
|
1183
|
-
"""
|
|
1184
|
-
std_img = std_filter(gauss_filter(img, 2), 4)
|
|
1185
|
-
mask = np.zeros_like(img)
|
|
1186
|
-
if threshold=='':
|
|
1187
|
-
pass
|
|
1188
|
-
mask[np.where(std_img > float(threshold))] = 1.0
|
|
1189
|
-
mask_int = mask.astype(int)
|
|
1190
|
-
mask_int = binary_fill_holes(mask_int).astype(float)
|
|
1191
|
-
# invert_mask = np.zeros_like(mask_int)
|
|
1192
|
-
# invert_mask[mask_int == 0] = 1
|
|
1193
|
-
# if isinstance(normalisation_operation,bool) and normalisation_operation:
|
|
1194
|
-
# normalisation_operation = 'Subtract'
|
|
1195
|
-
# else:
|
|
1196
|
-
# normalisation_operation = 'Divide'
|
|
1197
|
-
fluo_max, bg_fit = correct_image(img.astype(float), cell_masks=mask_int,
|
|
1198
|
-
normalisation_operation=normalisation_operation, clip=clip, mode=mode)
|
|
1199
|
-
return fluo_max, bg_fit
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
977
|
def blob_detection(image, label, threshold, diameter):
|
|
1203
978
|
"""
|
|
1204
979
|
Perform blob detection on an image based on labeled regions.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channels": [
|
|
3
|
+
"effector_fluo_channel",
|
|
4
|
+
"None"
|
|
5
|
+
],
|
|
6
|
+
"diameter": 30.0,
|
|
7
|
+
"cellprob_threshold": 0.0,
|
|
8
|
+
"flow_threshold": 0.4,
|
|
9
|
+
"normalization_percentile": [
|
|
10
|
+
true,
|
|
11
|
+
true
|
|
12
|
+
],
|
|
13
|
+
"normalization_clip": [
|
|
14
|
+
true,
|
|
15
|
+
true
|
|
16
|
+
],
|
|
17
|
+
"normalization_values": [
|
|
18
|
+
[
|
|
19
|
+
0.5,
|
|
20
|
+
99.0
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
1.0,
|
|
24
|
+
99.0
|
|
25
|
+
]
|
|
26
|
+
],
|
|
27
|
+
"model_type": "cellpose",
|
|
28
|
+
"spatial_calibration": 0.21783999999999998
|
|
29
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"model_name": "cp-cfse-transfer",
|
|
3
|
+
"model_type": "cellpose",
|
|
4
|
+
"pretrained": "/home/limozin/Documents/GitHub/celldetective/celldetective/models/segmentation_generic/CP_cyto2",
|
|
5
|
+
"spatial_calibration": 0.21783999999999998,
|
|
6
|
+
"channel_option": [
|
|
7
|
+
"effector_fluo_channel",
|
|
8
|
+
"None"
|
|
9
|
+
],
|
|
10
|
+
"normalization_percentile": [
|
|
11
|
+
true,
|
|
12
|
+
true
|
|
13
|
+
],
|
|
14
|
+
"normalization_clip": [
|
|
15
|
+
true,
|
|
16
|
+
true
|
|
17
|
+
],
|
|
18
|
+
"normalization_values": [
|
|
19
|
+
[
|
|
20
|
+
0.5,
|
|
21
|
+
99.0
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
1.0,
|
|
25
|
+
99.0
|
|
26
|
+
]
|
|
27
|
+
],
|
|
28
|
+
"ds": [
|
|
29
|
+
"/home/limozin/Desktop/primNK_w_MCF7/dataset"
|
|
30
|
+
],
|
|
31
|
+
"augmentation_factor": 1.5,
|
|
32
|
+
"validation_split": 0.2,
|
|
33
|
+
"learning_rate": 0.001,
|
|
34
|
+
"batch_size": 8,
|
|
35
|
+
"epochs": 3000,
|
|
36
|
+
"target_directory": "/home/limozin/Documents/GitHub/celldetective/celldetective/models/segmentation_effectors"
|
|
37
|
+
}
|
celldetective/neighborhood.py
CHANGED
|
@@ -916,7 +916,8 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
916
916
|
idx_B = np.where(mask_ids_B==int(mask_B))[0][0]
|
|
917
917
|
print(idx_A, idx_B)
|
|
918
918
|
indices_to_keep.append([idx_A,idx_B])
|
|
919
|
-
except:
|
|
919
|
+
except Exception as e:
|
|
920
|
+
print(f'{e} {t=} error something happened!!')
|
|
920
921
|
pass
|
|
921
922
|
|
|
922
923
|
print(f'Indices to keep: {indices_to_keep}...')
|
|
@@ -927,6 +928,8 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
927
928
|
mask[indices_to_keep[:,1],indices_to_keep[:,0]] = False
|
|
928
929
|
dist_map[mask] = 1.0E06
|
|
929
930
|
plot_map=True
|
|
931
|
+
else:
|
|
932
|
+
dist_map[:,:] = 1.0E06
|
|
930
933
|
else:
|
|
931
934
|
dist_map[:,:] = 1.0E06
|
|
932
935
|
|