many small improvements

This commit is contained in:
2023-06-04 18:51:06 +02:00
parent 95c4319340
commit cf144e5b28
20 changed files with 583 additions and 74 deletions

View File

@ -20,6 +20,8 @@ toast_container.position_end = 0
def onclick(_):
# NOTE: you can also use app.toast(...) and app.alert_success(...) to show toasts
# and alerts at the default bottom right position
alert = bHTML.AlertSuccess("You clicked me!", parent=app.main)
alert.w = 25
toast = bHTML.Toast("You clicked me!", parent=toast_container)
@ -27,4 +29,3 @@ def onclick(_):
toast.show()
btn.onclick = onclick

View File

@ -1,5 +1,5 @@
const pwa_version = "01_hello_world_202306031006"
const pwa_version = "01_hello_world_202306041650"
const assets = ["./index.html",
"./main.py",
"./resources/bootstrap.css",
@ -48,4 +48,15 @@ self.addEventListener('fetch', event => {
return response;
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if(key !== pwa_version) {
return caches.delete(key);
}
}));
})
);
});

View File

@ -1,5 +1,5 @@
const pwa_version = "02_image_filter_202306031006"
const pwa_version = "02_image_filter_202306041650"
const assets = ["./index.html",
"./main.py",
"./resources/bootstrap.css",
@ -48,4 +48,15 @@ self.addEventListener('fetch', event => {
return response;
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if(key !== pwa_version) {
return caches.delete(key);
}
}));
})
);
});

View File

@ -1,5 +1,5 @@
const pwa_version = "03_numpy_grid_demo_202306031006"
const pwa_version = "03_numpy_grid_demo_202306041650"
const assets = ["./index.html",
"./main.py",
"./resources/bootstrap.css",
@ -48,4 +48,15 @@ self.addEventListener('fetch', event => {
return response;
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if(key !== pwa_version) {
return caches.delete(key);
}
}));
})
);
});

View File

