|
1 | | -from utils.analysis import EllipseROI, RectangleROI |
| 1 | +from utils.analysis import angle_between_vectors, calculate_distance, EllipseROI, RectangleROI |
| 2 | +from utils.configloader import RESOLUTION |
| 3 | + |
| 4 | + |
| 5 | +class HeaddirectionTrigger: |
| 6 | + """Trigger to check if animal is turning head in a specific angle and egocentric direction""" |
| 7 | + def __init__(self, angle: int, head_dir: str = 'both', debug: bool = False): |
| 8 | + """ |
| 9 | + Initialising trigger with following parameters: |
| 10 | + :param int angle: angle to meet for condition |
| 11 | + :param str head_dir: head direction from egocentric position of the animal (left, right or both) |
| 12 | +
|
| 13 | + """ |
| 14 | + self._head_dir = head_dir |
| 15 | + self._angle = angle |
| 16 | + self._debug = debug |
| 17 | + |
| 18 | + def check_skeleton(self, skeleton: dict): |
| 19 | + """ |
| 20 | + Checking skeleton for trigger |
| 21 | + :param skeleton: a skeleton dictionary, returned by calculate_skeletons() from poser file |
| 22 | + :return: response, a tuple of result (bool) and response body |
| 23 | + Response body is used for plotting and outputting results to trials dataframes |
| 24 | + ['tailroot', 'neck', 'nose'] you need to pass this to angle between vectors to get headdirection |
| 25 | + """ |
| 26 | + tailroot_x, tailroot_y = skeleton['tailroot'] |
| 27 | + neck_x, neck_y = skeleton['neck'] |
| 28 | + nose_x, nose_y = skeleton['nose'] |
| 29 | + ret_head_dir, angle = angle_between_vectors(tailroot_x, tailroot_y, neck_x, neck_y , nose_x, nose_y) |
| 30 | + true_angle = 180 - abs(angle) |
| 31 | + |
| 32 | + if true_angle <= self._angle: |
| 33 | + if self._head_dir == ret_head_dir: |
| 34 | + result = True |
| 35 | + elif self._head_dir == 'both': |
| 36 | + result = True |
| 37 | + else: |
| 38 | + result = False |
| 39 | + |
| 40 | + |
| 41 | + color = (0, 255, 0) if result else (0, 0, 255) |
| 42 | + if self._debug: |
| 43 | + center = (nose_x, nose_y) |
| 44 | + |
| 45 | + response_body = {'plot': {'text': dict(text=str(true_angle), |
| 46 | + org=skeleton[self._end_point], |
| 47 | + color=(255, 255, 255)), |
| 48 | + 'circle': dict(center= center, |
| 49 | + radius= 5, |
| 50 | + color=color) |
| 51 | + }} |
| 52 | + else: |
| 53 | + response_body = {'angle': true_angle} |
| 54 | + |
| 55 | + response = (result, response_body) |
| 56 | + return response |
| 57 | + |
| 58 | + |
| 59 | +class DirectionTrigger: |
| 60 | + """ |
| 61 | + Trigger to check if animal is looking in direction of some point |
| 62 | + """ |
| 63 | + def __init__(self, point: tuple, angle: int, bodyparts: iter, debug: bool = False): |
| 64 | + """ |
| 65 | + Initialising trigger with following parameters: |
| 66 | + :param tuple point: a point of interest in (x,y) format. |
| 67 | + :param int angle: angle, at which animal is considered looking at the screen |
| 68 | + :param iter bodyparts: a pair of joints of animal (tuple or list) that represent 'looking vector' like (start, end) |
| 69 | + For example, |
| 70 | + ('neck', 'nose') pair would mean that direction in which animal is looking defined by vector from neck to nose |
| 71 | + """ |
| 72 | + self._angle = angle |
| 73 | + self._debug = debug |
| 74 | + self._point = point |
| 75 | + self._start_point, self._end_point = bodyparts |
| 76 | + |
| 77 | + def check_skeleton(self, skeleton: dict): |
| 78 | + """ |
| 79 | + Checking skeleton for trigger |
| 80 | + :param skeleton: a skeleton dictionary, returned by calculate_skeletons() from poser file |
| 81 | + :return: response, a tuple of result (bool) and response body |
| 82 | + Response body is used for plotting and outputting results to trials dataframes |
| 83 | + """ |
| 84 | + start_x, start_y = skeleton[self._start_point] |
| 85 | + end_x, end_y = skeleton[self._end_point] |
| 86 | + direction_x, direction_y = self._point |
| 87 | + head_dir, angle = angle_between_vectors(direction_x, direction_y, start_x, start_y, end_x, end_y) |
| 88 | + true_angle = 180 - abs(angle) |
| 89 | + |
| 90 | + result = true_angle <= self._angle |
| 91 | + |
| 92 | + color = (0, 255, 0) if result else (0, 0, 255) |
| 93 | + if self._debug: |
| 94 | + response_body = {'plot': {'line': dict(pt1=skeleton[self._end_point], |
| 95 | + pt2=self._point, |
| 96 | + color=color), |
| 97 | + 'text': dict(text=str(true_angle), |
| 98 | + org=skeleton[self._end_point], |
| 99 | + color=(255, 255, 255))}} |
| 100 | + else: |
| 101 | + response_body = {'angle': true_angle} |
| 102 | + |
| 103 | + response = (result, response_body) |
| 104 | + return response |
| 105 | + |
| 106 | + |
| 107 | +class ScreenTrigger(DirectionTrigger): |
| 108 | + """ |
| 109 | + Trigger to check if animal is looking at the screen |
| 110 | + """ |
| 111 | + def __init__(self, direction: str, angle: int, bodyparts: iter, debug: bool = False): |
| 112 | + """ |
| 113 | + Initialising trigger with following parameters: |
| 114 | + :param direction: a direction where the screen is located in the stream or video. |
| 115 | + All possible directions: 'North' (or top of the frame), 'East' (right), 'South' (bottom), 'West' (left) |
| 116 | + Note that directions are not tied to real-world cardinal directions |
| 117 | + :param angle: angle, at which animal is considered looking at the screen |
| 118 | + :param bodyparts: a pair of joints of animal (tuple or list) that represent 'looking vector' like (start, end) |
| 119 | + For example, |
| 120 | + ('neck', 'nose') pair would mean that direction in which animal is looking defined by vector from neck to nose |
| 121 | + """ |
| 122 | + self._direction = direction |
| 123 | + max_x, max_y = RESOLUTION |
| 124 | + direction_dict = {'North': (int(max_x / 2), 0), 'South': (int(max_x / 2), max_y), |
| 125 | + 'West': (0, int(max_y / 2)), 'East': (max_x, int(max_y / 2))} |
| 126 | + super().__init__(direction_dict[self._direction], angle, bodyparts, debug) |
2 | 127 |
|
3 | 128 |
|
4 | 129 | class RegionTrigger: |
@@ -87,3 +212,51 @@ def check_skeleton(self, skeleton: dict): |
87 | 212 | result, response_body = super().check_skeleton(skeleton) |
88 | 213 | response = (not result, response_body) # flips result bool |
89 | 214 | return response |
| 215 | + |
| 216 | + |
| 217 | +class FreezeTrigger: |
| 218 | + """ |
| 219 | + Trigger to check if animal is in freezing state |
| 220 | + """ |
| 221 | + def __init__(self, threshold: int, debug: bool = False): |
| 222 | + """ |
| 223 | + Initializing trigger with given threshold |
| 224 | + :param threshold: int in pixel how much of a movement does not count |
| 225 | + For example threshold of 5 would mean that all movements less then 5 pixels would be ignored |
| 226 | + """ |
| 227 | + self._threshold = threshold |
| 228 | + self._skeleton = None |
| 229 | + self._debug = debug # not used in this trigger |
| 230 | + |
| 231 | + def check_skeleton(self, skeleton: dict): |
| 232 | + """ |
| 233 | + Checking skeleton for trigger |
| 234 | + :param skeleton: a skeleton dictionary, returned by calculate_skeletons() from poser file |
| 235 | + :return: response, a tuple of result (bool) and response body |
| 236 | + Response body is used for plotting and outputting results to trials dataframes |
| 237 | + """ |
| 238 | + # choosing a point to draw near the skeleton |
| 239 | + org_point = skeleton[list(skeleton.keys())[0]] |
| 240 | + joint_moved = [] |
| 241 | + if self._skeleton is None: |
| 242 | + result = False |
| 243 | + text = 'Not freezing' |
| 244 | + self._skeleton = skeleton |
| 245 | + else: |
| 246 | + for joint in skeleton: |
| 247 | + joint_travel = calculate_distance(skeleton[joint], self._skeleton[joint]) |
| 248 | + joint_moved.append(abs(joint_travel) <= self._threshold) |
| 249 | + if all(joint_moved): |
| 250 | + result = True |
| 251 | + text = 'Freezing' |
| 252 | + else: |
| 253 | + result = False |
| 254 | + text = 'Not freezing' |
| 255 | + self._skeleton = skeleton |
| 256 | + |
| 257 | + color = (0, 255, 0) if result else (0, 0, 255) |
| 258 | + response_body = {'plot': {'text': dict(text=text, |
| 259 | + org=org_point, |
| 260 | + color=color)}} |
| 261 | + response = (result, response_body) |
| 262 | + return response |
0 commit comments