EstimateBoundingBox.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import cv2
  2. import uuid
  3. import numpy as np
  4. import typing as T
  5. from flask import abort
  6. from flask import make_response
  7. from flask import request
  8. from flask.views import View
  9. from pycs import db
  10. from pycs.database.File import File
  11. from pycs.database.Result import Result
  12. from pycs.frontend.notifications.NotificationManager import NotificationManager
  13. from pycs.jobs.JobGroupBusyException import JobGroupBusyException
  14. from pycs.jobs.JobRunner import JobRunner
  15. class EstimateBoundingBox(View):
  16. """
  17. create a result for a file
  18. """
  19. # pylint: disable=arguments-differ
  20. methods = ['POST']
  21. def __init__(self, nm: NotificationManager, jobs: JobRunner,):
  22. # pylint: disable=invalid-name
  23. self.nm = nm
  24. self.jobs = jobs
  25. def dispatch_request(self, file_id: int):
  26. file = File.get_or_404(file_id)
  27. request_data = request.get_json(force=True)
  28. if 'x' not in request_data or 'y' not in request_data:
  29. abort(400, "coordinates for the estimation are missing")
  30. x,y = map(request_data.get, "xy")
  31. # get project
  32. project = file.project
  33. try:
  34. rnd = str(uuid.uuid4())[:10]
  35. self.jobs.run(project,
  36. "Estimation",
  37. f'{project.name} (create predictions)',
  38. f"{project.id}/estimation/{rnd}",
  39. estimate,
  40. file.id, x, y,
  41. result=self.nm.create_result
  42. )
  43. except JobGroupBusyException:
  44. abort(400, "Job is already running!")
  45. return make_response()
  46. def estimate(file_id: int, x: float, y: float) -> Result:
  47. file = File.query.get(file_id)
  48. im = cv2.imread(file.absolute_path, cv2.IMREAD_GRAYSCALE)
  49. h, w = im.shape
  50. pos = int(x * w), int(y * h)
  51. x0, y0, x1, y1 = detect(im, pos,
  52. window_size=1000,
  53. pixel_delta=50,
  54. enlarge=1e-2,
  55. )
  56. data = dict(
  57. x=x0 / w,
  58. y=y0 / h,
  59. w=(x1-x0) / w,
  60. h=(y1-y0) / h
  61. )
  62. return file.create_result('pipeline', 'bounding-box', label=None, data=data)
  63. def detect(im: np.ndarray,
  64. pos: T.Tuple[int, int],
  65. window_size: int = 1000,
  66. pixel_delta: int = 0,
  67. enlarge: float = -1) -> T.Tuple[int, int, int, int]:
  68. # im = blur(im, 3)
  69. x, y = pos
  70. pixel = im[y, x]
  71. min_pix, max_pix = pixel - pixel_delta, pixel + pixel_delta
  72. mask = np.logical_and(min_pix < im, im < max_pix).astype(np.float32)
  73. # mask = open_close(mask)
  74. # mask = blur(mask)
  75. pad = window_size // 2
  76. mask = np.pad(mask, pad, mode="constant")
  77. window = mask[y: y + window_size, x: x + window_size]
  78. sum_x, sum_y = window.sum(axis=0), window.sum(axis=1)
  79. enlarge = int(enlarge * max(im.shape))
  80. (x0, x1), (y0, y1) = get_borders(sum_x, enlarge), get_borders(sum_y, enlarge)
  81. x0 = max(x + x0 - pad, 0)
  82. y0 = max(y + y0 - pad, 0)
  83. x1 = min(x + x1 - pad, im.shape[1])
  84. y1 = min(y + y1 - pad, im.shape[0])
  85. return x0, y0, x1, y1
  86. def get_borders(arr, enlarge: int, eps=5e-1):
  87. mid = len(arr) // 2
  88. arr0, arr1 = arr[:mid], arr[mid:]
  89. thresh = arr[mid] * eps
  90. lowers = np.where(arr0 < thresh)[0]
  91. lower = 0 if len(lowers) == 0 else lowers[-1]
  92. uppers = np.where(arr1 < thresh)[0]
  93. upper = arr1.argmin() if len(uppers) == 0 else uppers[0]
  94. # since the second half starts after the first
  95. upper = len(arr0) + upper
  96. if enlarge > 0:
  97. lower = max(lower - enlarge, 0)
  98. upper = min(upper + enlarge, len(arr)-1)
  99. return int(lower), int(upper)
  100. """
  101. def blur(im, sigma=5):
  102. from skimage import filters
  103. return filters.gaussian(im, sigma=sigma, preserve_range=True)
  104. def open_close(im, kernel_size=3):
  105. kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8)
  106. im = cv2.morphologyEx(im, cv2.MORPH_OPEN, kernel)
  107. im = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel)
  108. return im
  109. """