Skip to content

Commit

Permalink
codes for coco, clip video, bash scripts (#64)
Browse files Browse the repository at this point in the history
adding helper scripts for labelling prep tasks
  • Loading branch information
nikk-nikaznan authored Oct 25, 2023
1 parent f2b7e2a commit 539ae9b
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 0 deletions.
143 changes: 143 additions & 0 deletions bboxes_labelling/clip_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import argparse
import cv2
from pathlib import Path
from datetime import datetime


def real_time_to_frame_number(
real_time: datetime, video_fps: float, start_real_time: datetime
) -> int:
"""
Convert a real-time timestamp to the corresponding frame number in a video.
Parameters:
real_time (datetime): The real-time timestamp.
video_fps (float): Frames per second of the video.
start_real_time (datetime): The starting real-time timestamp of the video.
Returns:
int: The corresponding frame number in the video.
"""
time_difference = real_time - start_real_time
total_seconds = time_difference.total_seconds()
return int(total_seconds * video_fps)


def create_clip(
input_file: str, start_frame: int, end_frame: int, output_file: str
) -> None:
"""
Create a video clip from the input video file, starting from a specific frame
and ending at another frame.
Parameters:
input_file (str): Path to the input video file.
start_frame (int): Starting frame number.
end_frame (int): Ending frame number.
output_file (str): Path to the output video file to be created.
Returns:
None
"""
cap = cv2.VideoCapture(input_file)
video_fps = cap.get(cv2.CAP_PROP_FPS)

fourcc = cv2.VideoWriter_fourcc(*"avc1")
out = cv2.VideoWriter(
output_file, fourcc, video_fps, (int(cap.get(3)), int(cap.get(4))), isColor=True
)

cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)

while cap.isOpened():
ret, frame = cap.read()
if not ret or cap.get(cv2.CAP_PROP_POS_FRAMES) > end_frame:
break

out.write(frame)

cap.release()
out.release()
cv2.destroyAllWindows()


def argument_parser() -> argparse.Namespace:
"""Parse command-line arguments for the script.
Returns
-------
argparse.Namespace
An object containing the parsed command-line arguments.
The attributes of this object correspond to the defined
command-line arguments in the script.
"""

parser = argparse.ArgumentParser()
parser.add_argument(
"--video_path",
type=str,
required=True,
help="Location of video file.",
)
parser.add_argument(
"--start_time",
type=str,
default="12:00:00",
help="Start time in the format 'HH:MM:SS'.",
)
parser.add_argument(
"--event_time",
type=str,
default="12:01:00",
help="Event time in the format 'HH:MM:SS'.",
)
parser.add_argument(
"--end_time",
type=str,
default="12:03:00",
help="Time after the event in the format 'HH:MM:SS'.",
)
parser.add_argument(
"--out_path",
type=str,
required=True,
help="Location of video file.",
)
args = parser.parse_args()
return args


if __name__ == "__main__":
args = argument_parser()

input_file = args.video_path
file_name = f"{Path(args.video_path).parent.stem}_" f"{Path(args.video_path).stem}_"

start_real_time = datetime.strptime(args.start_time, "%H:%M:%S")
event_time = datetime.strptime(args.event_time, "%H:%M:%S")
after_event_time = datetime.strptime(args.end_time, "%H:%M:%S")

# Convert event times to frame numbers
cap = cv2.VideoCapture(args.video_path)
video_fps = cap.get(cv2.CAP_PROP_FPS)
start_frame = real_time_to_frame_number(start_real_time, video_fps, start_real_time)
event_frame = real_time_to_frame_number(event_time, video_fps, start_real_time)
after_event_frame = real_time_to_frame_number(
after_event_time, video_fps, start_real_time
)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
cap.release()

# Create pre-event clip
pre_event_clip = f"{args.out_path}/{file_name}_pre_event.mp4"
create_clip(args.video_path, start_frame, event_frame - 1, pre_event_clip)

# Create event clip
event_clip = f"{args.out_path}/{file_name}_event.mp4"
create_clip(args.video_path, event_frame, after_event_frame - 1, event_clip)

# Create post-event clip
post_event_clip = f"{args.out_path}/{file_name}_post_event.mp4"
create_clip(args.video_path, after_event_frame, total_frames - 1, post_event_clip)

print("Clips created successfully!")
91 changes: 91 additions & 0 deletions bboxes_labelling/combine_coco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import json
import argparse


def combine_coco(coco1: str, coco2: str) -> None:
"""
Combine two COCO format JSON files and save the combined data to a new file.
Parameters:
coco1 (str): Path to the first COCO format JSON file.
coco2 (str): Path to the second COCO format JSON file.
Returns:
None
"""
# Load the contents of the first JSON file
with open(coco1, "r") as file1:
data1 = json.load(file1)

# Load the contents of the second JSON file
with open(coco2, "r") as file2:
data2 = json.load(file2)

# Calculate the offset for image and annotation IDs in the second dataset
offset_image_id = max([image["id"] for image in data1["images"]])
offset_annotation_id = max(
[annotation["id"] for annotation in data1["annotations"]]
)

# Update the image and annotation IDs in the second dataset
for image in data2["images"]:
image["id"] += offset_image_id
for annotation in data2["annotations"]:
annotation["id"] += offset_annotation_id
annotation["image_id"] += offset_image_id

