2up1down commited on
Commit
f6b0eeb
1 Parent(s): de8d03b

Upload 4 files

Browse files
Files changed (3) hide show
  1. app.py +95 -38
  2. example2.jpg +0 -0
  3. example3.jpg +0 -0
app.py CHANGED
@@ -6,14 +6,13 @@ from ultralytics import YOLO
6
  from google.cloud import vision
7
  _api_key = os.environ["API_KEY"]
8
  _project_id = os.environ["PROJECT_ID"]
9
-
10
  client = vision.ImageAnnotatorClient(client_options={"quota_project_id": _project_id, "api_key": _api_key})
11
 
12
  import math
13
  from scipy.spatial import KDTree
14
  import io
15
  from time import time
16
- from PIL import Image, ImageDraw
17
  import numpy as np
18
  import cv2
19
 
@@ -23,9 +22,10 @@ modelPh = r'corners-best.pt'
23
 
24
  model1DIM = 640
25
  keypointModel = r'keypoints-best.pt'
 
26
 
27
 
28
- _examples = ["example1.jpg", "example2.jpg"]
29
 
30
 
31
  def unwarp_image(warped_image, src_points, dst_points, output_width, output_height):
@@ -57,14 +57,18 @@ def get_corners(results:list, img):
57
  # keypoints ie corners for homography
58
  KP = "topLeft topRight bottomRight bottomLeft".split()
59
  r = results[0]
60
- kpco = r.keypoints.xy.cpu().squeeze()
61
- assert len(kpco)>0, "not found"
62
- keypoints = {k:v.numpy() for v,k in zip(kpco,KP)}
63
- sz = model1DIM
64
- dstCorners = np.array([(0,0),(sz,0),(sz,sz),(0,sz)])
65
- planar = unwarp_image(img, np.array(list(keypoints.values())),dstCorners, sz,sz)
66
- # planar.save("temp-ph.jpg")
67
- return planar, keypoints
 
 
 
 
68
 
69
 
70
  model = None
@@ -88,6 +92,8 @@ def preprocessImg(planar):
88
  elif w!=h:
89
  img = img.resize((_,_))
90
 
 
 
91
  return img
92
 
93
 
@@ -174,7 +180,8 @@ def result_as_validvalue(contents:list[dict])->tuple[list[dict], list[str]]:
174
  continue
175
  b = f["boxCorners"]
176
  m = median_point_of_bounding_box(*np.array(b).flatten())
177
- valid.append({"text":f["text"], "value": value, "mid": m})
 
178
 
179
  valid.sort(key=lambda e: e["value"])
180
  return valid, other
@@ -200,9 +207,9 @@ def determine_ocr_neighbors(center, valid:list[dict])->tuple[ list, float ]:
200
  a = np.array(values[-1]["mid"]) - center
201
  b = np.array(v["mid"]) - center
202
  ang,_ = cosangle(a,b)
203
- if _ <0:
204
- Warning(f"skipping {u['value']} rot:{_}")
205
- continue
206
  angS += ang
207
  u["dang"] = ang
208
  u["dvda"] = u["dv"] / ang
@@ -250,6 +257,17 @@ def angles_from_tip(keypoints, values, nearestIx):
250
  return values
251
 
252
 
 
 
 
 
 
 
 
 
 
 
 
253
  def get_needle_value(img, keypoints):
254
 
255
  tic2 = time()
@@ -260,18 +278,32 @@ def get_needle_value(img, keypoints):
260
  assert len(contents)
261
  valid,other = result_as_validvalue(contents)
262
  assert len(valid)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  center = np.array(keypoints["center"])
264
  values, rate = determine_ocr_neighbors(center, valid)
265
  assert len(values)>=2, "failed to find at least 2 OCR values"
266
 
267
  # import pandas as pd
268
  # print(pd.DataFrame.from_dict(values))
269
-
270
- tree = KDTree([v["mid"] for v in values])
271
- # find bounding ocr values of tip
272
- dist, nearestIx = tree.query(keypoints["tip"],k=2)
273
- nearestIx.sort()
274
- dist, nearestIx
275
 
276
  values = angles_from_tip(keypoints, values, nearestIx)
277
  # compare against start and end
@@ -336,36 +368,61 @@ def predict(img, detect_gauge_first):
336
  if detect_gauge_first:
337
  model0 = get_load_PhModel()
338
  results = model0.predict(img)
339
- phimg,_ = get_corners(results, img)
 
 
340
  else:
341
- phimg = img.copy()
342
 
