Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Created on Thu Mar 20 14:23:27 2025 | |
| @author: mattc | |
| """ | |
| import os | |
| import cv2 | |
| from PIL import Image | |
| def pad(img_np, tw=2048, th=1536): | |
| """ | |
| Pads a numpy image (grayscale or RGB) to 2048x1536 (width x height) with white pixels. | |
| Pads at the bottom and right as needed. | |
| """ | |
| height, width = img_np.shape[:2] | |
| pad_bottom = max(0, th - height) | |
| pad_right = max(0, tw - width) | |
| # Padding: (top, bottom, left, right) | |
| if img_np.ndim == 3: | |
| # Color image (H, W, 3) | |
| border_value = [255, 255, 255] | |
| else: | |
| # Grayscale image (H, W) | |
| border_value = 255 | |
| padded = cv2.copyMakeBorder( | |
| img_np, | |
| top=0, bottom=pad_bottom, | |
| left=0, right=pad_right, | |
| borderType=cv2.BORDER_CONSTANT, | |
| value=border_value | |
| ) | |
| return padded | |
| #this is the huggingface version | |
| import numpy as np | |
| from PIL import Image | |
| def cut_img(img, patch_size=512): | |
| img_map = {} | |
| width, height = img.size | |
| i_num = height // patch_size | |
| j_num = width // patch_size | |
| count = 1 | |
| for i in range(i_num): | |
| for j in range(j_num): | |
| cropped_img = img.crop(( | |
| patch_size * j, | |
| patch_size * i, | |
| patch_size * (j + 1), | |
| patch_size * (i + 1) | |
| )) | |
| img_map[count] = cropped_img | |
| count += 1 | |
| return img_map, i_num, j_num # Return rows and cols for stitching | |
| import numpy as np | |
| import numpy as np | |
| from PIL import Image | |
| def stitch(img_map, i_num, j_num, min_width=2048, min_height=1536): | |
| tiles = [] | |
| count = 1 | |
| for i in range(i_num): | |
| row_tiles = [] | |
| for j in range(j_num): | |
| tile = np.array(img_map[count]) | |
| row_tiles.append(tile) | |
| count += 1 | |
| row_img = np.hstack(row_tiles) | |
| tiles.append(row_img) | |
| stitched = np.vstack(tiles) | |
| # Pad the stitched image if it's less than min_width/min_height | |
| h, w = stitched.shape[:2] | |
| pad_h = max(0, min_height - h) | |
| pad_w = max(0, min_width - w) | |
| if pad_h > 0 or pad_w > 0: | |
| # Pad as (top, bottom), (left, right), (channels) | |
| if stitched.ndim == 3: | |
| stitched = np.pad(stitched, ((0, pad_h), (0, pad_w), (0, 0)), 'constant') | |
| else: | |
| stitched = np.pad(stitched, ((0, pad_h), (0, pad_w)), 'constant') | |
| return stitched | |
| import matplotlib.pyplot as plt | |
| def visualize_segmentation(mask, image=0): | |
| plt.figure(figsize=(10, 5)) | |
| if(not np.isscalar(image)): | |
| # Show original image if it is entered | |
| plt.subplot(1, 2, 1) | |
| plt.imshow(image) | |
| plt.title("Original Image") | |
| plt.axis("off") | |
| # Show segmentation mask | |
| plt.subplot(1, 2, 2) | |
| plt.imshow(mask, cmap="gray") # Show as grayscale | |
| plt.title("Segmentation Mask") | |
| plt.axis("off") | |
| plt.show() | |
| import torch | |
| from transformers import SegformerForSemanticSegmentation | |
| # Load fine-tuned model | |
| #ReyaLabColumbia/Segformer_Colony_Counter | |
| #ReyaLabColumbia/OrganoidCounter | |
| model = SegformerForSemanticSegmentation.from_pretrained("ReyaLabColumbia/Segformer_Organoid_Counter_GP") | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| model.to(device) | |
| model.eval() # Set to evaluation mode | |
| # Load image processor | |
| from transformers import SegformerForSemanticSegmentation, SegformerImageProcessor | |
| image_processor = SegformerImageProcessor.from_pretrained("nvidia/segformer-b3-finetuned-cityscapes-1024-1024") | |
| def preprocess_image(image): | |
| image = image.convert("RGB") # Open and convert to RGB | |
| inputs = image_processor(image, return_tensors="pt") # Preprocess for model | |
| return image, inputs["pixel_values"] | |
| def postprocess_mask(logits): | |
| mask = torch.argmax(logits, dim=1) # Take argmax across the class dimension | |
| return mask.squeeze().cpu().numpy() # Convert to NumPy array | |
| def eval_img(image): | |
| # Load and preprocess image | |
| image, pixel_values = preprocess_image(image) | |
| pixel_values = pixel_values.to(device) | |
| with torch.no_grad(): # No gradient calculation for inference | |
| outputs = model(pixel_values=pixel_values) # Run model | |
| logits = outputs.logits | |
| # Convert logits to segmentation mask | |
| segmentation_mask = postprocess_mask(logits) | |
| #visualize_segmentation(segmentation_mask,image) | |
| segmentation_mask = cv2.resize(segmentation_mask, (512, 512), interpolation=cv2.INTER_LINEAR_EXACT) | |
| return(segmentation_mask) | |
| # for x in img_map: | |
| # mask = eval_img(img_map[x]) | |
| # cv2.imwrite(img_map[x], mask) | |
| # del mask,x | |
| # p = stitch(img_map) | |
| # visualize_segmentation(p) | |
| # num_colony = np.count_nonzero(p == 1) # Counts number of 1s | |
| # num_necrosis = np.count_nonzero(p == 2) | |
| # num_necrosis/num_colony | |
| def find_colonies(mask, size_cutoff, circ_cutoff): | |
| binary_mask = np.where(mask == 1, 255, 0).astype(np.uint8) | |
| #print(np.max(binary_mask)) | |
| contours, _ = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| contoursf = [] | |
| for x in contours: | |
| area = cv2.contourArea(x) | |
| if (area < size_cutoff): | |
| continue | |
| perimeter = cv2.arcLength(x, True) | |
| # Avoid division by zero | |
| if perimeter == 0: | |
| continue | |
| # Calculate circularity | |
| circularity = (4 * np.pi * area) / (perimeter ** 2) | |
| if circularity >= circ_cutoff: | |
| contoursf.append(x) | |
| return(contoursf) | |
| def find_necrosis(mask): | |
| binary_mask = np.where(mask == 2, 255, 0).astype(np.uint8) | |
| #print(np.max(binary_mask)) | |
| contours, _ = cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| return(contours) | |
| # contour_image = np.zeros_like(p) | |
| # contours = find_necrosis(p) | |
| # cv2.drawContours(contour_image, contours, -1, (255), 2) | |
| # visualize_segmentation(contour_image) | |
| import pandas as pd | |
| def compute_centroid(contour): | |
| M = cv2.moments(contour) | |
| if M["m00"] == 0: # Avoid division by zero | |
| return None | |
| cx = int(M["m10"] / M["m00"]) | |
| cy = int(M["m01"] / M["m00"]) | |
| return (cx, cy) | |
| def contours_overlap_using_mask(contour1, contour2, image_shape=(1536, 2048)): | |
| """Check if two contours overlap using a bitwise AND mask.""" | |
| import numpy as np | |
| import cv2 | |
| mask1 = np.zeros(image_shape, dtype=np.uint8) | |
| mask2 = np.zeros(image_shape, dtype=np.uint8) | |
| # Draw each contour as a white shape on its respective mask | |
| cv2.drawContours(mask1, [contour1], -1, 255, thickness=cv2.FILLED) | |
| cv2.drawContours(mask2, [contour2], -1, 255, thickness=cv2.FILLED) | |
| # Compute bitwise AND to find overlapping regions | |
| overlap = cv2.bitwise_and(mask1, mask2) | |
| return np.any(overlap) | |
| def analyze_colonies(mask, size_cutoff, circ_cutoff, img): | |
| colonies = find_colonies(mask, size_cutoff, circ_cutoff) | |
| necrosis = find_necrosis(mask) | |
| data = [] | |
| for colony in colonies: | |
| colony_area = cv2.contourArea(colony) | |
| centroid = compute_centroid(colony) | |
| if colony_area <= 50: | |
| continue | |
| mask = np.zeros(img.shape, np.uint8) | |
| cv2.drawContours(mask, [colony], -1, 255, cv2.FILLED) | |
| pix = img[mask == 255] | |
| # Check if any necrosis contour is inside the colony | |
| necrosis_area = 0 | |
| nec_list =[] | |
| for nec in necrosis: | |
| # Check if the first point of the necrosis contour is inside the colony | |
| if contours_overlap_using_mask(colony, nec): | |
| nec_area = cv2.contourArea(nec) | |
| necrosis_area += nec_area | |
| nec_list.append(nec) | |
| data.append({ | |
| "organoid_area": colony_area, | |
| "necrotic_area": necrosis_area, | |
| "centroid": centroid, | |
| "percent_necrotic": necrosis_area/colony_area, | |
| "contour": colony, | |
| "nec_contours": nec_list, | |
| 'mean_pixel_value':np.mean(pix) | |
| }) | |
| # Convert results to a DataFrame | |
| df = pd.DataFrame(data) | |
| df.index = range(1,len(df.index)+1) | |
| return(df) | |
| def main(args): | |
| min_size = args[1] | |
| min_circ = args[2] | |
| do_necrosis = args[3] | |
| colonies = {} | |
| img_map, i_num, j_num = cut_img(Image.fromarray(pad(np.array(args[0]),512,512))) | |
| for z in img_map: | |
| img_map[z] = eval_img(img_map[z]) | |
| del z | |
| p = stitch(img_map, i_num, j_num) | |
| colonies = analyze_colonies(p, min_size, min_circ, np.array(args[0])) | |
| if len(colonies) <=0: | |
| img = pad(np.array(args[0])) | |
| caption = np.ones((150, 2048, 3), dtype=np.uint8) * 255 # Multiply by 255 to make it white | |
| cv2.putText(caption, 'No organoids detected.', (40, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 3) | |
| cv2.imwrite('results.png', np.vstack((img, caption))) | |
| colonies = pd.DataFrame({"organoid_number":[], 'organoid_volume':[], "organoid_area":[],'mean_pixel_value':[], "centroid":[], "necrotic_area":[],"percent_necrotic":[]}) | |
| with pd.ExcelWriter('results.xlsx') as writer: | |
| colonies.to_excel(writer, sheet_name="Colony data", index=False) | |
| return(np.vstack((img, caption)), 'results.png', 'results.xlsx') | |
| img =pad(np.array(args[0])) | |
| img = cv2.copyMakeBorder(img,top=0, bottom=10,left=0,right=10, borderType=cv2.BORDER_CONSTANT, value=[255, 255, 255]) | |
| #print(colonies.to_string()) | |
| colonies = colonies.sort_values(by=["organoid_area"], ascending=False) | |
| colonies = colonies[colonies["organoid_area"]>= min_size] | |
| colonies.index = range(1,len(colonies.index)+1) | |
| for i in range(len(colonies)): | |
| cv2.drawContours(img, [list(colonies["contour"])[i]], -1, (0, 255, 0), 2) | |
| if do_necrosis == True: | |
| cv2.drawContours(img, list(colonies['nec_contours'])[i], -1, (0, 0, 255), 2) | |
| coords = list(list(colonies["centroid"])[i]) | |
| if coords[0] > 1950: | |
| #if a colony is too close to the right edge, makes the label move to left | |
| coords[0] = 1950 | |
| cv2.putText(img, str(colonies.index[i]), tuple(coords), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 0), 2) | |
| img = cv2.copyMakeBorder(img,top=10, bottom=0,left=10,right=0, borderType=cv2.BORDER_CONSTANT, value=[255, 255, 255]) | |
| colonies = colonies.drop('contour', axis=1) | |
| colonies = colonies.drop('nec_contours', axis=1) | |
| colonies.insert(loc=0, column="organoid_number", value=[str(x) for x in range(1, len(colonies)+1)]) | |
| total_area_dark = sum(colonies['necrotic_area']) | |
| total_area_light = sum(colonies['organoid_area']) | |
| ratio = total_area_dark/(abs(total_area_light)+1) | |
| radii = [np.sqrt(x/3.1415) for x in list(colonies['organoid_area'])] | |
| volumes = [4.189*(x**3) for x in radii] | |
| colonies['organoid_volume'] = volumes | |
| del radii, volumes | |
| meanpix = sum(colonies['mean_pixel_value'] * colonies['organoid_area'])/total_area_light | |
| colonies.loc[len(colonies)+1] = ["Total", total_area_light, total_area_dark, None, ratio, meanpix, sum(colonies['organoid_volume'])] | |
| del meanpix | |
| colonies = colonies[["organoid_number", 'organoid_volume', "organoid_area",'mean_pixel_value', "centroid", "necrotic_area","percent_necrotic"]] | |
| if do_necrosis == False: | |
| colonies = colonies.drop('necrotic_area', axis=1) | |
| colonies = colonies.drop('percent_necrotic', axis=1) | |
| Parameters = pd.DataFrame({"Minimum organoid size in pixels":[min_size], "Minimum organoid circularity":[min_circ]}) | |
| with pd.ExcelWriter('results.xlsx') as writer: | |
| colonies.to_excel(writer, sheet_name="Organoid data", index=False) | |
| Parameters.to_excel(writer, sheet_name="Parameters", index=False) | |
| caption = np.ones((150, 2068, 3), dtype=np.uint8) * 255 # Multiply by 255 to make it white | |
| if do_necrosis == True: | |
| cv2.putText(caption, "Total area necrotic: "+str(total_area_dark)+ ", Total area living: "+str(total_area_light)+", Ratio: "+str(ratio), (40, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 3) | |
| else: | |
| cv2.putText(caption, "Total area: "+str(total_area_light), (40, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 3) | |
| cv2.putText(caption, "Total number of organoids: "+str(len(colonies)-1), (40, 110), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 3) | |
| cv2.imwrite('results.png', np.vstack((img, caption))) | |
| return(np.vstack((img, caption)), 'results.png', 'results.xlsx') | |