@ -9,13 +9,13 @@ import numpy as np
from PIL import Image
import cv2
loaded_img = None
loaded_img:np.ndarray = None
app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Pyscript Cell Detector")
app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Pyscript Cell Colony Detector")
main_div = bHTML.BootstrapContainer(parent=app.main)
main_div.w = 100
result_div = bHTML.BootstrapContainer(parent=main_div)
result_div = bHTML.BootstrapContainer (parent=main_div)
def process_image(image,
@ -24,13 +24,20 @@ def process_image(image,
hough_param2 = 500,
minRadius = 100,
maxRadius=500,
inner_hough_circles=True):
inner_hough_param1 = 25,
inner_hough_param2 = 50,
inner_hough_circles=True,
cell_colony_color_channel:int = None):
for child in result_div.children:
child.destroy()
#image = cv2.imread(str(test_img_path), cv2.IMREAD_COLOR)
# Convert the image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
if cell_colony_color_channel is None:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image.astype(float)[...,cell_colony_color_channel]
gray = (255 * gray / gray.max()).astype(np.uint8)
# Use median blur to reduce noise
gray = cv2.medianBlur(gray, 5)
@ -46,7 +53,7 @@ def process_image(image,
maxRadius=maxRadius)
if circles is None:
bHTML.AlertDanger("No cell areas detected in image", parent=app.main)
app.alert_danger("No cell areas detected in image")
return False
# Convert to integers
@ -84,7 +91,14 @@ def process_image(image,
for well, well_gray in zip(first_wells, first_wells_gray):
circles = cv2.HoughCircles(well_gray, cv2.HOUGH_GRADIENT, 1, minDist=hough_min_dist, param1=50, param2=50, minRadius=(well.shape[0] // 2) - 50, maxRadius=(well.shape[0] // 2) - 10)
circles = cv2.HoughCircles(well_gray,
cv2.HOUGH_GRADIENT,
1,
minDist=hough_min_dist,
param1=inner_hough_param1,
param2=inner_hough_param2,
minRadius=int((min(well.shape[0],well.shape[1]) // 2)*0.79),
maxRadius=int((min(well.shape[0],well.shape[1]) // 2)*0.95))
if circles is not None:
circles = np.uint16(np.around(circles))
i = circles[0,:][0]
@ -109,9 +123,11 @@ def process_image(image,
second_wells = first_wells
second_wells_gray = first_wells_gray
tabs = {}
for well, well_gray in zip(second_wells, second_wells_gray):
for well in second_wells:
well_gray = cv2.cvtColor(well, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(255 - well_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
@ -120,29 +136,37 @@ def process_image(image,
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
radius = well.shape[0] // 2 -1
radius = min(well.shape[0], well.shape[1]) // 2 -1
# Now clear all pixels outside the circular well.
# We do this by creating a mask for the well and applying it to the image.
well_mask = np.zeros_like(cleaned, dtype=np.uint8)
cv2.circle(well_mask, (radius, radius), radius, 1, thickness=-1)
cv2.circle(well_mask, (well.shape[1] // 2, well.shape[0] // 2), radius, 1, thickness=-1)
cleaned = cleaned * well_mask
circle = well_mask > 0
# now create an image overlay to display
ratio = np.sum((cleaned > 0).astype(int)) / np.sum(circle.astype(int))
div_result = bHTML.BootstrapContainer(f"ratio: {ratio * 100}%", parent=result_div)
div_result = bHTML.BootstrapContainer(f"ratio: {ratio * 100}%")
div_result.shadow = bHTML.Shadow.LARGE
div_result.rounded = True
div_result.w = 50
div_result.w = 75
div_result.h = 75
div_result.rounded_size = 50
div_result.p = 3
well = well.copy()
well[cleaned > 0,0] = 255
final_img = bHTML.Image.from_numpy_array(well, parent=div_result)
final_img.rounded = True
final_img.rounded_size = 10
final_img.rounded_size = 50
final_img.shadow = bHTML.Shadow.LARGE
final_img.w = 100
tabs[f"Well #{len(tabs) + 1}"] = div_result
tabs = bHTML.Tabs(tabs, parent=result_div)
tabs.w = 100
div = bHTML.BootstrapContainer("Controls", parent=app.sidebar)
@ -150,64 +174,135 @@ div.font_size = 4
image_input = bInputs.InputFile(label_text="choose image file", parent=app.sidebar)
i_hough_min_dist = bInputs.InputInt("hough min distance [px]", parent=app.sidebar)
i_hough_min_dist.value = 500
i_hough_param1 = bInputs.InputInt("hough param1", parent=app.sidebar)
i_hough_param1.value = 80
i_hough_param2 = bInputs.InputInt("hough param2", parent=app.sidebar)
i_hough_param2.value = 500
i_hough_min_radius = bInputs.InputInt("hough min radius [px]", parent=app.sidebar)
i_hough_min_radius.value = 100
i_hough_max_radius = bInputs.InputInt("hough max radius [px]", parent=app.sidebar)
i_hough_max_radius.value = 500
btn = bHTML.ButtonPrimary("Process", parent=app.sidebar)
btn.w = 100
btn.mt = 3
btn.mb = 3
btn.ml = 4
btn.mr = 4
i_hough_min_dist = bInputs.InputInt("hough min distance [px]",
parent=app.sidebar,
help_text="minimal distance between wells in pixels")
i_hough_min_dist.value = 500
i_hough_param1 = bInputs.InputInt("hough param1",
parent=app.sidebar,
help_text="parameter for canny edge detector")
i_hough_param1.value = 80
i_hough_param2 = bInputs.InputInt("hough param2",
parent=app.sidebar,
help_text="increase this value to prevent false circle detection")
i_hough_param2.value = 500
i_hough_min_radius = bInputs.InputInt("hough min radius [px]",
parent=app.sidebar,
help_text="min radius for circle detection")
i_hough_min_radius.value = 100
i_hough_max_radius = bInputs.InputInt("hough max radius [px]",
parent=app.sidebar,
help_text="max radius for circle detection")
i_hough_max_radius.value = 500
i_inner_hough = bInputs.InputCheckboxSingle("nested hough transform?",
parent=app.sidebar,
help_text="if set, circle detection will be applied twice on detected wells")
i_inner_hough.value = True
i_inner_hough.m = 3
i_inner_hough_param1 = bInputs.InputInt("inner hough param1", parent=app.sidebar)
i_inner_hough_param1.value = 25
i_inner_hough_param2 = bInputs.InputInt("inner hough param2", parent=app.sidebar)
i_inner_hough_param2.value = 50
i_use_color_channel = bInputs.InputSelect(["all",
"red",
"green",
"blue"],
label_text="cell colony color channel",
parent=app.sidebar,
help_text="if cells are more present in a specific channel, select it here")
i_use_color_channel.value = "blue"
def on_image_change(f, *args):
global loaded_img
f.seek(0)
img = np.array(Image.open(f))
for child in result_div.children:
child.destroy()
output = bHTML.Image.from_numpy_array(img, parent=result_div)
output.rounded = True
output.rounded_size = 10
output.shadow = bHTML.Shadow.LARGE
output.w=50
loaded_img = img
try:
global loaded_img
f.seek(0)
img = np.array(Image.open(f))
for child in result_div.children:
child.destroy()
bHTML.BootstrapContainer("input image:", parent=result_div)
output = bHTML.Image.from_numpy_array(img, parent=result_div)
output.rounded = True
output.rounded_size = 10
output.shadow = bHTML.Shadow.LARGE
output.width = "100%"
output.height = "100%"
loaded_img = img
app.toast("successfully loaded image", "info")
#toast = bHTML.Toast("successfully loaded image", title="info", parent=toast_container)
#toast.animation = True
#toast.show()
except Exception as e:
for child in result_div.children:
child.destroy()
bHTML.AlertDanger(f"error while loading image: {str(e)}", parent=alert_container)
image_input.onchange = on_image_change
def on_click(*args, **kwargs):
if loaded_img is None:
bHTML.AlertDanger("No image loaded", parent=app.main)
app.alert_danger("No image loaded")
return
h_min_dist = int(i_hough_min_dist.value)
h_param1 = int(i_hough_param1.value)
h_param2 = int(i_hough_param2.value)
h_min_radius = int(i_hough_min_radius.value)
h_max_radius = int(i_hough_max_radius.value)
for child in result_div.children:
child.destroy()
try:
h_min_dist = int(i_hough_min_dist.value)
h_param1 = int(i_hough_param1.value)
h_param2 = int(i_hough_param2.value)
h_inner_param1 = int(i_inner_hough_param1.value)
h_inner_param2 = int(i_inner_hough_param2.value)
h_min_radius = int(i_hough_min_radius.value)
h_max_radius = int(i_hough_max_radius.value)
inner_hough = bool(i_inner_hough.value)
color_channel = {
"all": None,
"red": 0,
"green": 1,
"blue": 2
}[i_use_color_channel.value]
process_image(loaded_img,
hough_min_dist=h_min_dist,
hough_param1=h_param1,
hough_param2=h_param2,
minRadius=h_min_radius,
maxRadius=h_max_radius,
inner_hough_circles=True)
process_image(loaded_img,
hough_min_dist=h_min_dist,
hough_param1=h_param1,
hough_param2=h_param2,
minRadius=h_min_radius,
maxRadius=h_max_radius,
inner_hough_param1=h_inner_param1,
inner_hough_param2=h_inner_param2,
inner_hough_circles=inner_hough,
cell_colony_color_channel=color_channel)
app.toast("successfully processed image", title="info")
except Exception as e:
for child in result_div.children:
child.destroy()
app.alert_danger(f"error while processing image: {str(e)}")
btn.onclick = on_click

View File

@ -1,5 +1,5 @@
const pwa_version = "04_cell_detector_202306031006"
const pwa_version = "04_cell_detector_202306041650"
const assets = ["./index.html",
"./main.py",
"./resources/bootstrap.css",
@ -48,4 +48,15 @@ self.addEventListener('fetch', event => {
return response;
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if(key !== pwa_version) {
return caches.delete(key);
}
}));
})
);
});