def read_headers( cls, file: format.filehandle.FileHandle, video_search: Union[Callable, List[Text], None] = None, match_to: Optional[Labels] = None, ): f = file.file # Extract the Labels JSON metadata and create Labels object with just this # metadata. dicts = json_loads( f.require_group("metadata").attrs["json"].tostring().decode() ) # These items are stored in separate lists because the metadata group got to be # too big. for key in ("videos", "tracks", "suggestions"): hdf5_key = f"{key}_json" if hdf5_key in f: items = [json_loads(item_json) for item_json in f[hdf5_key]] dicts[key] = items # Video path "." means the video is saved in same file as labels, so replace # these paths. for video_item in dicts["videos"]: if video_item["backend"]["filename"] == ".": video_item["backend"]["filename"] = file.filename # Use the video_callback for finding videos with broken paths: # 1. Accept single string as video search path if isinstance(video_search, str): video_search = [video_search] # 2. Accept list of strings as video search paths if hasattr(video_search, "__iter__"): # If the callback is an iterable, then we'll expect it to be a list of # strings and build a non-gui callback with those as the search paths. search_paths = [ # os.path.dirname(path) if os.path.isfile(path) else path path for path in video_search ] # Make the search function from list of paths video_search = Labels.make_video_callback(search_paths) # 3. Use the callback function (either given as arg or build from paths) if callable(video_search): video_search(dicts["videos"]) # Create the Labels object with the header data we've loaded labels = labels_json.LabelsJsonAdaptor.from_json_data(dicts, match_to=match_to) return labels
def read( cls, file: FileHandle, video_search: Union[Callable, List[Text], None] = None, match_to: Optional[Labels] = None, *args, **kwargs, ) -> Labels: pass """ Deserialize JSON file as new :class:`Labels` instance. Args: filename: Path to JSON file. video_callback: A callback function that which can modify video paths before we try to create the corresponding :class:`Video` objects. Usually you'll want to pass a callback created by :meth:`make_video_callback` or :meth:`make_gui_video_callback`. Alternately, if you pass a list of strings we'll construct a non-gui callback with those strings as the search paths. match_to: If given, we'll replace particular objects in the data dictionary with *matching* objects in the match_to :class:`Labels` object. This ensures that the newly instantiated :class:`Labels` can be merged without duplicate matching objects (e.g., :class:`Video` objects ). Returns: A new :class:`Labels` object. """ tmp_dir = None filename = file.filename # Check if the file is a zipfile for not. if zipfile.is_zipfile(filename): # Make a tmpdir, located in the directory that the file exists, to unzip # its contents. tmp_dir = os.path.join( os.path.dirname(filename), f"tmp_{os.getpid()}_{os.path.basename(filename)}", ) if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir, ignore_errors=True) try: os.mkdir(tmp_dir) except FileExistsError: pass # tmp_dir = tempfile.mkdtemp(dir=os.path.dirname(filename)) try: # Register a cleanup routine that deletes the tmpdir on program exit # if something goes wrong. The True is for ignore_errors atexit.register(shutil.rmtree, tmp_dir, True) # Uncompress the data into the directory shutil.unpack_archive(filename, extract_dir=tmp_dir) # We can now open the JSON file, save the zip file and # replace file with the first JSON file we find in the archive. json_files = [ os.path.join(tmp_dir, file) for file in os.listdir(tmp_dir) if file.endswith(".json") ] if len(json_files) == 0: raise ValueError( f"No JSON file found inside {filename}. Are you sure this is a valid sLEAP dataset." ) filename = json_files[0] except Exception as ex: # If we had problems, delete the temp directory and reraise the exception. shutil.rmtree(tmp_dir, ignore_errors=True) raise # Open and parse the JSON in filename with open(filename, "r") as file: # FIXME: Peek into the json to see if there is version string. # We do this to tell apart old JSON data from leap_dev vs the # newer format for sLEAP. json_str = file.read() dicts = json_loads(json_str) # If we have a version number, then it is new sLEAP format if "version" in dicts: # Cache the working directory. cwd = os.getcwd() # Replace local video paths (for imagestore) if tmp_dir: for vid in dicts["videos"]: vid["backend"]["filename"] = os.path.join( tmp_dir, vid["backend"]["filename"]) # Use the video_callback for finding videos with broken paths: # 1. Accept single string as video search path if isinstance(video_search, str): video_search = [video_search] # 2. Accept list of strings as video search paths if hasattr(video_search, "__iter__"): # If the callback is an iterable, then we'll expect it to be a # list of strings and build a non-gui callback with those as # the search paths. # When path is to a file, use the path of parent directory. search_paths = [ os.path.dirname(path) if os.path.isfile(path) else path for path in video_search ] # Make the search function from list of paths video_search = Labels.make_video_callback(search_paths) # 3. Use the callback function (either given as arg or build from paths) if callable(video_search): abort = video_search(dicts["videos"]) if abort: raise FileNotFoundError # Try to load the labels filename. try: labels = cls.from_json_data(dicts, match_to=match_to) except FileNotFoundError: # FIXME: We are going to the labels JSON that has references to # video files. Lets change directory to the dirname of the json file # so that relative paths will be from this directory. Maybe # it is better to feed the dataset dirname all the way down to # the Video object. This seems like less coupling between classes # though. if os.path.dirname(filename) != "": os.chdir(os.path.dirname(filename)) # Try again labels = cls.from_json_data(dicts, match_to=match_to) except Exception as ex: # Ok, we give up, where the hell are these videos! raise # Re-raise. finally: os.chdir( cwd) # Make sure to change back if we have problems. return labels else: frames = load_labels_json_old(data_path=filename, parsed_json=dicts) return Labels(frames)
def main(): parser = argparse.ArgumentParser() parser.add_argument("input_path", help="Path to input file.") parser.add_argument( "-o", "--output", default="", help="Path to output file (optional)." ) parser.add_argument( "--format", default="slp", help="Output format. Default ('slp') is SLEAP dataset; " "'analysis' results in analysis.h5 file; " "'h5' or 'json' results in SLEAP dataset " "with specified file format.", ) parser.add_argument( "--video", default="", help="Path to video (if needed for conversion)." ) args = parser.parse_args() video_callback = Labels.make_video_callback([os.path.dirname(args.input_path)]) try: labels = Labels.load_file(args.input_path, video_search=video_callback) except TypeError: print("Input file isn't SLEAP dataset so attempting other importers...") from sleap.io.format import read video_path = args.video if args.video else None labels = read( args.input_path, for_object="labels", as_format="*", video_search=video_callback, video=video_path, ) if args.format == "analysis": from sleap.info.write_tracking_h5 import main as write_analysis if args.output: output_path = args.output else: output_path = args.input_path output_path = re.sub("(\.json(\.zip)?|\.h5|\.slp)$", "", output_path) output_path = output_path + ".analysis.h5" write_analysis(labels, output_path=output_path, all_frames=True) elif args.output: print(f"Output SLEAP dataset: {args.output}") Labels.save_file(labels, args.output) elif args.format in ("slp", "h5", "json"): output_path = f"{args.input_path}.{args.format}" print(f"Output SLEAP dataset: {output_path}") Labels.save_file(labels, output_path) else: print("You didn't specify how to convert the file.") print(args)