343
- model = get_load_KpModel()
344
- phimg = preprocessImg(phimg)
345
- results = model.predict(phimg)
346
- keypoints = get_keypoints(results)
 
 
347
 
348
- angle2tip, totalAngle = calculate_sweep_angles(keypoints)
349
 
350
- payload = get_needle_value(phimg, keypoints)
351
- payload["angleToTip"] = round(float(angle2tip),2)
352
- payload["totalAngle"] = round(float(totalAngle),2)
353
- for k,v in payload.items():
354
- print(k, type(v),v)
 
 
355
 
356
- return payload
357
 
358
 
359
  def test(img, detect_gauge_first):
360
  return {"msg":str(img.size), "other": detect_gauge_first}
361
 
362
-
363
- gr.Interface(fn=predict,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  inputs=[
365
- gr.Image(type="pil", sources=["upload","clipboard"],streaming=False, min_width=640),
366
  gr.Checkbox(True, label="detect gauge first", info="if input image is zoomed in on only one gauge, uncheck box")
367
  ],
368
  outputs="json",
369
- examples=[_examples],
370
  cache_examples=True)\
371
  .launch()
 
6
  from google.cloud import vision
7
  _api_key = os.environ["API_KEY"]
8
  _project_id = os.environ["PROJECT_ID"]
 
9
  client = vision.ImageAnnotatorClient(client_options={"quota_project_id": _project_id, "api_key": _api_key})
10
 
11
  import math
12
  from scipy.spatial import KDTree
13
  import io
14
  from time import time
15
+ from PIL import Image, ImageDraw, ImageFilter
16
  import numpy as np
17
  import cv2
18
 
 
22
 
23
  model1DIM = 640
24
  keypointModel = r'keypoints-best.pt'
25
+ minSz = 1280
26
 
27
 
28
+ _examples = [["example1.jpg",True], ["example2.jpg",False], ["example3.jpg",True]]
29
 
30
 
31
  def unwarp_image(warped_image, src_points, dst_points, output_width, output_height):
 
57
  # keypoints ie corners for homography
58
  KP = "topLeft topRight bottomRight bottomLeft".split()
59
  r = results[0]
60
+ planars = []
61
+ kps = []
62
+ for kpco in r.keypoints.xy.cpu():#.squeeze()
63
+ assert len(kpco)>0, "not found"
64
+ keypoints = {k:v.numpy() for v,k in zip(kpco,KP)}
65
+ sz = model1DIM
66
+ dstCorners = np.array([(0,0),(sz,0),(sz,sz),(0,sz)])
67
+ planar = unwarp_image(img, np.array(list(keypoints.values())),dstCorners, sz,sz)
68
+ # planar.save("temp-ph.jpg")
69
+ planars.append(planar)
70
+ kps.append(keypoints)
71
+ return planars, kps
72
 
73
 
74
  model = None
 
92
  elif w!=h:
93
  img = img.resize((_,_))
94
 
95
+ if _ < minSz:
96
+ img = img.resize((minSz,minSz))
97
  return img
98
 
99
 
 
180
  continue
181
  b = f["boxCorners"]
182
  m = median_point_of_bounding_box(*np.array(b).flatten())
183
+ a = cv2.contourArea(np.array(b)) / len(f["text"])
184
+ valid.append({"text":f["text"], "value": value, "mid": m, "apchar":a, "box":b})
185
 
186
  valid.sort(key=lambda e: e["value"])
187
  return valid, other
 
207
  a = np.array(values[-1]["mid"]) - center
208
  b = np.array(v["mid"]) - center
209
  ang,_ = cosangle(a,b)
210
+ # if _ <0:
211
+ # Warning(f"skipping {u['value']} rot:{_}")
212
+ # continue
213
  angS += ang
214
  u["dang"] = ang
215
  u["dvda"] = u["dv"] / ang
 
257
  return values
258
 
259
 
260
+ def sort_clockwise_with_start(coordinates, x_center, y_center, starting_index):
261
+ angles = [math.atan2(y - y_center, x - x_center) for x, y in coordinates]
262
+ sorted_indices = sorted(range(len(angles)), key=lambda i: (angles[i] - angles[starting_index] + 2 * math.pi) % (2 * math.pi))
263
+ return sorted_indices, angles
264
+
265
+ def remove_nonrange_value(valid):
266
+ meanArea = np.mean([e["apchar"] for e in valid])
267
+ cutoff = 0.5
268
+ valid = list(filter(lambda e: abs(e["apchar"]-meanArea)/meanArea < cutoff, valid))
269
+ return valid
270
+
271
  def get_needle_value(img, keypoints):
