From 325ace94422176d855e07d9cddecd49debe0164e Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 11 Apr 2022 18:37:45 +0200 Subject: Making ROI2DScene and ROI3DScene as DictObject --- src/argaze/RegionOfInterest/ROI2DScene.py | 48 +++++++----- src/argaze/RegionOfInterest/ROI3DScene.py | 89 +++++++++------------- .../utils/export_tobii_segment_aruco_rois.py | 32 +++++++- 3 files changed, 96 insertions(+), 73 deletions(-) diff --git a/src/argaze/RegionOfInterest/ROI2DScene.py b/src/argaze/RegionOfInterest/ROI2DScene.py index acf8e0a..066d8f1 100644 --- a/src/argaze/RegionOfInterest/ROI2DScene.py +++ b/src/argaze/RegionOfInterest/ROI2DScene.py @@ -9,25 +9,22 @@ class ROI2D(DataStructures.DictObject): """Define Region Of Interest 2D ``` { - 'name': str, - 'vertices': array of (x, y) tuples + 'vertices': array of (x, y) tuples, 'pointer': (x, y) tuple or None } ``` """ - def __init__(self, name, vertices, pointer = None): + def __init__(self, vertices, pointer = None): - super().__init__(type(self).__name__, **{'name': name, 'vertices': vertices, 'pointer': pointer}) + super().__init__(type(self).__name__, **{'vertices': vertices, 'pointer': pointer}) -class ROI2DScene(list): - """List of ROI2D.""" +class ROI2DScene(DataStructures.DictObject): + """Define ROI 2D scene as dictionnary of named ROI2Ds.""" - def __new__(cls): - return super(ROI2DScene, cls).__new__(cls) + def __init__(self, **rois_2d): - def __init__(self): - pass + super().__init__(type(self).__name__, **rois_2d) def __del__(self): pass @@ -35,28 +32,41 @@ class ROI2DScene(list): def inside(self, pointer): """Store pointer position if it is inside ROIs.""" - for roi in self: + for name in self.keys(): + + roi2D = self[name] - if mpath.Path(roi.vertices).contains_points([pointer])[0]: + if mpath.Path(roi2D.vertices).contains_points([pointer])[0]: - roi.pointer = pointer + roi2D.pointer = pointer else: - roi.pointer = None + roi2D.pointer = None def draw(self, frame): """Draw ROI polygons on frame.""" - for roi in self: + for name in self.keys(): - inside = roi.pointer != None + roi2D = self[name] + inside = roi2D.pointer != None color = (0, 255, 0) if inside else (0, 0, 255) if inside: - cv.putText(frame, roi.name, (roi.vertices[3][0], roi.vertices[3][1]), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv.LINE_AA) + cv.putText(frame, name, (roi2D.vertices[3][0], roi2D.vertices[3][1]), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv.LINE_AA) - cv.line(frame, roi.vertices[-1], roi.vertices[0], color, 1) - for A, B in zip(roi.vertices, roi.vertices[1:]): + cv.line(frame, roi2D.vertices[-1], roi2D.vertices[0], color, 1) + for A, B in zip(roi2D.vertices, roi2D.vertices[1:]): cv.line(frame, A, B, color, 1) + +class TimeStampedROI2DScenes(DataStructures.TimeStampedBuffer): + """Define timestamped buffer to store ROI2D scenes""" + + def __setitem__(self, key, value: ROI2DScene): + """Force value to be a ROI2DScene""" + if type(value) != ROI2DScene: + raise ValueError('value must be a ROI2DScene') + + super().__setitem__(key, value) diff --git a/src/argaze/RegionOfInterest/ROI3DScene.py b/src/argaze/RegionOfInterest/ROI3DScene.py index 683110c..9b753a3 100644 --- a/src/argaze/RegionOfInterest/ROI3DScene.py +++ b/src/argaze/RegionOfInterest/ROI3DScene.py @@ -13,34 +13,35 @@ class ROI3D(DataStructures.DictObject): """Define Region Of Interest 3D ``` { - 'name': str, 'vertices': array of (x, y, z) tuples } ``` """ - def __init__(self, name, vertices): + def __init__(self, vertices): - super().__init__(type(self).__name__, **{'name': name, 'vertices': vertices}) + super().__init__(type(self).__name__, **{'vertices': vertices}) -class ROI3DScene(list): - """List of ROI3D dictionary. - ``` - { - 'NAME': str, - 'VERTICES': array of (x, y, z) tuples - } - ``` +class ROI3DScene(DataStructures.DictObject): + """Define ROI 3D scene as dictionnary of named ROI3Ds. + ``` + { + 'rotation': (x, y, z) tuples, + 'translation': (x, y, z) tuples, + 'ROI name 1': ROI3D, + 'ROI name 2': ROI3D, + ... + } + ``` """ - def __new__(cls): - return super(ROI3DScene, cls).__new__(cls) + def __init__(self, **rois_3d): - def __init__(self): + # append rotation and translation matrix + rois_3d['rotation'] = [0, 0, 0] + rois_3d['translation'] = [0, 0, 0] - # define rotation and translation matrix - self.__rotation = [0, 0, 0] - self.__translation = [0, 0, 0] + super().__init__(type(self).__name__, **rois_3d) def __del__(self): pass @@ -70,10 +71,9 @@ class ROI3DScene(list): # start parsing try: - roi3D_list = [] - roi3D = {} + name = None vertices = [] - faces = [] + faces = {} # open the file and read through it line by line with open(obj_filepath, 'r') as file: @@ -92,7 +92,7 @@ class ROI3DScene(list): # extract roi3D name elif key == 'name': - roi3D['name'] = str(match.group(1)) + name = str(match.group(1)) # fill vertices array elif key == 'vertice': @@ -102,13 +102,7 @@ class ROI3DScene(list): # extract roi3D vertice id elif key == 'face': - roi3D['face'] = [int(i) for i in match.group(1).split()] - - # store roi3D dict into scene array - roi3D_list.append(roi3D) - - # clear roi3D dict - roi3D = {} + faces[name] = [int(i) for i in match.group(1).split()] # go to next line line = file.readline() @@ -116,41 +110,32 @@ class ROI3DScene(list): file.close() # retreive all roi3D vertices - for roi3D in roi3D_list: - roi3D['vertices'] = [ vertices[i-1] for i in roi3D['face'] ] - roi3D.pop('face', None) - self.append(ROI3D(**roi3D)) + for name, face in faces.items(): + self.append(name, ROI3D(**{'vertices': [ vertices[i-1] for i in face ]})) except IOError: raise IOError(f'File not found: {obj_filepath}') - def set_rotation(self, rvec: list): - """Set scene rotation vector.""" - self.__rotation = rvec - - def set_translation(self, tvec: list): - """Set scene translation vector.""" - self.__translation = tvec - def project(self, K, D): """Project 3D scene onto 2D scene according optical parameters. - **Returns:** AOI2DScene""" + **Returns:** ROI2DScene""" + + roi2D_scene = {} + + for name in self.keys(): - roi2D_scene = ROI2DScene.ROI2DScene() + if name == 'rotation' or name == 'translation': + continue - for roi3D in self: + roi3D = self[name] vertices_3D = numpy.array(roi3D.vertices).astype('float32') - vertices_2D, J = cv.projectPoints(vertices_3D, self.__rotation, self.__translation, K, D) - vertices_2D = vertices_2D.astype('int').reshape((len(vertices_2D), 2)) + vertices_2D, J = cv.projectPoints(vertices_3D, self.rotation, self.translation, K, D) + vertices_2D = vertices_2D.astype('int').reshape((len(vertices_2D), 2)).tolist() - roi2D = { - 'name': roi3D.name, - 'vertices': vertices_2D, - 'pointer': None - } + roi2D = ROI2DScene.ROI2D(vertices_2D) - roi2D_scene.append(ROI2DScene.ROI2D(**roi2D)) + roi2D_scene[name] = roi2D - return roi2D_scene + return ROI2DScene.ROI2DScene(**roi2D_scene) diff --git a/src/argaze/utils/export_tobii_segment_aruco_rois.py b/src/argaze/utils/export_tobii_segment_aruco_rois.py index f9c4cb2..8919249 100644 --- a/src/argaze/utils/export_tobii_segment_aruco_rois.py +++ b/src/argaze/utils/export_tobii_segment_aruco_rois.py @@ -3,6 +3,7 @@ import argparse import bisect +from argaze import DataStructures from argaze import GazeFeatures from argaze.TobiiGlassesPro2 import TobiiEntities, TobiiVideo from argaze.ArUcoMarkers import * @@ -24,10 +25,25 @@ def main(): parser.add_argument('-r', '--roi_scene', metavar='ROI_SCENE', type=str, default='roi3D_scene.obj', help='obj roi scene filepath') parser.add_argument('-d', '--dictionary', metavar='DICT', type=str, default='DICT_ARUCO_ORIGINAL', help='aruco marker dictionnary') parser.add_argument('-m', '--marker_size', metavar='MKR', type=float, default=6, help='aruco marker size (cm)') + parser.add_argument('-o', '--output', metavar='OUT', type=str, default=None, help='destination path (segment folder by default)') args = parser.parse_args() if args.segment_path != None: + # Manage destination path + if args.output != None: + + if not os.path.exists(os.path.dirname(args.output)): + + os.makedirs(os.path.dirname(args.output)) + print(f'{os.path.dirname(args.output)} folder created') + + rois_filepath = args.output + + else: + + rois_filepath = f'{args.segment_path}/rois.json' + # Load a tobii segment tobii_segment = TobiiEntities.TobiiSegment(args.segment_path) @@ -53,6 +69,10 @@ def main(): # Create ROIs 3D scene roi3D_scene = ROI3DScene.ROI3DScene() roi3D_scene.load(args.roi_scene) + print(f'ROIs names: {roi3D_scene.keys()[2::]}') + + # Create Timestamped buffer to store 2D ROIs + roi2D_timestamped_buffer = ROI2DScene.TimeStampedROI2DScenes() # Video and data replay loop try: @@ -88,8 +108,8 @@ def main(): marker_rotation = aruco_tracker.get_marker_rotation(i) marker_translation = aruco_tracker.get_marker_translation(i) - roi3D_scene.set_rotation(marker_rotation) - roi3D_scene.set_translation(marker_translation) + roi3D_scene.rotation = marker_rotation + roi3D_scene.translation = marker_translation # Edit Zero distorsion matrix D0 = numpy.asarray([0.0, 0.0, 0.0, 0.0, 0.0]) @@ -105,6 +125,9 @@ def main(): # Draw 2D rois roi2D_scene.draw(video_frame.matrix) + # Store 2D rois + roi2D_timestamped_buffer[video_ts] = roi2D_scene + # Close window using 'Esc' key if cv.waitKey(1) == 27: break @@ -118,6 +141,11 @@ def main(): # Stop frame display cv.destroyAllWindows() + # Export fixations analysis results + roi2D_timestamped_buffer.export_as_json(rois_filepath) + + print(f'Timestamped ROIs positions saved into {rois_filepath}') + if __name__ == '__main__': main() \ No newline at end of file -- cgit v1.1