def adddatasetstovideolistandviceversa(config): """ First run comparevideolistsanddatafolders(config) to compare the folders in labeled-data and the ones listed under video_sets (in the config file). If you detect differences this function can be used to maker sure each folder has a video entry & vice versa. It corrects this problem in the following way: If a video entry in the config file does not contain a folder in labeled-data, then the entry is removed. If a folder in labeled-data does not contain a video entry in the config file then the prefix path will be added in front of the name of the labeled-data folder and combined with the suffix variable as an ending. Width and height will be added as cropping variables as passed on. Handle with care! Parameter ---------- config : string String containing the full path of the config file in the project. """ cfg = auxiliaryfunctions.read_config(config) videos = cfg["video_sets"] video_names = [Path(i).stem for i in videos] alldatafolders = [ fn for fn in os.listdir(Path(config).parent / "labeled-data") if "_labeled" not in fn and not fn.startswith(".") ] print("Config file contains:", len(video_names)) print("Labeled-data contains:", len(alldatafolders)) toberemoved = [] for vn in video_names: if vn not in alldatafolders: print(vn, " is missing as a labeled folder >> removing key!") for fullvideo in videos: if vn in fullvideo: toberemoved.append(fullvideo) for vid in toberemoved: del videos[vid] # Load updated lists: video_names = [Path(i).stem for i in videos] for vn in alldatafolders: if vn not in video_names: print(vn, " is missing in config file >> adding it!") # Find the corresponding video file found = False for file in os.listdir(os.path.join(cfg["project_path"], "videos")): if os.path.splitext(file)[0] == vn: found = True break if found: video_path = os.path.join(cfg["project_path"], "videos", file) clip = VideoReader(video_path) videos.update({ video_path: { "crop": ", ".join(map(str, clip.get_bbox())) } }) auxiliaryfunctions.write_config(config, cfg)
def create_new_project( project, experimenter, videos, working_directory=None, copy_videos=False, videotype=".avi", multianimal=False, ): """Creates a new project directory, sub-directories and a basic configuration file. The configuration file is loaded with the default values. Change its parameters to your projects need. Parameters ---------- project : string String containing the name of the project. experimenter : string String containing the name of the experimenter. videos : list A list of string containing the full paths of the videos to include in the project. Attention: Can also be a directory, then all videos of videotype will be imported. working_directory : string, optional The directory where the project will be created. The default is the ``current working directory``; if provided, it must be a string. copy_videos : bool, optional If this is set to True, the videos are copied to the ``videos`` directory. If it is False,symlink of the videos are copied to the project/videos directory. The default is ``False``; if provided it must be either ``True`` or ``False``. multianimal: bool, optional. Default: False. For creating a multi-animal project (introduced in DLC 2.2) Example -------- Linux/MacOs >>> deeplabcut.create_new_project('reaching-task','Linus',['/data/videos/mouse1.avi','/data/videos/mouse2.avi','/data/videos/mouse3.avi'],'/analysis/project/') >>> deeplabcut.create_new_project('reaching-task','Linus',['/data/videos'],videotype='.mp4') Windows: >>> deeplabcut.create_new_project('reaching-task','Bill',[r'C:\yourusername\rig-95\Videos\reachingvideo1.avi'], copy_videos=True) Users must format paths with either: r'C:\ OR 'C:\\ <- i.e. a double backslash \ \ ) """ from datetime import datetime as dt from deeplabcut.utils import auxiliaryfunctions date = dt.today() month = date.strftime("%B") day = date.day d = str(month[0:3] + str(day)) date = dt.today().strftime("%Y-%m-%d") if working_directory == None: working_directory = "." wd = Path(working_directory).resolve() project_name = "{pn}-{exp}-{date}".format(pn=project, exp=experimenter, date=date) project_path = wd / project_name # Create project and sub-directories if not DEBUG and project_path.exists(): print('Project "{}" already exists!'.format(project_path)) return video_path = project_path / "videos" data_path = project_path / "labeled-data" shuffles_path = project_path / "training-datasets" results_path = project_path / "dlc-models" for p in [video_path, data_path, shuffles_path, results_path]: p.mkdir(parents=True, exist_ok=DEBUG) print('Created "{}"'.format(p)) # Add all videos in the folder. Multiple folders can be passed in a list, similar to the video files. Folders and video files can also be passed! vids = [] for i in videos: # Check if it is a folder if os.path.isdir(i): vids_in_dir = [ os.path.join(i, vp) for vp in os.listdir(i) if videotype in vp ] vids = vids + vids_in_dir if len(vids_in_dir) == 0: print("No videos found in", i) print( "Perhaps change the videotype, which is currently set to:", videotype, ) else: videos = vids print( len(vids_in_dir), " videos from the directory", i, "were added to the project.", ) else: if os.path.isfile(i): vids = vids + [i] videos = vids videos = [Path(vp) for vp in videos] dirs = [data_path / Path(i.stem) for i in videos] for p in dirs: """ Creates directory under data """ p.mkdir(parents=True, exist_ok=True) destinations = [video_path.joinpath(vp.name) for vp in videos] if copy_videos == True: print("Copying the videos") for src, dst in zip(videos, destinations): shutil.copy( os.fspath(src), os.fspath(dst) ) # https://www.python.org/dev/peps/pep-0519/ else: # creates the symlinks of the video and puts it in the videos directory. print("Attempting to create a symbolic link of the video ...") for src, dst in zip(videos, destinations): if dst.exists() and not DEBUG: raise FileExistsError("Video {} exists already!".format(dst)) try: src = str(src) dst = str(dst) os.symlink(src, dst) except OSError: import subprocess subprocess.check_call("mklink %s %s" % (dst, src), shell=True) print("Created the symlink of {} to {}".format(src, dst)) videos = destinations if copy_videos == True: videos = ( destinations ) # in this case the *new* location should be added to the config file # adds the video list to the config.yaml file video_sets = {} for video in videos: print(video) try: # For windows os.path.realpath does not work and does not link to the real video. [old: rel_video_path = os.path.realpath(video)] rel_video_path = str(Path.resolve(Path(video))) except: rel_video_path = os.readlink(str(video)) try: vid = VideoReader(rel_video_path) video_sets[rel_video_path] = {"crop": ", ".join(map(str, vid.get_bbox()))} except IOError: warnings.warn("Cannot open the video file! Skipping to the next one...") os.remove(video) # Removing the video or link from the project if not len(video_sets): # Silently sweep the files that were already written. shutil.rmtree(project_path, ignore_errors=True) warnings.warn( "No valid videos were found. The project was not created... " "Verify the video files and re-create the project." ) return "nothingcreated" # Set values to config file: if multianimal: # parameters specific to multianimal project cfg_file, ruamelFile = auxiliaryfunctions.create_config_template(multianimal) cfg_file["multianimalproject"] = multianimal cfg_file["identity"] = False cfg_file["individuals"] = ["individual1", "individual2", "individual3"] cfg_file["multianimalbodyparts"] = ["bodypart1", "bodypart2", "bodypart3"] cfg_file["uniquebodyparts"] = [] cfg_file["bodyparts"] = "MULTI!" cfg_file["skeleton"] = [ ["bodypart1", "bodypart2"], ["bodypart2", "bodypart3"], ["bodypart1", "bodypart3"], ] cfg_file["default_augmenter"] = "multi-animal-imgaug" else: cfg_file, ruamelFile = auxiliaryfunctions.create_config_template() cfg_file["multianimalproject"] = False cfg_file["bodyparts"] = ["bodypart1", "bodypart2", "bodypart3", "objectA"] cfg_file["skeleton"] = [["bodypart1", "bodypart2"], ["objectA", "bodypart3"]] cfg_file["default_augmenter"] = "default" cfg_file["croppedtraining"] = False # common parameters: cfg_file["Task"] = project cfg_file["scorer"] = experimenter cfg_file["video_sets"] = video_sets cfg_file["project_path"] = str(project_path) cfg_file["date"] = d cfg_file["cropping"] = False cfg_file["start"] = 0 cfg_file["stop"] = 1 cfg_file["numframes2pick"] = 20 cfg_file["TrainingFraction"] = [0.95] cfg_file["iteration"] = 0 cfg_file["default_net_type"] = "resnet_50" cfg_file["snapshotindex"] = -1 cfg_file["x1"] = 0 cfg_file["x2"] = 640 cfg_file["y1"] = 277 cfg_file["y2"] = 624 cfg_file[ "batch_size" ] = ( 8 ) # batch size during inference (video - analysis); see https://www.biorxiv.org/content/early/2018/10/30/457242 cfg_file["corner2move2"] = (50, 50) cfg_file["move2corner"] = True cfg_file["skeleton_color"] = "black" cfg_file["pcutoff"] = 0.6 cfg_file["dotsize"] = 12 # for plots size of dots cfg_file["alphavalue"] = 0.7 # for plots transparency of markers cfg_file["colormap"] = "rainbow" # for plots type of colormap projconfigfile = os.path.join(str(project_path), "config.yaml") # Write dictionary to yaml config file auxiliaryfunctions.write_config(projconfigfile, cfg_file) print('Generated "{}"'.format(project_path / "config.yaml")) print( "\nA new project with name %s is created at %s and a configurable file (config.yaml) is stored there. Change the parameters in this file to adapt to your project's needs.\n Once you have changed the configuration file, use the function 'extract_frames' to select frames for labeling.\n. [OPTIONAL] Use the function 'add_new_videos' to add new videos to your project (at any stage)." % (project_name, str(wd)) ) return projconfigfile
def add_new_videos(config, videos, copy_videos=False, coords=None): """ Add new videos to the config file at any stage of the project. Parameters ---------- config : string String containing the full path of the config file in the project. videos : list A list of string containing the full paths of the videos to include in the project. copy_videos : bool, optional If this is set to True, the symlink of the videos are copied to the project/videos directory. The default is ``False``; if provided it must be either ``True`` or ``False``. coords: list, optional A list containing the list of cropping coordinates of the video. The default is set to None. Examples -------- Video will be added, with cropping dimenions according to the frame dimensinos of mouse5.avi >>> deeplabcut.add_new_videos('/home/project/reaching-task-Tanmay-2018-08-23/config.yaml',['/data/videos/mouse5.avi']) Video will be added, with cropping dimenions [0,100,0,200] >>> deeplabcut.add_new_videos('/home/project/reaching-task-Tanmay-2018-08-23/config.yaml',['/data/videos/mouse5.avi'],copy_videos=False,coords=[[0,100,0,200]]) Two videos will be added, with cropping dimenions [0,100,0,200] and [0,100,0,250], respectively. >>> deeplabcut.add_new_videos('/home/project/reaching-task-Tanmay-2018-08-23/config.yaml',['/data/videos/mouse5.avi','/data/videos/mouse6.avi'],copy_videos=False,coords=[[0,100,0,200],[0,100,0,250]]) """ import os import shutil from pathlib import Path from deeplabcut.utils import auxiliaryfunctions from deeplabcut.utils.auxfun_videos import VideoReader # Read the config file cfg = auxiliaryfunctions.read_config(config) video_path = Path(config).parents[0] / "videos" data_path = Path(config).parents[0] / "labeled-data" videos = [Path(vp) for vp in videos] dirs = [data_path / Path(i.stem) for i in videos] for p in dirs: """ Creates directory under data & perhaps copies videos (to /video) """ p.mkdir(parents=True, exist_ok=True) destinations = [video_path.joinpath(vp.name) for vp in videos] if copy_videos: for src, dst in zip(videos, destinations): if dst.exists(): pass else: print("Copying the videos") shutil.copy(os.fspath(src), os.fspath(dst)) else: for src, dst in zip(videos, destinations): if dst.exists(): pass else: print("Creating the symbolic link of the video") src = str(src) dst = str(dst) os.symlink(src, dst) if copy_videos: videos = destinations # in this case the *new* location should be added to the config file # adds the video list to the config.yaml file for idx, video in enumerate(videos): try: # For windows os.path.realpath does not work and does not link to the real video. video_path = str(Path.resolve(Path(video))) # video_path = os.path.realpath(video) except: video_path = os.readlink(video) vid = VideoReader(video_path) if coords is not None: c = coords[idx] else: c = vid.get_bbox() params = {video_path: {"crop": ", ".join(map(str, c))}} if "video_sets_original" not in cfg: cfg["video_sets"].update(params) else: cfg["video_sets_original"].update(params) auxiliaryfunctions.write_config(config, cfg) print( "New video was added to the project! Use the function 'extract_frames' to select frames for labeling." )
def add_new_videos(config, videos, copy_videos=False, coords=None, extract_frames=False): """ Add new videos to the config file at any stage of the project. Parameters ---------- config : string String containing the full path of the config file in the project. videos : list A list of strings containing the full paths of the videos to include in the project. copy_videos : bool, optional If this is set to True, the symlink of the videos are copied to the project/videos directory. The default is ``False``; if provided it must be either ``True`` or ``False``. coords: list, optional A list containing the list of cropping coordinates of the video. The default is set to None. extract_frames: bool, optional if this is set to True extract_frames will be run on the new videos Examples -------- Video will be added, with cropping dimensions according to the frame dimensions of mouse5.avi >>> deeplabcut.add_new_videos('/home/project/reaching-task-Tanmay-2018-08-23/config.yaml',['/data/videos/mouse5.avi']) Video will be added, with cropping dimensions [0,100,0,200] >>> deeplabcut.add_new_videos('/home/project/reaching-task-Tanmay-2018-08-23/config.yaml',['/data/videos/mouse5.avi'],copy_videos=False,coords=[[0,100,0,200]]) Two videos will be added, with cropping dimensions [0,100,0,200] and [0,100,0,250], respectively. >>> deeplabcut.add_new_videos('/home/project/reaching-task-Tanmay-2018-08-23/config.yaml',['/data/videos/mouse5.avi','/data/videos/mouse6.avi'],copy_videos=False,coords=[[0,100,0,200],[0,100,0,250]]) """ import os import shutil from pathlib import Path from deeplabcut.utils import auxiliaryfunctions from deeplabcut.utils.auxfun_videos import VideoReader from deeplabcut.generate_training_dataset import frame_extraction # Read the config file cfg = auxiliaryfunctions.read_config(config) video_path = Path(config).parents[0] / "videos" data_path = Path(config).parents[0] / "labeled-data" videos = [Path(vp) for vp in videos] dirs = [data_path / Path(i.stem) for i in videos] for p in dirs: """ Creates directory under data & perhaps copies videos (to /video) """ p.mkdir(parents=True, exist_ok=True) destinations = [video_path.joinpath(vp.name) for vp in videos] if copy_videos: for src, dst in zip(videos, destinations): if dst.exists(): pass else: print("Copying the videos") shutil.copy(os.fspath(src), os.fspath(dst)) else: # creates the symlinks of the video and puts it in the videos directory. print("Attempting to create a symbolic link of the video ...") for src, dst in zip(videos, destinations): if dst.exists(): pass try: src = str(src) dst = str(dst) os.symlink(src, dst) print("Created the symlink of {} to {}".format(src, dst)) except OSError: try: import subprocess subprocess.check_call("mklink %s %s" % (dst, src), shell=True) except (OSError, subprocess.CalledProcessError): print( "Symlink creation impossible (exFat architecture?): " "cutting/pasting the video instead." ) shutil.move(os.fspath(src), os.fspath(dst)) print("{} moved to {}".format(src, dst)) videos = destinations if copy_videos: videos = destinations # in this case the *new* location should be added to the config file # adds the video list to the config.yaml file for idx, video in enumerate(videos): try: # For windows os.path.realpath does not work and does not link to the real video. video_path = str(Path.resolve(Path(video))) # video_path = os.path.realpath(video) except: video_path = os.readlink(video) vid = VideoReader(video_path) if coords is not None: c = coords[idx] else: c = vid.get_bbox() params = {video_path: {"crop": ", ".join(map(str, c))}} if "video_sets_original" not in cfg: cfg["video_sets"].update(params) else: cfg["video_sets_original"].update(params) videos_str = [str(video) for video in videos] if extract_frames: frame_extraction.extract_frames(config, userfeedback=False, videos_list=videos_str) print( "New videos were added to the project and frames have been extracted for labeling!" ) else: print( "New videos were added to the project! Use the function 'extract_frames' to select frames for labeling." ) auxiliaryfunctions.write_config(config, cfg)