regionofinterest.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. from math import floor, ceil
  2. class RegionOfInterest:
  3. """Abstract base class"""
  4. pass
  5. class CircularRegionOfInterest(RegionOfInterest):
  6. """Representation of a circular ROI
  7. *Usage:*
  8. >>> roi = CircularRegionOfInterest(20.0, 20.0, radius=5.0)
  9. >>> signal = image_sequence.signal_from_region(roi)
  10. *Required attributes/properties*:
  11. :x, y: (integers or floats)
  12. Pixel coordinates of the centre of the ROI
  13. :radius: (integer or float)
  14. Radius of the ROI in pixels
  15. """
  16. def __init__(self, x, y, radius):
  17. self.y = y
  18. self.x = x
  19. self.radius = radius
  20. @property
  21. def centre(self):
  22. return (self.x, self.y)
  23. @property
  24. def center(self):
  25. return self.centre
  26. def is_inside(self, x, y):
  27. if ((x - self.x) * (x - self.x) +
  28. (y - self.y) * (y - self.y) <= self.radius * self.radius):
  29. return True
  30. else:
  31. return False
  32. def pixels_in_region(self):
  33. """Returns a list of pixels whose *centres* are within the circle"""
  34. pixel_in_list = []
  35. for y in range(int(floor(self.y - self.radius)), int(ceil(self.y + self.radius))):
  36. for x in range(int(floor(self.x - self.radius)), int(ceil(self.x + self.radius))):
  37. if self.is_inside(x, y):
  38. pixel_in_list.append([x, y])
  39. return pixel_in_list
  40. class RectangularRegionOfInterest(RegionOfInterest):
  41. """Representation of a rectangular ROI
  42. *Usage:*
  43. >>> roi = RectangularRegionOfInterest(20.0, 20.0, width=5.0, height=5.0)
  44. >>> signal = image_sequence.signal_from_region(roi)
  45. *Required attributes/properties*:
  46. :x, y: (integers or floats)
  47. Pixel coordinates of the centre of the ROI
  48. :width: (integer or float)
  49. Width (x-direction) of the ROI in pixels
  50. :height: (integer or float)
  51. Height (y-direction) of the ROI in pixels
  52. """
  53. def __init__(self, x, y, width, height):
  54. self.x = x
  55. self.y = y
  56. self.width = width
  57. self.height = height
  58. def is_inside(self, x, y):
  59. if (self.x - self.width/2.0 <= x < self.x + self.width/2.0
  60. and self.y - self.height/2.0 <= y < self.y + self.height/2.0):
  61. return True
  62. else:
  63. return False
  64. def pixels_in_region(self):
  65. """Returns a list of pixels whose *centres* are within the rectangle"""
  66. pixel_list = []
  67. h = self.height
  68. w = self.width
  69. for y in range(int(floor(self.y - h / 2.0)), int(ceil(self.y + h / 2.0))):
  70. for x in range(int(floor(self.x - w / 2.0)), int(ceil(self.x + w / 2.0))):
  71. if self.is_inside(x, y):
  72. pixel_list.append([x, y])
  73. return pixel_list
  74. class PolygonRegionOfInterest(RegionOfInterest):
  75. """Representation of a polygonal ROI
  76. *Usage:*
  77. >>> roi = PolygonRegionOfInterest(
  78. ... (20.0, 20.0),
  79. ... (30.0, 20.0),
  80. ... (25.0, 25.0)
  81. ... )
  82. >>> signal = image_sequence.signal_from_region(roi)
  83. *Required attributes/properties*:
  84. :vertices:
  85. tuples containing the (x, y) coordinates, as integers or floats,
  86. of the vertices of the polygon
  87. """
  88. def __init__(self, *vertices):
  89. self.vertices = vertices
  90. def polygon_ray_casting(self, bounding_points, bounding_box_positions):
  91. # from https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
  92. # user Noresourses
  93. # Arrays containing the x- and y-coordinates of the polygon's vertices.
  94. vertx = [point[0] for point in bounding_points]
  95. verty = [point[1] for point in bounding_points]
  96. # Number of vertices in the polygon
  97. nvert = len(bounding_points)
  98. # Points that are inside
  99. points_inside = []
  100. # For every candidate position within the bounding box
  101. for idx, pos in enumerate(bounding_box_positions):
  102. testx, testy = (pos[0], pos[1])
  103. c = 0
  104. for i in range(0, nvert):
  105. j = i - 1 if i != 0 else nvert - 1
  106. if (((verty[i]*1.0 > testy*1.0) != (verty[j]*1.0 > testy*1.0)) and
  107. (testx*1.0 < (vertx[j]*1.0 - vertx[i]*1.0) * (testy*1.0 - verty[i]*1.0) /
  108. (verty[j]*1.0 - verty[i]*1.0) + vertx[i]*1.0)):
  109. c += 1
  110. # If odd, that means that we are inside the polygon
  111. if c % 2 == 1:
  112. points_inside.append(pos)
  113. return points_inside
  114. def pixels_in_region(self):
  115. min_x, max_x, min_y, max_y = (self.vertices[0][0], self.vertices[0][0],
  116. self.vertices[0][1], self.vertices[0][1])
  117. for i in self.vertices:
  118. if i[0] < min_x:
  119. min_x = i[0]
  120. if i[0] > max_x:
  121. max_x = i[0]
  122. if i[1] < min_y:
  123. min_y = i[1]
  124. if i[1] > max_y:
  125. max_y = i[1]
  126. list_coord = []
  127. for y in range(int(floor(min_y)), int(ceil(max_y))):
  128. for x in range(int(floor(min_x)), int(ceil(max_x))):
  129. list_coord.append((x, y))
  130. pixel_list = self.polygon_ray_casting(self.vertices, list_coord)
  131. return pixel_list