# Combine the images and annotations from both datasets
combined_images = data1["images"] + data2["images"]
combined_annotations = data1["annotations"] + data2["annotations"]

# Create a new COCO dataset dictionary
combined_data = {
"images": combined_images,
"annotations": combined_annotations,
"categories": data1["categories"],
}

# Extract filenames without extensions from input paths
filename1 = coco1.split(".")[0]
filename2 = coco2.split(".")[0]

# Create a new filename for the combined JSON file
combined_filename = f"{filename1}_{filename2}_combine.json"

# Save the combined data to the new JSON file
with open(combined_filename, "w") as combined_file:
json.dump(combined_data, combined_file)


def argument_parser() -> argparse.Namespace:
"""
Parse command-line arguments for the script.
Returns
-------
argparse.Namespace
An object containing the parsed command-line arguments.
The attributes of this object correspond to the defined
command-line arguments in the script.
"""
parser = argparse.ArgumentParser()

parser.add_argument(
"--coco_one_path",
type=str,
required=True,
help="Path for the the first coco file to be combined",
)
parser.add_argument(
"--coco_two_path",
type=str,
required=True,
help="Path for the the second coco file to be combined",
)
return parser.parse_args()


if __name__ == "__main__":
args = argument_parser()

combine_coco(args.coco_one_path, args.coco_two_path)
102 changes: 102 additions & 0 deletions bboxes_labelling/convert_coco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import json
import argparse
from typing import Dict, Any


def coco_conversion(json_file_path: str) -> None:
"""
Convert annotation data in a JSON format to COCO format.
Parameters:
json_file (str): Path to the input JSON file containing annotation data.
Returns:
None
This function takes annotation data in a specific JSON format and converts it
into COCO format, which is widely used for object detection datasets.
Note:
The function assumes that the input JSON data has a specific structure
that includes image metadata and regions of interest. The JSON data used
has been produced by VIA.
"""

# Load the JSON data
with open(json_file_path, "r") as json_file:
annotation_data = json.load(json_file)

# Create COCO format data structures
coco_data: Dict[str, Any] = {
"info": {},
"licenses": [],
"categories": [{"id": 1, "name": "crab", "supercategory": "animal"}],
"images": [],
"annotations": [],
}

# Iterate through each image and annotation
image_id = 1
annotation_id = 1
for image_filename, image_info in annotation_data["_via_img_metadata"].items():
image_data: Dict[str, Any] = {
"id": image_id,
"width": 0, # Set the image width here
"height": 0, # Set the image height here
"file_name": image_info["filename"],
}
coco_data["images"].append(image_data)

for region in image_info["regions"]:
x, y, width, height = (
region["shape_attributes"]["x"],
region["shape_attributes"]["y"],
region["shape_attributes"]["width"],
region["shape_attributes"]["height"],
)

annotation_data = {
"id": annotation_id,
"image_id": image_id,
"category_id": 1,
"bbox": [x, y, width, height],
"area": width * height,
"iscrowd": 0,
}
coco_data["annotations"].append(annotation_data)
annotation_id += 1

image_id += 1

# Write the COCO data to a JSON file
new_file_name = f"{json_file_path.split('.')[0]}_coco.json"
with open(new_file_name, "w") as f:
json.dump(coco_data, f)


def argument_parser() -> argparse.Namespace:
"""
Parse command-line arguments for the script.
Returns
-------
argparse.Namespace
An object containing the parsed command-line arguments.
The attributes of this object correspond to the defined
command-line arguments in the script.
"""
parser = argparse.ArgumentParser()

parser.add_argument(
"--json_path",
type=str,
required=True,
help="Path for the json saved from VIA",
)
return parser.parse_args()


if __name__ == "__main__":
args = argument_parser()

coco_conversion(args.json_path)
36 changes: 36 additions & 0 deletions bboxes_labelling/run_additional_channel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash

#SBATCH -p gpu # partition
#SBATCH -N 1 # number of nodes
#SBATCH --mem 8G # memory pool for all cores
#SBATCH -n 2 # number of cores
#SBATCH -t 3-00:00 # time (D-HH:MM)
#SBATCH --mail-type=ALL
#SBATCH [email protected]

# ---------------------
# Load required modules
# ----------------------
module load SLEAP

# ---------------------
# Define environment variables
# ----------------------
# input/output dirs
INPUT_DIR=/ceph/zoo/users/sminano/crabs_bboxes_labels/20230816_ramalhete2023_day2_combined/extracted_frames.json
OUTPUT_DIR=/ceph/zoo/users/nikkna/event_clips/stacked_images/

# script location
SCRATCH_PERSONAL_DIR=/ceph/scratch/nikkna
SCRIPT_DIR=$SCRATCH_PERSONAL_DIR/crabs-exploration/"bboxes labelling"

# TODO: set NUMEXPR_MAX_THREADS?
# NumExpr detected 40 cores but "NUMEXPR_MAX_THREADS" not set,
# so enforcing safe limit of 8.

# -------------------
# Run python script
# -------------------
python "$SCRIPT_DIR"/additional_channels_extraction.py \
--json_path $INPUT_DIR \
--out_dir $OUTPUT_DIR \;Q:q
Loading

0 comments on commit 539ae9b

Please sign in to comment.