272
 
273
  tic2 = time()
 
278
  assert len(contents)
279
  valid,other = result_as_validvalue(contents)
280
  assert len(valid)
281
+
282
+ valid.append({"text":"tip", "mid":keypoints["tip"]})
283
+ ix,an = sort_clockwise_with_start([e["mid"] for e in valid],*keypoints["center"], 0)
284
+ valid = [valid[i] for i in ix]
285
+ assert valid[-1]["text"]!="tip" and valid[0]["text"]!="tip", "failed to properly detect tip"
286
+ nearestIx=[]
287
+ for i,v in enumerate(valid):
288
+ if "tip"==v["text"]:
289
+ nearestIx = [i-1,i]
290
+ valid.pop(i)
291
+ break
292
+ nearestIx = np.array(nearestIx)
293
+ valid = remove_nonrange_value(valid)
294
+
295
  center = np.array(keypoints["center"])
296
  values, rate = determine_ocr_neighbors(center, valid)
297
  assert len(values)>=2, "failed to find at least 2 OCR values"
298
 
299
  # import pandas as pd
300
  # print(pd.DataFrame.from_dict(values))
301
+
302
+ # tree = KDTree([v["mid"] for v in values])
303
+ # # find bounding ocr values of tip
304
+ # dist, nearestIx = tree.query(keypoints["tip"],k=2)
305
+ # nearestIx.sort()
306
+ # dist, nearestIx
307
 
308
  values = angles_from_tip(keypoints, values, nearestIx)
309
  # compare against start and end
 
368
  if detect_gauge_first:
369
  model0 = get_load_PhModel()
370
  results = model0.predict(img)
371
+ phimgs,_ = get_corners(results, img)
372
+ if len(phimgs)==0:
373
+ raise gr.Error("no gauge found")
374
  else:
375
+ phimgs = [img.copy()]
376
 
377
+ payloads = []
378
+ for phimg in phimgs:
379
+ model = get_load_KpModel()
380
+ phimg = preprocessImg(phimg)
381
+ results = model.predict(phimg)
382
+ keypoints = get_keypoints(results)
383
 
384
+ angle2tip, totalAngle = calculate_sweep_angles(keypoints)
385
 
386
+ phimg = phimg.filter(ImageFilter.UnsharpMask(radius=3))
387
+ payload = get_needle_value(phimg, keypoints)
388
+ payload["angleToTip"] = round(float(angle2tip),2)
389
+ payload["totalAngle"] = round(float(totalAngle),2)
390
+ for k,v in payload.items():
391
+ print(k, type(v),v)
392
+ payloads.append(payload)
393
 
394
+ return payloads
395
 
396
 
397
  def test(img, detect_gauge_first):
398
  return {"msg":str(img.size), "other": detect_gauge_first}
399
 
400
+ description = r"""
401
+ <b>Official 🤗 Gradio demo</b> for <a href='https://synanthropic.com/reading-analog-gauge' target='_blank'><b>Reading Analog Gauges: Automate Gauge Readings with AI in Days, Not Months
402
+ </b></a>.<br>
403
+ <br>
404
+ This model reads analog dial gauge by detecting, applying perspective correction, and gauge reading.
405
+ <br>
406
+ The model was build <i><strong>only</strong></i> with synthetic data.<br>
407
+ <br>
408
+ You can read more about it [here](https://synanthropic.com/reading-analog-gauge).
409
+ <br>
410
+ <br>
411
+ ❗️Usage steps:<br>
412
+ 1️⃣ Upload an image with analog dial gauge with readable values. The gauge face in the uploaded image should <b>occupy the majority of the image</b>.<br>
413
+ 2️⃣ If the image has only one gauge and is a direct flat view, uncheck <strong>detect gauge first</strong>.</br>
414
+ 3️⃣ Click the <b>Submit</b> button to start inference.<br>
415
+ <br>
416
+ """
417
+
418
+ gr.Interface(title="title",
419
+ description=description,
420
+ fn=predict,
421
  inputs=[
422
+ gr.Image(type="pil", sources=["upload"],streaming=False, min_width=640),
423
  gr.Checkbox(True, label="detect gauge first", info="if input image is zoomed in on only one gauge, uncheck box")
424
  ],
425
  outputs="json",
426
+ examples=_examples,
427
  cache_examples=True)\
428
  .launch()
example2.jpg CHANGED
example3.jpg ADDED