def stitch_tracklets( config_path, pickle_file, n_tracks=None, min_length=10, split_tracklets=True, prestitch_residuals=True, max_gap=None, weight_func=None, output_name="", ): """ Stitch sparse tracklets into full tracks via a graph-based, minimum-cost flow optimization problem. Parameters ---------- config_path : str Path to the main project config.yaml file. pickle_file : str Path to the pickle file containing the tracklets. It is obtained after deeplabcut.convert_detections2tracklets() and typically ends with _bx or _sk.pickle. n_tracks : int, optional Number of tracks to reconstruct. By default, taken as the number of individuals defined in the config.yaml. Another number can be passed if the number of animals in the video is different from the number of animals the model was trained on. min_length : int, optional Tracklets less than `min_length` frames of length are considered to be residuals; i.e., they do not participate in building the graph and finding the solution to the optimization problem, but are rather added last after "almost-complete" tracks are formed. The higher the value, the lesser the computational cost, but the higher the chance of discarding relatively long and reliable tracklets that are essential to solving the stitching task. Default is 10, and must be 3 at least. split_tracklets : bool, optional By default, tracklets whose time indices are not consecutive integers are split in shorter tracklets whose time continuity is guaranteed. This is for example very powerful to get rid of tracking errors (e.g., identity switches) which are often signaled by a missing time frame at the moment they occur. Note though that for long occlusions where tracker re-identification capability can be trusted, setting `split_tracklets` to False is preferable. prestitch_residuals : bool, optional Residuals will by default be grouped together according to their temporal proximity prior to being added back to the tracks. This is done to improve robustness and simultaneously reduce complexity. max_gap : int, optional Maximal temporal gap to allow between a pair of tracklets. This is automatically determined by the TrackletStitcher by default. weight_func : callable, optional Function accepting two tracklets as arguments and returning a scalar that must be inversely proportional to the likelihood that the tracklets belong to the same track; i.e., the higher the confidence that the tracklets should be stitched together, the lower the returned value. output_name : str, optional Name of the output h5 file. By default, tracks are automatically stored into the same directory as the pickle file and with its name. Returns ------- A TrackletStitcher object """ cfg = read_config(config_path) animal_names = cfg["individuals"] if n_tracks is None: n_tracks = len(animal_names) stitcher = TrackletStitcher.from_pickle(pickle_file, n_tracks, min_length, split_tracklets, prestitch_residuals) with_id = any(tracklet.identity != -1 for tracklet in stitcher) if with_id and weight_func is None: # Add in identity weighing before building the graph def weight_func(t1, t2): w = 0.01 if t1.identity == t2.identity else 1 return w * stitcher.calculate_edge_weight(t1, t2) stitcher.build_graph(max_gap=max_gap, weight_func=weight_func) stitcher.stitch() stitcher.write_tracks(output_name, animal_names) return stitcher
def __init__(self, parent, gui_size, cfg): """Constructor""" wx.Panel.__init__(self, parent=parent) # variable initilization self.config = cfg self.cfg = utils.read_config(cfg) self.filelist = [] # design the panel self.sizer = wx.GridBagSizer(5, 5) text = wx.StaticText( self, label="DeepLabCut - Step 8. Extract outlier frames") self.sizer.Add(text, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=15) # Add logo of DLC icon = wx.StaticBitmap(self, bitmap=wx.Bitmap(logo)) self.sizer.Add(icon, pos=(0, 4), flag=wx.TOP | wx.RIGHT | wx.ALIGN_RIGHT, border=5) line1 = wx.StaticLine(self) self.sizer.Add(line1, pos=(1, 0), span=(1, 5), flag=wx.EXPAND | wx.BOTTOM, border=10) self.cfg_text = wx.StaticText(self, label="Select the config file") self.sizer.Add(self.cfg_text, pos=(2, 0), flag=wx.TOP | wx.LEFT, border=5) if sys.platform == "darwin": self.sel_config = wx.FilePickerCtrl( self, path="", style=wx.FLP_USE_TEXTCTRL, message="Choose the config.yaml file", wildcard="*.yaml", ) else: self.sel_config = wx.FilePickerCtrl( self, path="", style=wx.FLP_USE_TEXTCTRL, message="Choose the config.yaml file", wildcard="config.yaml", ) # self.sel_config = wx.FilePickerCtrl(self, path="",style=wx.FLP_USE_TEXTCTRL,message="Choose the config.yaml file", wildcard="config.yaml") self.sizer.Add(self.sel_config, pos=(2, 1), span=(1, 3), flag=wx.TOP | wx.EXPAND, border=5) self.sel_config.SetPath(self.config) self.sel_config.Bind(wx.EVT_FILEPICKER_CHANGED, self.select_config) self.vids = wx.StaticText(self, label="Choose the videos") self.sizer.Add(self.vids, pos=(3, 0), flag=wx.TOP | wx.LEFT, border=10) self.sel_vids = wx.Button(self, label="Select videos to analyze") self.sizer.Add(self.sel_vids, pos=(3, 1), flag=wx.TOP | wx.EXPAND, border=5) self.sel_vids.Bind(wx.EVT_BUTTON, self.select_videos) sb = wx.StaticBox(self, label="Optional Attributes") boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) videotype_text = wx.StaticBox(self, label="Specify the videotype") videotype_text_boxsizer = wx.StaticBoxSizer(videotype_text, wx.VERTICAL) videotypes = [".avi", ".mp4", ".mov"] self.videotype = wx.ComboBox(self, choices=videotypes, style=wx.CB_READONLY) self.videotype.SetValue(".avi") videotype_text_boxsizer.Add(self.videotype, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) shuffles_text = wx.StaticBox(self, label="Specify the shuffle") shuffles_text_boxsizer = wx.StaticBoxSizer(shuffles_text, wx.VERTICAL) self.shuffles = wx.SpinCtrl(self, value="1", min=0, max=100) shuffles_text_boxsizer.Add(self.shuffles, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) trainingindex = wx.StaticBox(self, label="Specify the trainingset index") trainingindex_boxsizer = wx.StaticBoxSizer(trainingindex, wx.VERTICAL) self.trainingindex = wx.SpinCtrl(self, value="0", min=0, max=100) trainingindex_boxsizer.Add(self.trainingindex, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) outlier_algo_text = wx.StaticBox(self, label="Specify the algorithm") outlier_algo_text_boxsizer = wx.StaticBoxSizer(outlier_algo_text, wx.VERTICAL) algotypes = ["jump", "fitting", "uncertain", "manual"] self.algotype = wx.ComboBox(self, choices=algotypes, style=wx.CB_READONLY) self.algotype.SetValue("jump") outlier_algo_text_boxsizer.Add(self.algotype, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) hbox1.Add(videotype_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox1.Add(shuffles_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox1.Add(trainingindex_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox2.Add(outlier_algo_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) if self.cfg.get("multianimalproject", False): tracker_text = wx.StaticBox(self, label="Specify the Tracker Method!") tracker_text_boxsizer = wx.StaticBoxSizer(tracker_text, wx.VERTICAL) trackertypes = ["skeleton", "box"] self.trackertypes = wx.ComboBox(self, choices=trackertypes, style=wx.CB_READONLY) self.trackertypes.SetValue("box") tracker_text_boxsizer.Add(self.trackertypes, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) hbox2.Add(tracker_text_boxsizer, 5, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) boxsizer.Add(hbox1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) boxsizer.Add(hbox2, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) self.sizer.Add( boxsizer, pos=(4, 0), span=(1, 5), flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10, ) self.help_button = wx.Button(self, label="Help") self.sizer.Add(self.help_button, pos=(6, 0), flag=wx.LEFT, border=10) self.help_button.Bind(wx.EVT_BUTTON, self.help_function) self.ok = wx.Button(self, label="Ok") self.sizer.Add(self.ok, pos=(6, 4)) self.ok.Bind(wx.EVT_BUTTON, self.extract_outlier_frames) self.reset = wx.Button(self, label="Reset") self.sizer.Add(self.reset, pos=(6, 1), span=(1, 1), flag=wx.BOTTOM | wx.RIGHT, border=10) self.reset.Bind(wx.EVT_BUTTON, self.reset_extract_outlier_frames) self.sizer.AddGrowableCol(2) self.SetSizer(self.sizer) self.sizer.Fit(self)
def __init__(self, parent, gui_size, cfg): """Constructor""" wx.Panel.__init__(self, parent=parent) # variable initialization self.config = cfg self.cfg = utils.read_config(cfg) self.filelist = [] # design the panel self.sizer = wx.GridBagSizer(5, 5) text = wx.StaticText( self, label= "DeepLabCut - OPTIONAL: Unsupervised ID Tracking with Transformer") self.sizer.Add(text, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=15) # Add logo of DLC icon = wx.StaticBitmap(self, bitmap=wx.Bitmap(LOGO_PATH)) self.sizer.Add(icon, pos=(0, 4), flag=wx.TOP | wx.RIGHT | wx.ALIGN_RIGHT, border=5) line1 = wx.StaticLine(self) self.sizer.Add(line1, pos=(1, 0), span=(1, 5), flag=wx.EXPAND | wx.BOTTOM, border=10) self.cfg_text = wx.StaticText(self, label="Select the config file") self.sizer.Add(self.cfg_text, pos=(2, 0), flag=wx.TOP | wx.LEFT, border=5) if sys.platform == "darwin": self.sel_config = wx.FilePickerCtrl( self, path="", style=wx.FLP_USE_TEXTCTRL, message="Choose the config.yaml file", wildcard="*.yaml", ) else: self.sel_config = wx.FilePickerCtrl( self, path="", style=wx.FLP_USE_TEXTCTRL, message="Choose the config.yaml file", wildcard="config.yaml", ) # self.sel_config = wx.FilePickerCtrl(self, path="",style=wx.FLP_USE_TEXTCTRL,message="Choose the config.yaml file", wildcard="config.yaml") self.sizer.Add(self.sel_config, pos=(2, 1), span=(1, 3), flag=wx.TOP | wx.EXPAND, border=5) self.sel_config.SetPath(self.config) self.sel_config.Bind(wx.EVT_FILEPICKER_CHANGED, self.select_config) self.vids = wx.StaticText(self, label="Choose the videos") self.sizer.Add(self.vids, pos=(3, 0), flag=wx.TOP | wx.LEFT, border=10) self.sel_vids = wx.Button(self, label="Select videos to analyze") self.sizer.Add(self.sel_vids, pos=(3, 1), flag=wx.TOP | wx.EXPAND, border=5) self.sel_vids.Bind(wx.EVT_BUTTON, self.select_videos) sb = wx.StaticBox(self, label="Optional Attributes") boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) videotype_text = wx.StaticBox(self, label="Specify the videotype") videotype_text_boxsizer = wx.StaticBoxSizer(videotype_text, wx.VERTICAL) videotypes = [".avi", ".mp4", ".mov"] self.videotype = wx.ComboBox(self, choices=videotypes, style=wx.CB_READONLY) self.videotype.SetValue(".avi") videotype_text_boxsizer.Add(self.videotype, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) shuffles_text = wx.StaticBox(self, label="Specify the shuffle") shuffles_text_boxsizer = wx.StaticBoxSizer(shuffles_text, wx.VERTICAL) self.shuffles = wx.SpinCtrl(self, value="1", min=0, max=100) shuffles_text_boxsizer.Add(self.shuffles, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) ntracks_text = wx.StaticBox(self, label="Specify the no. of animals") ntracks_text_boxsizer = wx.StaticBoxSizer(ntracks_text, wx.VERTICAL) self.n_tracks = wx.SpinCtrl(self, value="2", min=0, max=100) ntracks_text_boxsizer.Add(self.n_tracks, 2, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) ntriplets_text = wx.StaticBox(self, label="Specify the no. triplets") ntriplets_text_boxsizer = wx.StaticBoxSizer(ntriplets_text, wx.VERTICAL) self.ntriplets = wx.SpinCtrl(self, value="1000", min=100, max=5000) ntriplets_text_boxsizer.Add(self.ntriplets, 2, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) trackertype_text = wx.StaticBox( self, label="Specify the tracking type (ellipse recommended)") trackertype_text_boxsizer = wx.StaticBoxSizer(trackertype_text, wx.VERTICAL) trackertype = ["ellipse", "box"] self.trackertype = wx.ComboBox(self, choices=trackertype, style=wx.CB_READONLY) self.trackertype.SetValue("ellipse") trackertype_text_boxsizer.Add(self.trackertype, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) hbox1.Add(videotype_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox1.Add(shuffles_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox2.Add(ntracks_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox2.Add(ntriplets_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox2.Add(trackertype_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) boxsizer.Add(hbox1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) boxsizer.Add(hbox2, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) self.sizer.Add( boxsizer, pos=(4, 0), span=(1, 5), flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10, ) ### train and go ### self.help_button = wx.Button(self, label="Help") self.sizer.Add(self.help_button, pos=(6, 0), flag=wx.LEFT, border=10) self.help_button.Bind(wx.EVT_BUTTON, self.help_function) self.ok = wx.Button(self, label="Run Transformer") self.sizer.Add(self.ok, pos=(6, 4)) self.ok.Bind(wx.EVT_BUTTON, self.transformer_reID) self.reset = wx.Button(self, label="Reset") self.sizer.Add(self.reset, pos=(6, 1), span=(1, 1), flag=wx.BOTTOM | wx.RIGHT, border=10) self.reset.Bind(wx.EVT_BUTTON, self.reset_transformer_reID) self.sizer.AddGrowableCol(2) self.SetSizer(self.sizer) self.sizer.Fit(self)
def stitch_tracklets( config_path, videos, videotype="avi", shuffle=1, trainingsetindex=0, n_tracks=None, min_length=10, split_tracklets=True, prestitch_residuals=True, max_gap=None, weight_func=None, track_method="ellipse", destfolder=None, modelprefix="", output_name="", ): """ Stitch sparse tracklets into full tracks via a graph-based, minimum-cost flow optimization problem. Parameters ---------- config_path : str Path to the main project config.yaml file. videos : list A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored. videotype: string, optional Checks for the extension of the video in case the input to the video is a directory.\n Only videos with this extension are analyzed. The default is ``.avi`` shuffle: int, optional An integer specifying the shuffle index of the training dataset used for training the network. The default is 1. trainingsetindex: int, optional Integer specifying which TrainingsetFraction to use. By default the first (note that TrainingFraction is a list in config.yaml). n_tracks : int, optional Number of tracks to reconstruct. By default, taken as the number of individuals defined in the config.yaml. Another number can be passed if the number of animals in the video is different from the number of animals the model was trained on. min_length : int, optional Tracklets less than `min_length` frames of length are considered to be residuals; i.e., they do not participate in building the graph and finding the solution to the optimization problem, but are rather added last after "almost-complete" tracks are formed. The higher the value, the lesser the computational cost, but the higher the chance of discarding relatively long and reliable tracklets that are essential to solving the stitching task. Default is 10, and must be 3 at least. split_tracklets : bool, optional By default, tracklets whose time indices are not consecutive integers are split in shorter tracklets whose time continuity is guaranteed. This is for example very powerful to get rid of tracking errors (e.g., identity switches) which are often signaled by a missing time frame at the moment they occur. Note though that for long occlusions where tracker re-identification capability can be trusted, setting `split_tracklets` to False is preferable. prestitch_residuals : bool, optional Residuals will by default be grouped together according to their temporal proximity prior to being added back to the tracks. This is done to improve robustness and simultaneously reduce complexity. max_gap : int, optional Maximal temporal gap to allow between a pair of tracklets. This is automatically determined by the TrackletStitcher by default. weight_func : callable, optional Function accepting two tracklets as arguments and returning a scalar that must be inversely proportional to the likelihood that the tracklets belong to the same track; i.e., the higher the confidence that the tracklets should be stitched together, the lower the returned value. track_method: str, optional Method used to track animals, either 'box', 'skeleton', or 'ellipse' (default). destfolder: string, optional Specifies the destination folder for analysis data (default is the path of the video). Note that for subsequent analysis this folder also needs to be passed. output_name : str, optional Name of the output h5 file. By default, tracks are automatically stored into the same directory as the pickle file and with its name. Returns ------- A TrackletStitcher object """ vids = auxiliaryfunctions.Getlistofvideos(videos, videotype) if not vids: print("No video(s) found. Please check your path!") return cfg = read_config(config_path) animal_names = cfg["individuals"] if n_tracks is None: n_tracks = len(animal_names) DLCscorer, _ = auxiliaryfunctions.GetScorerName( cfg, shuffle, cfg["TrainingFraction"][trainingsetindex], modelprefix=modelprefix, ) for video in vids: print("Processing... ", video) videofolder = str(Path(video).parents[0]) dest = destfolder or videofolder auxiliaryfunctions.attempttomakefolder(dest) vname = Path(video).stem dataname = os.path.join(dest, vname + DLCscorer + ".h5") if track_method == "ellipse": method = "el" elif track_method == "box": method = "bx" else: method = "sk" pickle_file = dataname.split(".h5")[0] + f"_{method}.pickle" try: stitcher = TrackletStitcher.from_pickle(pickle_file, n_tracks, min_length, split_tracklets, prestitch_residuals) with_id = any(tracklet.identity != -1 for tracklet in stitcher) if with_id and weight_func is None: # Add in identity weighing before building the graph def weight_func(t1, t2): w = 0.01 if t1.identity == t2.identity else 1 return w * stitcher.calculate_edge_weight(t1, t2) stitcher.build_graph(max_gap=max_gap, weight_func=weight_func) stitcher.stitch() stitcher.write_tracks(output_name, animal_names) except FileNotFoundError as e: print(e, "\nSkipping...")
def __init__(self, parent, gui_size, cfg): """Constructor""" wx.Panel.__init__(self, parent=parent) # variable initialization self.config = cfg self.cfg = utils.read_config(cfg) self.filelist = [] # design the panel self.sizer = wx.GridBagSizer(5, 5) text = wx.StaticText( self, label= "DeepLabCut - OPTIONAL: Extract and Refine labels on Outlier Frames" ) self.sizer.Add(text, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=15) # Add logo of DLC icon = wx.StaticBitmap(self, bitmap=wx.Bitmap(LOGO_PATH)) self.sizer.Add(icon, pos=(0, 4), flag=wx.TOP | wx.RIGHT | wx.ALIGN_RIGHT, border=5) line1 = wx.StaticLine(self) self.sizer.Add(line1, pos=(1, 0), span=(1, 5), flag=wx.EXPAND | wx.BOTTOM, border=10) self.cfg_text = wx.StaticText(self, label="Select the config file") self.sizer.Add(self.cfg_text, pos=(2, 0), flag=wx.TOP | wx.LEFT, border=5) if sys.platform == "darwin": self.sel_config = wx.FilePickerCtrl( self, path="", style=wx.FLP_USE_TEXTCTRL, message="Choose the config.yaml file", wildcard="*.yaml", ) else: self.sel_config = wx.FilePickerCtrl( self, path="", style=wx.FLP_USE_TEXTCTRL, message="Choose the config.yaml file", wildcard="config.yaml", ) # self.sel_config = wx.FilePickerCtrl(self, path="",style=wx.FLP_USE_TEXTCTRL,message="Choose the config.yaml file", wildcard="config.yaml") self.sizer.Add(self.sel_config, pos=(2, 1), span=(1, 3), flag=wx.TOP | wx.EXPAND, border=5) self.sel_config.SetPath(self.config) self.sel_config.Bind(wx.EVT_FILEPICKER_CHANGED, self.select_config) self.vids = wx.StaticText(self, label="Choose the videos") self.sizer.Add(self.vids, pos=(3, 0), flag=wx.TOP | wx.LEFT, border=10) self.sel_vids = wx.Button(self, label="Select videos to analyze") self.sizer.Add(self.sel_vids, pos=(3, 1), flag=wx.TOP | wx.EXPAND, border=5) self.sel_vids.Bind(wx.EVT_BUTTON, self.select_videos) sb = wx.StaticBox(self, label="Optional Attributes") boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) videotype_text = wx.StaticBox(self, label="Specify the videotype") videotype_text_boxsizer = wx.StaticBoxSizer(videotype_text, wx.VERTICAL) self.videotype = wx.ComboBox(self, choices=("", ) + SUPPORTED_VIDEOS, style=wx.CB_READONLY) self.videotype.SetValue("") videotype_text_boxsizer.Add(self.videotype, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) shuffles_text = wx.StaticBox(self, label="Specify the shuffle") shuffles_text_boxsizer = wx.StaticBoxSizer(shuffles_text, wx.VERTICAL) self.shuffles = wx.SpinCtrl(self, value="1", min=0, max=100) shuffles_text_boxsizer.Add(self.shuffles, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) outlier_algo_text = wx.StaticBox(self, label="Specify the algorithm") outlier_algo_text_boxsizer = wx.StaticBoxSizer(outlier_algo_text, wx.VERTICAL) algotypes = ["jump", "fitting", "uncertain", "manual"] self.algotype = wx.ComboBox(self, choices=algotypes, style=wx.CB_READONLY) self.algotype.SetValue("jump") outlier_algo_text_boxsizer.Add(self.algotype, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 1) hbox1.Add(videotype_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox1.Add(shuffles_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) hbox1.Add(outlier_algo_text_boxsizer, 10, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) boxsizer.Add(hbox1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 10) self.sizer.Add( boxsizer, pos=(4, 0), span=(1, 5), flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10, ) ### LABELING ### self.ok = wx.Button(self, label="LAUNCH GUI") self.sizer.Add(self.ok, pos=(7, 4)) self.ok.Bind(wx.EVT_BUTTON, self.refine_labels) self.merge = wx.Button(self, label="Merge dataset") self.sizer.Add(self.merge, pos=(7, 3), flag=wx.BOTTOM | wx.RIGHT, border=10) self.merge.Bind(wx.EVT_BUTTON, self.merge_dataset) self.merge.Enable(False) self.help_button = wx.Button(self, label="Help") self.sizer.Add(self.help_button, pos=(6, 0), flag=wx.LEFT, border=10) self.help_button.Bind(wx.EVT_BUTTON, self.help_function) self.ok = wx.Button(self, label="EXTRACT FRAMES") self.sizer.Add(self.ok, pos=(6, 4)) self.ok.Bind(wx.EVT_BUTTON, self.extract_outlier_frames) self.reset = wx.Button(self, label="Reset") self.sizer.Add(self.reset, pos=(6, 1), span=(1, 1), flag=wx.BOTTOM | wx.RIGHT, border=10) self.reset.Bind(wx.EVT_BUTTON, self.reset_extract_outlier_frames) self.sizer.AddGrowableCol(2) self.SetSizer(self.sizer) self.sizer.Fit(self)