def project_details(project): """ Show the details for the selected project. """ project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) config = project_utils.load_project_config(path) stats = {} for class_name, tags in config['classes'].items(): stats[class_name] = {} for split in SPLITS: videos_dir = directories.get_videos_dir(path, split, class_name) tags_dir = directories.get_tags_dir(path, split, class_name) stats[class_name][split] = { 'total': len(os.listdir(videos_dir)), 'tagged': len(os.listdir(tags_dir)) if os.path.exists(tags_dir) else 0, } return render_template('project_details.html', config=config, path=path, stats=stats, project=config['name'])
def testing_page(project): project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) output_path_prefix = os.path.join(os.path.basename(path), 'output_videos', '') classifiers = [] checkpoints_path = os.path.join(path, 'checkpoints') if os.path.exists(checkpoints_path): # Add main checkpoints directory if best_classifier is available default_checkpoint = os.path.join(checkpoints_path, 'best_classifier.checkpoint') if os.path.exists(default_checkpoint): classifiers.append(os.path.join('checkpoints', '')) # Add sub-directories of checkpoints directory if best_classifier is available in there classifiers.extend([ os.path.join('checkpoints', d, '') for d in os.listdir(checkpoints_path) if os.path.isdir(os.path.join(checkpoints_path, d)) and os.path.exists( os.path.join(checkpoints_path, d, 'best_classifier.checkpoint')) ]) classifiers = natsorted(classifiers, alg=ns.IC) return render_template('testing.html', project=project, path=path, output_path_prefix=output_path_prefix, classifiers=classifiers)
def training_page(project): project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) project_config = project_utils.load_project_config(path) output_path_prefix = os.path.join(os.path.basename(path), 'checkpoints', '') return render_template('training.html', project=project, path=path, models=utils.get_available_backbone_models(), output_path_prefix=output_path_prefix, project_config=project_config)
def save_video(project, split, label): project = urllib.parse.unquote(project) split = urllib.parse.unquote(split) label = urllib.parse.unquote(label) path = project_utils.lookup_project_path(project) # Read given video to a file input_stream = request.files['video'] output_path = os.path.join(path, f'videos_{split}', label) temp_file_name = os.path.join(output_path, 'temp_video.webm') with open(temp_file_name, 'wb') as temp_file: temp_file.write(input_stream.read()) # Find a video name that is not used yet existing_files = set( glob.glob(os.path.join(output_path, 'video_[0-9]*.mp4'))) video_idx = 0 output_file = os.path.join(output_path, f'video_{video_idx}.mp4') while output_file in existing_files: video_idx += 1 output_file = os.path.join(output_path, f'video_{video_idx}.mp4') # Convert video to target frame rate and save to output name subprocess.call(f'ffmpeg -i "{temp_file_name}" -r 30 "{output_file}"', shell=True) # Remove temp video file os.remove(temp_file_name) return jsonify(success=True)
def download_file(project, split, label, video_name, img_file): """ Load an image from the given path. """ dataset_path = project_utils.lookup_project_path(project) img_dir = os.path.join( directories.get_frames_dir(dataset_path, split, label), video_name) return send_from_directory(img_dir, img_file, as_attachment=True)
def edit_class(project, class_name): """ Edit the class name and tags for an existing class in the given project. """ project = urllib.parse.unquote(project) class_name = urllib.parse.unquote(class_name) path = project_utils.lookup_project_path(project) # Get new class name and tags new_class_name, new_tag1, new_tag2 = utils.get_class_name_and_tags( request.form) # Update project config config = project_utils.load_project_config(path) del config['classes'][class_name] config['classes'][new_class_name] = [new_tag1, new_tag2] project_utils.write_project_config(path, config) # Update directory names data_dirs = [] for split in SPLITS: data_dirs.extend([ directories.get_videos_dir(path, split), directories.get_frames_dir(path, split), directories.get_tags_dir(path, split), ]) # Feature directories follow the format <dataset_dir>/<split>/<model>/<num_layers_to_finetune>/<label> features_dir = directories.get_features_dir(path, split) if os.path.exists(features_dir): model_dirs = [ os.path.join(features_dir, model_dir) for model_dir in os.listdir(features_dir) ] data_dirs.extend([ os.path.join(model_dir, tuned_layers) for model_dir in model_dirs for tuned_layers in os.listdir(model_dir) ]) logreg_dir = directories.get_logreg_dir(path) if os.path.exists(logreg_dir): data_dirs.extend([ os.path.join(logreg_dir, model_dir) for model_dir in os.listdir(logreg_dir) ]) for base_dir in data_dirs: class_dir = os.path.join(base_dir, class_name) if os.path.exists(class_dir): new_class_dir = os.path.join(base_dir, new_class_name) os.rename(class_dir, new_class_dir) return redirect(url_for('project_details', project=project))
def project_config(): """ Provide the config for a given project. """ data = request.json name = data['name'] path = project_utils.lookup_project_path(name) # Get config config = project_utils.load_project_config(path) return jsonify(config)
def show_video_list(project, split, label): """ Show the list of videos for the given split, class label and project. If the necessary files for annotation haven't been prepared yet, this is done now. """ project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) split = urllib.parse.unquote(split) label = urllib.parse.unquote(label) # load feature extractor inference_engine, model_config = utils.load_feature_extractor(path) videos_dir = directories.get_videos_dir(path, split, label) frames_dir = directories.get_frames_dir(path, split, label) features_dir = directories.get_features_dir(path, split, model_config, label=label) tags_dir = directories.get_tags_dir(path, split, label) logreg_dir = directories.get_logreg_dir(path, model_config, label) os.makedirs(logreg_dir, exist_ok=True) os.makedirs(tags_dir, exist_ok=True) # compute the features and frames missing compute_frames_and_features(inference_engine=inference_engine, project_path=path, videos_dir=videos_dir, frames_dir=frames_dir, features_dir=features_dir) videos = os.listdir(frames_dir) videos = natsorted(videos, alg=ns.IC) tagged_list = set(os.listdir(tags_dir)) tagged = [f'{video}.json' in tagged_list for video in videos] num_videos = len(videos) num_tagged = len(tagged_list) num_untagged = num_videos - num_tagged video_list = zip(videos, tagged, list(range(len(videos)))) return render_template('video_list.html', video_list=video_list, split=split, label=label, path=path, project=project, num_videos=num_videos, num_tagged=num_tagged, num_untagged=num_untagged)
def edit_tag(project, tag_idx): data = request.form project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) config = project_utils.load_project_config(path) tags = config['tags'] new_tag_name = data['newTagName'] # Update tag name tags[tag_idx] = new_tag_name project_utils.write_project_config(path, config) return redirect(url_for('project_details', project=project))
def remove_class(project, class_name): """ Remove the given class from the config file of the given project. No data will be deleted. """ project = urllib.parse.unquote(project) class_name = urllib.parse.unquote(class_name) path = project_utils.lookup_project_path(project) # Update project config config = project_utils.load_project_config(path) del config['classes'][class_name] project_utils.write_project_config(path, config) return redirect(url_for("project_details", project=project))
def create_tag(project): data = request.form project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) config = project_utils.load_project_config(path) tag_name = data['newTagName'] tags = config['tags'] new_tag_index = config['max_tag_index'] + 1 tags[new_tag_index] = tag_name config['max_tag_index'] = new_tag_index project_utils.write_project_config(path, config) return redirect(url_for('project_details', project=project))
def remove_tag(project, tag_idx): project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) config = project_utils.load_project_config(path) tags = config['tags'] # Remove tag from the overall tags list del tags[tag_idx] # Remove tag from the classes for class_label, class_tags in config['classes'].items(): if tag_idx in class_tags: class_tags.remove(tag_idx) project_utils.write_project_config(path, config) return redirect(url_for('project_details', project=project))
def record_video(project, split, label): """ Display the video recording screen. """ project = urllib.parse.unquote(project) split = urllib.parse.unquote(split) label = urllib.parse.unquote(label) path = project_utils.lookup_project_path(project) countdown, recording = project_utils.get_timer_default(path) return render_template('video_recording.html', project=project, split=split, label=label, path=path, countdown=countdown, recording=recording)
def send_training_logs(msg): global train_process global queue_train_logs global confmat_event try: while train_process.is_alive(): try: output = queue_train_logs.get(timeout=1) emit('training_logs', {'log': output}) except queue.Empty: # No message received during the last second pass train_process.terminate() train_process = None except AttributeError: # train_process has been cancelled and is None pass finally: queue_train_logs.close() project = msg['project'] output_folder = msg['outputFolder'] path = project_utils.lookup_project_path(project) img_path = os.path.join(path, 'checkpoints', output_folder, 'confusion_matrix.png') if confmat_event.is_set() and os.path.exists(img_path): with open(img_path, 'rb') as f: data = f.read() img_base64 = base64.b64encode(data).decode('utf-8') if img_base64: emit('success', { 'status': 'Complete', 'img': f'data:image/png;base64,{img_base64}' }) else: emit('failed', {'status': 'Failed'}) else: emit('failed', {'status': 'Failed'})
def add_class(project): """ Add a new class to the given project. """ data = request.form project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) class_name = data['className'] # Update project config config = project_utils.load_project_config(path) config['classes'][class_name] = [] project_utils.write_project_config(path, config) # Setup directory structure for split in SPLITS: videos_dir = directories.get_videos_dir(path, split, class_name) if not os.path.exists(videos_dir): os.mkdir(videos_dir) return redirect(url_for("project_details", project=project))
def project_details(project): """ Show the details for the selected project. """ project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) config = project_utils.load_project_config(path) stats = {} for class_name in config['classes']: stats[class_name] = {} for split in SPLITS: videos_dir = directories.get_videos_dir(path, split, class_name) tags_dir = directories.get_tags_dir(path, split, class_name) stats[class_name][split] = { 'total': len(os.listdir(videos_dir)), 'tagged': len(os.listdir(tags_dir)) if os.path.exists(tags_dir) else 0, 'videos': natsorted([video for video in os.listdir(videos_dir) if video.endswith(VIDEO_EXT)], alg=ns.IC) } tags = config['tags'] return render_template('project_details.html', config=config, path=path, stats=stats, project=config['name'], tags=tags)
def flip_videos(): """ Flip the videos horizontally for given class and copy tags of selected original videos for flipped version of it. """ data = request.json project = data['projectName'] path = project_utils.lookup_project_path(project) config = project_utils.load_project_config(path) counterpart_class_name = str(data['counterpartClassName']) original_class_name = str(data['originalClassName']) copy_video_tags = data['videosToCopyTags'] if counterpart_class_name not in config['classes']: config['classes'][counterpart_class_name] = config['classes'][original_class_name] \ if copy_video_tags['train'] or copy_video_tags['valid'] else [] project_utils.write_project_config(path, config) for split in SPLITS: videos_path_in = os.path.join(path, f'videos_{split}', original_class_name) videos_path_out = os.path.join(path, f'videos_{split}', counterpart_class_name) original_tags_path = os.path.join(path, f'tags_{split}', original_class_name) counterpart_tags_path = os.path.join(path, f'tags_{split}', counterpart_class_name) # Create directory to save flipped videos os.makedirs(videos_path_out, exist_ok=True) os.makedirs(counterpart_tags_path, exist_ok=True) video_list = [video for video in os.listdir(videos_path_in) if video.endswith(VIDEO_EXT)] for video in video_list: output_video_list = [op_video for op_video in os.listdir(videos_path_out) if op_video.endswith(VIDEO_EXT)] print(f'Processing video: {video}') if '_flipped' in video: flipped_video_name = ''.join(video.split('_flipped')) else: flipped_video_name = video.split('.')[0] + '_flipped' + VIDEO_EXT if flipped_video_name not in output_video_list: # Original video as input original_video = ffmpeg.input(os.path.join(videos_path_in, video)) # Do horizontal flip flipped_video = ffmpeg.hflip(original_video) # Get flipped video output flipped_video_output = ffmpeg.output(flipped_video, filename=os.path.join(videos_path_out, flipped_video_name)) # Run to render and save video ffmpeg.run(flipped_video_output) # Copy tags of original video to flipped video (in train/valid set) if video in copy_video_tags[split]: original_tags_file = os.path.join(original_tags_path, video.replace(VIDEO_EXT, '.json')) flipped_tags_file = os.path.join(counterpart_tags_path, flipped_video_name.replace(VIDEO_EXT, '.json')) if os.path.exists(original_tags_file): with open(original_tags_file) as f: original_video_tags = json.load(f) original_video_tags['file'] = flipped_video_name with open(flipped_tags_file, 'w') as f: f.write(json.dumps(original_video_tags, indent=2)) print("Processing complete!") return jsonify(status=True, url=url_for("project_details", project=project))
def annotate(project, split, label, idx): """ For the given class label, show all frames for annotating the selected video. """ project = urllib.parse.unquote(project) path = project_utils.lookup_project_path(project) label = urllib.parse.unquote(label) split = urllib.parse.unquote(split) _, model_config = utils.load_feature_extractor(path) frames_dir = directories.get_frames_dir(path, split, label) features_dir = directories.get_features_dir(path, split, model_config, label=label) tags_dir = directories.get_tags_dir(path, split, label) logreg_dir = directories.get_logreg_dir(path, model_config, label) videos = os.listdir(frames_dir) videos = natsorted(videos, alg=ns.IC) # The list of images in the folder images = [ image for image in glob.glob(os.path.join(frames_dir, videos[idx], '*')) if utils.is_image_file(image) ] classes = [-1] * len(images) # Load logistic regression model if available and assisted tagging is enabled if utils.get_project_setting(path, 'assisted_tagging'): logreg_path = os.path.join(logreg_dir, 'logreg.joblib') features_path = os.path.join(features_dir, f'{videos[idx]}.npy') if os.path.isfile(logreg_path) and os.path.isfile(features_path): logreg = load(logreg_path) features = np.load(features_path).mean(axis=(2, 3)) classes = list(logreg.predict(features)) # Natural sort images, so that they are sorted by number images = natsorted(images, alg=ns.IC) # Extract image file name (without full path) and include class label images = [(os.path.basename(image), _class) for image, _class in zip(images, classes)] # Load existing annotations annotations_file = os.path.join(tags_dir, f'{videos[idx]}.json') if os.path.exists(annotations_file): with open(annotations_file, 'r') as f: data = json.load(f) annotations = data['time_annotation'] else: # Use "background" label for all frames per default annotations = [0] * len(images) # Read tags from config config = project_utils.load_project_config(path) tags = config['classes'][label] return render_template('frame_annotation.html', images=images, annotations=annotations, idx=idx, fps=16, n_images=len(images), video_name=videos[idx], project_config=config, split=split, label=label, path=path, tags=tags, project=project, n_videos=len(videos))
def inject_project_config(project): path = project_utils.lookup_project_path(project) return project_utils.load_project_config(path)