bhavibhatt commited on
Commit
9da23f0
·
verified ·
1 Parent(s): 19258aa

Upload 5 files

Browse files
Files changed (5) hide show
  1. README.md +19 -0
  2. config.json +1 -0
  3. ensemble.py +43 -0
  4. handler.py +13 -0
  5. requirements.txt +6 -0
README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - computer-vision
5
+ - image-segmentation
6
+ - welding
7
+ - weld-inspection
8
+ - yolo
9
+ pipeline_tag: image-segmentation
10
+ ---
11
+ # WeldVision Ensemble
12
+
13
+ Four-model YOLO segmentation ensemble for prototype weld-surface defect inspection.
14
+
15
+ Models: `best.pt`, `best_v0.pt`, `crack_specialist.pt`, `spatters_specialist.pt`.
16
+
17
+ The custom `handler.py` loads all four weights and performs class-aware mask-IoU merging.
18
+
19
+ **Prototype only:** the score and PASS/REVIEW/FAIL rules are not welding-code acceptance criteria and do not replace qualified human inspection.
config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"name":"WeldVision-Ensemble","version":"1.0","models":["best.pt","best_v0.pt","crack_specialist.pt","spatters_specialist.pt"],"confidence":0.25,"ensemble_mask_iou":0.50,"image_size":640}
ensemble.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from ultralytics import YOLO
4
+
5
+ class WeldVisionEnsemble:
6
+ CLASS_NAMES={0:'Bad Welding',1:'Crack',2:'Excess Reinforcement',3:'Good Welding',4:'Porosity',5:'Spatters'}
7
+ PENALTIES={'Crack':40,'Porosity':15,'Spatters':5,'Excess Reinforcement':20,'Bad Welding':50,'Good Welding':0}
8
+ def __init__(self, model_dir, conf=.25, ensemble_iou=.50, imgsz=640):
9
+ self.conf=conf; self.ensemble_iou=ensemble_iou; self.imgsz=imgsz
10
+ self.base1=YOLO(f'{model_dir}/best.pt'); self.base2=YOLO(f'{model_dir}/best_v0.pt')
11
+ self.crack=YOLO(f'{model_dir}/crack_specialist.pt'); self.spatters=YOLO(f'{model_dir}/spatters_specialist.pt')
12
+ @staticmethod
13
+ def mask_iou(a,b):
14
+ a=a.astype(bool); b=b.astype(bool); u=np.logical_or(a,b).sum(); return float(np.logical_and(a,b).sum()/u) if u else 0.0
15
+ def extract(self,r,source):
16
+ if r.boxes is None or r.masks is None: return []
17
+ out=[]
18
+ for box,mask in zip(r.boxes.data.cpu().numpy(),r.masks.data.cpu().numpy()):
19
+ x1,y1,x2,y2,conf,cls=box; cls=int(cls)
20
+ name='Crack' if source=='crack' else 'Spatters' if source=='spatters' else self.CLASS_NAMES.get(cls,str(cls))
21
+ out.append({'box':np.array([x1,y1,x2,y2],dtype=np.float32),'conf':float(conf),'class_name':name,'mask':mask.astype(np.float32),'source':source})
22
+ return out
23
+ def run(self,model,img,source):
24
+ return self.extract(model.predict(img,conf=self.conf,imgsz=self.imgsz,verbose=False)[0],source)
25
+ def merge(self,preds):
26
+ selected=[]
27
+ for p in sorted(preds,key=lambda x:x['conf'],reverse=True):
28
+ if not any(p['class_name']==q['class_name'] and self.mask_iou(p['mask'],q['mask'])>=self.ensemble_iou for q in selected): selected.append(p)
29
+ return selected
30
+ def predict(self,image):
31
+ image=np.asarray(image)
32
+ if image.ndim!=3 or image.shape[2]!=3: raise ValueError('image must be HxWx3 RGB')
33
+ bgr=cv2.cvtColor(image,cv2.COLOR_RGB2BGR)
34
+ p1=self.run(self.base1,bgr,'base_1'); p2=self.run(self.base2,bgr,'base_2'); pc=self.run(self.crack,bgr,'crack'); ps=self.run(self.spatters,bgr,'spatters')
35
+ merged=self.merge(p1+p2+pc+ps); detections=[]; h,w=bgr.shape[:2]
36
+ for p in merged:
37
+ c=p['class_name']
38
+ if c=='Good Welding': continue
39
+ sev='HIGH' if self.PENALTIES.get(c,0)>=30 else 'MEDIUM' if self.PENALTIES.get(c,0)>=15 else 'LOW' if self.PENALTIES.get(c,0)>0 else 'NONE'
40
+ x1,y1,x2,y2=p['box']; detections.append({'class':c,'confidence':round(p['conf'],4),'severity':sev,'box':[round(float(max(0,min(w,x1))),2),round(float(max(0,min(h,y1))),2),round(float(max(0,min(w,x2))),2),round(float(max(0,min(h,y2))),2)],'source':p['source']})
41
+ score=max(0,100-sum(self.PENALTIES.get(d['class'],0) for d in detections)); high=any(d['severity']=='HIGH' for d in detections); med=any(d['severity']=='MEDIUM' for d in detections)
42
+ decision='FAIL' if score<70 or high else 'REVIEW' if score<85 or med else 'PASS'
43
+ return {'model':'WeldVision-Ensemble','version':'1.0','decision':decision,'score':score,'detections':detections,'model_counts':{'best.pt':len(p1),'best_v0.pt':len(p2),'crack_specialist.pt':len(pc),'spatters_specialist.pt':len(ps),'ensemble':len(merged)},'recommendation':'Significant visible defect detected. Human inspection required.' if decision=='FAIL' else 'Moderate defect or quality concern detected. Human review required.' if decision=='REVIEW' else 'No significant visible defect detected.'}
handler.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io, os
2
+ import numpy as np
3
+ from PIL import Image
4
+ from ensemble import WeldVisionEnsemble
5
+ MODEL=WeldVisionEnsemble(os.path.join(os.path.dirname(__file__),'models'))
6
+ def _image(data):
7
+ if isinstance(data,dict): data=data.get('image',data.get('inputs'))
8
+ if isinstance(data,str):
9
+ import base64; data=base64.b64decode(data)
10
+ if isinstance(data,(bytes,bytearray)): return np.array(Image.open(io.BytesIO(data)).convert('RGB'))
11
+ if isinstance(data,np.ndarray): return data
12
+ raise ValueError('Expected image bytes, base64, or numpy array')
13
+ def handler(data): return MODEL.predict(_image(data))
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ ultralytics==8.4.126
2
+ opencv-python-headless
3
+ numpy
4
+ Pillow
5
+ torch
6
+ torchvision