Stroke-ia commited on
Commit
4eb0ae3
·
verified ·
1 Parent(s): a6999c5

Upload 3 files

Browse files
Files changed (3) hide show
  1. app (2).py +176 -0
  2. pipline.py +250 -0
  3. requirements (7).txt +78 -0
app (2).py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pipline import Transformer_Regression, extract_regions_Last , compute_ratios
2
+ import torch
3
+ import torchvision.transforms as transforms
4
+ from torch.nn import functional as F
5
+ import cv2
6
+ import gradio as gr
7
+ import numpy as np
8
+ from PIL import Image
9
+
10
+
11
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
12
+
13
+ ## Define some parameters
14
+ image_shape = 384 #### 512 got 87
15
+ batch_size=1
16
+ dim_patch=4
17
+ num_classes=3
18
+ label_smoothing=0.1
19
+ scale=1
20
+ import time
21
+ start = time.time()
22
+ torch.manual_seed(0)
23
+ #import random
24
+
25
+
26
+ tfms = transforms.Compose([
27
+ transforms.Resize((image_shape, image_shape)),
28
+ transforms.ToTensor(),
29
+ transforms.Normalize(0.5,0.5)
30
+ #transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
31
+ #transforms.Normalize(mean=(0.48145466, 0.4578275, 0.40821073), std=(0.26862954, 0.26130258, 0.27577711))
32
+
33
+ ])
34
+
35
+ def Final_Compute_regression_results_Sample(Model, batch_sampler,num_head=2):
36
+ Model.eval()
37
+ score_cup = []
38
+ score_disc = []
39
+ yreg_pred = []
40
+ yreg_true = []
41
+ with torch.no_grad():
42
+ #for batch_sampler in loader:
43
+ train_batch_tfms = batch_sampler['image'].to(device=device)
44
+ #ytrue_seg = batch_sampler['image_original'] #.detach().cpu().numpy()
45
+ ytrue_seg = batch_sampler['image_original'] # .detach().cpu().numpy()
46
+ scores = Model(train_batch_tfms.unsqueeze(0))
47
+
48
+ yseg_pred = F.interpolate(scores['seg'], size=(ytrue_seg.shape[0], ytrue_seg.shape[1]), mode='bilinear',
49
+ align_corners=True)
50
+
51
+
52
+ # Regions_crop=extract_regions_Last(np.array(batch_sampler['image_original'][0]),yseg_pred[0].detach().cpu().numpy())
53
+ Regions_crop = extract_regions_Last(np.array(batch_sampler['image_original']),
54
+ yseg_pred.argmax(1).long()[0].detach().cpu().numpy())
55
+ Regions_crop['image'] = Image.fromarray(np.uint8(Regions_crop['image'])).convert('RGB')
56
+
57
+ ### Get back if two heads
58
+ ytrue_seg_crop = ytrue_seg[Regions_crop['cord'][0]:Regions_crop['cord'][1],
59
+ Regions_crop['cord'][2]:Regions_crop['cord'][3]]
60
+ ytrue_seg_crop = np.expand_dims(ytrue_seg_crop, axis=0)
61
+
62
+ if num_head==2:
63
+ scores = Model((tfms(Regions_crop['image']).unsqueeze(0)).to(device))
64
+ yseg_pred_crop = F.interpolate(scores['seg_aux_1'], size=(ytrue_seg_crop.shape[1], ytrue_seg_crop.shape[2]),
65
+ mode='bilinear', align_corners=True)
66
+ yseg_pred[:, :, Regions_crop['cord'][0]:Regions_crop['cord'][1],
67
+ Regions_crop['cord'][2]:Regions_crop['cord'][3]] = yseg_pred_crop
68
+ # yseg_pred[:, :, Regions_crop['cord'][0]:Regions_crop['cord'][1],
69
+ # Regions_crop['cord'][2]:Regions_crop['cord'][3]]+yseg_pred_crop
70
+ yseg_pred = torch.softmax(yseg_pred, dim=1)
71
+ yseg_pred = yseg_pred.argmax(1).long()
72
+ yseg_pred = ((yseg_pred).long()).detach().cpu().numpy()
73
+ ratios = compute_ratios(yseg_pred[0])
74
+ yreg_pred.append(ratios.vcdr)
75
+
76
+ ### Plot
77
+ p_img = batch_sampler['image'].to(device=device).unsqueeze(0)
78
+ p_img = F.interpolate(p_img, size=(yseg_pred.shape[1], yseg_pred.shape[2]),
79
+ mode='bilinear', align_corners=True)
80
+ ### Get reversed image
81
+ image_orig = (p_img[0] * 0.5 + 0.5).permute(1, 2, 0).detach().cpu().numpy()
82
+ image_orig=np.uint8(image_orig*255)
83
+ ####
84
+ # train_batch_tfms
85
+ #plt.imshow(image_orig)
86
+ # make a copy as these operations are destructive
87
+ image_cont = image_orig.copy()
88
+ ###### plot for Prediction....
89
+ # threshold for 2 value
90
+ ret, thresh = cv2.threshold(np.uint8(yseg_pred[0]), 1, 2, 0)
91
+ # find and draw contour for 2 value (red)
92
+ conts, hir = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
93
+ cv2.drawContours(image_cont, conts, -1, (0, 255, 0), 2)
94
+ #threshold for 1 value
95
+ ret, thresh = cv2.threshold(np.uint8(yseg_pred[0]), 0, 2, 0)
96
+ #find and draw contour for 1 value (blue)
97
+ conts, hir = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
98
+ cv2.drawContours(image_cont, conts, -1, (0, 0, 255), 2)
99
+ #plot contoured image
100
+
101
+ # plt.imshow(image_cont)
102
+ # plt.axis('off')
103
+
104
+ # print('Vertical cup to disc ratio:')
105
+ # print(ratios.vcdr)
106
+ if ratios.vcdr < 0.6:
107
+ glaucoma = 'None'
108
+ else:
109
+ glaucoma = 'May be there is a risk of Glaucoma'
110
+
111
+ # print('Galucoma:')
112
+
113
+
114
+ return image_cont, ratios.vcdr, glaucoma, Regions_crop
115
+
116
+ #load model
117
+ DeepLab=Transformer_Regression(image_dim=image_shape,dim_patch=dim_patch,num_classes=3,scale=scale,feat_dim=128)
118
+ DeepLab.to(device=device)
119
+ DeepLab.load_state_dict(torch.load("TrainAll_Maghrabi84_50iteration_SWIN.pth.tar", map_location=torch.device(device)))
120
+
121
+ def infer(img):
122
+ # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
123
+
124
+ sample_batch = dict()
125
+ sample_batch['image_original'] = img
126
+
127
+ im_retina_pil = Image.fromarray(img)
128
+
129
+ im_retina_pil = tfms(im_retina_pil)
130
+ sample_batch['image'] = im_retina_pil
131
+
132
+ # plt.figure('Head2')
133
+ result, ratio, diagnosis, cropped = Final_Compute_regression_results_Sample(DeepLab, sample_batch, num_head=2)
134
+
135
+ # cropped = cv2.cvtColor(np.asarray(cropped), cv2.COLOR_BGR2RGB)
136
+ cropped = result[cropped['cord'][0] :cropped['cord'][1] ,
137
+ cropped['cord'][2] :cropped['cord'][3] ]
138
+
139
+ return ratio, diagnosis, result, cropped
140
+
141
+
142
+ title = "Glaucoma Detection in Retinal Fundus Images"
143
+ description = "The method detects disc and cup in the retinal image, then it computes the Vertical cup to disc ratio"
144
+
145
+ outputs = [gr.Textbox(label="Vertical cup to disc ratio:"), gr.Textbox(label="predicted diagnosis (Rule of thumb ~0.6 or greater is suspicious)"), gr.Image(label='labeled image'), gr.Image(label='zoomed in')]
146
+ with gr.Blocks(css='#title {text-align : center;} ') as demo:
147
+ with gr.Row():
148
+ gr.Markdown(
149
+ f'''
150
+ # {title}
151
+ {description}
152
+
153
+ ''',
154
+ elem_id='title'
155
+ )
156
+ with gr.Row():
157
+ with gr.Column():
158
+ prompt = gr.Image(label="Upload Your Retinal Fundus Image")
159
+ btn = gr.Button(value='Submit')
160
+ examples = gr.Examples(
161
+ ['M00027.png','M00056.png','M00073.png','M00093.png', 'M00018.png', 'M00034.png'],
162
+ inputs=[prompt], fn=infer, outputs=[outputs], cache_examples=False)
163
+ with gr.Column():
164
+ with gr.Row():
165
+ text1 = gr.Textbox(label="Vertical Cup to Disc Ratio:")
166
+ text2 = gr.Textbox(label="Predicted Diagnosis (Rule of thumb ~0.6 or greater is suspicious)")
167
+ img = gr.Image(label='Detected disc and cup')
168
+ zoom = gr.Image(label='Croppped')
169
+
170
+ outputs = [text1,text2,img,zoom]
171
+
172
+ btn.click(fn=infer, inputs=prompt, outputs=outputs)
173
+
174
+
175
+ if __name__ == '__main__':
176
+ demo.launch()
pipline.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #### This is an implmentation of deeplabv3 plus for retina detection
2
+ from skimage.measure import label, regionprops
3
+ import torch
4
+ import torchvision
5
+ from torch.nn import functional as F
6
+ import torch.nn as nn
7
+ import numpy as np
8
+ import cv2
9
+ import torch
10
+ from collections import namedtuple
11
+
12
+ # check you have the right version of timm
13
+ # assert timm.__version__ == "0.3.2"
14
+ from timm.models.swin_transformer import swin_base_patch4_window12_384_in22k, SwinTransformer
15
+
16
+ torch.manual_seed(0)
17
+ device = "cuda" if torch.cuda.is_available() else "cpu"
18
+ pad_value = 10
19
+
20
+ def forward_features(self, x):
21
+ x = self.patch_embed(x)
22
+ if self.absolute_pos_embed is not None:
23
+ x = x + self.absolute_pos_embed
24
+ x = self.pos_drop(x)
25
+
26
+ hide=[]
27
+ for layer in self.layers:
28
+ x = layer(x)
29
+ #print(x.shape)
30
+ hide.append(x)
31
+
32
+ #x = self.layers(x)
33
+ x = self.norm(x) # B L C
34
+ return hide
35
+
36
+ def forward(self, x):
37
+ x = self.forward_features(x)
38
+ #x = self.forward_head(x)
39
+ return x
40
+
41
+ SwinTransformer.forward_features = forward_features
42
+ SwinTransformer.forward = forward
43
+
44
+
45
+
46
+
47
+ def extract_regions_Last(img_test, ytruth, pad1=pad_value, pad2=pad_value, pad3=pad_value, pad4=pad_value):
48
+
49
+ y_truth_copy = ytruth.copy()
50
+ y_truth_copy[y_truth_copy == 2] = 1
51
+ label_img = label(y_truth_copy)
52
+
53
+ regions = regionprops(label_img)
54
+ max_Area = -1
55
+ cropped_results = dict()
56
+ for props in regions:
57
+ if props.area > max_Area:
58
+ max_Area = props.area
59
+ minr, minc, maxr, maxc = props.bbox
60
+ bx = (minc, maxc, maxc, minc, minc)
61
+ by = (minr, minr, maxr, maxr, minr)
62
+ # print(minr,maxr)
63
+ # print(bx)
64
+ # ax.plot(bx, by, '-b', linewidth=2.5)
65
+ # cropped_image= pred_class[minr-pad:maxr+pad, minc-pad:maxc+pad]
66
+ # cropped_pred_mask = pred_class[minr - pad:maxr + pad, minc - pad:maxc + pad]
67
+ if minr - pad1 < 0:
68
+ pad1 = 5
69
+ if minr - pad1 < 0:
70
+ pad1 = 0
71
+
72
+ if minc - pad2 < 0:
73
+ pad2 = 5
74
+ if minc - pad2 < 0:
75
+ pad2 = 0
76
+ if maxr + pad3 > label_img.shape[0]:
77
+ pad3 = 5
78
+ if maxr + pad3 > label_img.shape[0]:
79
+ pad3 = 0
80
+
81
+ if maxc + pad4 > label_img.shape[1]:
82
+ pad4 = 5
83
+ if maxc + pad4 > label_img.shape[1]:
84
+ pad4 = 0
85
+
86
+ cropped_image = img_test[minr - pad1:maxr + pad3, minc - pad2:maxc + pad4, :]
87
+ cropped_truth = ytruth[minr - pad1:maxr + pad3, minc - pad2:maxc + pad4]
88
+ txcordi = []
89
+ txcordi.append(minr - pad1)
90
+ txcordi.append(maxr + pad3)
91
+ txcordi.append(minc - pad2)
92
+ txcordi.append(maxc + pad4)
93
+ cropped_results['image'] = cropped_image
94
+ cropped_results['truth'] = cropped_truth
95
+ cropped_results['cord'] = txcordi
96
+
97
+ return cropped_results
98
+
99
+
100
+ class BasicBlock(nn.Module):
101
+ def __init__(self, channel_num):
102
+ super(BasicBlock, self).__init__()
103
+ # TODO: 3x3 convolution -> relu
104
+ # the input and output channel number is channel_num
105
+ self.conv_block1 = nn.Sequential(
106
+ nn.Conv2d(channel_num, 48, 1, padding=0),
107
+ nn.GroupNorm(num_groups=8, num_channels=48),
108
+ nn.GELU(),
109
+ )
110
+ self.conv_block2 = nn.Sequential(
111
+ nn.Conv2d(48, channel_num, 3, padding=1),
112
+ nn.GroupNorm(num_groups=8, num_channels=channel_num),
113
+ nn.GELU(),
114
+ )
115
+ self.relu = nn.GELU()
116
+
117
+ def forward(self, x):
118
+ # TODO: forward
119
+ residual = x
120
+ x = self.conv_block1(x)
121
+ x = self.conv_block2(x)
122
+ x = x + residual
123
+ return x
124
+
125
+
126
+ class ASPP(nn.Module):
127
+ def __init__(self, image_dim=384, head=1):
128
+ super(ASPP, self).__init__()
129
+ self.image_dim = image_dim
130
+ self.Residual2 = BasicBlock(channel_num=head)
131
+ self.pixel_shuffle = nn.PixelShuffle(2)
132
+ self.head = head
133
+
134
+ def forward(self, x):
135
+ x21 = F.interpolate(x, size=(self.image_dim, self.image_dim), mode='bilinear',
136
+ align_corners=True)
137
+ return x21
138
+
139
+
140
+
141
+ class Transformer_Regression(nn.Module):
142
+ def __init__(self, image_dim=224, dim_patch=24, num_classes=3, scale=1, feat_dim=192):
143
+ super(Transformer_Regression, self).__init__()
144
+ self.backbone = swin_base_patch4_window12_384_in22k(pretrained=True)
145
+ self.aux = 1
146
+ self.dim_patch = dim_patch
147
+ self.image_dim = image_dim
148
+ self.num_classes = num_classes
149
+ self.ASPP1 = ASPP(image_dim, head=128)
150
+ self.ASPP2 = ASPP(image_dim, head=128)
151
+ # self.ASPP3=ASPP(image_dim,scale,feat_dim)
152
+ self.feat_dim = feat_dim
153
+ # self.scale=1
154
+ self.Classifier_main = nn.Sequential(
155
+ # nn.Dropout(0.1),
156
+ nn.Conv2d(128, self.num_classes, 3, bias=True, padding=1),
157
+ )
158
+ self.Classifier_aux1 = nn.Sequential(
159
+ # nn.Dropout(0.1),
160
+ nn.Conv2d(128, self.num_classes, 3, bias=True, padding=1),
161
+ )
162
+
163
+ self.conv1 = nn.Sequential(nn.Conv2d(448, 128, kernel_size=(1, 1), padding=1), nn.GELU())
164
+ self.pixelshufler1 = nn.PixelShuffle(2)
165
+ self.pixelshufler2 = nn.PixelShuffle(4)
166
+
167
+ def forward(self, x):
168
+ hide1 = self.backbone(x)
169
+ x1 = []
170
+ x1.append((hide1[0][:, 0:].reshape(-1, 48, 48, 256)))
171
+ x1.append((hide1[1][:, 0:].reshape(-1, 24, 24, 512)))
172
+ x1.append((hide1[2][:, 0:].reshape(-1, 12, 12, 1024)))
173
+ for jk in range(len(x1)):
174
+ x1[jk] = x1[jk].permute(0, 3, 1, 2)
175
+ x1[1] = self.pixelshufler1(x1[1])
176
+ x1[2] = self.pixelshufler2(x1[2])
177
+
178
+ x1[0] = torch.cat((x1[0], x1[1], x1[2]), 1)
179
+
180
+ x1[0] = self.conv1(x1[0])
181
+ Score = dict()
182
+ x_main1 = self.ASPP1(x1[0])
183
+ x_main = self.Classifier_main(x_main1)
184
+ x_aux_1 = self.ASPP2(x1[0])
185
+ x_aux_1 = self.Classifier_aux1(x_aux_1) ####### x_aux_1
186
+
187
+ Score['seg'] = x_main
188
+ Score['seg_aux_1'] = x_aux_1
189
+ # Score['seg_aux_2'] = x_aux_2
190
+
191
+ return Score
192
+
193
+
194
+ Ratios = namedtuple("Ratios", 'cdr hcdr vcdr')
195
+ eps = np.finfo(np.float32).eps
196
+
197
+
198
+ def compute_ratios(mask_image):
199
+ '''
200
+ Given an input image containing the cup and disc masks the function returns
201
+ a tuple with the area, horizontal, and vertical cup-to-disc ratios
202
+ Input:
203
+ mask_image: an image with values (0,1,2) or (255,128,0)
204
+ for bg, disc, cup respectively
205
+ Output:
206
+ Ratios(cdr,hcdr,vcdr): a named tuple containing the computed ratios
207
+ '''
208
+
209
+ # if mask_image.max() == 2:
210
+ # make sure correct values are provided in the image
211
+ # if np.setdiff1d(np.unique(mask_image),np.array([0,1,2])).shape[0]>0:
212
+ # raise ValueError(('Mask values can only be (0,1,2) '
213
+ # 'or (255,128,0) for bg, disc, cup'))
214
+ # disc = np.uint8(mask_image > 0)
215
+ # cup = np.uint8(mask_image > 1)
216
+ # elif mask_image.max() == 255:
217
+ # # make sure correct values are provided in the image
218
+ # if np.setdiff1d(np.unique(mask_image),np.array([0,128,255])).shape[0]>0:
219
+ # raise ValueError(('Mask values can only be (0,1,2) '
220
+ # 'or (255,128,0) for bg, disc, cup'))
221
+ # disc = np.uint8(mask_image < 255)
222
+ # cup = np.uint8(mask_image == 0)
223
+ # else:
224
+ # raise ValueError(("Mask values can only be (0,1,2) or (255,128,0) "
225
+ # "for bg, disc, cup"))
226
+
227
+ # get the area
228
+ disc = 0
229
+ cup = 0
230
+ disc = disc + np.uint8(mask_image > 0)
231
+ cup = cup + np.uint8(mask_image > 1)
232
+
233
+ disc_area = np.sum(disc)
234
+ cup_area = np.sum(cup)
235
+ # get the vertical and horizontal mesure of the cup
236
+ cup_vert = np.sum(cup, axis=0).max().astype(np.int32)
237
+ cup_horz = np.sum(cup, axis=1).max().astype(np.int32)
238
+ # get the vertical and horizontal mesure of the disc
239
+ disc_vert = np.sum(disc, axis=0).max().astype(np.int32)
240
+ disc_horz = np.sum(disc, axis=1).max().astype(np.int32)
241
+ # calculate the cup to disc ratio
242
+ cdr = (cup_area + eps) / (disc_area + eps) # add eps to avoid div by 0
243
+ # calculate the horizontal and vertical cup to disc ration
244
+ hcdr = (cup_horz + eps) / (disc_horz + eps)
245
+ vcdr = (cup_vert + eps) / (disc_vert + eps)
246
+
247
+ return Ratios(cdr, hcdr, vcdr)
248
+
249
+
250
+
requirements (7).txt ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==23.1.0
2
+ aiohttp==3.8.4
3
+ aiosignal==1.3.1
4
+ altair==4.2.2
5
+ anyio==3.6.2
6
+ async-timeout==4.0.2
7
+ attrs==22.2.0
8
+ certifi==2022.12.7
9
+ charset-normalizer==3.1.0
10
+ click==8.1.3
11
+ colorama==0.4.6
12
+ contourpy==1.0.7
13
+ cycler==0.11.0
14
+ entrypoints==0.4
15
+ fastapi==0.95.0
16
+ ffmpy==0.3.0
17
+ filelock==3.10.7
18
+ fonttools==4.39.2
19
+ frozenlist==1.3.3
20
+ fsspec==2023.3.0
21
+ gradio==3.23.0
22
+ h11==0.14.0
23
+ httpcore==0.16.3
24
+ httpx==0.23.3
25
+ huggingface-hub==0.13.3
26
+ idna==3.4
27
+ imageio==2.27.0
28
+ importlib-resources==5.12.0
29
+ Jinja2==3.1.2
30
+ jsonschema==4.17.3
31
+ kiwisolver==1.4.4
32
+ lazy_loader==0.2
33
+ linkify-it-py==2.0.0
34
+ markdown-it-py==2.2.0
35
+ MarkupSafe==2.1.2
36
+ matplotlib==3.7.1
37
+ mdit-py-plugins==0.3.3
38
+ mdurl==0.1.2
39
+ mpmath==1.3.0
40
+ multidict==6.0.4
41
+ networkx==3.0
42
+ numpy==1.24.2
43
+ opencv-python==4.7.0.72
44
+ orjson==3.8.8
45
+ packaging==23.0
46
+ pandas==1.5.3
47
+ Pillow==9.4.0
48
+ pydantic==1.10.7
49
+ pydub==0.25.1
50
+ pyparsing==3.0.9
51
+ pyrsistent==0.19.3
52
+ python-dateutil==2.8.2
53
+ python-multipart==0.0.6
54
+ pytz==2023.2
55
+ PyWavelets==1.4.1
56
+ PyYAML==6.0
57
+ requests==2.28.2
58
+ rfc3986==1.5.0
59
+ scikit-image==0.20.0
60
+ scipy==1.9.1
61
+ semantic-version==2.10.0
62
+ six==1.16.0
63
+ sniffio==1.3.0
64
+ starlette==0.26.1
65
+ sympy==1.11.1
66
+ tifffile==2023.3.21
67
+ timm==0.6.13
68
+ toolz==0.12.0
69
+ torch==2.0.0
70
+ torchvision==0.15.1
71
+ tqdm==4.65.0
72
+ typing_extensions==4.5.0
73
+ uc-micro-py==1.0.1
74
+ urllib3==1.26.15
75
+ uvicorn==0.21.1
76
+ websockets==10.4
77
+ yarl==1.8.2
78
+ zipp==3.15.0