Kalbe-x-Bangkit commited on
Commit
fcc071b
1 Parent(s): 797df57

Upload 13 files

Browse files
BBox_List_2017.xlsx ADDED
Binary file (67.5 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official lightweight Python image.
2
+ # https://hub.docker.com/_/python
3
+ FROM python:3.9-slim
4
+ EXPOSE 8080
5
+
6
+ # Install Python dependencies
7
+ COPY requirements.txt .
8
+ RUN pip install -r requirements.txt
9
+
10
+ # Copy local code to the container image
11
+ WORKDIR /app
12
+ COPY . ./
13
+
14
+ # Run the web service on container startup
15
+ CMD ["streamlit", "run", "app.py", "--server.port=8080", "--server.address=0.0.0.0"]
app-flask-api.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, Form, UploadFile
2
+ from fastapi.responses import JSONResponse
3
+ from google.cloud import storage
4
+ import os
5
+ import io
6
+ import numpy as np
7
+ import cv2
8
+ from PIL import Image
9
+ import uuid
10
+ import base64
11
+
12
+ os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = "./da-kalbe-63ee33c9cdbb.json"
13
+ bucket_name = "da-kalbe-ml-result-png"
14
+ storage_client = storage.Client()
15
+ bucket = storage_client.bucket(bucket_name)
16
+
17
+ app = FastAPI()
18
+
19
+ # Function to upload file to Google Cloud Storage
20
+ def upload_to_gcs(image: Image, filename: str):
21
+ """Uploads an image to Google Cloud Storage."""
22
+ try:
23
+ blob = bucket.blob(filename)
24
+ image_buffer = io.BytesIO()
25
+ image.save(image_buffer, format='PNG')
26
+ image_buffer.seek(0)
27
+ blob.upload_from_file(image_buffer, content_type='image/png')
28
+ except Exception as e:
29
+ return {'error': f"An unexpected error occurred: {e}"}
30
+
31
+ def upload_folder_images(image_path, enhanced_image_path):
32
+ # Extract the base name of the uploaded image without the extension
33
+ folder_name = os.path.splitext(os.path.basename(image_path))[0]
34
+
35
+ # Create the folder in Cloud Storage
36
+ bucket.blob(folder_name + '/').upload_from_string('', content_type='application/x-www-form-urlencoded')
37
+
38
+ # Open the images
39
+ original_image = Image.open(image_path)
40
+ enhanced_image = Image.open(enhanced_image_path)
41
+
42
+ # Upload images to GCS
43
+ upload_to_gcs(original_image, folder_name + '/' + 'original_image.png')
44
+ upload_to_gcs(enhanced_image, folder_name + '/' + enhancement_type + '.png')
45
+
46
+ def calculate_mse(original_image, enhanced_image):
47
+ mse = np.mean((original_image - enhanced_image) ** 2)
48
+ return mse
49
+
50
+ def calculate_psnr(original_image, enhanced_image):
51
+ mse = calculate_mse(original_image, enhanced_image)
52
+ if mse == 0:
53
+ return float('inf')
54
+ max_pixel_value = 255.0
55
+ psnr = 20 * np.log10(max_pixel_value / np.sqrt(mse))
56
+ return psnr
57
+
58
+ def calculate_maxerr(original_image, enhanced_image):
59
+ maxerr = np.max((original_image - enhanced_image) ** 2)
60
+ return maxerr
61
+
62
+ def calculate_l2rat(original_image, enhanced_image):
63
+ l2norm_ratio = np.sum(original_image ** 2) / np.sum((original_image - enhanced_image) ** 2)
64
+ return l2norm_ratio
65
+
66
+ def process_image(original_image, enhancement_type, fix_monochrome=True):
67
+ if fix_monochrome and original_image.shape[-1] == 3:
68
+ original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
69
+
70
+ image = original_image - np.min(original_image)
71
+ image = image / np.max(original_image)
72
+ image = (image * 255).astype(np.uint8)
73
+
74
+ enhanced_image = enhance_image(image, enhancement_type)
75
+
76
+ mse = calculate_mse(original_image, enhanced_image)
77
+ psnr = calculate_psnr(original_image, enhanced_image)
78
+ maxerr = calculate_maxerr(original_image, enhanced_image)
79
+ l2rat = calculate_l2rat(original_image, enhanced_image)
80
+
81
+ return enhanced_image, mse, psnr, maxerr, l2rat
82
+
83
+ def apply_clahe(image):
84
+ clahe = cv2.createCLAHE(clipLimit=40.0, tileGridSize=(8, 8))
85
+ return clahe.apply(image)
86
+
87
+ def invert(image):
88
+ return cv2.bitwise_not(image)
89
+
90
+ def hp_filter(image, kernel=None):
91
+ if kernel is None:
92
+ kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
93
+ return cv2.filter2D(image, -1, kernel)
94
+
95
+ def unsharp_mask(image, radius=5, amount=2):
96
+ def usm(image, radius, amount):
97
+ blurred = cv2.GaussianBlur(image, (0, 0), radius)
98
+ sharpened = cv2.addWeighted(image, 1.0 + amount, blurred, -amount, 0)
99
+ return sharpened
100
+ return usm(image, radius, amount)
101
+
102
+ def hist_eq(image):
103
+ return cv2.equalizeHist(image)
104
+
105
+ def enhance_image(image, enhancement_type):
106
+ if enhancement_type == "Invert":
107
+ return invert(image)
108
+ elif enhancement_type == "High Pass Filter":
109
+ return hp_filter(image)
110
+ elif enhancement_type == "Unsharp Masking":
111
+ return unsharp_mask(image)
112
+ elif enhancement_type == "Histogram Equalization":
113
+ return hist_eq(image)
114
+ elif enhancement_type == "CLAHE":
115
+ return apply_clahe(image)
116
+ else:
117
+ raise ValueError(f"Unknown enhancement type: {enhancement_type}")
118
+
119
+ @app.post("/process_image")
120
+ async def process_image_api(image: UploadFile = File(...), enhancement_type: str = Form(...)):
121
+ """Processes an uploaded image and returns the enhanced image and metrics."""
122
+
123
+ if not image:
124
+ return JSONResponse(status_code=400, content={'error': 'No image file provided'})
125
+
126
+ allowed_extensions = {'png', 'jpg', 'jpeg'}
127
+ if '.' not in image.filename or image.filename.split('.')[-1].lower() not in allowed_extensions:
128
+ return JSONResponse(status_code=400, content={'error': 'Invalid image file'})
129
+
130
+ try:
131
+ # Open the image using Pillow
132
+ image_pil = Image.open(image.file).convert('RGB')
133
+
134
+ # Convert to NumPy array
135
+ image_np = np.array(image_pil)
136
+
137
+ # Apply image processing
138
+ enhanced_image, mse, psnr, maxerr, l2rat = process_image(image_np, enhancement_type)
139
+
140
+ # Convert processed image back to PIL format for saving
141
+ enhanced_image_pil = Image.fromarray(enhanced_image)
142
+
143
+ # Save to in-memory buffer
144
+ image_buffer = io.BytesIO()
145
+ enhanced_image_pil.save(image_buffer, format='PNG')
146
+ image_buffer.seek(0)
147
+
148
+ # Encode to base64
149
+ image_base64 = base64.b64encode(image_buffer.getvalue()).decode('utf-8')
150
+
151
+ response = {
152
+ 'message': 'Image processed successfully!',
153
+ 'processed_image': image_base64,
154
+ 'mse': float(mse),
155
+ 'psnr': float(psnr),
156
+ 'maxerr': float(maxerr),
157
+ 'l2rat': float(l2rat)
158
+ }
159
+ upload_folder_images(image, enhanced_image_pil)
160
+ return JSONResponse(status_code=200, content=response)
161
+
162
+ except Exception as e:
163
+ return JSONResponse(status_code=500, content={'error': f'Error processing image: {str(e)}'})
app-gradio.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import cv2
3
+ import numpy as np
4
+ import tensorflow as tf
5
+ from tensorflow.keras.preprocessing import image
6
+ from tensorflow.keras.models import load_model
7
+ import pandas as pd
8
+
9
+ class GradCAM:
10
+ def __init__(self, model, layer_name):
11
+ self.model = model
12
+ self.layer_name = layer_name
13
+ self.grad_model = tf.keras.models.Model(
14
+ [self.model.inputs],
15
+ [self.model.get_layer(layer_name).output, self.model.output]
16
+ )
17
+
18
+ def __call__(self, img_array, cls):
19
+ with tf.GradientTape() as tape:
20
+ conv_outputs, predictions = self.grad_model(img_array)
21
+ loss = predictions[:, cls]
22
+
23
+ output = conv_outputs[0]
24
+ grads = tape.gradient(loss, conv_outputs)[0]
25
+ gate_f = tf.cast(output > 0, 'float32')
26
+ gate_r = tf.cast(grads > 0, 'float32')
27
+ guided_grads = gate_f * gate_r * grads
28
+
29
+ weights = tf.reduce_mean(guided_grads, axis=(0, 1))
30
+
31
+ cam = np.zeros(output.shape[0:2], dtype=np.float32)
32
+
33
+ for index, w in enumerate(weights):
34
+ cam += w * output[:, :, index]
35
+
36
+ cam = cv2.resize(cam.numpy(), (224, 224))
37
+ cam = np.maximum(cam, 0)
38
+ cam = cam / cam.max()
39
+
40
+ return cam
41
+
42
+ def apply_heatmap(img, heatmap, heatmap_ratio=0.6):
43
+ heatmap = cv2.applyColorMap(np.uint8(255 * heatmap), cv2.COLORMAP_JET)
44
+ heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
45
+ return np.uint8(heatmap * heatmap_ratio + img * (1 - heatmap_ratio))
46
+
47
+ def load_image(img_path, df, preprocess=True, H=320, W=320):
48
+ mean, std = get_mean_std_per_batch(img_path, df, H=H, W=W)
49
+ x = image.load_img(img_path, target_size=(H, W))
50
+ x = image.img_to_array(x)
51
+ if preprocess:
52
+ x -= mean
53
+ x /= std
54
+ x = np.expand_dims(x, axis=0)
55
+ return x
56
+
57
+ def get_mean_std_per_batch(image_path, df, H=320, W=320):
58
+ sample_data = []
59
+ for idx, img in enumerate(df.sample(100)["Image Index"].values):
60
+ sample_data.append(
61
+ np.array(image.load_img(image_path, target_size=(H, W))))
62
+ mean = np.mean(sample_data[0])
63
+ std = np.std(sample_data[0])
64
+ return mean, std
65
+
66
+ def compute_gradcam(img, model, df, labels, layer_name='bn'):
67
+ preprocessed_input = load_image(img, df)
68
+ predictions = model.predict(preprocessed_input)
69
+
70
+ top_indices = np.argsort(predictions[0])[-3:][::-1]
71
+ top_labels = [labels[i] for i in top_indices]
72
+ top_predictions = [predictions[0][i] for i in top_indices]
73
+
74
+ original_image = load_image(img, df, preprocess=False)
75
+
76
+ grad_cam = GradCAM(model, layer_name)
77
+
78
+ gradcam_images = []
79
+ for i in range(3):
80
+ idx = top_indices[i]
81
+ label = top_labels[i]
82
+ prob = top_predictions[i]
83
+
84
+ gradcam = grad_cam(preprocessed_input, idx)
85
+ gradcam_image = apply_heatmap(original_image, gradcam)
86
+ gradcam_images.append((gradcam_image, f"{label}: p={prob:.3f}"))
87
+
88
+ return gradcam_images
89
+
90
+ def calculate_mse(original_image, enhanced_image):
91
+ mse = np.mean((original_image - enhanced_image) ** 2)
92
+ return mse
93
+
94
+ def calculate_psnr(original_image, enhanced_image):
95
+ mse = calculate_mse(original_image, enhanced_image)
96
+ if mse == 0:
97
+ return float('inf')
98
+ max_pixel_value = 255.0
99
+ psnr = 20 * np.log10(max_pixel_value / np.sqrt(mse))
100
+ return psnr
101
+
102
+ def calculate_maxerr(original_image, enhanced_image):
103
+ maxerr = np.max((original_image - enhanced_image) ** 2)
104
+ return maxerr
105
+
106
+ def calculate_l2rat(original_image, enhanced_image):
107
+ l2norm_ratio = np.sum(original_image ** 2) / np.sum((original_image - enhanced_image) ** 2)
108
+ return l2norm_ratio
109
+
110
+ def process_image(original_image, enhancement_type, fix_monochrome=True):
111
+ if fix_monochrome and original_image.shape[-1] == 3:
112
+ original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
113
+
114
+ image = original_image - np.min(original_image)
115
+ image = image / np.max(original_image)
116
+ image = (image * 255).astype(np.uint8)
117
+
118
+ enhanced_image = enhance_image(image, enhancement_type)
119
+
120
+ mse = calculate_mse(original_image, enhanced_image)
121
+ psnr = calculate_psnr(original_image, enhanced_image)
122
+ maxerr = calculate_maxerr(original_image, enhanced_image)
123
+ l2rat = calculate_l2rat(original_image, enhanced_image)
124
+
125
+ return enhanced_image, mse, psnr, maxerr, l2rat
126
+
127
+ def apply_clahe(image):
128
+ clahe = cv2.createCLAHE(clipLimit=40.0, tileGridSize=(8, 8))
129
+ return clahe.apply(image)
130
+
131
+ def invert(image):
132
+ return cv2.bitwise_not(image)
133
+
134
+ def hp_filter(image, kernel=None):
135
+ if kernel is None:
136
+ kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
137
+ return cv2.filter2D(image, -1, kernel)
138
+
139
+ def unsharp_mask(image, radius=5, amount=2):
140
+ def usm(image, radius, amount):
141
+ blurred = cv2.GaussianBlur(image, (0, 0), radius)
142
+ sharpened = cv2.addWeighted(image, 1.0 + amount, blurred, -amount, 0)
143
+ return sharpened
144
+ return usm(image, radius, amount)
145
+
146
+ def hist_eq(image):
147
+ return cv2.equalizeHist(image)
148
+
149
+ def enhance_image(image, enhancement_type):
150
+ if enhancement_type == "Invert":
151
+ return invert(image)
152
+ elif enhancement_type == "High Pass Filter":
153
+ return hp_filter(image)
154
+ elif enhancement_type == "Unsharp Masking":
155
+ return unsharp_mask(image)
156
+ elif enhancement_type == "Histogram Equalization":
157
+ return hist_eq(image)
158
+ elif enhancement_type == "CLAHE":
159
+ return apply_clahe(image)
160
+ else:
161
+ raise ValueError(f"Unknown enhancement type: {enhancement_type}")
162
+
163
+ st.title("Image Enhancement and Quality Evaluation")
164
+
165
+ uploaded_file = st.file_uploader("Upload Original Image", type=["png", "jpg", "jpeg"])
166
+ enhancement_type = st.radio("Enhancement Type", ["Invert", "High Pass Filter", "Unsharp Masking", "Histogram Equalization", "CLAHE"])
167
+
168
+ if uploaded_file is not None:
169
+ original_image = np.array(image.load_img(uploaded_file, color_mode='rgb' if enhancement_type == "Invert" else 'grayscale'))
170
+ enhanced_image, mse, psnr, maxerr, l2rat = process_image(original_image, enhancement_type)
171
+
172
+ st.image(original_image, caption='Original Image', use_column_width=True)
173
+ st.image(enhanced_image, caption='Enhanced Image', use_column_width=True)
174
+
175
+ st.write("MSE:", mse)
176
+ st.write("PSNR:", psnr)
177
+ st.write("Maxerr:", maxerr)
178
+ st.write("L2Rat:", l2rat)
179
+
180
+ st.title("Grad-CAM Visualization")
181
+
182
+ uploaded_gradcam_file = st.file_uploader("Upload Image for Grad-CAM", type=["png", "jpg", "jpeg"], key="gradcam")
183
+ if uploaded_gradcam_file is not None:
184
+ df_file = st.file_uploader("Upload DataFrame for Mean/Std Calculation", type=["csv"])
185
+ labels = st.text_area("Labels", placeholder="Enter labels separated by commas")
186
+ model_path = st.text_input("Model Path", 'model/densenet.hdf5')
187
+ pretrained_model_path = st.text_input("Pretrained Model Path", 'model/pretrained_model.h5')
188
+
189
+ if df_file and labels and model_path and pretrained_model_path:
190
+ df = pd.read_csv(df_file)
191
+ labels = labels.split(',')
192
+ model = load_model(model_path)
193
+ pretrained_model = load_model(pretrained_model_path)
194
+ gradcam_images = compute_gradcam(uploaded_gradcam_file, pretrained_model, df, labels)
195
+
196
+ for idx, (gradcam_image, label) in enumerate(gradcam_images):
197
+ st.image(gradcam_image, caption=f'Grad-CAM {idx+1}: {label}', use_column_width=True)
app-streamlit.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import cv2
3
+ import numpy as np
4
+ import pydicom
5
+ import tensorflow as tf
6
+ import keras
7
+ from pydicom.dataset import Dataset, FileDataset
8
+ from pydicom.uid import generate_uid
9
+ from google.cloud import storage
10
+ import os
11
+ import io
12
+ from PIL import Image
13
+ import uuid
14
+ import pandas as pd
15
+ import tensorflow as tf
16
+ from datetime import datetime
17
+ import SimpleITK as sitk
18
+ from tensorflow import image
19
+ from tensorflow.python.keras.models import load_model
20
+ from pydicom.pixel_data_handlers.util import apply_voi_lut
21
+
22
+ # Environment Configuration
23
+ os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = "./da-kalbe-63ee33c9cdbb.json"
24
+ bucket_name = "da-kalbe-ml-result-png"
25
+ storage_client = storage.Client()
26
+ bucket_result = storage_client.bucket(bucket_name)
27
+ bucket_name_load = "da-ml-models"
28
+ bucket_load = storage_client.bucket(bucket_name_load)
29
+
30
+ model_path = os.path.join("model.h5")
31
+ model = tf.keras.models.load_model(model_path)
32
+
33
+ H, W = 512, 512
34
+
35
+ test_samples_folder = 'object_detection_test_samples'
36
+
37
+ def cal_iou(y_true, y_pred):
38
+ x1 = max(y_true[0], y_pred[0])
39
+ y1 = max(y_true[1], y_pred[1])
40
+ x2 = min(y_true[2], y_pred[2])
41
+ y2 = min(y_true[3], y_pred[3])
42
+
43
+ intersection_area = max(0, x2 - x1 + 1) * max(0, y2 - y1 + 1)
44
+
45
+ true_area = (y_true[2] - y_true[0] + 1) * (y_true[3] - y_true[1] + 1)
46
+ bbox_area = (y_pred[2] - y_pred[0] + 1) * (y_pred[3] - y_pred[1] + 1)
47
+
48
+ iou = intersection_area / float(true_area + bbox_area - intersection_area)
49
+ return iou
50
+
51
+ df = pd.read_excel('BBox_List_2017.xlsx')
52
+ labels_dict = dict(zip(df['Image Index'], df['Finding Label']))
53
+
54
+ def predict(image):
55
+ H, W = 512, 512
56
+
57
+ image_resized = cv2.resize(image, (W, H))
58
+ image_normalized = (image_resized - 127.5) / 127.5
59
+ image_normalized = np.expand_dims(image_normalized, axis=0)
60
+
61
+ # Prediction
62
+ pred_bbox = model.predict(image_normalized, verbose=0)[0]
63
+
64
+ # Rescale the bbox points
65
+ pred_x1 = int(pred_bbox[0] * image.shape[1])
66
+ pred_y1 = int(pred_bbox[1] * image.shape[0])
67
+ pred_x2 = int(pred_bbox[2] * image.shape[1])
68
+ pred_y2 = int(pred_bbox[3] * image.shape[0])
69
+
70
+ return (pred_x1, pred_y1, pred_x2, pred_y2)
71
+
72
+ st.title("AI Integration for Chest X-Ray Imaging")
73
+
74
+ # Concept 1: Select from test samples
75
+ # st.header("Select Test Sample Images")
76
+ # test_sample_images = [os.path.join(test_samples_folder, f) for f in os.listdir(test_samples_folder) if f.endswith('.jpg') or f.endswith('.png')]
77
+ # test_sample_selected = st.selectbox("Select a test sample image", test_sample_images)
78
+ # if test_sample_selected:
79
+ # st.image(test_sample_selected, caption='Selected Test Sample Image', use_column_width=True)
80
+
81
+
82
+ # Utility Functions
83
+ def upload_to_gcs(image_data: io.BytesIO, filename: str, content_type='application/dicom'):
84
+ """Uploads an image to Google Cloud Storage."""
85
+ try:
86
+ blob = bucket_result.blob(filename)
87
+ blob.upload_from_file(image_data, content_type=content_type)
88
+ st.write("File ready to be seen in OHIF Viewer.")
89
+ except Exception as e:
90
+ st.error(f"An unexpected error occurred: {e}")
91
+
92
+ def load_dicom_from_gcs(file_name: str = "dicom_00000001_000.dcm"):
93
+ # Get the blob object
94
+ blob = bucket_load.blob(file_name)
95
+
96
+ # Download the file as a bytes object
97
+ dicom_bytes = blob.download_as_bytes()
98
+
99
+ # Wrap bytes object into BytesIO (file-like object)
100
+ dicom_stream = io.BytesIO(dicom_bytes)
101
+
102
+ # Load the DICOM file
103
+ ds = pydicom.dcmread(dicom_stream)
104
+
105
+ return ds
106
+
107
+ def png_to_dicom(image_path: str, image_name: str, dicom: str = None):
108
+ if dicom is None:
109
+ ds = load_dicom_from_gcs()
110
+ else:
111
+ ds = load_dicom_from_gcs(dicom)
112
+
113
+ jpg_image = Image.open(image_path) # Open the image using the path
114
+ print("Image Mode:", jpg_image.mode)
115
+ if jpg_image.mode == 'L':
116
+ np_image = np.array(jpg_image.getdata(), dtype=np.uint8)
117
+ ds.Rows = jpg_image.height
118
+ ds.Columns = jpg_image.width
119
+ ds.PhotometricInterpretation = "MONOCHROME1"
120
+ ds.SamplesPerPixel = 1
121
+ ds.BitsStored = 8
122
+ ds.BitsAllocated = 8
123
+ ds.HighBit = 7
124
+ ds.PixelRepresentation = 0
125
+ ds.PixelData = np_image.tobytes()
126
+ ds.save_as(image_name)
127
+
128
+ elif jpg_image.mode == 'RGBA':
129
+ np_image = np.array(jpg_image.getdata(), dtype=np.uint8)[:, :3]
130
+ ds.Rows = jpg_image.height
131
+ ds.Columns = jpg_image.width
132
+ ds.PhotometricInterpretation = "RGB"
133
+ ds.SamplesPerPixel = 3
134
+ ds.BitsStored = 8
135
+ ds.BitsAllocated = 8
136
+ ds.HighBit = 7
137
+ ds.PixelRepresentation = 0
138
+ ds.PixelData = np_image.tobytes()
139
+ ds.save_as(image_name)
140
+ elif jpg_image.mode == 'RGB':
141
+ np_image = np.array(jpg_image.getdata(), dtype=np.uint8)[:, :3] # Remove alpha if present
142
+ ds.Rows = jpg_image.height
143
+ ds.Columns = jpg_image.width
144
+ ds.PhotometricInterpretation = "RGB"
145
+ ds.SamplesPerPixel = 3
146
+ ds.BitsStored = 8
147
+ ds.BitsAllocated = 8
148
+ ds.HighBit = 7
149
+ ds.PixelRepresentation = 0
150
+ ds.PixelData = np_image.tobytes()
151
+ ds.save_as(image_name)
152
+ else:
153
+ raise ValueError("Unsupported image mode:", jpg_image.mode)
154
+ return ds
155
+
156
+ def save_dicom_to_bytes(dicom):
157
+ dicom_bytes = io.BytesIO()
158
+ dicom.save_as(dicom_bytes)
159
+ dicom_bytes.seek(0)
160
+ return dicom_bytes
161
+
162
+ def upload_folder_images(original_image_path, enhanced_image_path):
163
+ # Extract the base name of the uploaded image without the extension
164
+ folder_name = os.path.splitext(uploaded_file.name)[0]
165
+ # Create the folder in Cloud Storage
166
+ bucket_result.blob(folder_name + '/').upload_from_string('', content_type='application/x-www-form-urlencoded')
167
+ enhancement_name = enhancement_type.split('_')[-1]
168
+ # Convert images to DICOM
169
+ original_dicom = png_to_dicom(original_image_path, "original_image.dcm")
170
+ enhanced_dicom = png_to_dicom(enhanced_image_path, enhancement_name + ".dcm")
171
+
172
+ # Convert DICOM to byte stream for uploading
173
+ original_dicom_bytes = io.BytesIO()
174
+ enhanced_dicom_bytes = io.BytesIO()
175
+ original_dicom.save_as(original_dicom_bytes)
176
+ enhanced_dicom.save_as(enhanced_dicom_bytes)
177
+ original_dicom_bytes.seek(0)
178
+ enhanced_dicom_bytes.seek(0)
179
+
180
+ # Upload images to GCS
181
+ upload_to_gcs(original_dicom_bytes, folder_name + '/' + 'original_image.dcm', content_type='application/dicom')
182
+ upload_to_gcs(enhanced_dicom_bytes, folder_name + '/' + enhancement_name + '.dcm', content_type='application/dicom')
183
+
184
+
185
+ def get_mean_std_per_batch(image_path, df, H=320, W=320):
186
+ sample_data = []
187
+ for idx, img in enumerate(df.sample(100)["Image Index"].values):
188
+ # path = image_dir + img
189
+ sample_data.append(
190
+ np.array(keras.utils.load_img(image_path, target_size=(H, W))))
191
+
192
+ mean = np.mean(sample_data[0])
193
+ std = np.std(sample_data[0])
194
+ return mean, std
195
+
196
+ def load_image(img_path, preprocess=True, height=320, width=320):
197
+ mean, std = get_mean_std_per_batch(img_path, df, height, width)
198
+ x = keras.utils.load_img(img_path, target_size=(height, width))
199
+ x = keras.utils.img_to_array(x)
200
+ if preprocess:
201
+ x -= mean
202
+ x /= std
203
+ x = np.expand_dims(x, axis=0)
204
+ return x
205
+
206
+ def grad_cam(input_model, img_array, cls, layer_name):
207
+ grad_model = tf.keras.models.Model(
208
+ [input_model.inputs],
209
+ [input_model.get_layer(layer_name).output, input_model.output]
210
+ )
211
+
212
+ with tf.GradientTape() as tape:
213
+ conv_outputs, predictions = grad_model(img_array)
214
+ loss = predictions[:, cls]
215
+
216
+ output = conv_outputs[0]
217
+ grads = tape.gradient(loss, conv_outputs)[0]
218
+ gate_f = tf.cast(output > 0, 'float32')
219
+ gate_r = tf.cast(grads > 0, 'float32')
220
+ guided_grads = gate_f * gate_r * grads
221
+
222
+ weights = tf.reduce_mean(guided_grads, axis=(0, 1))
223
+
224
+ cam = np.dot(output, weights)
225
+
226
+ for index, w in enumerate(weights):
227
+ cam += w * output[:, :, index]
228
+
229
+ cam = cv2.resize(cam.numpy(), (320, 320), cv2.INTER_LINEAR)
230
+ cam = np.maximum(cam, 0)
231
+ cam = cam / cam.max()
232
+
233
+ return cam
234
+
235
+
236
+ # Compute Grad-CAM
237
+ def compute_gradcam(model, img_path, layer_name='bn'):
238
+ preprocessed_input = load_image(img_path)
239
+ predictions = model.predict(preprocessed_input)
240
+
241
+ original_image = load_image(img_path, preprocess=False)
242
+
243
+ # Assuming you have 14 classes as previously mentioned
244
+ labels = ['Cardiomegaly', 'Emphysema', 'Effusion', 'Hernia', 'Infiltration', 'Mass',
245
+ 'Nodule', 'Atelectasis', 'Pneumothorax', 'Pleural_Thickening',
246
+ 'Pneumonia', 'Fibrosis', 'Edema', 'Consolidation']
247
+
248
+ for i in range(len(labels)):
249
+ st.write(f"Generating gradcam for class {labels[i]}")
250
+ gradcam = grad_cam(model, preprocessed_input, i, layer_name)
251
+ gradcam = (gradcam * 255).astype(np.uint8)
252
+ gradcam = cv2.applyColorMap(gradcam, cv2.COLORMAP_JET)
253
+ gradcam = cv2.addWeighted(gradcam, 0.5, original_image.squeeze().astype(np.uint8), 0.5, 0)
254
+ st.image(gradcam, caption=f"{labels[i]}: p={predictions[0][i]:.3f}", use_column_width=True)
255
+
256
+ def calculate_mse(original_image, enhanced_image):
257
+ mse = np.mean((original_image - enhanced_image) ** 2)
258
+ return mse
259
+
260
+ def calculate_psnr(original_image, enhanced_image):
261
+ mse = calculate_mse(original_image, enhanced_image)
262
+ if mse == 0:
263
+ return float('inf')
264
+ max_pixel_value = 255.0
265
+ psnr = 20 * np.log10(max_pixel_value / np.sqrt(mse))
266
+ return psnr
267
+
268
+ def calculate_maxerr(original_image, enhanced_image):
269
+ maxerr = np.max((original_image - enhanced_image) ** 2)
270
+ return maxerr
271
+
272
+ def calculate_l2rat(original_image, enhanced_image):
273
+ l2norm_ratio = np.sum(original_image ** 2) / np.sum((original_image - enhanced_image) ** 2)
274
+ return l2norm_ratio
275
+
276
+ def process_image(original_image, enhancement_type, fix_monochrome=True):
277
+ if fix_monochrome and original_image.shape[-1] == 3:
278
+ original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
279
+
280
+ image = original_image - np.min(original_image)
281
+ image = image / np.max(original_image)
282
+ image = (image * 255).astype(np.uint8)
283
+
284
+ enhanced_image = enhance_image(image, enhancement_type)
285
+
286
+ mse = calculate_mse(original_image, enhanced_image)
287
+ psnr = calculate_psnr(original_image, enhanced_image)
288
+ maxerr = calculate_maxerr(original_image, enhanced_image)
289
+ l2rat = calculate_l2rat(original_image, enhanced_image)
290
+
291
+ return enhanced_image, mse, psnr, maxerr, l2rat
292
+
293
+ def apply_clahe(image):
294
+ clahe = cv2.createCLAHE(clipLimit=40.0, tileGridSize=(8, 8))
295
+ return clahe.apply(image)
296
+
297
+ def invert(image):
298
+ return cv2.bitwise_not(image)
299
+
300
+ def hp_filter(image, kernel=None):
301
+ if kernel is None:
302
+ kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
303
+ return cv2.filter2D(image, -1, kernel)
304
+
305
+ def unsharp_mask(image, radius=5, amount=2):
306
+ def usm(image, radius, amount):
307
+ blurred = cv2.GaussianBlur(image, (0, 0), radius)
308
+ sharpened = cv2.addWeighted(image, 1.0 + amount, blurred, -amount, 0)
309
+ return sharpened
310
+ return usm(image, radius, amount)
311
+
312
+ def hist_eq(image):
313
+ return cv2.equalizeHist(image)
314
+
315
+ def enhance_image(image, enhancement_type):
316
+ if enhancement_type == "Invert":
317
+ return invert(image)
318
+ elif enhancement_type == "High Pass Filter":
319
+ return hp_filter(image)
320
+ elif enhancement_type == "Unsharp Masking":
321
+ return unsharp_mask(image)
322
+ elif enhancement_type == "Histogram Equalization":
323
+ return hist_eq(image)
324
+ elif enhancement_type == "CLAHE":
325
+ return apply_clahe(image)
326
+ else:
327
+ raise ValueError(f"Unknown enhancement type: {enhancement_type}")
328
+
329
+ # Function to add a button to redirect to the URL
330
+ def redirect_button(url):
331
+ button = st.button('Go to OHIF Viewer')
332
+ if button:
333
+ st.markdown(f'<meta http-equiv="refresh" content="0;url={url}" />', unsafe_allow_html=True)
334
+
335
+ def load_model():
336
+ model = tf.keras.models.load_model('./model.h5')
337
+ return model
338
+
339
+ ###########################################################################################
340
+ ########################### Streamlit Interface ###########################################
341
+ ###########################################################################################
342
+
343
+
344
+ st.sidebar.title("Configuration")
345
+ uploaded_file = st.sidebar.file_uploader("Upload Original Image", type=["png", "jpg", "jpeg", "dcm"])
346
+ enhancement_type = st.sidebar.selectbox(
347
+ "Enhancement Type",
348
+ ["Invert", "High Pass Filter", "Unsharp Masking", "Histogram Equalization", "CLAHE"]
349
+ )
350
+
351
+ # File uploader for DICOM files
352
+ if uploaded_file is not None:
353
+ if hasattr(uploaded_file, 'name'):
354
+ file_extension = uploaded_file.name.split(".")[-1] # Get the file extension
355
+ if file_extension.lower() == "dcm":
356
+ # Process DICOM file
357
+ dicom_data = pydicom.dcmread(uploaded_file)
358
+ pixel_array = dicom_data.pixel_array
359
+ # Process the pixel_array further if needed
360
+ # Extract all metadata
361
+ metadata = {elem.keyword: elem.value for elem in dicom_data if elem.keyword}
362
+ metadata_dict = {str(key): str(value) for key, value in metadata.items()}
363
+ df = pd.DataFrame.from_dict(metadata_dict, orient='index', columns=['Value'])
364
+
365
+ # Display metadata in the left-most column
366
+ with st.expander("Lihat Metadata"):
367
+ st.write("Metadata:")
368
+ st.dataframe(df)
369
+
370
+ # Read the pixel data
371
+ pixel_array = dicom_data.pixel_array
372
+ img_array = pixel_array.astype(float)
373
+ img_array = (np.maximum(img_array, 0) / img_array.max()) * 255.0 # Normalize to 0-255
374
+ img_array = np.uint8(img_array) # Convert to uint8
375
+ img = Image.fromarray(img_array)
376
+
377
+ col1, col2 = st.columns(2)
378
+ # Check the number of dimensions of the image
379
+ if img_array.ndim == 3:
380
+ n_slices = img_array.shape[0]
381
+ if n_slices > 1:
382
+ slice_ix = st.sidebar.slider('Slice', 0, n_slices - 1, int(n_slices / 2))
383
+ # Display the selected slice
384
+ st.image(img_array[slice_ix, :, :], caption=f"Slice {slice_ix}", use_column_width=True)
385
+ else:
386
+ # If there's only one slice, just display it
387
+ st.image(img_array[0, :, :], caption="Single Slice Image", use_column_width=True)
388
+ elif img_array.ndim == 2:
389
+ # If the image is 2D, just display it
390
+ with col1:
391
+ st.image(img_array, caption="Original Image", use_column_width=True)
392
+ else:
393
+ st.error("Unsupported image dimensions")
394
+
395
+ original_image = img_array
396
+
397
+ # Example: convert to grayscale if it's a color image
398
+ if len(pixel_array.shape) > 2:
399
+ pixel_array = pixel_array[:, :, 0] # Take only the first channel
400
+ # Perform image enhancement and evaluation on pixel_array
401
+ enhanced_image, mse, psnr, maxerr, l2rat = process_image(pixel_array, enhancement_type)
402
+ else:
403
+ # Process regular image file
404
+ original_image = np.array(keras.utils.load_img(uploaded_file, color_mode='rgb' if enhancement_type == "Invert" else 'grayscale'))
405
+ # Perform image enhancement and evaluation on original_image
406
+ enhanced_image, mse, psnr, maxerr, l2rat = process_image(original_image, enhancement_type)
407
+ col1, col2 = st.columns(2)
408
+ with col1:
409
+ st.image(original_image, caption="Original Image", use_column_width=True)
410
+ with col2:
411
+ st.image(enhanced_image, caption='Enhanced Image', use_column_width=True)
412
+
413
+ col1, col2 = st.columns(2)
414
+ col3, col4 = st.columns(2)
415
+
416
+ col1.metric("MSE", round(mse,3))
417
+ col2.metric("PSNR", round(psnr,3))
418
+ col3.metric("Maxerr", round(maxerr,3))
419
+ col4.metric("L2Rat", round(l2rat,3))
420
+
421
+ # Save enhanced image to a file
422
+ enhanced_image_path = "enhanced_image.png"
423
+ cv2.imwrite(enhanced_image_path, enhanced_image)
424
+
425
+
426
+ # Save enhanced image to a file
427
+ enhanced_image_path = "enhanced_image.png"
428
+ cv2.imwrite(enhanced_image_path, enhanced_image)
429
+
430
+ # Save original image to a file
431
+ original_image_path = "original_image.png"
432
+ cv2.imwrite(original_image_path, original_image)
433
+
434
+ # Add the redirect button
435
+ col1, col2, col3 = st.columns(3)
436
+ with col1:
437
+ redirect_button("https://new-ohif-viewer-k7c3gdlxua-et.a.run.app/")
438
+
439
+ with col2:
440
+ if st.button('Auto Detect'):
441
+ name = uploaded_file.name.split("/")[-1].split(".")[0]
442
+ true_bbox_row = df[df['Image Index'] == uploaded_file.name]
443
+
444
+ if not true_bbox_row.empty:
445
+ x1, y1 = int(true_bbox_row['Bbox [x']), int(true_bbox_row['y'])
446
+ x2, y2 = int(true_bbox_row['x_max']), int(true_bbox_row['y_max'])
447
+ true_bbox = [x1, y1, x2, y2]
448
+ label = true_bbox_row['Finding Label'].values[0]
449
+
450
+ pred_bbox = predict(image)
451
+ iou = cal_iou(true_bbox, pred_bbox)
452
+
453
+ image = cv2.rectangle(image, (x1, y1), (x2, y2), (255, 0, 0), 5) # BLUE
454
+ image = cv2.rectangle(image, (pred_bbox[0], pred_bbox[1]), (pred_bbox[2], pred_bbox[3]), (0, 0, 255), 5) # RED
455
+
456
+ x_pos = int(image.shape[1] * 0.05)
457
+ y_pos = int(image.shape[0] * 0.05)
458
+ font_size = 0.7
459
+
460
+ cv2.putText(image, f"IoU: {iou:.4f}", (x_pos, y_pos), cv2.FONT_HERSHEY_SIMPLEX, font_size, (255, 0, 0), 2)
461
+ cv2.putText(image, f"Label: {label}", (x_pos, y_pos + 30), cv2.FONT_HERSHEY_SIMPLEX, font_size, (255, 255, 255), 2)
462
+
463
+ st.image(image, channels="BGR")
464
+ else:
465
+ st.write("No bounding box and label found for this image.")
466
+
467
+ with col3:
468
+ if st.button('Generate Grad-CAM'):
469
+ model = load_model()
470
+ # Compute and show Grad-CAM
471
+ st.write("Generating Grad-CAM visualizations")
472
+ compute_gradcam(model, uploaded_file)
app.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ runtime: custom
2
+ env: flex
da-kalbe-63ee33c9cdbb.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "service_account",
3
+ "project_id": "da-kalbe",
4
+ "private_key_id": "63ee33c9cdbb52d73de5ec6a70bcdaff2cd1f0c7",
5
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAM43NzySSZuVV\nfD4Csno9tUtCuG40A6ZGBmr/5D8e5X4Sjm2Zvdmt5KC16eIqVRoD4Tmk9vSY2VWB\njUjOU3Atx0GLab1ixM091b1NhdqTpPYMgFOkGPYKoOnPpGhy33rRNNhDmuZVM96y\nHklJSWtaapAMa3VJdXacwY9o2ZKYYs/QoG3y5Mls2LQtuFGya14C9e30b8E7+Tpm\nwsfn1Ek6SU8ICJDQJKvMN2+wyfDvav0XKRUGXX2gRUtPt2mIVc3cIx4TB9QoWPAS\nZ4yzPjdL4CuCrOiz0HrQTJseHSjMiCV1b5D17vAejqn/UU6apkebLjilsduAkpd8\nUUWIgGQRAgMBAAECggEACTwg5t/k/l97yu2h5CAeZVXPPy4x5gVmiUGjMAGP8oSC\ngtKS8RARGWdyvQg3F04d+8w9UxAQ72wtN9Y+8TegnE574ik15zzqA1ErBpfKdA15\nILTPcAMer/g3K8SBm+1heb5b2sm391A7Sxx57Hp0MkoMUFB0eE+wyouMAAoeM/yh\ndb6jVkiJZUWiHr6Bu3VRD8tPL17vJ4l8VCFKaoFAIuqLWqqgp1Uql1BPOjPGNg/U\no82rjEVhQURsv0NKUtpkOhm71Zi+1oBNCiaDHnq6ppA6XwSxAGJyYmC7TC9PQgp8\n2L6yZQICkqYUo2XvnhWqMcKe4v6sT7EVLYsWbAuTnQKBgQDe6u5Q7qCuaPL/8Ib3\nRVVFR3ebPt4VSsixQYkQ5LEwbUYgTpHEEOua7XdTxh6tjrk33RxTsaIfMDx95SQP\nu1R1XqRGk0q5qXqzWMUu92ghgUBQGDnCuAvcu2xznCL2s3rcivZX5pJ/a4hV9Wh8\n08uZ11zz3ybkO5ow70Hf3UbqTQKBgQDcuabHejnSAwVfHdzMdLX12Ly9vJku0ElV\n8E+rF3Yof4Hj6BVDijKPvmUskzL/NZN6NIDObLo3DqU0ZE5LeVUTbap6FookUcAG\nZSJvieQgIhjhhumZAAiZ7Cm/mjV64NkunFfB6/huO+JFCn4ph4AR7FGcxWLhqzKG\ni4VgahI61QKBgFHBkA4Wts/Apf7AVH+z6gYyo+dIM2NTvi9kfcS4W1QLf8CxkDx8\niCyWE0GiU/YLdlrKuWzopTETG9yAga1TYzJ4t3863hkh02KbmBYVnxdKtC+r8uM7\nZP5brvbnuNsKEHF6dcbfRbN92M7N5riBZRbTKcA3g6gJRA/WcC6OlUexAoGBAMC7\nFubtUdmQK7uPLsIUUuI1ezDNopAe7TouGNKTLOhV1gw0FYUhAd9D58IRUiFIHdtC\nRwp4I+15S0XBJCCA9Il/Y7ExRzRJcbvjyTrNk7V/MGyUyhp/zgLhFN8p/vKprNon\nR/WbXxSaE4wnilwm5exAhk62iA3216pl33N9iyd1AoGBAMmBGCDr0GfKlXFB0r0Q\nO0WSxwGIftbYC8peDJNIEkI/LvMd0GtOKccf8d3sVpX1+IelmOjc5b0vHqdRwj+s\nEMbngVXtdpQYmnoNqJNQIYT88m+7LMUQpIbANG5VeT1cbeMp/UdY6PjHStdMcvjb\nwQ7/93dz/GqQUjovtMEww8Py\n-----END PRIVATE KEY-----\n",
6
+ "client_email": "[email protected]",
7
+ "client_id": "103965486441709077075",
8
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
+ "token_uri": "https://oauth2.googleapis.com/token",
10
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ml-model%40da-kalbe.iam.gserviceaccount.com",
12
+ "universe_domain": "googleapis.com"
13
+ }
enhanced_image.png ADDED
model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3cdd09666c70aa6c1edc7be435432ce26aa7958d5ae5fb9d6c19cdda5be99104
3
+ size 63529920
object_detection_test_samples/00000193_019.png ADDED
object_detection_test_samples/00000398_003.png ADDED
original_image.png ADDED
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ opencv-python-headless
3
+ numpy
4
+ scikit-image
5
+ pydicom
6
+ matplotlib
7
+ scipy
8
+ tensorflow
9
+ keras
10
+ fastapi
11
+ uvicorn
12
+ pillow
13
+ pandas
14
+ google-cloud-storage
15
+ starlette