From d767dae4ccf1188082d495402680431c60def316 Mon Sep 17 00:00:00 2001 From: Brian Geuther Date: Tue, 19 Aug 2025 16:06:08 -0400 Subject: [PATCH 1/5] Adding prototype of compression testing --- nextflow/configs/profiles/development.config | 1 + run_compression.nf | 124 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 run_compression.nf diff --git a/nextflow/configs/profiles/development.config b/nextflow/configs/profiles/development.config index ba49c04..25d56e5 100644 --- a/nextflow/configs/profiles/development.config +++ b/nextflow/configs/profiles/development.config @@ -110,6 +110,7 @@ singularity { process { executor = 'local' + maxForks = 1 // Since we can't assume local has multiple GPUs, we limit all tasks // Note that maxForks attached to GPU label will only limit processes of the same name diff --git a/run_compression.nf b/run_compression.nf new file mode 100644 index 0000000..d3249e4 --- /dev/null +++ b/run_compression.nf @@ -0,0 +1,124 @@ +nextflow.enable.dsl=2 + +include { PREPARE_DATA } from './nextflow/workflows/io' +include { MULTI_MOUSE_TRACKING } from './nextflow/workflows/multi_mouse_pipeline' + +/** + * Video compression using bitrate control + * + * @param tuple + * - video_file The input video file to compress + * - bitrate The target bitrate for the compression + * - keyframe_interval The keyframe interval for the compression + * + * @return tuple files + * - Original video + * - Compressed video + */ +process COMPRESS_VIDEO_CRF { + label "compression" + + input: + tuple path(video_file), val(crf), val(keyframe_interval) + + output: + tuple path(video_file), path("${video_file.baseName}_g${keyframe_interval}_crf${crf}.mp4"), emit: files + + script: + """ + ffmpeg -i ${video_file} -c:v libx264 -pix_fmt yuv420p -preset slow -crf ${crf} -g ${keyframe_interval} -f mp4 ${video_file.baseName}_g${keyframe_interval}_crf${crf}.mp4 + """ +} + +/** + * Video compression using bitrate control + * + * @param tuple + * - video_file The input video file to compress + * - bitrate The target bitrate for the compression + * - keyframe_interval The keyframe interval for the compression + * + * @return tuple files + * - Original video + * - Compressed video + */ +process COMPRESS_VIDEO_BR { + label "compression" + + input: + tuple path(video_file), val(bitrate), val(keyframe_interval) + + output: + tuple path(video_file), path("${video_file.baseName}_r${bitrate}_g${keyframe_interval}.mp4"), emit: files + + script: + """ + ffmpeg -i ${video_file} -c:v libx264 -b:v ${bitrate}k -maxrate ${bitrate}k -bufsize \$((${bitrate}*2))k -g ${keyframe_interval} -pass 1 -f mp4 ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 && \ + ffmpeg -i ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 -c:v libx264 -b:v ${bitrate}k -maxrate ${bitrate}k -bufsize \$((${bitrate}*2))k -g ${keyframe_interval} -pass 2 ${video_file.baseName}_r${bitrate}_g${keyframe_interval}.mp4 && \ + rm ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 ffmpeg2pass-0.log ffmpeg2pass-0.log.mbtree + + """ +} + +/** + * Computes a difference between two videos + * + * @param in_video1 The first input video file + * @param in_video2 The second input video file + * + * @return The output video file with the computed difference + */ +process COMPUTE_VIDEO_DIFFERENCE { + label "tracking" + publishDir "compressed/", mode:'copy' + + input: + tuple path(in_video1), path(in_video2) + + output: + path "${in_video2.baseName}_diff.mp4" + + script: + """ + ffmpeg -i ${in_video1} -i ${in_video2} -filter_complex '[0:v]setsar=1:1[v1];[v1][1:v]blend=all_mode=difference128' -c:v mpeg4 -q 0 ${in_video2.baseName}_diff.mp4 + """ +} + +/** + * Render pose on video + * + * @param in_video The input video file + * @param in_pose The input pose file + * + * @return Rendered video + */ +process RENDER_POSE { + label "tracking" + + input: + tuple path(in_video), path(in_pose) + + output: + path "${in_video.baseName}_pose.mp4" + + script: + """ + python3 /kumar_lab_models/mouse-tracking-runtime/render_pose.py --in-vid ${in_video} --in-pose ${in_pose} --out-vid ${in_video.baseName}_pose.mp4 + """ +} + +workflow { + crf_set = channel.of(17, 23, 29) + bitrate_set = channel.of(1000, 500, 100) + keyframe_set = channel.of(3000) + + // Iterate over all combinations of CRF, bitrate, and keyframe interval + in_videos = PREPARE_DATA(params.input_batch, params.location, false).file_processing_channel + crf_videos = COMPRESS_VIDEO_CRF(in_videos.combine(crf_set).combine(keyframe_set)).files + bitrate_videos = COMPRESS_VIDEO_BR(in_videos.combine(bitrate_set).combine(keyframe_set)).files + all_videos = crf_videos.map { original, compressed -> compressed }.concat(bitrate_videos.map { original, compressed -> compressed }) + + COMPUTE_VIDEO_DIFFERENCE(crf_videos.concat(bitrate_videos)) + //pose_out = MULTI_MOUSE_TRACKING(all_videos, params.num_mice).pose_v6 + //RENDER_POSE(pose_out) +} \ No newline at end of file From 4ee7a919d0f72a4074e7e14e30abacf3353e0e69 Mon Sep 17 00:00:00 2001 From: Brian Geuther Date: Wed, 20 Aug 2025 10:03:14 -0400 Subject: [PATCH 2/5] Enabling pose estimation and rendering --- run_compression.nf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run_compression.nf b/run_compression.nf index d3249e4..4823004 100644 --- a/run_compression.nf +++ b/run_compression.nf @@ -94,6 +94,7 @@ process COMPUTE_VIDEO_DIFFERENCE { */ process RENDER_POSE { label "tracking" + publishDir "compressed/pose/", mode:'copy' input: tuple path(in_video), path(in_pose) @@ -119,6 +120,6 @@ workflow { all_videos = crf_videos.map { original, compressed -> compressed }.concat(bitrate_videos.map { original, compressed -> compressed }) COMPUTE_VIDEO_DIFFERENCE(crf_videos.concat(bitrate_videos)) - //pose_out = MULTI_MOUSE_TRACKING(all_videos, params.num_mice).pose_v6 - //RENDER_POSE(pose_out) + pose_out = MULTI_MOUSE_TRACKING(all_videos, params.num_mice).pose_v6 + RENDER_POSE(pose_out) } \ No newline at end of file From 120b97bf799aa48034366bb6ed832e1a32901e81 Mon Sep 17 00:00:00 2001 From: Brian Geuther Date: Fri, 22 Aug 2025 13:51:28 -0400 Subject: [PATCH 3/5] Organizing folder of results. --- run_compression.nf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/run_compression.nf b/run_compression.nf index 4823004..2731856 100644 --- a/run_compression.nf +++ b/run_compression.nf @@ -17,6 +17,7 @@ include { MULTI_MOUSE_TRACKING } from './nextflow/workflows/multi_mouse_pipeline */ process COMPRESS_VIDEO_CRF { label "compression" + publishDir "compressed/compressed/", mode:'copy' input: tuple path(video_file), val(crf), val(keyframe_interval) @@ -44,6 +45,7 @@ process COMPRESS_VIDEO_CRF { */ process COMPRESS_VIDEO_BR { label "compression" + publishDir "compressed/compressed/", mode:'copy' input: tuple path(video_file), val(bitrate), val(keyframe_interval) @@ -70,7 +72,7 @@ process COMPRESS_VIDEO_BR { */ process COMPUTE_VIDEO_DIFFERENCE { label "tracking" - publishDir "compressed/", mode:'copy' + publishDir "compressed/difference/", mode:'copy' input: tuple path(in_video1), path(in_video2) @@ -117,7 +119,7 @@ workflow { in_videos = PREPARE_DATA(params.input_batch, params.location, false).file_processing_channel crf_videos = COMPRESS_VIDEO_CRF(in_videos.combine(crf_set).combine(keyframe_set)).files bitrate_videos = COMPRESS_VIDEO_BR(in_videos.combine(bitrate_set).combine(keyframe_set)).files - all_videos = crf_videos.map { original, compressed -> compressed }.concat(bitrate_videos.map { original, compressed -> compressed }) + all_videos = in_videos.concat(crf_videos.map { original, compressed -> compressed }).concat(bitrate_videos.map { original, compressed -> compressed }) COMPUTE_VIDEO_DIFFERENCE(crf_videos.concat(bitrate_videos)) pose_out = MULTI_MOUSE_TRACKING(all_videos, params.num_mice).pose_v6 From e30f6b6b54d3d2ada497379829372805393ad842 Mon Sep 17 00:00:00 2001 From: Brian Geuther Date: Thu, 4 Sep 2025 13:24:00 -0400 Subject: [PATCH 4/5] Moving modules into more permanent locations Removed difference calculation for changed module return tuple --- nextflow/modules/compression.nf | 81 +++++++++++++++++++++++ nextflow/modules/pose_qc.nf | 23 +++++++ run_compression.nf | 112 +------------------------------- 3 files changed, 107 insertions(+), 109 deletions(-) create mode 100644 nextflow/modules/compression.nf create mode 100644 nextflow/modules/pose_qc.nf diff --git a/nextflow/modules/compression.nf b/nextflow/modules/compression.nf new file mode 100644 index 0000000..fed540d --- /dev/null +++ b/nextflow/modules/compression.nf @@ -0,0 +1,81 @@ +/** + * Video compression using quality control + * + * @param tuple + * - video_file The input video file to compress + * - crf The constant rate factor for the compression + * - keyframe_interval The keyframe interval for the compression + * + * @return file Path to compressed video + */ +process COMPRESS_VIDEO_CRF { + label "compression" + + input: + tuple path(video_file), val(crf), val(keyframe_interval) + + output: + path("${video_file.baseName}_g${keyframe_interval}_crf${crf}.mp4"), emit: file + + script: + """ + ffmpeg -i ${video_file} -c:v libx264 -pix_fmt yuv420p -preset slow -crf ${crf} -g ${keyframe_interval} -f mp4 ${video_file.baseName}_g${keyframe_interval}_crf${crf}.mp4 + """ +} + +/** + * Video compression using bitrate control + * + * Runs a 2-pass encoding of the video with a target bitrate. + * + * @param tuple + * - video_file The input video file to compress + * - bitrate The target bitrate for the compression + * - keyframe_interval The keyframe interval for the compression + * + * @return file Path to compressed video + * + * @note COMPRESS_VIDEO_CRF will typically produce better videos with a low signal-to-noise ratio. + */ +process COMPRESS_VIDEO_BR { + label "compression" + + input: + tuple path(video_file), val(bitrate), val(keyframe_interval) + + output: + path("${video_file.baseName}_r${bitrate}_g${keyframe_interval}.mp4"), emit: file + + script: + """ + ffmpeg -i ${video_file} -c:v libx264 -b:v ${bitrate}k -maxrate ${bitrate}k -bufsize \$((${bitrate}*2))k -g ${keyframe_interval} -pass 1 -f mp4 ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 && \ + ffmpeg -i ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 -c:v libx264 -b:v ${bitrate}k -maxrate ${bitrate}k -bufsize \$((${bitrate}*2))k -g ${keyframe_interval} -pass 2 ${video_file.baseName}_r${bitrate}_g${keyframe_interval}.mp4 && \ + rm ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 ffmpeg2pass-0.log ffmpeg2pass-0.log.mbtree + + """ +} + +/** + * Computes a difference between two videos + * + * @param in_video1 The first input video file + * @param in_video2 The second input video file + * + * @return The output video file with the computed difference + * + * @note Difference videos are useful in comparing changes due to compression. + */ +process COMPUTE_VIDEO_DIFFERENCE { + label "tracking" + + input: + tuple path(in_video1), path(in_video2) + + output: + path "${in_video2.baseName}_diff.mp4" + + script: + """ + ffmpeg -i ${in_video1} -i ${in_video2} -filter_complex '[0:v]setsar=1:1[v1];[v1][1:v]blend=all_mode=difference128' -c:v mpeg4 -q 0 ${in_video2.baseName}_diff.mp4 + """ +} diff --git a/nextflow/modules/pose_qc.nf b/nextflow/modules/pose_qc.nf new file mode 100644 index 0000000..1a480d4 --- /dev/null +++ b/nextflow/modules/pose_qc.nf @@ -0,0 +1,23 @@ +/** + * Render pose on video + * + * @param in_video The input video file + * @param in_pose The input pose file + * + * @return Rendered video + */ +process RENDER_POSE { + label "tracking" + publishDir "compressed/pose/", mode:'copy' + + input: + tuple path(in_video), path(in_pose) + + output: + path "${in_video.baseName}_pose.mp4" + + script: + """ + python3 /kumar_lab_models/mouse-tracking-runtime/render_pose.py --in-vid ${in_video} --in-pose ${in_pose} --out-vid ${in_video.baseName}_pose.mp4 + """ +} diff --git a/run_compression.nf b/run_compression.nf index 2731856..a8ccf74 100644 --- a/run_compression.nf +++ b/run_compression.nf @@ -2,113 +2,8 @@ nextflow.enable.dsl=2 include { PREPARE_DATA } from './nextflow/workflows/io' include { MULTI_MOUSE_TRACKING } from './nextflow/workflows/multi_mouse_pipeline' - -/** - * Video compression using bitrate control - * - * @param tuple - * - video_file The input video file to compress - * - bitrate The target bitrate for the compression - * - keyframe_interval The keyframe interval for the compression - * - * @return tuple files - * - Original video - * - Compressed video - */ -process COMPRESS_VIDEO_CRF { - label "compression" - publishDir "compressed/compressed/", mode:'copy' - - input: - tuple path(video_file), val(crf), val(keyframe_interval) - - output: - tuple path(video_file), path("${video_file.baseName}_g${keyframe_interval}_crf${crf}.mp4"), emit: files - - script: - """ - ffmpeg -i ${video_file} -c:v libx264 -pix_fmt yuv420p -preset slow -crf ${crf} -g ${keyframe_interval} -f mp4 ${video_file.baseName}_g${keyframe_interval}_crf${crf}.mp4 - """ -} - -/** - * Video compression using bitrate control - * - * @param tuple - * - video_file The input video file to compress - * - bitrate The target bitrate for the compression - * - keyframe_interval The keyframe interval for the compression - * - * @return tuple files - * - Original video - * - Compressed video - */ -process COMPRESS_VIDEO_BR { - label "compression" - publishDir "compressed/compressed/", mode:'copy' - - input: - tuple path(video_file), val(bitrate), val(keyframe_interval) - - output: - tuple path(video_file), path("${video_file.baseName}_r${bitrate}_g${keyframe_interval}.mp4"), emit: files - - script: - """ - ffmpeg -i ${video_file} -c:v libx264 -b:v ${bitrate}k -maxrate ${bitrate}k -bufsize \$((${bitrate}*2))k -g ${keyframe_interval} -pass 1 -f mp4 ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 && \ - ffmpeg -i ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 -c:v libx264 -b:v ${bitrate}k -maxrate ${bitrate}k -bufsize \$((${bitrate}*2))k -g ${keyframe_interval} -pass 2 ${video_file.baseName}_r${bitrate}_g${keyframe_interval}.mp4 && \ - rm ${video_file.baseName}_r${bitrate}_g${keyframe_interval}_PASS1.mp4 ffmpeg2pass-0.log ffmpeg2pass-0.log.mbtree - - """ -} - -/** - * Computes a difference between two videos - * - * @param in_video1 The first input video file - * @param in_video2 The second input video file - * - * @return The output video file with the computed difference - */ -process COMPUTE_VIDEO_DIFFERENCE { - label "tracking" - publishDir "compressed/difference/", mode:'copy' - - input: - tuple path(in_video1), path(in_video2) - - output: - path "${in_video2.baseName}_diff.mp4" - - script: - """ - ffmpeg -i ${in_video1} -i ${in_video2} -filter_complex '[0:v]setsar=1:1[v1];[v1][1:v]blend=all_mode=difference128' -c:v mpeg4 -q 0 ${in_video2.baseName}_diff.mp4 - """ -} - -/** - * Render pose on video - * - * @param in_video The input video file - * @param in_pose The input pose file - * - * @return Rendered video - */ -process RENDER_POSE { - label "tracking" - publishDir "compressed/pose/", mode:'copy' - - input: - tuple path(in_video), path(in_pose) - - output: - path "${in_video.baseName}_pose.mp4" - - script: - """ - python3 /kumar_lab_models/mouse-tracking-runtime/render_pose.py --in-vid ${in_video} --in-pose ${in_pose} --out-vid ${in_video.baseName}_pose.mp4 - """ -} +include { COMPRESS_VIDEO_CRF; COMPRESS_VIDEO_BR; COMPUTE_VIDEO_DIFFERENCE } from './nextflow/workflows/compression' +include { RENDER_POSE } from './nextflow/workflows/pose_qc' workflow { crf_set = channel.of(17, 23, 29) @@ -119,9 +14,8 @@ workflow { in_videos = PREPARE_DATA(params.input_batch, params.location, false).file_processing_channel crf_videos = COMPRESS_VIDEO_CRF(in_videos.combine(crf_set).combine(keyframe_set)).files bitrate_videos = COMPRESS_VIDEO_BR(in_videos.combine(bitrate_set).combine(keyframe_set)).files - all_videos = in_videos.concat(crf_videos.map { original, compressed -> compressed }).concat(bitrate_videos.map { original, compressed -> compressed }) + all_videos = in_videos.concat(crf_videos).concat(bitrate_videos) - COMPUTE_VIDEO_DIFFERENCE(crf_videos.concat(bitrate_videos)) pose_out = MULTI_MOUSE_TRACKING(all_videos, params.num_mice).pose_v6 RENDER_POSE(pose_out) } \ No newline at end of file From 43435fd8267f477e6dabd70d98ce13af4e8a98b0 Mon Sep 17 00:00:00 2001 From: Brian Geuther Date: Thu, 4 Sep 2025 14:40:59 -0400 Subject: [PATCH 5/5] Integrating compression into multimouse and removing experimental workflow --- nextflow/workflows/multi_mouse_pipeline.nf | 7 +++++++ run_compression.nf | 21 --------------------- 2 files changed, 7 insertions(+), 21 deletions(-) delete mode 100644 run_compression.nf diff --git a/nextflow/workflows/multi_mouse_pipeline.nf b/nextflow/workflows/multi_mouse_pipeline.nf index 9c71d7f..d8453fc 100644 --- a/nextflow/workflows/multi_mouse_pipeline.nf +++ b/nextflow/workflows/multi_mouse_pipeline.nf @@ -10,6 +10,7 @@ include { PREDICT_ARENA_CORNERS; include { VIDEO_TO_POSE; PUBLISH_RESULT_FILE as PUBLISH_MM_POSE_V6; } from "${projectDir}/nextflow/modules/utils" + include { COMPRESS_VIDEO_CRF } from "${projectDir}/nextflow/modules/compression" workflow MULTI_MOUSE_TRACKING { take: @@ -33,6 +34,12 @@ workflow MULTI_MOUSE_TRACKING { } PUBLISH_MM_POSE_V6(v_6_poses_renamed) + // Compress the original video + compressed_videos = COMPRESS_VIDEO_CRF(input_video.combine([23]).combine([3000])) + compressed_renamed = compressed_videos.map { video -> + tuple(video, "results/${video.baseName.replace("_g3000_crf23", "")}_compressed.mp4") + } + emit: pose_v6 } diff --git a/run_compression.nf b/run_compression.nf deleted file mode 100644 index a8ccf74..0000000 --- a/run_compression.nf +++ /dev/null @@ -1,21 +0,0 @@ -nextflow.enable.dsl=2 - -include { PREPARE_DATA } from './nextflow/workflows/io' -include { MULTI_MOUSE_TRACKING } from './nextflow/workflows/multi_mouse_pipeline' -include { COMPRESS_VIDEO_CRF; COMPRESS_VIDEO_BR; COMPUTE_VIDEO_DIFFERENCE } from './nextflow/workflows/compression' -include { RENDER_POSE } from './nextflow/workflows/pose_qc' - -workflow { - crf_set = channel.of(17, 23, 29) - bitrate_set = channel.of(1000, 500, 100) - keyframe_set = channel.of(3000) - - // Iterate over all combinations of CRF, bitrate, and keyframe interval - in_videos = PREPARE_DATA(params.input_batch, params.location, false).file_processing_channel - crf_videos = COMPRESS_VIDEO_CRF(in_videos.combine(crf_set).combine(keyframe_set)).files - bitrate_videos = COMPRESS_VIDEO_BR(in_videos.combine(bitrate_set).combine(keyframe_set)).files - all_videos = in_videos.concat(crf_videos).concat(bitrate_videos) - - pose_out = MULTI_MOUSE_TRACKING(all_videos, params.num_mice).pose_v6 - RENDER_POSE(pose_out) -} \ No newline at end of file