File size: 6,036 Bytes
6f7e88c
 
 
 
 
6ee26b7
6f7e88c
511a575
6f7e88c
 
 
ccef6d5
6f7e88c
 
 
ccef6d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f7e88c
 
 
 
 
 
3d340b6
6f7e88c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45e8490
6f7e88c
 
 
 
 
45e8490
6f7e88c
 
 
 
 
45e8490
6f7e88c
 
 
 
45e8490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d340b6
 
45e8490
3d340b6
 
 
be66cb9
3d340b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db18e99
 
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import streamlit as st
from PIL import Image
import numpy as np
import cv2
from huggingface_hub import from_pretrained_keras
import pandas as pd

st.header("Tooth detection and segmentation in panoramic X-Rays")
st.subheader("Iteration to improve demo")
st.markdown(
    """
    Demo for testing image segmentation CNN model
"""
)

st.markdown(
    """
    ### Technical overview
    
    * **Architecture:** It utilizes the U-Net architecture, a popular "encoder-decoder" convolutional neural network (CNN) specifically optimized for biomedical image segmentation where pixel-level accuracy is critical.
    * **Performance:** In the accompanying research, the model achieved a Dice overlap score of 95.4% for overall teeth segmentation.
    * **Post-processing:** A key highlight of this specific implementation is the use of grayscale morphological filtering and operations applied to the sigmoid output. This reduces tooth counting errors significantly (from 26.8% down to roughly 6.2%).
    * **Dataset:** The model was trained on a relatively small but highly curated dataset (approximately 105 to 116 panoramic images) based on work by Abdi et al. (2015).
    
    ### Key applications
    
    * **Clinical diagnosis:** Assists dentists in identifying the boundaries of individual teeth to detect caries, lesions, or bone loss.
    * **Forensics and identification:** Automates the process of identifying dental patterns for human remains or age/gender determination.
    * **Treatment planning:** Provides a baseline for orthodontic therapy workups by isolating dental structures from the surrounding mandible and maxilla.
    """
)

## Select and load the model
model_id = "SerdarHelli/Segmentation-of-Teeth-in-Panoramic-X-ray-Image-Using-U-Net"
model = from_pretrained_keras(model_id)

## Allow the user to upload an image
archivo_imagen = st.file_uploader("Upload your image here.", type=["png", "jpg", "jpeg"])

## If an image has more than one channel, it is converted to grayscale (1 channel)
def convertir_one_channel(img):
    if len(img.shape) > 2:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        return img
    else:
        return img

def convertir_rgb(img):
    if len(img.shape) == 2:
        img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
        return img
    else:
        return img

## We will manipulate the interface so we can use example images
## If the user clicks on an example, the model will run with the following:
ejemplos = ["dientes_1.png", "dientes_2.png", "dientes_3.png"]

## Create three columns; an example image will be in each one
col1, col2, col3 = st.columns(3)

with col1:
    ## The image is loaded and displayed in the interface
    ex = Image.open(ejemplos[0])
    st.image(ex, width=200)
    ## If the button is pressed, we will use this example in the model
    if st.button("Run this example 1"):
        archivo_imagen = ejemplos[0]

with col2:
    ex1 = Image.open(ejemplos[1])
    st.image(ex1, width=200)
    if st.button("Run this example 2"):
        archivo_imagen = ejemplos[1]

with col3:
    ex2 = Image.open(ejemplos[2])
    st.image(ex2, width=200)
    if st.button("Run this example 3"):
        archivo_imagen = ejemplos[2]

## If we have an image to input into the model, 
## we process it and feed it to the model
if archivo_imagen is not None:
    ## Load the image with PIL and display it immediately
    img = Image.open(archivo_imagen)
    st.image(img, width=850)
    
    ## Trigger the loading spinner for the heavy lifting
    with st.spinner('Analyzing panoramic X-ray. This may take a few seconds...'):
        img = np.array(img)    #Creates a writable copy

        ## Process the image for model input
        img_cv = convertir_one_channel(img)
        img_cv = cv2.resize(img_cv, (512, 512), interpolation=cv2.INTER_LANCZOS4)
        img_cv = np.float32(img_cv / 255)
        img_cv = np.reshape(img_cv, (1, 512, 512, 1))

        ## Feed the NumPy array into the model
        predicted = model.predict(img_cv)
        predicted = predicted[0]

        ## Resize the image back to its original shape and add the segmentation masks
        predicted = cv2.resize(
            predicted, (img.shape[1], img.shape[0]), interpolation=cv2.INTER_LANCZOS4)
        
        mask = np.uint8(predicted * 255)
        _, mask = cv2.threshold(
            mask, thresh=0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU
        )
        
        kernel = np.ones((5, 5), dtype=np.float32)
        mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
        mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=1)
        
        cnts, hieararch = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
        output = cv2.drawContours(convertir_one_channel(img), cnts, -1, (255, 0, 0), 3)

    ## If we successfully obtained a result, display it in the interface
    if output is not None:
        st.subheader("Image segmentation:")
        st.write(output.shape)
        st.image(output, width=850)

        st.subheader("Diagnostic metrics overview")
        
        conteo_dientes = len(cnts)
        area_total = np.sum(mask > 0)
        area_promedio = area_total / conteo_dientes if conteo_dientes > 0 else 0
        
        col_m1, col_m2, col_m3 = st.columns(3)
        col_m1.metric("Estimated tooth count", conteo_dientes)
        col_m2.metric("Total dental area (px)", f"{area_total:,}")
        col_m3.metric("Average tooth area (px)", f"{int(area_promedio):,}")
        
        st.markdown("### Detected instances data")
        
        datos_dientes = []
        for i, c in enumerate(cnts):
            area = cv2.contourArea(c)
            x, y, w, h = cv2.boundingRect(c)
            datos_dientes.append({
                "ID": i + 1,
                "Area (px)": area,
                "Width (px)": w,
                "Height (px)": h
            })
            
        df_dientes = pd.DataFrame(datos_dientes)
        st.dataframe(df_dientes, use_container_width=True)