""" app_utils.py Description: This file contains utility functions to support the Streamlit app. These functions handle file processing, video conversion, and inference running on uploaded images and videos. Author: Austin Powell """ import pandas as pd import altair as alt import io import av from tqdm import tqdm import numpy as np import logging import streamlit as st def extract_file_datetime(fname): """Extract datetime from file name Args: fname (str): File name Returns: pd.datetime: Datetime extracted from file name """ fname = os.path.basename(fname) dt = fname.split("_")[1] h,m,s = fname.split("_")[2].split(".")[0].split("-") return pd.to_datetime(f"{dt} {h}:{m}:{s}") def frames_to_video(frames=None, fps=12): """ Convert frames to video for Streamlit Args: frames: frame from cv2.VideoCapture as numpy. E.g. frame.astype(np.uint8) fps: Frames per second. Useful if the inference video is compressed to slow down for analysis """ # Grab information from the first frame height, width, layers = frames[0].shape # Create a BytesIO "in memory file" output_memory_file = io.BytesIO() # Open "in memory file" as MP4 video output output = av.open(output_memory_file, "w", format="mp4") # Add H.264 video stream to the MP4 container, with framerate = fps stream = output.add_stream("h264", str(fps)) # Set frame width and height stream.width = width stream.height = height # Set pixel format (yuv420p for better compatibility) stream.pix_fmt = "yuv420p" # Select low crf for high quality (the price is larger file size) stream.options = { "crf": "17" } # Iterate through the frames, encode, and write to MP4 memory file logging.info("INFO: Encoding frames and writing to MP4 format.") for frame in tqdm(frames): # Convert frame to av.VideoFrame format frame = av.VideoFrame.from_ndarray(frame.astype(np.uint8), format="bgr24") # Encode the video frame packet = stream.encode(frame) # "Mux" the encoded frame (add the encoded frame to MP4 file) output.mux(packet) # Flush the encoder packet = stream.encode(None) output.mux(packet) # Close the output video file output.close() # Reset the file pointer to the beginning of the memory file output_memory_file.seek(0) # Return the output memory file return output_memory_file def process_uploaded_file(): st.subheader("Upload your own video...") # Initialize accepted file types for upload img_types = ["jpg", "png", "jpeg"] video_types = ["mp4", "avi"] # Allow user to upload an image or video file uploaded_file = st.file_uploader("Select an image or video file...", type=img_types + video_types) # Display the uploaded file if uploaded_file is not None: if str(uploaded_file.type).split("/")[-1] in img_types: # Display uploaded image image = Image.open(uploaded_file) st.image(image, caption="Uploaded image", use_column_width=True) # TBD: Inference code to run and display for single image elif str(uploaded_file.type).split("/")[-1] in video_types: # Display uploaded video st.video(uploaded_file) # Convert streamlit video object to OpenCV format to run inferences tfile = tempfile.NamedTemporaryFile(delete=False) tfile.write(uploaded_file.read()) vf = cv.VideoCapture(tfile.name) # Run inference on the uploaded video with st.spinner("Running inference..."): frames, counts, timestamps = inference.main(vf) logging.info("INFO: Completed running inference on frames") st.balloons() # Convert OpenCV Numpy frames in-memory to IO Bytes for streamlit streamlit_video_file = frames_to_video(frames=frames, fps=11) # Show processed video and provide download button st.video(streamlit_video_file) st.download_button( label="Download processed video", data=streamlit_video_file, mime="mp4", file_name="processed_video.mp4", ) # Create dataframe for fish counts and timestamps df_counts_time = pd.DataFrame( data={"fish_count": counts, "timestamps": timestamps[1:]} ) # Display fish count vs. timestamp chart st.altair_chart( plot_count_date(dataframe=df_counts_time), use_container_width=True, ) else: st.write("No file uploaded")