Skip to content

Commit 389314a

Browse files
author
Corentin
committed
first commit
0 parents  commit 389314a

13 files changed

+3880
-0
lines changed

LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# MyoQuant
2+
3+
MyoQuant is a web application for quantifying the number of cells in a histological image.
4+
It is built using CellPose, Stardist and custom models and image analysis techniques to automatically analyze myopathy histology images.
5+
This web application is intended for demonstration purposes only.
6+
7+
## How to install or deploy
8+
9+
A Streamlit cloud demo instance should be deployed at https://lbgi.fr/MyoQuant/. I am currently working on proper docker images and tutorial to deploy the application. Meanwhile you can still use the following instructions:
10+
11+
### Docker
12+
13+
You can build the docker image by running `docker build -t streamlit .` and launch the container using `docker run -p 8501:8501 streamlit`.
14+
15+
### Non-Docker
16+
17+
If you do not want to use Docker you can install the poetry package in a miniconda (python 3.9) base env, run `poetry install` to install the python env, activate the env with `poetry shell` and launch the app by running `streamlit run Home.py`.
18+
19+
### Deploy on Google Colab for GPU
20+
21+
As this application uses various deep-learning model, you could benefit from using a deployment solution that provides a GPU.
22+
To do so, you can leverage Google Colab free GPU to boost this Streamlit application.
23+
To run this app on Google Colab, simply clone the notebook called `google_colab_deploy.ipynb` into Colab and run the four cells. It will automatically download the latest code version, install dependencies and run the app. A link will appear in the output of the lat cell with a structure like `https://word1-word2-try-01-234-567-890.loca.lt`. Click it and the click continue and you’re ready to use the app!
24+
25+
## How to Use
26+
27+
A Streamlit cloud demo instance should be deployed at https://lbgi.fr/MyoQuant/. I am currently working on docker images and tutorial to deploy the application.
28+
Once on the demo, click on the corresponding staining analysis on the sidebar, and upload your histology image. Results will be displayed in the main area automatically.
29+
For HE Staining analysis, you can download this sample image: [HERE](https://www.lbgi.fr/~meyer/SDH_models/sample_he.jpg)
30+
For SDH Staining analysis, you can download this sample image: [HERE](https://www.lbgi.fr/~meyer/SDH_models/sample_sdh.jpg)
31+
32+
## Who and how
33+
34+
- Creator and Maintainer: [Corentin Meyer, 3rd year PhD Student in the CSTB Team, ICube—CNRS—Unistra] (https://lambda-science.github.io/)
35+
- The source code for this application is available [HERE] (https://github.com/lambda-science/MyoQuant)

myoquant/HE_analysis.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import numpy as np
2+
import pandas as pd
3+
from skimage.draw import line
4+
from skimage.measure import regionprops_table
5+
6+
from .draw_line import *
7+
8+
9+
def extract_ROIs(histo_img, index, cellpose_df, mask_stardist):
10+
single_cell_img = histo_img[
11+
cellpose_df.iloc[index, 5] : cellpose_df.iloc[index, 7],
12+
cellpose_df.iloc[index, 6] : cellpose_df.iloc[index, 8],
13+
].copy()
14+
nucleus_single_cell_img = mask_stardist[
15+
cellpose_df.iloc[index, 5] : cellpose_df.iloc[index, 7],
16+
cellpose_df.iloc[index, 6] : cellpose_df.iloc[index, 8],
17+
].copy()
18+
single_cell_mask = cellpose_df.iloc[index, 9]
19+
single_cell_img[~single_cell_mask] = 0
20+
nucleus_single_cell_img[~single_cell_mask] = 0
21+
22+
props_nuc_single = regionprops_table(
23+
nucleus_single_cell_img,
24+
intensity_image=single_cell_img,
25+
properties=[
26+
"label",
27+
"area",
28+
"centroid",
29+
"eccentricity",
30+
"bbox",
31+
"image",
32+
"perimeter",
33+
],
34+
)
35+
df_nuc_single = pd.DataFrame(props_nuc_single)
36+
return single_cell_img, nucleus_single_cell_img, single_cell_mask, df_nuc_single
37+
38+
39+
def single_cell_analysis(
40+
single_cell_img,
41+
single_cell_mask,
42+
df_nuc_single,
43+
x_fiber,
44+
y_fiber,
45+
internalised_threshold=0.75,
46+
):
47+
n_nuc, n_nuc_intern, n_nuc_periph = 0, 0, 0
48+
for _, value in df_nuc_single.iterrows():
49+
n_nuc += 1
50+
# Extend line and find closest point
51+
m, b = line_equation(x_fiber, y_fiber, value[3], value[2])
52+
53+
intersections_lst = calculate_intersection(
54+
m, b, (single_cell_img.shape[0], single_cell_img.shape[1])
55+
)
56+
border_point = calculate_closest_point(value[3], value[2], intersections_lst)
57+
rr, cc = line(
58+
int(y_fiber),
59+
int(x_fiber),
60+
int(border_point[1]),
61+
int(border_point[0]),
62+
)
63+
for index3, coords in enumerate(list(zip(rr, cc))):
64+
try:
65+
if single_cell_mask[coords] == 0:
66+
dist_nuc_cent = calculate_distance(
67+
x_fiber, y_fiber, value[3], value[2]
68+
)
69+
dist_out_of_fiber = calculate_distance(
70+
x_fiber, y_fiber, coords[1], coords[0]
71+
)
72+
ratio_dist = dist_nuc_cent / dist_out_of_fiber
73+
if ratio_dist < internalised_threshold:
74+
n_nuc_intern += 1
75+
else:
76+
n_nuc_periph += 1
77+
break
78+
except IndexError:
79+
coords = list(zip(rr, cc))[index3 - 1]
80+
dist_nuc_cent = calculate_distance(x_fiber, y_fiber, value[3], value[2])
81+
dist_out_of_fiber = calculate_distance(
82+
x_fiber, y_fiber, coords[1], coords[0]
83+
)
84+
ratio_dist = dist_nuc_cent / dist_out_of_fiber
85+
if ratio_dist < internalised_threshold:
86+
n_nuc_intern += 1
87+
else:
88+
n_nuc_periph += 1
89+
break
90+
91+
return n_nuc, n_nuc_intern, n_nuc_periph
92+
93+
94+
def predict_all_cells(
95+
histo_img, cellpose_df, mask_stardist, internalised_threshold=0.75
96+
):
97+
list_n_nuc, list_n_nuc_intern, list_n_nuc_periph = [], [], []
98+
for index in range(len(cellpose_df)):
99+
(
100+
single_cell_img,
101+
_,
102+
single_cell_mask,
103+
df_nuc_single,
104+
) = extract_ROIs(histo_img, index, cellpose_df, mask_stardist)
105+
x_fiber = cellpose_df.iloc[index, 3] - cellpose_df.iloc[index, 6]
106+
y_fiber = cellpose_df.iloc[index, 2] - cellpose_df.iloc[index, 5]
107+
n_nuc, n_nuc_intern, n_nuc_periph = single_cell_analysis(
108+
single_cell_img, single_cell_mask, df_nuc_single, x_fiber, y_fiber
109+
)
110+
list_n_nuc.append(n_nuc)
111+
list_n_nuc_intern.append(n_nuc_intern)
112+
list_n_nuc_periph.append(n_nuc_periph)
113+
df_nuc_analysis = pd.DataFrame(
114+
list(zip(list_n_nuc, list_n_nuc_intern, list_n_nuc_periph)),
115+
columns=["N° Nuc", "N° Nuc Intern", "N° Nuc Periph"],
116+
)
117+
return df_nuc_analysis
118+
119+
120+
def paint_histo_img(histo_img, cellpose_df, prediction_df):
121+
paint_img = np.zeros((histo_img.shape[0], histo_img.shape[1]), dtype=np.uint8)
122+
for index in range(len(cellpose_df)):
123+
single_cell_mask = cellpose_df.iloc[index, 9].copy()
124+
if prediction_df.iloc[index, 1] == 0:
125+
paint_img[
126+
cellpose_df.iloc[index, 5] : cellpose_df.iloc[index, 7],
127+
cellpose_df.iloc[index, 6] : cellpose_df.iloc[index, 8],
128+
][single_cell_mask] = 1
129+
elif prediction_df.iloc[index, 1] > 0:
130+
paint_img[
131+
cellpose_df.iloc[index, 5] : cellpose_df.iloc[index, 7],
132+
cellpose_df.iloc[index, 6] : cellpose_df.iloc[index, 8],
133+
][single_cell_mask] = 2
134+
return paint_img
135+
136+
137+
def run_he_analysis(image_ndarray, mask_cellpose, mask_stardist, eccentricity_thresh):
138+
props_cellpose = regionprops_table(
139+
mask_cellpose,
140+
properties=[
141+
"label",
142+
"area",
143+
"centroid",
144+
"eccentricity",
145+
"bbox",
146+
"image",
147+
"perimeter",
148+
],
149+
)
150+
df_cellpose = pd.DataFrame(props_cellpose)
151+
df_nuc_analysis = predict_all_cells(image_ndarray, df_cellpose, mask_stardist)
152+
153+
# Result table dict
154+
headers = ["Feature", "Raw Count", "Proportion (%)"]
155+
data = []
156+
data.append(["N° Nuclei", df_nuc_analysis["N° Nuc"].sum(), 100])
157+
data.append(
158+
[
159+
"N° Intern. Nuclei",
160+
df_nuc_analysis["N° Nuc Intern"].sum(),
161+
100
162+
* df_nuc_analysis["N° Nuc Intern"].sum()
163+
/ df_nuc_analysis["N° Nuc"].sum(),
164+
]
165+
)
166+
data.append(
167+
[
168+
"N° Periph. Nuclei",
169+
df_nuc_analysis["N° Nuc Periph"].sum(),
170+
100
171+
* df_nuc_analysis["N° Nuc Periph"].sum()
172+
/ df_nuc_analysis["N° Nuc"].sum(),
173+
]
174+
)
175+
data.append(
176+
[
177+
"N° Cells with 1+ intern. nuc.",
178+
df_nuc_analysis["N° Nuc Intern"].astype(bool).sum(axis=0),
179+
100
180+
* df_nuc_analysis["N° Nuc Intern"].astype(bool).sum(axis=0)
181+
/ len(df_nuc_analysis),
182+
]
183+
)
184+
185+
result_df = pd.DataFrame(columns=headers, data=data)
186+
label_map_he = paint_histo_img(image_ndarray, df_cellpose, df_nuc_analysis)
187+
return result_df, label_map_he

myoquant/SDH_analysis.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import os
2+
3+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
4+
5+
import pandas as pd
6+
import tensorflow as tf
7+
from rich.progress import track
8+
from skimage.measure import regionprops_table
9+
10+
from .gradcam import *
11+
from .random_brightness import *
12+
13+
labels_predict = ["control", "sick"]
14+
tf.random.set_seed(42)
15+
np.random.seed(42)
16+
17+
18+
def predict_single_cell(single_cell_img, _model_SDH):
19+
img_array = np.empty((1, 256, 256, 3))
20+
img_array[0] = tf.image.resize(single_cell_img, (256, 256))
21+
prediction = _model_SDH.predict(img_array)
22+
predicted_class = prediction.argmax()
23+
predicted_proba = round(np.amax(prediction), 2)
24+
heatmap = make_gradcam_heatmap(
25+
img_array, _model_SDH.get_layer("resnet50v2"), "conv5_block3_3_conv"
26+
)
27+
grad_cam_img = save_and_display_gradcam(img_array[0], heatmap)
28+
return grad_cam_img, predicted_class, predicted_proba
29+
30+
31+
def resize_batch_cells(histo_img, cellpose_df):
32+
img_array_full = np.empty((len(cellpose_df), 256, 256, 3))
33+
for index in track(range(len(cellpose_df)), description="Resizing cells"):
34+
single_cell_img = histo_img[
35+
cellpose_df.iloc[index, 5] : cellpose_df.iloc[index, 7],
36+
cellpose_df.iloc[index, 6] : cellpose_df.iloc[index, 8],
37+
].copy()
38+
39+
single_cell_mask = cellpose_df.iloc[index, 9].copy()
40+
single_cell_img[~single_cell_mask] = 0
41+
42+
img_array_full[index] = tf.image.resize(single_cell_img, (256, 256))
43+
return img_array_full
44+
45+
46+
def predict_all_cells(histo_img, cellpose_df, _model_SDH):
47+
predicted_class_array = np.empty((len(cellpose_df)))
48+
predicted_proba_array = np.empty((len(cellpose_df)))
49+
img_array_full = resize_batch_cells(histo_img, cellpose_df)
50+
prediction = _model_SDH.predict(img_array_full)
51+
index_counter = 0
52+
for prediction_result in prediction:
53+
predicted_class_array[index_counter] = prediction_result.argmax()
54+
predicted_proba_array[index_counter] = np.amax(prediction_result)
55+
index_counter += 1
56+
return predicted_class_array, predicted_proba_array
57+
58+
59+
def paint_full_image(image_sdh, df_cellpose, class_predicted_all):
60+
image_sdh_paint = np.zeros((image_sdh.shape[0], image_sdh.shape[1]), dtype=np.uint8)
61+
for index in track(range(len(df_cellpose)), description="Painting cells"):
62+
single_cell_mask = df_cellpose.iloc[index, 9].copy()
63+
if class_predicted_all[index] == 0:
64+
image_sdh_paint[
65+
df_cellpose.iloc[index, 5] : df_cellpose.iloc[index, 7],
66+
df_cellpose.iloc[index, 6] : df_cellpose.iloc[index, 8],
67+
][single_cell_mask] = 1
68+
elif class_predicted_all[index] == 1:
69+
image_sdh_paint[
70+
df_cellpose.iloc[index, 5] : df_cellpose.iloc[index, 7],
71+
df_cellpose.iloc[index, 6] : df_cellpose.iloc[index, 8],
72+
][single_cell_mask] = 2
73+
return image_sdh_paint
74+
75+
76+
def run_sdh_analysis(image_array, model_SDH, mask_cellpose):
77+
props_cellpose = regionprops_table(
78+
mask_cellpose,
79+
properties=[
80+
"label",
81+
"area",
82+
"centroid",
83+
"eccentricity",
84+
"bbox",
85+
"image",
86+
"perimeter",
87+
],
88+
)
89+
df_cellpose = pd.DataFrame(props_cellpose)
90+
class_predicted_all, proba_predicted_all = predict_all_cells(
91+
image_array, df_cellpose, model_SDH
92+
)
93+
94+
count_per_label = np.unique(class_predicted_all, return_counts=True)
95+
class_and_proba_df = pd.DataFrame(
96+
list(zip(class_predicted_all, proba_predicted_all)),
97+
columns=["class", "proba"],
98+
)
99+
100+
# Result table dict
101+
headers = ["Feature", "Raw Count", "Proportion (%)"]
102+
data = []
103+
data.append(["Muscle Fibers", len(class_predicted_all), 100])
104+
for elem in count_per_label[0]:
105+
data.append(
106+
[
107+
labels_predict[int(elem)],
108+
count_per_label[1][int(elem)],
109+
100 * count_per_label[1][int(elem)] / len(class_predicted_all),
110+
]
111+
)
112+
result_df = pd.DataFrame(columns=headers, data=data)
113+
# Paint The Full Image
114+
full_label_map = paint_full_image(image_array, df_cellpose, class_predicted_all)
115+
return result_df, full_label_map

myoquant/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)