File size: 2,813 Bytes
6bcb3da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d31980
6bcb3da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
import cv2
import numpy as np
import gradio as gr

def process_image(image, calibration_factor):
    """
    Processes the input image to measure the hair diameter.
    
    Args:
        image (numpy array): Uploaded image in RGB format.
        calibration_factor (float): Conversion factor from pixels to micrometers.
    
    Returns:
        tuple: A result text and the annotated image.
    """
    if image is None:
        return "No image provided.", None

    # Convert image from RGB (Gradio default) to BGR (OpenCV default)
    image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    
    # Convert to grayscale
    gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
    
    # Apply Gaussian blur to reduce noise
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    
    # Apply thresholding (using Otsu's method) to create a binary image
    ret, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    
    # Find contours in the thresholded image
    contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    if not contours:
        return "No hair contour found.", image

    # Assume the hair strand is the largest contour
    hair_contour = max(contours, key=cv2.contourArea)
    
    # Fit a minimum area rectangle to the contour
    rect = cv2.minAreaRect(hair_contour)
    (center, (width, height), angle) = rect
    
    # The smaller dimension is considered the hair's diameter in pixels
    diameter_pixels = min(width, height)
    diameter_real = diameter_pixels * calibration_factor  # Convert to micrometers
    
    # Draw the fitted rectangle on the image for visualization
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    annotated = image_bgr.copy()
    cv2.drawContours(annotated, [box], 0, (0, 255, 0), 2)
    
    # Convert annotated image back to RGB for Gradio display
    annotated_rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
    
    result_text = f"Estimated Hair Diameter: {diameter_real:.2f} micrometers"
    return result_text, annotated_rgb

# Build the Gradio interface
iface = gr.Interface(
    fn=process_image,
    inputs=[
        gr.Image(type="numpy", label="Upload Hair Image"),
        gr.Number(value=0.05, label="Calibration Factor (micrometers per pixel)")
    ],
    outputs=[
        gr.Textbox(label="Result"),
        gr.Image(label="Annotated Image")
    ],
    title="Hair Diameter Measurement Tool",
    description=(
        "Upload a high-resolution image of a hair sample (captured with a dermascope) "
        "and provide a calibration factor based on a reference object included in the image. "
        "This tool processes the image to estimate the hair diameter using image processing techniques."
    )
)

if __name__ == '__main__':
    iface.launch()