Computer-Vision-Assistant-Package 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ """Detection metrics for object detectors.
2
+
3
+ Functions to score how well a detector's boxes match the ground-truth boxes:
4
+ IoU, precision / recall and mAP. All boxes are in
5
+ ``(x_min, y_min, x_max, y_max)`` pixel format.
6
+
7
+ The overall flow is::
8
+
9
+ detection_metrics
10
+ |-- provide_annotations() (Basic_Utils) -> ground-truth boxes
11
+ |-- ai_analytics() (Basic_Utils) -> predicted boxes
12
+ |-- IOU_calc() -> IoU of each prediction vs the GT boxes
13
+ |-- mAP_calc() -> precision / recall at each IoU threshold
14
+ `-- AUC_calc() -> smoothed precision curve for the area
15
+
16
+ Functions
17
+ ---------
18
+ IOU_calc
19
+ Intersection-over-Union of one box against a list of boxes.
20
+ mAP_calc
21
+ Precision and recall lists for IoU thresholds 0.50 to 0.90.
22
+ AUC_calc
23
+ Builds the (recall, smoothed precision) curve used to compute the area.
24
+ detection_metrics
25
+ End-to-end mAP@0.5 and mAP@0.5:0.95 for one image.
26
+
27
+ Author: Shounak Sanyal
28
+ """
29
+ import os, json, cv2
30
+ import numpy as np
31
+ from .Basic_Utils import *
32
+
33
+
34
+ def IOU_calc(target_pts, gt_pts):
35
+ """Compute the IoU of one predicted box against every ground-truth box.
36
+
37
+ IoU (Intersection over Union) tells how much two boxes overlap:
38
+ 0 means no overlap and 1 means the boxes are identical.
39
+
40
+ Parameters
41
+ ----------
42
+ target_pts : sequence of 4 numbers
43
+ The predicted box as ``(x_min, y_min, x_max, y_max)``.
44
+ gt_pts : list of sequence of 4 numbers
45
+ Ground-truth boxes, each as ``(x_min, y_min, x_max, y_max)``.
46
+
47
+ Returns
48
+ -------
49
+ IOU_arr : list of float
50
+ One IoU value (0 to 1) per ground-truth box, in the same order as
51
+ ``gt_pts``.
52
+
53
+ Examples
54
+ --------
55
+ >>> IOU_calc((0, 0, 10, 10), [(5, 5, 15, 15), (0, 0, 10, 10)])
56
+ [0.14285714285714285, 1.0]
57
+ """
58
+ IOU_arr=[]
59
+ for i in range(0, len(gt_pts)):
60
+ x_gt0, y_gt0,x_gt1, y_gt1=gt_pts[i]
61
+ x_tgt0, y_tgt0, x_tgt1, y_tgt1= target_pts
62
+ w_gt, h_gt, w_tgt, h_tgt= x_gt1- x_gt0, y_gt1- y_gt0, x_tgt1- x_tgt0, y_tgt1- y_tgt0
63
+ x_edge, y_edge= min(x_gt1, x_tgt1), min(y_gt1, y_tgt1)
64
+ x_ref, y_ref= max(x_gt0, x_tgt0), max(y_gt0, y_tgt0)
65
+
66
+ if (x_ref>=x_edge) or (y_ref>= y_edge):
67
+ Intersection=0
68
+ else:
69
+ w,h= x_edge-x_ref, y_edge-y_ref
70
+ Intersection= h*w
71
+ Union= (h_gt*w_gt)+ (h_tgt*w_tgt)- Intersection
72
+ IOU= Intersection/Union
73
+
74
+ IOU_arr.append(IOU)
75
+
76
+ return IOU_arr
77
+
78
+ def mAP_calc(bboxes, gt_bboxes):
79
+ """Compute running precision and recall for IoU thresholds 0.50 to 0.90.
80
+
81
+ The predictions must be sorted by confidence (highest first). For each IoU
82
+ threshold (0.50, 0.55, ..., 0.90) the predictions are visited in order. A
83
+ prediction counts as a true positive if its IoU reaches the threshold and its
84
+ matched ground-truth box has not been used yet; otherwise it is a false
85
+ positive. Precision and recall are recorded after every prediction.
86
+
87
+ Parameters
88
+ ----------
89
+ bboxes : array-like of float
90
+ The IoU value of each prediction, sorted by descending confidence.
91
+ gt_bboxes : array-like of int
92
+ For each prediction, the index of the ground-truth box it overlaps
93
+ most (same length and order as ``bboxes``).
94
+
95
+ Returns
96
+ -------
97
+ net_precision_arr : list of list of float
98
+ One list per threshold; each holds the precision after every
99
+ prediction.
100
+ net_recall_arr : list of list of float
101
+ Same layout as ``net_precision_arr``, but for recall.
102
+ """
103
+ thresh_limits= np.arange(0.5, 1, 0.05)
104
+ net_precision_arr, net_recall_arr=[],[]
105
+ for index in range(0, len(thresh_limits)):
106
+ net_positive= len(np.unique(gt_bboxes))
107
+ true_positive, false_positive=0,0
108
+ precision_arr, recall_arr=[],[]
109
+ checked_gt=[]
110
+ for i in range(0, len(bboxes)):
111
+ if bboxes[i]< thresh_limits[index]:
112
+
113
+ false_positive+=1
114
+ else:
115
+ if gt_bboxes[i] not in checked_gt:
116
+ true_positive+=1
117
+ checked_gt.append(gt_bboxes[i])
118
+ else:
119
+ false_positive+=1
120
+
121
+ precision= true_positive/(true_positive+false_positive)
122
+ recall= true_positive/net_positive
123
+ precision_arr.append(precision)
124
+ recall_arr.append(recall)
125
+ net_precision_arr.append(precision_arr)
126
+ net_recall_arr.append(recall_arr)
127
+
128
+ return net_precision_arr, net_recall_arr
129
+
130
+ def AUC_calc(precision, recall):
131
+ """Build the smoothed precision-recall curve used to get the area.
132
+
133
+ Each precision value is replaced by the highest precision found at or after
134
+ that point, so the curve never goes up as recall increases. The recall values
135
+ are returned unchanged.
136
+
137
+ Parameters
138
+ ----------
139
+ precision : sequence of float
140
+ Precision values in prediction order (one list from ``mAP_calc``).
141
+ recall : sequence of float
142
+ Recall values in the same order.
143
+
144
+ Returns
145
+ -------
146
+ x_arr : sequence of float
147
+ The recall values.
148
+ y_arr : list of float
149
+ The smoothed precision values, same length as ``precision``.
150
+ """
151
+ x_arr, y_arr=[],[]
152
+ for i in range(0, len(precision)):
153
+ if precision[i]> np.max(precision[i:]):
154
+ y_arr.append(precision[i])
155
+ else:
156
+ y_arr.append(np.max(precision[i:]))
157
+ x_arr= recall
158
+
159
+ return x_arr, y_arr
160
+
161
+ def detection_metrics(json_file, img_file, model_test):
162
+ """Evaluate a detector on one image and return mAP@0.5 and mAP@0.5:0.95.
163
+
164
+ Steps: load the ground truth from a COCO json, run the model, match every
165
+ prediction to its best-overlapping ground-truth box, sort by confidence,
166
+ compute precision / recall for each IoU threshold and integrate the area.
167
+
168
+ Parameters
169
+ ----------
170
+ json_file : str
171
+ Path to the COCO-format annotation file that lists ``img_file``.
172
+ img_file : str
173
+ Path to the image to evaluate.
174
+ model_test : object
175
+ Loaded detection model (RF-DETR style) with a ``.predict(image_path)``
176
+ method that returns boxes, labels and confidence scores.
177
+
178
+ Returns
179
+ -------
180
+ mAP_50 : float
181
+ Score at IoU threshold 0.50, rounded to 3 decimals.
182
+ mAP_50_95 : float
183
+ Score averaged over the IoU thresholds, rounded to 3 decimals.
184
+
185
+ Examples
186
+ --------
187
+ >>> mAP_50, mAP_50_95 = detection_metrics("annotations.json", "img_01.png", model)
188
+ """
189
+ gt_pts, gt_indices=[],[]
190
+ gt_coords, gt_labels=provide_annotations(json_file, img_file)
191
+
192
+ for j in range(0, len(gt_coords)):
193
+ x0, y0 = int(gt_coords[j][0]), int(gt_coords[j][1])
194
+ x1, y1 = int(gt_coords[j][2])+ x0, int(gt_coords[j][3])+ y0
195
+ gt_pts.append((x0,y0, x1,y1))
196
+ gt_indices.append(j)
197
+
198
+ IOU_arr=[]
199
+ IOU_checkpoints_gt=[]
200
+
201
+ _, coords, labels, _,confidence= ai_analytics(None, img_file, model_test)
202
+ for j in range(0, len(coords)):
203
+ x0, y0 = int(coords[j][0]), int(coords[j][1])
204
+ x1, y1 = int(coords[j][2]), int(coords[j][3])
205
+ tgt_pt=(x0,y0 ,x1,y1)
206
+ IOU=IOU_calc(tgt_pt, gt_pts)
207
+ IOU_checkpoints_gt.append(gt_indices[np.argmax(IOU)])
208
+ IOU=round(np.max(IOU),2)
209
+ IOU_arr.append(IOU)
210
+
211
+ confidence_arr= np.array(confidence)
212
+ IOU_arr= np.array(IOU_arr)
213
+ IOU_checkpoints_gt= np.array(IOU_checkpoints_gt)
214
+ index= np.argsort(confidence_arr)[::-1]
215
+ IOU_arr= IOU_arr[index]
216
+ IOU_checkpoints_gt= IOU_checkpoints_gt[index]
217
+
218
+ net_precision, net_recall= mAP_calc(IOU_arr, IOU_checkpoints_gt)
219
+ net_area=0
220
+ for j in range(0, len(net_precision)):
221
+ x,y= AUC_calc(net_precision[j], net_recall[j])
222
+
223
+ area_arr=[]
224
+ area_i=0
225
+ for k in range(0, len(x)):
226
+ if k<len(x)-1:
227
+ w= x[k+1]-x[k]
228
+ else:
229
+ w=0
230
+ h= y[k]
231
+ area=(h*w)+ area_i
232
+ area_arr.append(area)
233
+ area_i= area_arr[-1]
234
+ net_area+=area_i
235
+ if j==0:
236
+ mAP_50= area_i
237
+ mAP_50_95= net_area/10
238
+ mAP_50, mAP_50_95= round(mAP_50,3), round(mAP_50_95,3)
239
+
240
+ print('net mAP_50', mAP_50, 'net mAP_50:95', mAP_50_95)
241
+ return mAP_50, mAP_50_95
@@ -0,0 +1,597 @@
1
+ """Core dataset, annotation, inference and SAHI-tiling helpers.
2
+
3
+ This is the main working module of the package. It covers four jobs:
4
+
5
+ 1. **File discovery**: find images, ``.json`` and ``.txt`` files in a folder
6
+ tree (``img_loc``).
7
+ 2. **Dataset checks and annotations**: validate a COCO dataset folder
8
+ (``dataset_check``) and fetch the ground-truth boxes for one image
9
+ (``provide_annotations``).
10
+ 3. **Inference**: run a detector or classifier on one image and optionally
11
+ draw the predictions next to the ground truth (``ai_analytics``).
12
+ 4. **SAHI tiling**: cut a large image into tiles and run detection (and
13
+ optional classification) on every tile (``SAHI_generation``,
14
+ ``SAHI_analysis``).
15
+
16
+ Functions
17
+ ---------
18
+ img_loc
19
+ Recursively list image, json and txt files.
20
+ ai_analytics
21
+ Run a model on one image and return its predictions.
22
+ dataset_check
23
+ Sanity-check a folder of images plus a COCO json.
24
+ provide_annotations
25
+ Get the ground-truth boxes and labels for one image.
26
+ SAHI_generation
27
+ Slice a large image into tiles.
28
+ SAHI_analysis
29
+ Run detection / classification on tiles and draw the results on the
30
+ full image.
31
+
32
+ Module Constants
33
+ ----------------
34
+ IMAGE_EXTENSIONS : tuple of str
35
+ File extensions treated as images (matched case-insensitively).
36
+
37
+ Requires
38
+ --------
39
+ ``numpy``, ``opencv-python``, ``torch``, ``torchvision``, ``matplotlib`` and
40
+ ``sahi``. Uses ``cell_geometry`` and ``cell_transform`` from ``Image_Utils``.
41
+
42
+ Author: Shounak Sanyal
43
+ """
44
+ import os, cv2, json, torch, shutil
45
+ import numpy as np
46
+ from sahi.slicing import slice_image
47
+ import matplotlib.pyplot as plt # currently unused in this module
48
+ from torchvision.transforms import v2
49
+ from torchvision import datasets, models, transforms
50
+ from .Image_Utils import *
51
+
52
+ # Common image file extensions to recognize when scanning directories.
53
+ # Add/remove extensions here if your office image sources use other formats.
54
+ # Matching is case-insensitive (names are lowercased before comparing).
55
+ IMAGE_EXTENSIONS = ('png', 'jpg', 'jpeg', 'bmp', 'tif', 'tiff', 'webp')
56
+
57
+
58
+
59
+ def img_loc(path):
60
+ """Recursively collect image, json and txt file paths under a folder.
61
+
62
+ Parameters
63
+ ----------
64
+ path : str
65
+ Root directory to search. All sub-folders are searched too.
66
+
67
+ Returns
68
+ -------
69
+ img_list : list of str
70
+ Paths of all image files (extensions from ``IMAGE_EXTENSIONS``).
71
+ json_list : list of str
72
+ Paths of all ``.json`` files.
73
+ txt_list : list of str
74
+ Paths of all ``.txt`` files.
75
+
76
+ Examples
77
+ --------
78
+ >>> images, jsons, txts = img_loc("my_dataset")
79
+ """
80
+
81
+ global img_list
82
+ global json_list
83
+ global txt_list
84
+
85
+ img_list=[]
86
+ json_list=[]
87
+ txt_list=[]
88
+
89
+ def find_all_images(path):
90
+ """Append every image file under ``path`` to the global
91
+ ``img_list``, entering each sub-folder recursively."""
92
+ global img_list
93
+ list_path= os.listdir(path)
94
+ for i in range(0, len(list_path)):
95
+ new_path= os.path.join(path, list_path[i])
96
+ if os.path.isfile(new_path):
97
+ if new_path.lower().endswith(IMAGE_EXTENSIONS):
98
+ img_list.append(new_path)
99
+ if os.path.isdir(new_path):
100
+ find_all_images(new_path)
101
+
102
+ def find_all_json(path):
103
+ """Append every path ending in ``'json'`` under ``path`` to the
104
+ global ``json_list``, entering each sub-folder recursively."""
105
+ global json_list
106
+ list_path= os.listdir(path)
107
+ for i in range(0, len(list_path)):
108
+ new_path= os.path.join(path, list_path[i])
109
+
110
+ if new_path.endswith('json'):
111
+ json_list.append(new_path)
112
+ if os.path.isdir(new_path):
113
+ find_all_json(new_path)
114
+
115
+ def find_all_txt(path):
116
+ """Append every file ending in ``'txt'`` under ``path`` to the
117
+ global ``txt_list``, entering each sub-folder recursively."""
118
+ global txt_list
119
+ list_path= os.listdir(path)
120
+ for i in range(0, len(list_path)):
121
+ new_path= os.path.join(path, list_path[i])
122
+ if os.path.isfile(new_path):
123
+ if new_path.lower().endswith('txt'):
124
+ txt_list.append(new_path)
125
+ if os.path.isdir(new_path):
126
+ find_all_txt(new_path)
127
+
128
+
129
+ find_all_images(path)
130
+ find_all_json(path)
131
+ find_all_txt(path)
132
+ return img_list, json_list, txt_list
133
+
134
+ def ai_analytics(file, image_path, model, name='RFDETR', key=True, mode='Detection'):
135
+ """Run a model on one image and return its predictions.
136
+
137
+ For detection, the ground-truth boxes are also looked up in a COCO json so
138
+ the predictions can be compared with them (and optionally drawn on the
139
+ image).
140
+
141
+ Supported ``(name, mode)`` combinations:
142
+
143
+ * ``('RFDETR', 'Detection')``: ``model.predict(image_path)``
144
+ * ``('YOLO', 'Detection')``: ``model.predict(image_path)``
145
+ * ``('CNN', 'Classification')``: ``model(img)``, where ``img`` is a tensor
146
+
147
+ Parameters
148
+ ----------
149
+ file : str or None
150
+ Path to a COCO-format JSON annotation file. If it is ``None`` or cannot
151
+ be read, the function continues without ground truth. Use ``None`` for
152
+ classification.
153
+ image_path : str or torch.Tensor
154
+ Detection: path to the image. Classification: the image tensor itself.
155
+ model : object
156
+ The loaded model (moved to the GPU if available, else the CPU). For
157
+ detection it must have ``.predict()``.
158
+ name : {'RFDETR', 'YOLO', 'CNN'}, optional
159
+ Model type; decides how the output is read. Default is ``'RFDETR'``.
160
+ key : bool, optional
161
+ If True and the image has ground-truth annotations, draw the predicted
162
+ boxes (blue) and the ground-truth boxes (green) on the returned image.
163
+ Default is True.
164
+ mode : {'Detection', 'Classification'}, optional
165
+ Task type. Default is ``'Detection'``.
166
+
167
+ Returns
168
+ -------
169
+ Classification (``'CNN'``):
170
+ pred : numpy.int64
171
+ Index of the class with the highest score.
172
+ results : numpy.ndarray
173
+ Raw model scores for the first item in the batch.
174
+ Detection:
175
+ n_gt : int
176
+ Number of ground-truth boxes found for this image.
177
+ coords : array-like
178
+ Predicted boxes in ``xyxy`` format.
179
+ labels : array-like
180
+ Predicted labels (RFDETR: class names, YOLO: numeric class ids).
181
+ img : numpy.ndarray
182
+ The image (BGR), with the boxes drawn on it when ``key=True`` and
183
+ ground truth was found.
184
+ confidence : array-like
185
+ Confidence score of each prediction.
186
+
187
+ Examples
188
+ --------
189
+ >>> n_gt, coords, labels, img, conf = ai_analytics("annotations.json", "img_01.png", model)
190
+ >>> pred, scores = ai_analytics(None, tensor, cnn_model, name="CNN", mode="Classification")
191
+ """
192
+
193
+ device= "cuda" if torch.cuda.is_available() else "cpu"
194
+
195
+ def find_annots(id, file):
196
+ """Return all bboxes (``[x, y, w, h]``) and category ids from the
197
+ loaded COCO dict ``file`` whose annotations belong to the image id
198
+ ``id``."""
199
+ bbox_list, labels_list=[],[]
200
+ for i in range(0, len(file['annotations'])):
201
+ annot_id= file['annotations'][i]['image_id']
202
+ if annot_id == id:
203
+ bbox_list.append(file['annotations'][i]['bbox'])
204
+ labels_list.append(file['annotations'][i]['category_id'])
205
+
206
+ return bbox_list, labels_list
207
+
208
+ def find_img_id(id, file):
209
+ """Look up the COCO image id of the image file name ``id`` in the
210
+ loaded COCO dict ``file`` and return its ground truth as
211
+ ``(boxes, category ids)`` via ``find_annots``. Prints a message if
212
+ the image has no annotations or is not listed in the json."""
213
+ coords, labels=[],[]
214
+ for i in range(0, len(file['images'])):
215
+ if id== file['images'][i]['file_name']:
216
+ img_id= file['images'][i]['id']
217
+ coords, labels= find_annots(img_id, file)
218
+ if len(coords)==0:
219
+ print('The image', id, 'has no listed annotations')
220
+ break
221
+ if len(coords)==0:
222
+ # Note: this message also shows when the image IS listed but
223
+ # has no annotations (the message above is printed as well).
224
+ print('The image',id, 'is not listed in the json')
225
+ return coords, labels
226
+
227
+ # Run model inference on the target image.
228
+ if mode=='Detection':
229
+ results= model.predict(image_path)
230
+
231
+ if mode=='Classification':
232
+ # If image_path is a file path, read it with cv2; if that fails
233
+ # (e.g. it is already a tensor), use it as it is.
234
+ try:
235
+ img= cv2.imread(image_path)
236
+ except:
237
+ img= image_path
238
+ img= img.to(device)
239
+ results= model(img)
240
+
241
+ # The image file name (last part of the path) is used to find the
242
+ # matching entry in the COCO json. Skipped if image_path is not a string.
243
+ try:
244
+ img_title= os.path.basename(image_path)
245
+ except:
246
+ pass
247
+
248
+
249
+ # Attempt to load ground-truth annotations for this image; if the
250
+ # annotation file is missing/invalid, proceed with empty ground truth.
251
+ # (Note: 'file' is re-used here to hold the loaded json data.)
252
+ try:
253
+ with open(file, 'r') as f:
254
+ file= json.load(f)
255
+ gt_annots, gt_labels= find_img_id(img_title,file)
256
+ except:
257
+ print('No annotation files, proceeding without it')
258
+ gt_annots, gt_labels=[],[]
259
+
260
+ confidence=0
261
+ # Read the model output according to the model type.
262
+ if name=='RFDETR' and mode=='Detection':
263
+ # RFDETR (supervision-style Detections): boxes in .xyxy,
264
+ # class names in .data['class_name'].
265
+ coords= results.xyxy
266
+ labels= results.data['class_name']
267
+ confidence= results.confidence
268
+ img= cv2.imread(image_path)
269
+
270
+ if name=='YOLO' and mode=='Detection':
271
+ # YOLO (ultralytics-style Results): boxes in .boxes.xyxy,
272
+ # numeric class ids in .boxes.cls (moved to numpy).
273
+ coords= results[0].boxes.xyxy
274
+ labels= results[0].boxes.cls.cpu().numpy()
275
+ img= cv2.imread(image_path)
276
+
277
+ if name=='CNN' and mode=='Classification':
278
+ # Classification returns early: (predicted class index, raw scores).
279
+ results= results.cpu()
280
+ results=(results.detach().numpy())[0]
281
+ pred= np.argmax(results)
282
+ return pred, results
283
+
284
+ if key==True and len(gt_labels)>0:
285
+ # Draw predicted boxes with colour (255,0,0). OpenCV uses BGR,
286
+ # so this shows up as BLUE.
287
+ for i in range(0, len(coords)):
288
+ x0,y0= int(float(coords[i][0])),int(float(coords[i][1]))
289
+ x1,y1= int(float(coords[i][2])),int(float(coords[i][3]))
290
+ cv2.rectangle(img, (x0,y0), (x1,y1), (255,0,0), 5)
291
+
292
+ # Draw ground-truth boxes with colour (0,255,0) (GREEN).
293
+ # Note: COCO bbox format is [x, y, width, height], so x1/y1 here
294
+ # are computed as x0+width, y0+height.
295
+ for i in range(0, len(gt_annots)):
296
+ x0,y0= int(float(gt_annots[i][0])),int(float(gt_annots[i][1]))
297
+ x1,y1= int(float(gt_annots[i][2]))+x0,int(float(gt_annots[i][3]))+y0
298
+ cv2.rectangle(img, (x0,y0), (x1,y1), (0,255,0), 5)
299
+
300
+ print(labels, len(coords), len(gt_annots))
301
+ return len(gt_annots), coords, labels, img, confidence
302
+
303
+ def dataset_check(file):
304
+ """Sanity-check a dataset folder that holds images and a COCO json.
305
+
306
+ Checks done:
307
+
308
+ 1. Every image listed in the json exists in the folder.
309
+ 2. Every annotation points to an image id that exists in the json.
310
+
311
+ A warning is printed when a check fails, and the counts are always returned.
312
+
313
+ Parameters
314
+ ----------
315
+ file : str
316
+ Path to the dataset folder (only its top level is scanned). The first
317
+ ``.json`` file found is used as the annotation file.
318
+
319
+ Returns
320
+ -------
321
+ n_images : int
322
+ Number of image files in the folder.
323
+ n_json_images : int
324
+ Number of images listed in the json.
325
+ n_annotated_images : int
326
+ Number of distinct images that have at least one annotation.
327
+ n_categories : int
328
+ Number of categories defined in the json.
329
+ n_used_categories : int
330
+ Number of distinct categories actually used by annotations.
331
+
332
+ Examples
333
+ --------
334
+ >>> counts = dataset_check("my_dataset")
335
+ """
336
+ list_files= os.listdir(file)
337
+ # Locate the annotation file (first file ending with '.json').
338
+ json_path= [list_files[i] for i in range(0, len(list_files)) if list_files[i].endswith('.json')]
339
+ json_annotation_categories=[]
340
+ print(json_path)
341
+ json_path= os.path.join(file, json_path[0])
342
+ # Collect image files by any recognized image extension (case-insensitive)
343
+ # instead of only '.png'.
344
+ img_list= [os.path.basename(list_files[i]) for i in range(0, len(list_files)) if list_files[i].lower().endswith(IMAGE_EXTENSIONS)]
345
+
346
+ with open(json_path, 'r') as f:
347
+ json_file= json.load(f)
348
+
349
+ # Check that every image referenced in the COCO file is present in img_list.
350
+ for i in range(0,len(json_file['images'])):
351
+ if json_file['images'][i]['file_name'] not in img_list:
352
+ print('json images are not properly subset of the listed images!')
353
+ # return 0
354
+
355
+ json_img_list= [json_file['images'][i]['id'] for i in range(0, len(json_file['images']))]
356
+
357
+ # Check that every annotation's image_id corresponds to a known image.
358
+ for i in range(0, len(json_file['annotations'])):
359
+ if json_file['annotations'][i]['image_id'] not in json_img_list:
360
+ print('json annotations do not exists for few images!')
361
+ # return 0
362
+
363
+ json_annot_list= [json_file['annotations'][i]['image_id'] for i in range(0, len(json_file['annotations']))]
364
+ json_annotation_categories= [json_file['annotations'][i]['category_id'] for i in range(0, len(json_file['annotations']))]
365
+
366
+
367
+ return len(img_list), len(json_img_list), len(np.unique(json_annot_list)), len(json_file['categories']), len(np.unique(json_annotation_categories))
368
+
369
+ def provide_annotations(json_path, file_name, key='COCO'):
370
+ """Get the ground-truth boxes and labels for one image.
371
+
372
+ Two annotation formats are supported, chosen with ``key``.
373
+
374
+ Parameters
375
+ ----------
376
+ json_path : str or list of str
377
+ ``key='COCO'``: path to one COCO-format json file.
378
+ ``key='Vanila'``: list of json file paths (all are read and merged).
379
+ Each json is a list of entries with ``entry['image']['pathname']`` and
380
+ ``entry['objects']``; each object has ``'bounding_box'`` (with
381
+ ``'minimum'`` and ``'maximum'``, each holding ``'r'`` and ``'c'``) and
382
+ ``'category'``.
383
+ file_name : str
384
+ Image file name, or a full path (only the last part is used).
385
+ key : {'COCO', 'Vanila'}, optional
386
+ Annotation format. Default is ``'COCO'``.
387
+
388
+ Returns
389
+ -------
390
+ ``key='COCO'`` returns ``(coords, labels)``:
391
+ coords : list of list of float
392
+ Boxes in COCO format ``[x, y, width, height]``.
393
+ labels : list of str
394
+ Category names, one per box.
395
+ ``key='Vanila'`` returns ``coords``:
396
+ coords : list of list
397
+ ``[min_r, min_c, max_r, max_c, label]`` for each object
398
+ (``r`` = row, ``c`` = column), or ``0`` if the image is not found.
399
+
400
+ Examples
401
+ --------
402
+ >>> coords, labels = provide_annotations("annotations.json", "img_01.png")
403
+ >>> coords = provide_annotations(["a.json", "b.json"], "img_01.png", key="Vanila")
404
+ """
405
+
406
+
407
+ if key=='COCO':
408
+ with open(json_path, 'r') as f:
409
+ json_file= json.load(f)
410
+
411
+ file_name= os.path.basename(file_name)
412
+ def get_annots(json_file, image_id):
413
+ """Return all bboxes and category NAMES (looked up from
414
+ ``json_file['categories']``) whose annotations belong to the
415
+ COCO image id ``image_id``."""
416
+ coords, labels=[],[]
417
+ for i in range(0, len(json_file['annotations'])):
418
+ annot_id= json_file['annotations'][i]['image_id']
419
+ if annot_id==image_id:
420
+ coords.append(json_file['annotations'][i]['bbox'])
421
+ label=json_file['annotations'][i]['category_id']
422
+ label=[json_file['categories'][j]['name'] for j in range(0, len(json_file['categories'])) if json_file['categories'][j]['id']==label]
423
+ labels.append(label[0])
424
+ return coords, labels
425
+
426
+ # Find the image entry matching file_name, then fetch its annotations.
427
+ coords, labels=[],[]
428
+ for i in range(0, len(json_file['images'])):
429
+ image_file= json_file['images'][i]['file_name']
430
+
431
+ if image_file==file_name:
432
+ print(f'Image {image_file} found!')
433
+ image_id= json_file['images'][i]['id']
434
+ coords, labels= get_annots(json_file, image_id)
435
+
436
+ if len(coords)==0:
437
+ print(f'No coordinate annotations found for image {file_name}')
438
+ return coords, labels
439
+ else:
440
+ print(f'Coordinate annotations found for image {file_name}')
441
+ return coords, labels
442
+
443
+ if key=='Vanila':
444
+ # Build a lookup table {image name: [[min_r, min_c, max_r, max_c, label], ...]}
445
+ # from all the json files in the list.
446
+ img_bank={}
447
+ for index in range(0, len(json_path)):
448
+ with open (json_path[index],'rb') as f:
449
+ json_file=json.load(f)
450
+
451
+
452
+ for i in range(0, len(json_file)):
453
+ img_name= os.path.basename(json_file[i]['image']['pathname'])
454
+ img_bank[img_name]=[]
455
+
456
+ coord_list= json_file[i]['objects']
457
+
458
+
459
+ for j in range(0, len(coord_list)):
460
+ coord_min_x, coord_min_y= int(coord_list[j]['bounding_box']['minimum']['r']), int(coord_list[j]['bounding_box']['minimum']['c'])
461
+ coord_max_x, coord_max_y= int(coord_list[j]['bounding_box']['maximum']['r']), int(coord_list[j]['bounding_box']['maximum']['c'])
462
+ label= coord_list[j]['category']
463
+
464
+ img_bank[img_name].append([coord_min_x, coord_min_y, coord_max_x, coord_max_y, label])
465
+
466
+
467
+ def img_search(file_name):
468
+ """Look up an image name in ``img_bank`` and return its list of
469
+ boxes, or ``0`` if the image is not in the bank."""
470
+ img_name= os.path.basename(file_name)
471
+ img_name_list= list(img_bank.keys())
472
+ coords=0
473
+
474
+ if img_name in img_name_list:
475
+ coords= img_bank[img_name]
476
+ return coords
477
+
478
+ coords=img_search(file_name)
479
+ return coords
480
+
481
+ def SAHI_generation(image_path, output_file_name='tiled', output_dir='SAHI_results', slice_height=256, slice_width=256, overlap_h=0, overlap_w=0):
482
+ """Cut one large image into smaller tiles (SAHI slicing).
483
+
484
+ The tiles are saved to a folder, which is useful for running a detector on
485
+ small objects in big images. If ``output_dir`` already exists it is replaced
486
+ by the new tiles.
487
+
488
+ Parameters
489
+ ----------
490
+ image_path : str
491
+ Path to the image to slice.
492
+ output_file_name : str, optional
493
+ Base name for the saved tiles. Default is ``'tiled'``.
494
+ output_dir : str, optional
495
+ Folder where the tiles are saved. Default is ``'SAHI_results'``.
496
+ slice_height : int, optional
497
+ Tile height in pixels. Default is 256.
498
+ slice_width : int, optional
499
+ Tile width in pixels. Default is 256.
500
+ overlap_h : float, optional
501
+ Vertical overlap between tiles as a fraction of the tile height
502
+ (0 to 1). Default is 0 (no overlap).
503
+ overlap_w : float, optional
504
+ Horizontal overlap between tiles as a fraction of the tile width
505
+ (0 to 1). Default is 0 (no overlap).
506
+
507
+ Returns
508
+ -------
509
+ None
510
+ The tiles are written to ``output_dir``. Each tile file name ends with
511
+ its corner coordinates (``..._x0_y0_x1_y1``), which ``SAHI_analysis``
512
+ uses to place detections back on the full image.
513
+
514
+ Examples
515
+ --------
516
+ >>> SAHI_generation("large_image.png", slice_height=512, slice_width=512)
517
+ """
518
+ if os.path.exists(output_dir):
519
+ shutil.rmtree(output_dir)
520
+ slice_image_result = slice_image(
521
+ image=image_path,
522
+ output_file_name=output_file_name,
523
+ output_dir=output_dir,
524
+ slice_height=slice_height,
525
+ slice_width=slice_width,
526
+ overlap_height_ratio=overlap_h,
527
+ overlap_width_ratio=overlap_w,
528
+ )
529
+
530
+ def SAHI_analysis(image_path, model_det, model_clss=None, output_dir= 'SAHI_results',key=1):
531
+ """Run detection (and optional classification) on SAHI tiles and draw
532
+ the results on the full image.
533
+
534
+ For every tile in ``output_dir``: detect objects, optionally classify each
535
+ detected crop, shift the box from tile coordinates to full-image coordinates
536
+ and draw it on the original image.
537
+
538
+ Parameters
539
+ ----------
540
+ image_path : str
541
+ Path to the original full-size image (the one that was tiled).
542
+ model_det : object
543
+ Detection model (needs ``.predict()``), used through ``ai_analytics``.
544
+ model_clss : object, optional
545
+ Classification model (CNN) applied to every detected crop. If ``None``
546
+ (default), no classification is done.
547
+ output_dir : str, optional
548
+ Folder containing the tiles made by ``SAHI_generation``. Default is
549
+ ``'SAHI_results'``.
550
+ key : {0, 1}, optional
551
+ Transform key passed to ``Image_Utils.cell_transform`` (0 = with ImageNet
552
+ normalisation, 1 = without). Default is 1.
553
+
554
+ Returns
555
+ -------
556
+ img : numpy.ndarray
557
+ The full image (BGR) with the boxes drawn. Without a classifier every
558
+ box is blue. With a classifier, predicted class 0 is drawn green and any
559
+ other class red.
560
+
561
+ Examples
562
+ --------
563
+ >>> SAHI_generation("large_image.png")
564
+ >>> result = SAHI_analysis("large_image.png", det_model, cnn_model)
565
+ """
566
+ image_slice_path, json_path, txt_path= img_loc(output_dir)
567
+ IMAGE_EXTENSIONS = ('png', 'jpg', 'jpeg', 'bmp', 'tif', 'tiff', 'webp')
568
+ img=cv2.imread(image_path)
569
+
570
+ for i in range(0, len(image_slice_path)):
571
+ img_name = os.path.splitext(image_slice_path[i])[0]
572
+ x_n0,y_n0= int(img_name.split('_')[-4]), int(img_name.split('_')[-3])
573
+ x_n1, y_n1= int(img_name.split('_')[-2]), int(img_name.split('_')[-1])
574
+ print(img_name, x_n0, y_n0, x_n1, y_n1)
575
+ len_gt, coords,_,_,_= ai_analytics(None, image_slice_path[i], model_det)
576
+ img_SAHI= cv2.imread(image_slice_path[i])
577
+ for j in range(0, len(coords)):
578
+ x0,y0= int(float(coords[j][0])),int(float(coords[j][1]))
579
+ x1,y1= int(float(coords[j][2])),int(float(coords[j][3]))
580
+ color= (255,0,0)
581
+ if model_clss:
582
+ img_slice= img_SAHI[y0:y1, x0:x1]
583
+ img_slice= cell_geometry(img_slice)
584
+ img_slice= cv2.resize(img_slice, (224,224))
585
+ img_slice= cv2.cvtColor(img_slice, cv2.COLOR_BGR2RGB)
586
+ img_slice= cell_transform(img_slice, key)[np.newaxis,:,:]
587
+ pred, results= ai_analytics(None, img_slice, model_clss, name='CNN',mode='Classification')
588
+ print('pred', pred)
589
+ if pred==0:
590
+ color= (0,255,0)
591
+ else:
592
+ color=(0,0,255)
593
+ x0,y0= x0+x_n0, y0+y_n0
594
+ x1,y1= x1+x_n0, y1+y_n0
595
+ cv2.rectangle(img, (x0,y0), (x1,y1), color,2)
596
+
597
+ return img
@@ -0,0 +1,265 @@
1
+ """Image pre-processing helpers.
2
+
3
+ Functions that prepare images (for example single-cell crops) before they are
4
+ shown to a classifier or used for training: normalisation, augmentation,
5
+ padding to a square and conversion to tensors. Images are NumPy arrays read
6
+ with OpenCV, so colour images are in BGR order unless stated otherwise.
7
+
8
+ Functions
9
+ ---------
10
+ image_processing
11
+ Colour / contrast normalisation (CLAHE, Reinhard colour transfer or
12
+ Otsu thresholding).
13
+ data_augmentation
14
+ Generates rotated and blurred copies of an image.
15
+ cell_geometry
16
+ Makes an image even-sized and pads it with black to a square.
17
+ cell_transform
18
+ Converts an image to a PyTorch tensor, with or without ImageNet
19
+ normalisation.
20
+
21
+ Author: Shounak Sanyal
22
+ """
23
+ import os
24
+ import cv2
25
+ import matplotlib.pyplot as plt
26
+ import numpy as np
27
+ from torchvision.transforms import v2
28
+ import os, cv2, json, torch, shutil
29
+ from torchvision import datasets, models, transforms
30
+
31
+
32
+ def image_processing(img, ref_img=None, normalisation='Clahe', grid_window=(8,8)):
33
+ """Normalise an image using CLAHE, Reinhard colour transfer or Otsu.
34
+
35
+ Parameters
36
+ ----------
37
+ img : numpy.ndarray
38
+ Input image. BGR for ``'Clahe'`` (grayscale also works) and
39
+ ``'Reinhard'``; single-channel 8-bit grayscale for ``'Otsu'``.
40
+ ref_img : numpy.ndarray, optional
41
+ Reference image in BGR format. Needed only for ``'Reinhard'``.
42
+ normalisation : {'Clahe', 'Reinhard', 'Otsu'}, optional
43
+ Method to use (case-sensitive). Default is ``'Clahe'``.
44
+
45
+ * ``'Clahe'``: improves local contrast by equalising the lightness
46
+ channel (adaptive histogram equalisation, clip limit 2.0).
47
+ * ``'Reinhard'``: matches the colour statistics of ``img`` to those of
48
+ ``ref_img``.
49
+ * ``'Otsu'``: automatic thresholding that returns an inverted binary
50
+ mask (values 0 and 255).
51
+ grid_window : tuple of int, optional
52
+ CLAHE tile grid size ``(rows, cols)``. Used only by ``'Clahe'``.
53
+ Default is ``(8, 8)``.
54
+
55
+ Returns
56
+ -------
57
+ img_norm : numpy.ndarray
58
+ The normalised image (BGR for colour input, single-channel for
59
+ grayscale input and ``'Otsu'``).
60
+
61
+ Examples
62
+ --------
63
+ >>> img_norm = image_processing(img) # CLAHE
64
+ >>> img_norm = image_processing(img, ref_img, "Reinhard") # match colours
65
+ """
66
+ # --- Reinhard Color Transfer ---
67
+ if normalisation == 'Reinhard':
68
+ # Convert images from BGR to LAB color space (float32 for precise math)
69
+ img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB).astype(np.float32)
70
+ ref_lab = cv2.cvtColor(ref_img, cv2.COLOR_BGR2LAB).astype(np.float32)
71
+
72
+ # Calculate mean and standard deviation for reference image
73
+ ref_mean = ref_lab.reshape(-1, 3).mean(axis=0)
74
+ ref_sd = ref_lab.reshape(-1, 3).std(axis=0)
75
+
76
+ # Calculate mean and standard deviation for input image
77
+ img_mean = img_lab.reshape(-1, 3).mean(axis=0)
78
+ img_sd = img_lab.reshape(-1, 3).std(axis=0)
79
+
80
+ # Normalize channel distributions to match reference statistics
81
+ normalized = (
82
+ (img_lab - img_mean) / (img_sd + 1e-8) * ref_sd + ref_mean
83
+ )
84
+
85
+ # Ensure pixel values stay within valid range [0, 255]
86
+ normalized = np.clip(normalized, 0, 255).astype(np.uint8)
87
+ img_norm = cv2.cvtColor(normalized, cv2.COLOR_Lab2BGR)
88
+
89
+ # --- CLAHE (Contrast Limited Adaptive Histogram Equalization) ---
90
+ if normalisation == 'Clahe':
91
+ # Convert BGR image to LAB color space
92
+ try:
93
+ lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
94
+
95
+ # Configure CLAHE parameters
96
+ clahe = cv2.createCLAHE(
97
+ clipLimit=2.0,
98
+ tileGridSize=grid_window
99
+ )
100
+
101
+ # Apply histogram equalization exclusively to the L (lightness) channel
102
+ lab[:, :, 0] = clahe.apply(lab[:, :, 0])
103
+
104
+ # Convert image back from LAB to BGR color space
105
+ img_norm = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
106
+ except:
107
+ clahe = cv2.createCLAHE(
108
+ clipLimit=2.0,
109
+ tileGridSize=grid_window
110
+ )
111
+
112
+ img_norm= clahe.apply(img)
113
+
114
+
115
+ if normalisation=='Otsu':
116
+ _, img_norm= cv2.threshold(img, 0,255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
117
+ return img_norm
118
+
119
+ def data_augmentation(img, n_rotate=3, blur=True):
120
+ """Generate rotated (and optionally blurred) copies of an image.
121
+
122
+ Parameters
123
+ ----------
124
+ img : numpy.ndarray
125
+ Input image.
126
+ n_rotate : int, optional
127
+ Number of rotations to apply, from 0 to 3. Default is 3. The rotations
128
+ are, in order: 90 degrees clockwise, 180 degrees, 90 degrees
129
+ counter-clockwise.
130
+ blur : bool, optional
131
+ If True, also add a Gaussian-blurred copy of the original image
132
+ (5x5 kernel, sigma 1). Default is True.
133
+
134
+ Returns
135
+ -------
136
+ img_aug : list of numpy.ndarray
137
+ The rotated images in order, followed by the blurred image when
138
+ ``blur`` is True. The original image is not included.
139
+
140
+ Examples
141
+ --------
142
+ >>> augmented = data_augmentation(img) # 3 rotations + 1 blur = 4 images
143
+ """
144
+
145
+ img_aug=[]
146
+
147
+ for i in range(0,n_rotate):
148
+
149
+ img_aug.append(cv2.rotate(img, i))
150
+
151
+ if blur:
152
+
153
+ img_aug.append(cv2.GaussianBlur(img, (5,5),1))
154
+
155
+ return img_aug
156
+
157
+ def cell_geometry(img):
158
+ """Make an image even-sized and pad it with black to a square.
159
+
160
+ Steps:
161
+
162
+ 1. If the height or width is odd, that side is resized by +1 pixel.
163
+ 2. If the image is not square, the shorter side is padded with black so the
164
+ original image stays centred. The result is square, with side equal to
165
+ the longer dimension.
166
+
167
+ Parameters
168
+ ----------
169
+ img : numpy.ndarray
170
+ Grayscale image of shape ``(H, W)`` or colour image of shape
171
+ ``(H, W, C)``.
172
+
173
+ Returns
174
+ -------
175
+ numpy.ndarray
176
+ Square image of dtype ``uint8``.
177
+
178
+ Examples
179
+ --------
180
+ >>> square = cell_geometry(crop) # e.g. a 40x60 crop becomes 60x60
181
+ """
182
+ # Get dimensions
183
+ if img.ndim == 3:
184
+ h, w, c = img.shape
185
+ elif img.ndim == 2:
186
+ h, w = img.shape
187
+ else:
188
+ raise ValueError(f"Expected 2D or 3D image, got shape {img.shape}")
189
+
190
+ # Make dimensions even
191
+ if h % 2 != 0:
192
+ h_new = h + 1
193
+ img = cv2.resize(img, (w, h_new))
194
+ h = h_new
195
+
196
+ if w % 2 != 0:
197
+ w_new = w + 1
198
+ img = cv2.resize(img, (w_new, h))
199
+ w = w_new
200
+
201
+ # Already square
202
+ if h == w:
203
+ return img.astype(np.uint8)
204
+
205
+ # Width > height
206
+ if w > h:
207
+ dx = w - h
208
+
209
+ if img.ndim == 3:
210
+ new_img = np.zeros((w, w, c), dtype=np.uint8)
211
+ else:
212
+ new_img = np.zeros((w, w), dtype=np.uint8)
213
+
214
+ top = dx // 2
215
+ bottom = top + h
216
+
217
+ new_img[top:bottom, :] = img
218
+
219
+ # Height > width
220
+ else:
221
+ dy = h - w
222
+
223
+ if img.ndim == 3:
224
+ new_img = np.zeros((h, h, c), dtype=np.uint8)
225
+ else:
226
+ new_img = np.zeros((h, h), dtype=np.uint8)
227
+
228
+ left = dy // 2
229
+ right = left + w
230
+
231
+ new_img[:, left:right] = img
232
+
233
+ return new_img
234
+
235
+ def cell_transform(img, key):
236
+ """Convert an image to a float PyTorch tensor for a classifier.
237
+
238
+ Parameters
239
+ ----------
240
+ img : numpy.ndarray or PIL.Image.Image
241
+ Input image, typically RGB ``uint8`` of shape ``(H, W, 3)``.
242
+ key : {0, 1}
243
+ Transform to apply.
244
+
245
+ * ``0``: convert to a tensor with values in ``[0, 1]`` and normalise
246
+ with the ImageNet mean ``[0.485, 0.456, 0.406]`` and standard
247
+ deviation ``[0.229, 0.224, 0.225]``.
248
+ * ``1``: convert to a tensor with values in ``[0, 1]`` only (no
249
+ normalisation).
250
+
251
+ Returns
252
+ -------
253
+ img : torch.Tensor
254
+ Tensor of shape ``(C, H, W)`` and dtype ``float32``.
255
+
256
+ Examples
257
+ --------
258
+ >>> tensor = cell_transform(rgb_img, 0)
259
+ """
260
+ if key==0:
261
+ transform=v2.Compose([v2.ToImage(), v2.ToDtype(torch.float32, scale=True), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
262
+ if key==1:
263
+ transform=v2.Compose([v2.ToImage(), v2.ToDtype(torch.float32, scale=True)])
264
+ img= transform(img)
265
+ return img
@@ -0,0 +1,21 @@
1
+ """Computer_vision_assisting_tools
2
+ ================================
3
+
4
+ A small toolbox of helpers for computer-vision projects: image
5
+ pre-processing, dataset / annotation handling, model inference, SAHI tiling
6
+ and detection metrics.
7
+
8
+ Modules
9
+ -------
10
+ Image_Utils
11
+ Image normalisation, augmentation, padding and tensor transforms.
12
+ Basic_Utils
13
+ File discovery, COCO dataset checks, annotation lookup, model inference
14
+ and SAHI tiling.
15
+ AI_Metrics
16
+ IoU, precision / recall and mAP for object detection.
17
+
18
+ See ``README.md`` for the full description and the module hierarchy.
19
+
20
+ Author: Shounak Sanyal
21
+ """
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.5
2
+ Name: Computer_Vision_Assistant_Package
3
+ Version: 1.0
4
+ Summary: A user friendly package assiting in the most common computer vision tasks
5
+ Author-email: Shounak Sanyal <shounaksanyal1902@outlook.com>
6
+ License-Expression: MIT
7
+ License-File: License.txt
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Computer_vision_assisting_tools
13
+
14
+ **Author:** Shounak Sanyal
15
+
16
+ A small toolbox of helpers for computer-vision projects. It bundles the
17
+ functions that were most useful in a detection + classification workflow:
18
+
19
+ * find images and annotation files in a folder tree,
20
+ * check and read COCO-style datasets,
21
+ * run a detector (RF-DETR, YOLO) or a CNN classifier on an image and draw the
22
+ predictions next to the ground truth,
23
+ * slice large images into tiles (SAHI) and run detection / classification on
24
+ every tile,
25
+ * pre-process images (CLAHE, Reinhard colour transfer, Otsu, augmentation,
26
+ square padding, tensor transforms),
27
+ * score a detector with IoU, precision / recall and mAP.
28
+
29
+ ---
30
+
31
+ ## Package hierarchy
32
+
33
+ ```
34
+ .
35
+ ├── README.md
36
+ └── Computer_vision_assisting_tools/
37
+ ├── __innit__.py Package docstring (no code)
38
+ ├── AI_Metrics.py Detection metrics
39
+ │ ├── IOU_calc
40
+ │ ├── mAP_calc
41
+ │ ├── AUC_calc
42
+ │ └── detection_metrics
43
+ ├── Basic_Utils.py Files, datasets, annotations, inference, SAHI
44
+ │ ├── IMAGE_EXTENSIONS (constant)
45
+ │ ├── img_loc
46
+ │ ├── ai_analytics
47
+ │ ├── dataset_check
48
+ │ ├── provide_annotations
49
+ │ ├── SAHI_generation
50
+ │ └── SAHI_analysis
51
+ └── Image_Utils.py Image pre-processing
52
+ ├── image_processing
53
+ ├── data_augmentation
54
+ ├── cell_geometry
55
+ └── cell_transform
56
+ ```
57
+
58
+ ### How the modules depend on each other
59
+
60
+ ```
61
+ AI_Metrics.py ──► Basic_Utils.py ──► Image_Utils.py
62
+ (metrics) (data + models) (image helpers)
63
+ ```
64
+
65
+ `AI_Metrics` uses `provide_annotations` and `ai_analytics` from `Basic_Utils`;
66
+ `Basic_Utils` uses `cell_geometry` and `cell_transform` from `Image_Utils`.
67
+ `Image_Utils` depends on none of the others. The modules import each other
68
+ with relative imports (`from .Basic_Utils import *`), so they must be used as
69
+ part of the package, not run directly as scripts.
70
+
71
+ ---
72
+
73
+ ## Module descriptions
74
+
75
+ ### `Image_Utils.py` — image pre-processing
76
+
77
+ Helpers that prepare images (for example single-cell crops) before they reach
78
+ a classifier. Images are NumPy arrays read with OpenCV, so colour images are in
79
+ BGR order unless stated otherwise.
80
+
81
+ | Function | What it does |
82
+ |---|---|
83
+ | `image_processing(img, ref_img=None, normalisation='Clahe', grid_window=(8,8))` | Normalises an image with CLAHE (default), Reinhard colour transfer (needs `ref_img`) or Otsu thresholding (`'Otsu'`). |
84
+ | `data_augmentation(img, n_rotate=3, blur=True)` | Returns up to three rotated copies (90°, 180°, 270°; default 3) and, optionally, a Gaussian-blurred copy. |
85
+ | `cell_geometry(img)` | Makes both sides even, then pads the shorter side with black so the image becomes a centred square. |
86
+ | `cell_transform(img, key)` | Converts an image to a `float32` tensor. `key=0` adds ImageNet normalisation, `key=1` only scales to [0, 1]. |
87
+
88
+ ### `Basic_Utils.py` — data, annotations, inference and SAHI
89
+
90
+ The main working module. It finds files, checks datasets, reads ground truth,
91
+ runs models and handles SAHI tiling.
92
+
93
+ | Function | What it does |
94
+ |---|---|
95
+ | `IMAGE_EXTENSIONS` | Tuple of file extensions treated as images (case-insensitive). |
96
+ | `img_loc(path)` | Recursively lists image, `.json` and `.txt` files under a folder. Returns `(img_list, json_list, txt_list)`. |
97
+ | `ai_analytics(file, image_path, model, name='RFDETR', key=True, mode='Detection')` | Runs RF-DETR / YOLO detection or CNN classification on one image (the model is moved to the GPU if available). For detection it also reads the COCO ground truth and can draw predictions (blue) and ground truth (green). |
98
+ | `dataset_check(file)` | Checks that the images in a folder and the COCO json agree. Returns five counts (images, json images, annotated images, categories, used categories). |
99
+ | `provide_annotations(json_path, file_name, key='COCO')` | Returns the ground-truth boxes and labels for one image, from a COCO json or from the `'Vanila'` multi-json format. |
100
+ | `SAHI_generation(image_path, ...)` | Slices a large image into tiles and saves them (the output folder is deleted first if it exists). |
101
+ | `SAHI_analysis(image_path, model_det, model_clss=None, ...)` | Detects (and optionally classifies) objects on every tile and draws them on the full image. |
102
+
103
+ ### `AI_Metrics.py` — detection metrics
104
+
105
+ Scores a detector on a single image against COCO ground truth. Boxes are in
106
+ `(x_min, y_min, x_max, y_max)` format.
107
+
108
+ | Function | What it does |
109
+ |---|---|
110
+ | `IOU_calc(target_pts, gt_pts)` | IoU of one box against a list of ground-truth boxes. |
111
+ | `mAP_calc(bboxes, gt_bboxes)` | Running precision and recall for IoU thresholds 0.50 – 0.90. |
112
+ | `AUC_calc(precision, recall)` | Builds the smoothed precision curve used for the area under the curve. |
113
+ | `detection_metrics(json_file, img_file, model_test)` | End-to-end evaluation. Returns `(mAP_50, mAP_50_95)`. |
114
+
115
+ ---
116
+
117
+ ## Requirements
118
+
119
+ * Python 3.9+
120
+ * `numpy`
121
+ * `opencv-python`
122
+ * `torch`
123
+ * `torchvision` (0.16 or newer, for `torchvision.transforms.v2.ToImage`)
124
+ * `matplotlib` (imported by the modules)
125
+ * `sahi`
126
+ * A detector with a `.predict()` method (RF-DETR or Ultralytics YOLO) if you
127
+ use `ai_analytics`, `SAHI_analysis` or `detection_metrics`.
128
+
129
+ ## Quick example
130
+
131
+ ```python
132
+ from Computer_vision_assisting_tools.Basic_Utils import img_loc, dataset_check
133
+ from Computer_vision_assisting_tools.Image_Utils import image_processing
134
+
135
+ images, jsons, txts = img_loc("my_dataset") # find all files
136
+ print(dataset_check("my_dataset")) # sanity-check images vs json
137
+
138
+ import cv2
139
+ img = cv2.imread(images[0])
140
+ img_clahe = image_processing(img, normalisation="Clahe")
141
+ ```
142
+
143
+ ## Documentation style
144
+
145
+ Every module, function and inner helper has a NumPy-style docstring
146
+ (`Parameters`, `Returns`, `Examples`). Use `help(function_name)` to read
147
+ them.
148
+
149
+ ## Author
150
+
151
+ Created and maintained by **Shounak Sanyal**.
@@ -0,0 +1,8 @@
1
+ Computer_Vision_Assistant_Package/AI_Metrics.py,sha256=iQ3da7iqlgKuqyJlDDelegWCIhBJ5eXDJl24Z2-2o-4,8289
2
+ Computer_Vision_Assistant_Package/Basic_Utils.py,sha256=RPHRfnx8BYMYhPet-crzLqzngHt3KeGU9uyEUc2pLDQ,23720
3
+ Computer_Vision_Assistant_Package/Image_Utils.py,sha256=gGIOt-R9Odrw-B3Pw2mhy_i6nal5tFSPN9Qe51AktcE,8325
4
+ Computer_Vision_Assistant_Package/__innit__.py,sha256=Ee5avxb60YFZgm7DSw2qrkxq4BgpSZjg0oF8y57XUog,606
5
+ computer_vision_assistant_package-1.0.dist-info/METADATA,sha256=d9Een0WErNCDVCdngXyThP-esoiazh4TYNHYzEOs_fg,6420
6
+ computer_vision_assistant_package-1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
7
+ computer_vision_assistant_package-1.0.dist-info/licenses/License.txt,sha256=PlCLU6XTp8wrSeEQOxQBUoeLxWIfc4wP71Sc6FOE_t4,1070
8
+ computer_vision_assistant_package-1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shounak Sanyal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE