Fish 3D / 4D reconstruction from a static BRUV video

SAU23_011 (D2_5LB) · GoPro baited remote underwater video, Red Sea · page rebuilt automatically while the production runs — reload for updates.
44fish selected
44reconstructed (4D)
24 / 15 / 5models good / fair / poor
10species (top-1)
810-min windows
25,529clean frames (10 fps)
49 minof fish on screen
16 / 44v2 sharper than v1 (20 evaluated · details)
Pipeline. Frames (10 fps) → YOLO-World "fish" detector + BoT-SORT tracking → tracklet merge by DINOv2 appearance (no co-occurrence) → SAM 2.1 masks → identity / occlusion cleaning → per-frame 3D pose from silhouette moments + swimming-direction prior (Viterbi over yaw / pitch, head/tail and toward/away ambiguities resolved) → background plates + soft alpha (ViTMatte) for translucent fins → 30 fps re-extraction → 4D Gaussian splatting in the fish frame (camera "orbits" the fish): one canonical symmetric model + fin sheet, per-frame body bending and tail articulation, pose / colour refinement → species with BioCLIP 2.5 zero-shot over 332 Red Sea species (multi-crop vote, confidence label).

Timeline: where every fish appears in the video

0:005:0010:0015:0020:0025:0030:0035:0040:0045:0050:0055:0060:0065:0070:0075:00w02_fish1w02_fish1 02:46-02:50 Checkerboard wrassew02_fish1 04:16-04:19 Checkerboard wrassew02_fish1 05:12-05:17 Checkerboard wrassew02_fish1 05:41-05:44 Checkerboard wrassew02_fish1 07:55-08:03 Checkerboard wrassew02_fish1 08:13-08:19 Checkerboard wrassew02_fish1 08:33-08:36 Checkerboard wrassew02_fish1 08:40-08:42 Checkerboard wrassew02_fish1 08:45-08:52 Checkerboard wrassew02_fish1 08:56-08:59 Checkerboard wrassew02_fish1 09:19-09:21 Checkerboard wrassew02_fish1 09:27-09:30 Checkerboard wrassew02_fish10w02_fish10 02:56-02:58 Cheeklined wrassew02_fish10 03:02-03:05 Cheeklined wrassew02_fish10 04:54-04:56 Cheeklined wrassew02_fish10 07:30-07:35 Cheeklined wrassew02_fish10 07:38-07:42 Cheeklined wrassew02_fish10 07:56-07:59 Cheeklined wrassew02_fish10 10:25-10:26 Cheeklined wrassew02_fish10 10:37-10:39 Cheeklined wrassew02_fish2w02_fish2 02:22-02:23 Checkerboard wrassew02_fish2 02:31-02:33 Checkerboard wrassew02_fish2 02:42-02:45 Checkerboard wrassew02_fish2 02:58-03:11 Checkerboard wrassew02_fish2 03:46-03:49 Checkerboard wrassew02_fish2 04:20-04:24 Checkerboard wrassew02_fish2 04:29-04:31 Checkerboard wrassew02_fish2 04:34-04:39 Checkerboard wrassew02_fish2 04:49-04:52 Checkerboard wrassew02_fish2 05:37-05:41 Checkerboard wrassew02_fish2 06:44-06:55 Checkerboard wrassew02_fish2 07:13-07:16 Checkerboard wrassew02_fish2 07:25-07:33 Checkerboard wrassew02_fish2 07:35-07:38 Checkerboard wrassew02_fish2 08:13-08:16 Checkerboard wrassew02_fish2 09:36-09:40 Checkerboard wrassew02_fish4w02_fish4 05:01-05:03 Orange-lined triggerfishw02_fish4 08:09-08:14 Orange-lined triggerfishw02_fish4 09:03-09:10 Orange-lined triggerfishw02_fish4 09:17-09:24 Orange-lined triggerfishw02_fish4 09:41-09:46 Orange-lined triggerfishw02_fish4 11:16-11:26 Orange-lined triggerfishw02_fish4 11:47-11:51 Orange-lined triggerfishw02_fish6w02_fish6 05:43-05:45 Bluestreak cleaner wrassew02_fish6 05:55-05:57 Bluestreak cleaner wrassew02_fish6 06:58-07:03 Bluestreak cleaner wrassew02_fish6 07:24-07:28 Bluestreak cleaner wrassew02_fish6 09:20-09:22 Bluestreak cleaner wrassew02_fish6 11:27-11:30 Bluestreak cleaner wrassew02_fish7w02_fish7 03:22-03:24 Chocolate-dip chromisw02_fish7 03:28-03:31 Chocolate-dip chromisw02_fish7 07:37-07:40 Chocolate-dip chromisw02_fish7 08:20-08:23 Chocolate-dip chromisw02_fish7 08:26-08:29 Chocolate-dip chromisw02_fish7 08:54-08:58 Chocolate-dip chromisw02_fish7 09:09-09:20 Chocolate-dip chromisw02_fish7 09:36-09:39 Chocolate-dip chromisw02_fish7 10:16-10:20 Chocolate-dip chromisw02_fish7 10:36-10:37 Chocolate-dip chromisw02_fish7 10:58-11:00 Chocolate-dip chromisw02_fish7 11:54-11:59 Chocolate-dip chromisw02_fish8w02_fish8 07:10-07:12 Cheeklined wrassew02_fish8 07:27-07:32 Cheeklined wrassew02_fish8 08:21-08:30 Cheeklined wrassew02_fish8 09:18-09:20 Cheeklined wrassew02_fish8 11:57-11:59 Cheeklined wrassew02_fish9w02_fish9 02:34-02:37 Cheeklined wrassew02_fish9 05:15-05:17 Cheeklined wrassew02_fish9 06:28-06:30 Cheeklined wrassew02_fish9 06:46-06:48 Cheeklined wrassew02_fish9 07:16-07:18 Cheeklined wrassew02_fish9 08:27-08:30 Cheeklined wrassew02_fish9 08:47-08:50 Cheeklined wrassew02_fish9 09:28-09:34 Cheeklined wrassew02_fish9 11:02-11:04 Cheeklined wrassew02_fish9 11:14-11:15 Cheeklined wrassew12_fish1w12_fish1 12:59-13:24 Yellowfin hindw12_fish1 13:39-14:03 Yellowfin hindw12_fish1 14:54-15:23 Yellowfin hindw12_fish1 15:58-16:23 Yellowfin hindw12_fish1 17:38-18:06 Yellowfin hindw12_fish1 18:23-18:55 Yellowfin hindw12_fish1 19:01-19:19 Yellowfin hindw12_fish1 19:31-19:50 Yellowfin hindw12_fish2w12_fish2 13:23-13:25 Checkerboard wrassew12_fish2 13:43-13:46 Checkerboard wrassew12_fish2 13:53-14:05 Checkerboard wrassew12_fish2 14:46-15:03 Checkerboard wrassew12_fish2 15:39-15:47 Checkerboard wrassew12_fish2 15:51-16:10 Checkerboard wrassew12_fish2 16:12-16:19 Checkerboard wrassew12_fish2 16:50-16:55 Checkerboard wrassew12_fish2 21:45-21:52 Checkerboard wrassew12_fish2 21:56-22:00 Checkerboard wrassew12_fish3w12_fish3 14:12-14:14 Eritrean butterflyfishw12_fish3 14:17-14:25 Eritrean butterflyfishw12_fish3 14:30-14:32 Eritrean butterflyfishw12_fish3 15:20-15:22 Eritrean butterflyfishw12_fish3 15:44-15:49 Eritrean butterflyfishw12_fish3 16:28-16:37 Eritrean butterflyfishw12_fish3 16:44-16:46 Eritrean butterflyfishw12_fish3 16:49-17:04 Eritrean butterflyfishw12_fish3 19:43-19:54 Eritrean butterflyfishw12_fish3 20:13-20:20 Eritrean butterflyfishw12_fish3 20:23-20:23 Eritrean butterflyfishw12_fish3 20:44-20:47 Eritrean butterflyfishw12_fish5w12_fish5 12:05-12:07 Chocolate-dip chromisw12_fish5 13:11-13:14 Chocolate-dip chromisw12_fish5 16:04-16:06 Chocolate-dip chromisw12_fish5 16:42-16:44 Chocolate-dip chromisw12_fish5 17:49-17:50 Chocolate-dip chromisw12_fish5 18:51-18:53 Chocolate-dip chromisw12_fish5 19:00-19:01 Chocolate-dip chromisw12_fish5 19:21-19:24 Chocolate-dip chromisw12_fish5 19:52-19:53 Chocolate-dip chromisw12_fish5 20:01-20:06 Chocolate-dip chromisw12_fish5 20:46-20:48 Chocolate-dip chromisw12_fish5 21:14-21:15 Chocolate-dip chromisw12_fish9w12_fish9 12:23-12:25 Cheeklined wrassew12_fish9 12:30-12:36 Cheeklined wrassew12_fish9 13:22-13:24 Cheeklined wrassew12_fish9 14:12-14:13 Cheeklined wrassew12_fish9 15:16-15:19 Cheeklined wrassew12_fish9 15:27-15:30 Cheeklined wrassew12_fish9 16:08-16:14 Cheeklined wrassew12_fish9 16:59-17:04 Cheeklined wrassew12_fish9 18:07-18:12 Cheeklined wrassew12_fish9 20:01-20:03 Cheeklined wrassew12_fish9 20:09-20:11 Cheeklined wrassew12_fish9 20:16-20:18 Cheeklined wrassew12_fish9 21:34-21:36 Cheeklined wrassew12_fish9 21:41-21:43 Cheeklined wrassew22_fish1w22_fish1 23:18-23:31 Yellowfin hindw22_fish1 24:09-24:13 Yellowfin hindw22_fish1 24:16-24:21 Yellowfin hindw22_fish1 25:30-25:32 Yellowfin hindw22_fish1 25:35-25:53 Yellowfin hindw22_fish1 25:57-26:05 Yellowfin hindw22_fish1 26:11-26:21 Yellowfin hindw22_fish1 26:24-26:34 Yellowfin hindw22_fish1 27:37-27:39 Yellowfin hindw22_fish1 27:42-27:52 Yellowfin hindw22_fish1 27:57-27:58 Yellowfin hindw22_fish1 28:00-29:01 Yellowfin hindw22_fish1 30:00-30:04 Yellowfin hindw22_fish1 30:07-30:27 Yellowfin hindw22_fish1 30:37-30:39 Yellowfin hindw22_fish1 31:35-31:46 Yellowfin hindw22_fish2w22_fish2 25:02-25:03 Orange-lined triggerfishw22_fish2 28:13-28:25 Orange-lined triggerfishw22_fish2 29:28-29:31 Orange-lined triggerfishw22_fish2 29:46-29:52 Orange-lined triggerfishw22_fish2 30:11-30:15 Orange-lined triggerfishw22_fish2 31:05-31:07 Orange-lined triggerfishw22_fish3w22_fish3 25:23-25:26 Chocolate-dip chromisw22_fish3 26:10-26:12 Chocolate-dip chromisw22_fish3 26:18-26:20 Chocolate-dip chromisw22_fish3 26:27-26:29 Chocolate-dip chromisw22_fish3 26:56-26:58 Chocolate-dip chromisw22_fish3 27:41-27:43 Chocolate-dip chromisw22_fish3 27:52-27:54 Chocolate-dip chromisw22_fish3 28:57-29:00 Chocolate-dip chromisw22_fish3 30:40-30:44 Chocolate-dip chromisw22_fish3 31:10-31:13 Chocolate-dip chromisw22_fish3 31:39-31:41 Chocolate-dip chromisw22_fish4w22_fish4 29:04-29:11 Eritrean butterflyfishw22_fish4 29:50-30:02 Eritrean butterflyfishw32_fish1w32_fish1 33:00-33:06 Yellowfin hindw32_fish1 33:38-33:54 Yellowfin hindw32_fish1 34:21-34:39 Yellowfin hindw32_fish1 34:50-35:09 Yellowfin hindw32_fish1 35:17-35:36 Yellowfin hindw32_fish1 35:38-35:40 Yellowfin hindw32_fish1 35:47-35:52 Yellowfin hindw32_fish1 36:06-36:32 Yellowfin hindw32_fish1 36:45-36:46 Yellowfin hindw32_fish1 36:49-36:50 Yellowfin hindw32_fish1 36:52-37:22 Yellowfin hindw32_fish1 38:06-39:58 Yellowfin hindw32_fish3w32_fish3 32:26-32:28 Blacktip grouperw32_fish3 34:53-34:55 Blacktip grouperw32_fish3 35:10-35:20 Blacktip grouperw32_fish3 36:45-36:48 Blacktip grouperw32_fish3 38:54-39:00 Blacktip grouperw32_fish3 39:21-39:24 Blacktip grouperw32_fish4w32_fish4 32:08-32:10 Chocolate-dip chromisw32_fish4 33:12-33:14 Chocolate-dip chromisw32_fish4 34:22-34:26 Chocolate-dip chromisw32_fish4 34:57-34:59 Chocolate-dip chromisw32_fish4 35:06-35:08 Chocolate-dip chromisw32_fish4 35:40-35:42 Chocolate-dip chromisw32_fish4 36:08-36:09 Chocolate-dip chromisw32_fish4 36:19-36:21 Chocolate-dip chromisw32_fish4 36:38-36:40 Chocolate-dip chromisw32_fish4 39:53-39:53 Chocolate-dip chromisw32_fish4 39:56-39:58 Chocolate-dip chromisw32_fish5w32_fish5 37:43-38:02 Yellowfin hindw40_fish1w40_fish1 40:08-40:11 Yellowfin hindw40_fish1 40:42-40:51 Yellowfin hindw40_fish1 41:00-41:00 Yellowfin hindw40_fish1 41:04-41:46 Yellowfin hindw40_fish1 41:49-42:08 Yellowfin hindw40_fish1 42:50-42:54 Yellowfin hindw40_fish1 43:02-44:24 Yellowfin hindw40_fish1 44:26-44:52 Yellowfin hindw40_fish1 44:55-46:32 Yellowfin hindw40_fish1 46:46-47:12 Yellowfin hindw40_fish1 47:22-48:00 Yellowfin hindw40_fish2w40_fish2 42:07-42:10 Orange-lined triggerfishw40_fish2 42:33-42:39 Orange-lined triggerfishw40_fish2 43:15-43:22 Orange-lined triggerfishw40_fish2 43:49-43:53 Orange-lined triggerfishw40_fish2 44:07-44:20 Orange-lined triggerfishw40_fish2 44:47-44:55 Orange-lined triggerfishw40_fish2 45:02-45:18 Orange-lined triggerfishw40_fish2 45:56-46:02 Orange-lined triggerfishw40_fish2 46:12-46:14 Orange-lined triggerfishw40_fish2 46:33-46:36 Orange-lined triggerfishw40_fish2 47:41-47:58 Orange-lined triggerfishw40_fish3w40_fish3 40:00-40:02 Yellowfin hindw40_fish3 40:20-40:42 Yellowfin hindw40_fish3 40:44-40:50 Yellowfin hindw40_fish3 42:37-42:48 Yellowfin hindw40_fish4w40_fish4 40:01-40:05 Chocolate-dip chromisw40_fish4 40:10-40:12 Chocolate-dip chromisw40_fish4 40:16-40:22 Chocolate-dip chromisw40_fish4 40:27-40:28 Chocolate-dip chromisw40_fish4 40:36-40:39 Chocolate-dip chromisw40_fish4 41:04-41:05 Chocolate-dip chromisw40_fish4 41:47-41:51 Chocolate-dip chromisw40_fish4 43:43-43:47 Chocolate-dip chromisw40_fish4 44:22-44:26 Chocolate-dip chromisw40_fish4 44:30-44:32 Chocolate-dip chromisw40_fish4 44:40-44:47 Chocolate-dip chromisw40_fish4 45:13-45:26 Chocolate-dip chromisw40_fish4 45:41-45:46 Chocolate-dip chromisw40_fish4 47:14-47:15 Chocolate-dip chromisw40_fish5w40_fish5 41:25-41:26 Blacktip grouperw40_fish5 41:42-41:44 Blacktip grouperw40_fish5 42:01-42:05 Blacktip grouperw40_fish5 42:20-42:21 Blacktip grouperw40_fish5 42:47-42:50 Blacktip grouperw40_fish5 42:55-42:58 Blacktip grouperw40_fish5 43:05-43:14 Blacktip grouperw40_fish5 45:02-45:07 Blacktip grouperw40_fish5 45:18-45:25 Blacktip grouperw40_fish5 45:37-45:38 Blacktip grouperw40_fish5 45:40-45:45 Blacktip grouperw40_fish5 46:08-46:10 Blacktip grouperw40_fish5 46:14-46:16 Blacktip grouperw40_fish5 46:55-46:56 Blacktip grouperw40_fish6w40_fish6 41:56-42:08 Eritrean butterflyfishw40_fish6 42:11-42:11 Eritrean butterflyfishw40_fish6 42:14-42:28 Eritrean butterflyfishw40_fish7w40_fish7 40:27-40:36 Yellowfin hindw40_fish7 40:38-40:41 Yellowfin hindw40_fish7 42:10-42:31 Yellowfin hindfish1fish1 48:17-49:20 Yellowfin hindfish1 49:58-50:47 Yellowfin hindfish1 50:49-50:50 Yellowfin hindfish1 50:55-51:05 Yellowfin hindfish1 52:54-52:57 Yellowfin hindfish1 53:00-53:02 Yellowfin hindfish1 53:35-54:06 Yellowfin hindfish1 54:10-54:19 Yellowfin hindfish1 57:00-57:35 Yellowfin hindfish1 57:48-58:00 Yellowfin hindfish2fish2 48:02-48:12 Orange-lined triggerfishfish2 48:23-48:29 Orange-lined triggerfishfish2 49:41-49:47 Orange-lined triggerfishfish2 51:16-51:20 Orange-lined triggerfishfish2 52:58-53:09 Orange-lined triggerfishfish3fish3 48:09-48:12 Chocolate-dip chromisfish3 51:26-51:28 Chocolate-dip chromisfish3 51:57-51:59 Chocolate-dip chromisfish3 52:04-52:10 Chocolate-dip chromisfish3 52:15-52:18 Chocolate-dip chromisfish3 52:21-52:25 Chocolate-dip chromisfish3 52:30-52:38 Chocolate-dip chromisfish3 52:46-53:04 Chocolate-dip chromisfish3 53:08-53:11 Chocolate-dip chromisfish3 53:13-53:24 Chocolate-dip chromisfish3 53:26-53:31 Chocolate-dip chromisfish3 53:41-53:45 Chocolate-dip chromisfish3 53:53-53:55 Chocolate-dip chromisfish3 54:03-54:04 Chocolate-dip chromisfish3 54:13-54:16 Chocolate-dip chromisfish3 54:33-54:40 Chocolate-dip chromisfish3 54:44-54:47 Chocolate-dip chromisfish3 55:03-55:10 Chocolate-dip chromisfish3 55:32-55:35 Chocolate-dip chromisfish3 55:40-55:42 Chocolate-dip chromisfish3 55:58-56:02 Chocolate-dip chromisfish3 56:07-56:08 Chocolate-dip chromisfish3 56:27-56:28 Chocolate-dip chromisfish3 56:41-56:42 Chocolate-dip chromisfish3 57:14-57:16 Chocolate-dip chromisfish3 57:27-57:29 Chocolate-dip chromisfish3 57:33-57:35 Chocolate-dip chromisfish3 57:45-57:49 Chocolate-dip chromisw58_fish1w58_fish1 58:08-58:12 Yellowfin hindw58_fish1 59:04-59:08 Yellowfin hindw58_fish1 61:20-61:30 Yellowfin hindw58_fish1 62:06-62:18 Yellowfin hindw58_fish1 62:20-62:20 Yellowfin hindw58_fish1 62:30-62:35 Yellowfin hindw58_fish1 65:50-66:06 Yellowfin hindw58_fish1 66:22-66:38 Yellowfin hindw58_fish1 67:32-67:43 Yellowfin hindw58_fish10w58_fish10 58:29-58:29 Cheeklined wrassew58_fish10 59:01-59:04 Cheeklined wrassew58_fish10 61:00-61:02 Cheeklined wrassew58_fish10 61:07-61:09 Cheeklined wrassew58_fish10 61:45-61:49 Cheeklined wrassew58_fish10 62:24-62:27 Cheeklined wrassew58_fish10 63:02-63:09 Cheeklined wrassew58_fish10 63:15-63:17 Cheeklined wrassew58_fish10 64:12-64:14 Cheeklined wrassew58_fish10 64:40-64:42 Cheeklined wrassew58_fish10 65:08-65:14 Cheeklined wrassew58_fish10 65:35-65:36 Cheeklined wrassew58_fish10 66:54-66:56 Cheeklined wrassew58_fish10 67:26-67:28 Cheeklined wrassew58_fish11w58_fish11 58:31-58:33 Checkerboard wrassew58_fish11 58:38-58:41 Checkerboard wrassew58_fish11 58:45-58:48 Checkerboard wrassew58_fish11 59:55-59:56 Checkerboard wrassew58_fish11 60:16-60:23 Checkerboard wrassew58_fish11 62:14-62:18 Checkerboard wrassew58_fish11 62:33-62:36 Checkerboard wrassew58_fish11 64:28-64:33 Checkerboard wrassew58_fish11 65:14-65:19 Checkerboard wrassew58_fish11 66:12-66:14 Checkerboard wrassew58_fish11 66:17-66:19 Checkerboard wrassew58_fish2w58_fish2 59:40-59:41 Yellowfin hindw58_fish2 59:45-60:06 Yellowfin hindw58_fish3w58_fish3 58:45-58:46 Coral hindw58_fish3 58:49-58:54 Coral hindw58_fish3 59:20-59:25 Coral hindw58_fish3 59:33-59:41 Coral hindw58_fish3 59:52-60:00 Coral hindw58_fish3 60:02-60:08 Coral hindw58_fish3 60:40-60:48 Coral hindw58_fish3 60:51-60:58 Coral hindw58_fish3 61:53-61:58 Coral hindw58_fish3 62:06-62:28 Coral hindw58_fish3 63:07-63:13 Coral hindw58_fish3 63:32-63:35 Coral hindw58_fish3 66:50-66:56 Coral hindw58_fish3 67:00-67:11 Coral hindw58_fish3 67:34-67:44 Coral hindw58_fish5w58_fish5 62:36-63:25 Yellowfin hindw58_fish6w58_fish6 58:37-58:39 Orange-lined triggerfishw58_fish6 58:43-58:47 Orange-lined triggerfishw58_fish6 59:02-59:07 Orange-lined triggerfishw58_fish6 60:01-60:03 Orange-lined triggerfishw58_fish6 60:34-60:42 Orange-lined triggerfishw58_fish6 61:03-61:06 Orange-lined triggerfishw58_fish6 63:12-63:21 Orange-lined triggerfishw58_fish7w58_fish7 58:10-58:20 Chocolate-dip chromisw58_fish7 59:02-59:07 Chocolate-dip chromisw58_fish7 59:12-59:17 Chocolate-dip chromisw58_fish7 59:22-59:30 Chocolate-dip chromisw58_fish7 60:01-60:03 Chocolate-dip chromisw58_fish7 61:02-61:10 Chocolate-dip chromisw58_fish7 61:19-61:24 Chocolate-dip chromisw58_fish7 61:57-62:03 Chocolate-dip chromisw58_fish7 62:22-62:27 Chocolate-dip chromisw58_fish7 63:28-63:29 Chocolate-dip chromisw58_fish7 63:34-63:36 Chocolate-dip chromisw58_fish7 63:54-63:56 Chocolate-dip chromisw58_fish7 64:13-64:20 Chocolate-dip chromisw58_fish7 64:25-64:31 Chocolate-dip chromisw58_fish7 67:37-67:41 Chocolate-dip chromisw58_fish7 67:45-67:50 Chocolate-dip chromisw58_fish8w58_fish8 58:22-58:26 Redmouth grouperw58_fish8 58:52-59:00 Redmouth grouperw58_fish8 59:03-59:10 Redmouth grouperw58_fish8 64:10-64:18 Redmouth grouperw58_fish8 65:33-65:49 Redmouth grouperw58_fish8 66:51-66:53 Redmouth grouperw58_fish9w58_fish9 58:35-58:38 Checkerboard wrassew58_fish9 58:46-58:48 Checkerboard wrassew58_fish9 58:54-58:57 Checkerboard wrassew58_fish9 59:28-59:32 Checkerboard wrassew58_fish9 59:38-59:42 Checkerboard wrassew58_fish9 59:45-59:48 Checkerboard wrassew58_fish9 59:56-59:59 Checkerboard wrassew58_fish9 60:04-60:11 Checkerboard wrassew58_fish9 60:24-60:30 Checkerboard wrassew58_fish9 60:56-61:04 Checkerboard wrassew58_fish9 61:14-61:15 Checkerboard wrassew58_fish9 63:49-63:51 Checkerboard wrassew58_fish9 64:10-64:14 Checkerboard wrassew58_fish9 67:14-67:18 Checkerboard wrassew58_fish9 67:31-67:34 Checkerboard wrassew68_fish1w68_fish1 68:00-68:12 Chocolate-dip chromisw68_fish1 68:29-68:31 Chocolate-dip chromisw68_fish1 68:34-68:36 Chocolate-dip chromisw68_fish1 68:43-68:44 Chocolate-dip chromisw68_fish1 68:54-68:57 Chocolate-dip chromisw68_fish1 69:02-69:03 Chocolate-dip chromisw68_fish1 69:20-69:22 Chocolate-dip chromisw68_fish1 69:28-69:30 Chocolate-dip chromisw68_fish1 69:46-69:49 Chocolate-dip chromisw68_fish1 70:12-70:14 Chocolate-dip chromisw68_fish1 70:21-70:27 Chocolate-dip chromisw68_fish1 70:39-70:42 Chocolate-dip chromisw68_fish1 70:55-71:03 Chocolate-dip chromisw68_fish1 71:06-71:08 Chocolate-dip chromisw68_fish1 71:13-71:15 Chocolate-dip chromisw68_fish1 71:29-71:32 Chocolate-dip chromisw68_fish3w68_fish3 68:03-68:06 Checkerboard wrassew68_fish3 68:32-68:34 Checkerboard wrassew68_fish3 68:36-68:39 Checkerboard wrassew68_fish3 69:31-69:33 Checkerboard wrassew68_fish3 69:36-69:38 Checkerboard wrassew68_fish3 69:53-69:55 Checkerboard wrassew68_fish3 71:33-71:36 Checkerboard wrassew68_fish3 71:52-72:00 Checkerboard wrassew68_fish5w68_fish5 69:53-69:58 Redmouth grouperw68_fish5 70:29-70:32 Redmouth grouperw68_fish5 70:45-70:47 Redmouth grouperw68_fish5 71:37-71:46 Redmouth groupershaded: analysed 10-min windows · light band: static-camera range 01:49-72:06 · bars: visits of each fish (colour = species)

Species

speciesfamilyfishconfidence (per fish)
Yellowfin hind
Cephalopholis hemistiktos
Serranidae11
fish1 w12_fish1 w22_fish1 w32_fish1 w32_fish5 w40_fish1 w40_fish3 w40_fish7 w58_fish1 w58_fish2 w58_fish5
68% · medium 86% · high 83% · high 62% · medium 95% · high 85% · high 74% · medium 73% · medium 70% · medium 61% · medium 59% · medium
Chocolate-dip chromis
Chromis dimidiata
Pomacentridae8
fish3 w02_fish7 w12_fish5 w22_fish3 w32_fish4 w40_fish4 w58_fish7 w68_fish1
99% · high 97% · high 98% · high 92% · high 91% · high 99% · high 99% · high 100% · high
Checkerboard wrasse
Halichoeres hortulanus
Labridae6
w02_fish1 w02_fish2 w12_fish2 w58_fish9 w58_fish11 w68_fish3
100% · high 99% · high 100% · high 100% · high 99% · high 98% · high
Orange-lined triggerfish
Balistapus undulatus
Balistidae5
fish2 w02_fish4 w22_fish2 w40_fish2 w58_fish6
100% · high 100% · high 98% · high 100% · high 98% · high
Cheeklined wrasse
Oxycheilinus digramma
Labridae5
w02_fish8 w02_fish9 w02_fish10 w12_fish9 w58_fish10
67% · medium 36% · low 41% · low 36% · low 46% · low
Eritrean butterflyfish
Chaetodon paucifasciatus
Chaetodontidae3
w12_fish3 w22_fish4 w40_fish6
100% · high 100% · high 93% · high
Blacktip grouper
Epinephelus fasciatus
Serranidae2
w32_fish3 w40_fish5
60% · low 62% · low
Redmouth grouper
Aethaloperca rogaa
Serranidae2
w58_fish8 w68_fish5
83% · high 83% · high
Bluestreak cleaner wrasse
Labroides dimidiatus
Labridae1
w02_fish6
100% · high
Coral hind
Cephalopholis miniata
Serranidae1
w58_fish3
93% · high

Confidence: BioCLIP probability + agreement across crops; "low" should be read as "unknown, closest match". The same resident individual is probably counted in several windows (no cross-window re-identification).

All fish

Checkerboard wrasse · w02_fish1

Halichoeres hortulanus
100% · high model: good v2 · better
checker pattern and yellow tail
window 02:00 · seen 02:46-02:50, 04:16-04:19, 05:12-05:17 …
461 frames (10 fps) · PSNR 25.1 dB

Checkerboard wrasse · w02_fish2

Halichoeres hortulanus
99% · high model: good
banded wrasse body
window 02:00 · seen 02:22-02:23, 02:31-02:33, 02:42-02:45 …
611 frames (10 fps) · PSNR 27.8 dB

Orange-lined triggerfish · w02_fish4

Balistapus undulatus
100% · high model: good v2 · same
triggerfish outline with yellow caudal
window 02:00 · seen 05:01-05:03, 08:09-08:14, 09:03-09:10 …
355 frames (10 fps) · PSNR 26.9 dB

Bluestreak cleaner wrasse · w02_fish6

Labroides dimidiatus
100% · high model: good v2 · same
black lateral stripe on blue
window 02:00 · seen 05:43-05:45, 05:55-05:57, 06:58-07:03 …
166 frames (10 fps) · PSNR 25.1 dB

Chocolate-dip chromis · w02_fish7

Chromis dimidiata
97% · high model: good
two-tone body
window 02:00 · seen 03:22-03:24, 03:28-03:31, 07:37-07:40 …
355 frames (10 fps) · PSNR 25.8 dB

Checkerboard wrasse · w12_fish2

Halichoeres hortulanus
100% · high model: good v2 · better
checker pattern and yellow tail
window 12:00 · seen 13:23-13:25, 13:43-13:46, 13:53-14:05 …
709 frames (10 fps) · PSNR 25.1 dB

Eritrean butterflyfish · w12_fish3

Chaetodon paucifasciatus
100% · high model: good v2 · better
stripes and dark rear patch; side view is slab-like; ~36% of tracking frames have no pose
window 12:00 · seen 14:12-14:14, 14:17-14:25, 14:30-14:32 …
550 frames (10 fps) · PSNR 22.2 dB

Chocolate-dip chromis · w12_fish5

Chromis dimidiata
98% · high model: good
two-tone body, forked tail
window 12:00 · seen 12:05-12:07, 13:11-13:14, 16:04-16:06 …
220 frames (10 fps) · PSNR 26.5 dB

Orange-lined triggerfish · w22_fish2

Balistapus undulatus
98% · high model: good
triggerfish outline with yellow caudal
window 22:00 · seen 25:02-25:03, 28:13-28:25, 29:28-29:31 …
265 frames (10 fps) · PSNR 26.7 dB

Chocolate-dip chromis · w22_fish3

Chromis dimidiata
92% · high model: good
two-tone body, forked tail (39% coral-cut frames)
window 22:00 · seen 25:23-25:26, 26:10-26:12, 26:18-26:20 …
218 frames (10 fps) · PSNR 27.3 dB

Eritrean butterflyfish · w22_fish4

Chaetodon paucifasciatus
100% · high model: good v2 · better
stripes and dark rear patch; ~33% of tracking frames have no pose
window 22:00 · seen 29:04-29:11, 29:50-30:02
185 frames (10 fps) · PSNR 23.2 dB

Chocolate-dip chromis · w32_fish4

Chromis dimidiata
91% · high model: good
two-tone body, forked tail
window 32:00 · seen 32:08-32:10, 33:12-33:14, 34:22-34:26 …
194 frames (10 fps) · PSNR 26.9 dB

Orange-lined triggerfish · w40_fish2

Balistapus undulatus
100% · high model: good v2 · better
triggerfish outline with yellow caudal
window 40:00 · seen 42:07-42:10, 42:33-42:39, 43:15-43:22 …
708 frames (10 fps) · PSNR 27.1 dB

Chocolate-dip chromis · w40_fish4

Chromis dimidiata
99% · high model: good
two-tone body, forked tail
window 40:00 · seen 40:01-40:05, 40:10-40:12, 40:16-40:22 …
475 frames (10 fps) · PSNR 26.2 dB

Eritrean butterflyfish · w40_fish6

Chaetodon paucifasciatus
93% · high model: good v2 · better
stripes and dark rear patch; ~50% of tracking frames have no pose
window 40:00 · seen 41:56-42:08, 42:11-42:11, 42:14-42:28
230 frames (10 fps) · PSNR 25.6 dB

Orange-lined triggerfish · fish2

Balistapus undulatus
100% · high model: good v2 · same
translucent dorsal/anal fins and yellow caudal fan; tracks well even head-on
window 48:00 · seen 48:02-48:12, 48:23-48:29, 49:41-49:47 …
359 frames (10 fps) · PSNR 25.5 dB

Chocolate-dip chromis · fish3

Chromis dimidiata
99% · high model: good v2 · same
two-tone body, forked tail
window 48:00 · seen 48:09-48:12, 51:26-51:28, 51:57-51:59 …
910 frames (10 fps) · PSNR 28.0 dB

Checkerboard wrasse · w58_fish11

Halichoeres hortulanus
99% · high model: good
checker pattern and yellow tail
window 58:00 · seen 58:31-58:33, 58:38-58:41, 58:45-58:48 …
307 frames (10 fps) · PSNR 27.8 dB

Orange-lined triggerfish · w58_fish6

Balistapus undulatus
98% · high model: good v2 · better
triggerfish outline with yellow caudal
window 58:00 · seen 58:37-58:39, 58:43-58:47, 59:02-59:07 …
304 frames (10 fps) · PSNR 30.5 dB

Chocolate-dip chromis · w58_fish7

Chromis dimidiata
99% · high model: good v2 · better
two-tone body, forked tail
window 58:00 · seen 58:10-58:20, 59:02-59:07, 59:12-59:17 …
666 frames (10 fps) · PSNR 28.2 dB

Redmouth grouper · w58_fish8

Aethaloperca rogaa
83% · high model: good v2 · same
deep grouper body with caudal; outline matches
window 58:00 · seen 58:22-58:26, 58:52-59:00, 59:03-59:10 …
395 frames (10 fps) · PSNR 26.9 dB

Chocolate-dip chromis · w68_fish1

Chromis dimidiata
100% · high model: good
two-tone body, forked tail
window 68:00 · seen 68:00-68:12, 68:29-68:31, 68:34-68:36 …
469 frames (10 fps) · PSNR 26.5 dB

Checkerboard wrasse · w68_fish3

Halichoeres hortulanus
98% · high model: good v2 · better
banded wrasse body
window 68:00 · seen 68:03-68:06, 68:32-68:34, 68:36-68:39 …
209 frames (10 fps) · PSNR 29.0 dB

Redmouth grouper · w68_fish5

Aethaloperca rogaa
83% · high model: good v2 · better
deep grouper body with caudal; outline matches
window 68:00 · seen 69:53-69:58, 70:29-70:32, 70:45-70:47 …
149 frames (10 fps) · PSNR 26.4 dB

Cheeklined wrasse · w02_fish10

Oxycheilinus digramma
41% · low model: fair species doubtful
plain brown wrasse; merged from many short tracklets
window 02:00 · seen 02:56-02:58, 03:02-03:05, 04:54-04:56 …
161 frames (10 fps) · PSNR 30.7 dB

Cheeklined wrasse · w02_fish8

Oxycheilinus digramma
67% · medium model: fair species doubtful
plain dark body; blunt head and lunate tail look more like a parrotfish
window 02:00 · seen 07:10-07:12, 07:27-07:32, 08:21-08:30 …
206 frames (10 fps) · PSNR 30.2 dB

Cheeklined wrasse · w02_fish9

Oxycheilinus digramma
36% · low model: fair species doubtful
plain brown wrasse; merged from many short tracklets
window 02:00 · seen 02:34-02:37, 05:15-05:17, 06:28-06:30 …
225 frames (10 fps) · PSNR 26.3 dB

Yellowfin hind · w12_fish1

Cephalopholis hemistiktos
86% · high model: fair v2 · same
very dark, faint model; outline right
window 12:00 · seen 12:59-13:24, 13:39-14:03, 14:54-15:23 …
1776 frames (10 fps) · PSNR 24.6 dB

Cheeklined wrasse · w12_fish9

Oxycheilinus digramma
36% · low model: fair species doubtful
plain brown wrasse; merged from many short tracklets
window 12:00 · seen 12:23-12:25, 12:30-12:36, 13:22-13:24 …
392 frames (10 fps) · PSNR 29.1 dB

Yellowfin hind · w32_fish1

Cephalopholis hemistiktos
62% · medium model: fair
artifact: bright sand-coloured patch baked into the belly, visible in the side views of the turntable
window 32:00 · seen 33:00-33:06, 33:38-33:54, 34:21-34:39 …
2172 frames (10 fps) · PSNR 23.0 dB

Blacktip grouper · w32_fish3

Epinephelus fasciatus
60% · low model: fair species doubtful
plain reddish-brown body
window 32:00 · seen 32:26-32:28, 34:53-34:55, 35:10-35:20 …
211 frames (10 fps) · PSNR 29.1 dB

Yellowfin hind · w40_fish1

Cephalopholis hemistiktos
85% · high model: fair v2 · better
very dark, faint model; outline right
window 40:00 · seen 40:08-40:11, 40:42-40:51, 41:00-41:00 …
3259 frames (10 fps) · PSNR 24.4 dB

Yellowfin hind · w40_fish3

Cephalopholis hemistiktos
74% · medium model: fair
very dark model; outline right
window 40:00 · seen 40:00-40:02, 40:20-40:42, 40:44-40:50 …
341 frames (10 fps) · PSNR 32.4 dB

Blacktip grouper · w40_fish5

Epinephelus fasciatus
62% · low model: fair species doubtful
slender, pointed snout; runners-up are wrasses
window 40:00 · seen 41:25-41:26, 41:42-41:44, 42:01-42:05 …
393 frames (10 fps) · PSNR 26.7 dB

Yellowfin hind · fish1

Cephalopholis hemistiktos
68% · medium model: fair
plain dark body, fins smeared; aligns with the crops
window 48:00 · seen 48:17-49:20, 49:58-50:47, 50:49-50:50 …
1728 frames (10 fps) · PSNR 25.3 dB

Cheeklined wrasse · w58_fish10

Oxycheilinus digramma
46% · low model: fair species doubtful
plain brown wrasse; merged from many short tracklets
window 58:00 · seen 58:29-58:29, 59:01-59:04, 61:00-61:02 …
332 frames (10 fps) · PSNR 27.1 dB

Yellowfin hind · w58_fish2

Cephalopholis hemistiktos
61% · medium model: fair
plain dark body with tail fan
window 58:00 · seen 59:40-59:41, 59:45-60:06
203 frames (10 fps) · PSNR 23.5 dB

Coral hind · w58_fish3

Cephalopholis miniata
93% · high model: fair v2 · better
right shape and rounded tail; spots smeared
window 58:00 · seen 58:45-58:46, 58:49-58:54, 59:20-59:25 …
990 frames (10 fps) · PSNR 26.2 dB

Checkerboard wrasse · w58_fish9

Halichoeres hortulanus
100% · high model: fair v2 · better
wrasse shape, pattern lost (blurry crops)
window 58:00 · seen 58:35-58:38, 58:46-58:48, 58:54-58:57 …
478 frames (10 fps) · PSNR 28.9 dB

Yellowfin hind · w22_fish1

Cephalopholis hemistiktos
83% · high model: poor v2 · better
very dark, faint oval; head-on frames not explained (IoU 0.775)
window 22:00 · seen 23:18-23:31, 24:09-24:13, 24:16-24:21 …
1592 frames (10 fps) · PSNR 24.2 dB

Yellowfin hind · w32_fish5

Cephalopholis hemistiktos
95% · high model: poor
featureless oval without a readable tail; behind coral for its first ~9 s
window 32:00 · seen 37:43-38:02
189 frames (10 fps) · PSNR 22.4 dB

Yellowfin hind · w40_fish7

Cephalopholis hemistiktos
73% · medium model: poor
featureless blob: soft-coral occlusion and many head-on poses (one individual, not mixed)
window 40:00 · seen 40:27-40:36, 40:38-40:41, 42:10-42:31
263 frames (10 fps) · PSNR 19.7 dB

Yellowfin hind · w58_fish1

Cephalopholis hemistiktos
70% · medium model: poor
featureless lozenge without a readable tail
window 58:00 · seen 58:08-58:12, 59:04-59:08, 61:20-61:30 …
698 frames (10 fps) · PSNR 22.2 dB

Yellowfin hind · w58_fish5

Cephalopholis hemistiktos
59% · medium model: poor
featureless oval, faint tail
window 58:00 · seen 62:36-63:25
450 frames (10 fps) · PSNR 21.0 dB

Method

How the 3D direction of the fish is inferred (fish2): mask + moment ellipse, fitted ellipsoid, motion prior, trajectory, render at the refined pose.

Quality experiment

Why do the models look blurred, and what fixes it? Nine changes (more iterations, quality-based frame selection, colour / water / motion-blur image formation, lossless crops) were tested one at a time on six fish against a fixed held-out set, then reviewed. Only more iterations (12000 instead of 4000) passed and was rolled out to all fish as v2; the previous models stay available as v1. The gains are small: every render remains much blurrier than the photos (the remaining blur points at pose alignment).

Rollout (v2 = 12000 iterations, everything else as v1): 20 of 44 fish have a v2 model with videos; 20 are evaluated on their fixed held-out frames. v2 is sharper on 16 (grain-robust sharpness ratio; beyond training noise on 10; geometric mean x1.27) and has lower LPIPS on 19 (mean -0.0146). Verdict per fish (LPIPS beyond noise): better 14, same within noise 6, worse 0. Each fish page has a v1 vs v2 table and side-by-side turntables.

v1 vs v2 per fish (sorted by LPIPS change)
fishspeciesLPIPS v1 → v2dLPIPSPSNR mask v1 → v2sharpness xverdict
w12_fish2Checkerboard wrasse0.2214 → 0.1660-0.055518.59 → 18.58x2.62better
w58_fish3Coral hind0.1853 → 0.1350-0.050321.28 → 21.54x1.73better
w22_fish4Eritrean butterflyfish0.2057 → 0.1594-0.046316.93 → 17.80x1.56better
w02_fish1Checkerboard wrasse0.1882 → 0.1589-0.029319.26 → 19.44x1.46better
w40_fish6Eritrean butterflyfish0.2109 → 0.1961-0.014717.50 → 17.67x0.93better
w40_fish2Orange-lined triggerfish0.1626 → 0.1479-0.014722.71 → 23.09x1.70better
w58_fish6Orange-lined triggerfish0.1393 → 0.1250-0.014326.08 → 26.55x1.00better
w58_fish9Checkerboard wrasse0.1373 → 0.1251-0.012225.56 → 25.65x1.81better
w12_fish3Eritrean butterflyfish0.2143 → 0.2035-0.010817.15 → 17.36x0.98better
w22_fish1Yellowfin hind0.1942 → 0.1860-0.008221.52 → 21.36x1.43better
w68_fish5Redmouth grouper0.1287 → 0.1207-0.008123.52 → 23.82x1.52better
w68_fish3Checkerboard wrasse0.0923 → 0.0850-0.007327.18 → 27.58x1.21better
w40_fish1Yellowfin hind0.1806 → 0.1758-0.004718.79 → 18.89x1.25better
w58_fish7Chocolate-dip chromis0.1314 → 0.1272-0.004121.08 → 21.65x1.03better
w58_fish8Redmouth grouper0.1163 → 0.1132-0.003125.66 → 25.87x1.04same (within noise)
w02_fish4Orange-lined triggerfish0.1294 → 0.1266-0.002822.86 → 23.16x0.99same (within noise)
w02_fish6Bluestreak cleaner wrasse0.1039 → 0.1014-0.002617.93 → 18.37x1.05same (within noise)
fish2Orange-lined triggerfish0.1694 → 0.1672-0.002221.31 → 21.32x1.08same (within noise)
fish3Chocolate-dip chromis0.1571 → 0.1554-0.001821.81 → 21.88x1.03same (within noise)
w12_fish1Yellowfin hind0.1547 → 0.1558+0.001022.35 → 22.07x1.06same (within noise)
Plan docs/quality_experiment_plan.md

Experiment: sharper 3DGS models (frame selection + image formation)

Started 2026-11-11. Goal: find out why the final gs_final models (4D GS, fins preset, 30 fps, 4000 iters; see docs/method_choice.md, docs/RESULTS.md) look blurred, and fix it.

Hypotheses

Fish set (6)

fish2 (triggerfish, good), w12_fish3 (butterflyfish, good), w02_fish6 (cleaner wrasse, small), w58_fish8 (redmouth grouper), w32_fish1 (hind, fair, belly artifact), w40_fish7 (hind, poor).

Fixed evaluation (identical for all arms)

Phases

Decision rule: adopt a change if LPIPS and sharpness ratio improve on >= 4/6 fish with PSNR not dropping > 0.3 dB, and it looks better. Risks: coverage holes when too picky (hence the coverage constraint); water model trading off with per-frame colour gain (regularise); motion-blur model costs 3-5x render time. Not adopted (probe at most): per-frame deblur / super-resolution networks — hallucinated detail is inconsistent across views.

Results go to docs/quality_experiment.md.

Full report docs/quality_experiment.md

Quality experiment: sharper 3DGS models — results

Plan: quality_experiment_plan.md. This file collects the results phase by phase. Fish set (6): fish2, w12_fish3, w02_fish6, w58_fish8, w32_fish1, w40_fish7.

Shared evaluation infrastructure

piecefileoutput
per-frame quality features + scorescripts/12_frame_quality.py --fish <name> ...<dir>/frame_quality.json for the 10 fps base dir and the _hf dir, work/quality_exp/<name>/frame_quality.png
fixed held-out set + exclusion rulescripts/13_heldout.py --fish <name> ...; `quality_lib.training_exclusion(name, 'base'\'hf')`work/quality_exp/<name>/heldout.json, heldout_sheet.jpg
evaluation of any modelscripts/tests/quality_eval.py --model <dir> --fish <name> [--tag X]work/quality_exp/<name>/eval/<tag>/metrics.json, sheet.jpg, views.png, comp/
comparison sheet real \A \B ...scripts/tests/quality_eval.py --fish <name> --compare <evaldir>,<evaldir> --labels B0,S2compare_sheet.jpg
Phase A diagnosisscripts/tests/quality_diag.py --fish <name> ...work/quality_exp/diag/<name>.json, <name>_diag.png, diag_summary.json
shared helpersscripts/quality_lib.pypaths across windows, native sharpness, ellipsoid fit, exclusion, model rendering

None of these needs FISH_WINDOW: the window follows from the fish name.

Per-frame quality (12_frame_quality.py)

All image measures at native camera resolution: the crop square is cut from the full video frame (frames/f_*.jpg, hf_frames/<name>/h_*.jpg; integer offset, no resampling) and the crop's mask is resized onto it. Checked: the patch registers exactly with the crops (MAD 0 at zero offset, rising at +-0.5 px), and the 30 fps decode is pixel-identical to the 10 fps decode at k = 0.

Held-out set (13_heldout.py)

Candidates: 10 fps base frames (they have plates and soft alpha) that are kept, pose-valid, not slow, not outliers, bg_ok, not touching the border, unoccluded (no occlusion / overlap reason), ellipsoid fit >= the fish median and in the sharper half (thresholds relaxed in steps when too few pass). Greedy choice by score + view novelty + visit novelty - exclusion cost, >= 0.5 s apart.

Exclusion rule: every training frame (10 or 30 fps) within +-0.3 s of a held-out frame is excluded in every arm; heldout.json lists the ids (exclude.base, exclude.hf; 30 fps ids matched by time, id = 3*(i10-1)+1+k, t = start + (id-1)/30). Use quality_lib.training_exclusion(name, 'hf') in a trainer.

Deviation from the plan (40-60 frames): one held-out frame removes 7 frames at 10 fps and 19 at 30 fps. 40-60 held-out frames would remove most of the training data of the smaller fish (w02_fish6 has 135 usable 10 fps frames). The selection therefore stops (above 12 frames) before more than 35 % of the usable 10 fps frames or 30 % of the gs_final 30 fps training frames would be excluded, and penalises frames whose exclusion window is full of usable frames.

fishheld-outcandidatesvisits (held-out / usable)flank +z / -z / head / tailusable 10 fps excludedgs_final 30 fps train excludedheld-out near gs_final trainingstop
fish216635 / 76 / 4 / 1 / 531 %28 %16 / 16exclusion budget
w12_fish3246412 / 154 / 16 / 2 / 232 %28 %9 / 24no candidate >= 0.5 s apart
w02_fish612616 / 73 / 7 / 1 / 141 % (floor of 12)36 %10 / 12exclusion budget
w58_fish8231206 / 613 / 10 / 0 / 034 %13 %5 / 23exclusion budget
w32_fish14015913 / 3318 / 15 / 2 / 512 %3 %1 / 40target reached
w40_fish713413 / 62 / 3 / 3 / 529 %28 %10 / 13exclusion budget

128 held-out frames in total (plan: 40-60 per fish; see above). The arms must retrain with the exclusion, so they lose 3-36 % of the 30 fps training frames of gs_final. The fair baseline for the arms is therefore not B0 but "B0x" = the gs_final recipe retrained with the exclusion; B0 is kept as an optimistic reference.

quality_eval.py protocol

Same for every model, gaussians frozen, evaluated at the base crop resolution (256 px): initial pose = raw 05 pose (independent of what the model saw), discrete hypothesis search (current / twin / flip / twin+flip / upright twins / +-25 deg yaw, each translation-aligned and refined 20 steps at half resolution, lowest loss wins), then 200 Adam steps on pose, colour gain/bias and (deforming models) bending + caudal angle, coarse-to-fine. Loss = the 06_train_gs.py --bg_composite loss with fixed weights (composite over the plate, 0.8 L1 + 0.2 D-SSIM, soft-alpha / inside / far alpha priors, L2 on the deltas). Metrics: PSNR full (observed crop) and on the SAM mask, LPIPS-alex on the fish box (224 px) and on the full crop, sharpness ratio (trimmed gradient energy render / real, same mask and clip level), Laplacian ratio, IoU; six canonical views (gradient energy of the unbent model). contamination = held-out frames within 0.3 s of a training frame of the evaluated model.

A. Diagnosis

Date: 2026-11-11. Inputs: 12_frame_quality.py on the 6 fish (10 fps + 30 fps), 13_heldout.py, tests/quality_eval.py on gs_final (B0), tests/quality_diag.py (re-render of gs_final on its own training frames with the refined poses / bending / caudal / colour of training, composited over the plates at the training resolution). Raw outputs: work/quality_exp/diag/<fish>.json, <fish>_diag.png, diag_summary.json, work/quality_exp/<fish>/eval/B0/.

A1. B0 = gs_final on the held-out set

fishnPSNR fullPSNR maskLPIPS fishLPIPS fullsharpness ratio (median)IoUflank-view gradient energyraw pose switched by TTOcontaminated
fish21624.1421.310.1650.1440.1190.89712.51216/16
w12_fish32421.8517.150.2050.1890.3980.88559.32139/24
w02_fish61224.2717.930.1030.0930.5950.872280.02410/12
w58_fish82325.7925.660.1160.1050.1720.9190.13125/23
w32_fish14022.2218.680.1560.1380.1300.9026.70271/40
w40_fish71320.6317.850.1960.1740.0330.8700.63410/13
fish2, older 10 fps gs_robust_4d (generality check)1623.6820.210.1740.1510.0840.9049.39216/16

A2. gs_final on its own training frames: residual vs quality features

Per-frame residuals of gs_final (median over the training frames):

fishtrain framesPSNR maskLPIPS fishsharpness ratiorender / real sharpness: vs median frame, vs p90 framerenders sharper than the p10 real frameequivalent blur sigma (native px)
fish279323.380.1310.200.22, 0.0438 %1.6
w12_fish341718.550.1970.530.43, 0.1861 %0.7
w02_fish630917.100.0890.780.72, 0.3378 %0.9
w58_fish857127.200.0890.200.20, 0.102 %2.8
w32_fish164721.910.1140.130.12, 0.072 %2.1
w40_fish753720.190.2010.070.06, 0.024 %2.0

(Sharpness = trimmed gradient energy inside the fish, same mask and clip level for render and crop. Equivalent blur = Gaussian sigma for which the blurred real crop has the render's gradient energy, converted to native pixels.)

Spearman rho of the per-frame residual with the frame's features (training frames; PSNR mask: + = feature goes with a better fit; LPIPS: - = better):

featurePSNR mask: fish2 / w12 / w02 / w58 / w32 / w40LPIPS fish: same orderconsistent?
ellipsoid fit IoU (pose / silhouette agreement)+0.39 / +0.48 / +0.45 / +0.46 / +0.52 / +0.29-0.27 / -0.54 / -0.45 / -0.40 / -0.45 / -0.06yes, 6/6 and 5/6
side-on view (view_side)+0.14 / +0.44 / +0.41 / +0.62 / +0.51 / +0.29-0.61 / -0.58 / -0.67 / -0.62 / -0.77 / -0.63yes, 6/6 and 6/6
native length-0.19 / +0.16 / +0.30 / +0.34 / +0.68 / +0.25-0.39 / -0.39 / -0.52 / -0.57 / -0.88 / -0.54mostly, 5/6 and 6/6
input sharpness rank-0.47 / -0.17 / -0.11 / +0.52 / +0.18 / +0.03+0.39 / -0.15 / -0.02 / -0.21 / -0.18 / +0.23no (mixed signs)
image speed-0.24 / -0.04 / +0.53 / +0.36 / -0.25 / +0.020.00 / -0.09 / -0.66 / -0.53 / +0.36 / +0.04no
distance (pose depth in half-lengths)+0.19 / +0.14 / -0.09 / +0.32 / -0.31 / +0.23+0.02 / 0.00 / +0.10 / -0.18 / +0.18 / -0.24no
blue fraction of the fish+0.69 / +0.24 / +0.10 / +0.55 / +0.36 / -0.05-0.28 / -0.03 / +0.21 / -0.42 / -0.41 / -0.12weak (bluer = lower contrast, easier PSNR)
per-frame colour-gain deviation+0.21 / +0.14 / -0.29 / +0.14 / -0.25 / +0.09-0.03 / +0.01 / +0.23 / +0.02 / +0.33 / +0.34no
quality score (12)-0.41 / +0.06 / +0.01 / +0.58 / +0.41 / +0.08+0.16 / -0.35 / -0.10 / -0.22 / -0.38 / +0.13no (sharpness term pulls the wrong way)

Sharpness ratio (render / real) vs the input's sharpness rank: -0.58 / -0.26 / -0.59 / -0.79 / -0.63 / -0.78: the sharper the photo, the larger the gap, because the render's sharpness is nearly the same in every frame (scatter in <fish>_diag.png, panel 2). By decile of the input sharpness, fish2's LPIPS rises from 0.115 (deciles 4-6) to 0.20 (deciles 9-10), PSNR mask falls from 24.4 to 18.7 dB. By decile of the ellipsoid fit, LPIPS falls from the worst to the best decile in every fish (w12_fish3 0.265 -> 0.161, w02_fish6 0.183 -> 0.066, fish2 0.222 -> 0.145, w32_fish1 0.131 -> 0.114 with a peak of 0.194 in decile 3; w40_fish7 nearly flat 0.201 -> 0.191).

Measurement checks:

A3. Data selection of 04d (hypothesis 1)

The 30 fps decode is pixel-identical to the 10 fps decode, so any sharpness difference between the 30 fps training set and the other 10 fps frames is a selection effect of cap_segments (longest visits, 24 s cap). Native sharpness (trimmed Laplacian energy, usable frames):

fishusable 10 fps framesinside the 30 fps segmentsmedian sharpness inside / outside10 fps frames outside that are sharper than the median 30 fps training frame
fish23103040.24x (only 6 outside, not meaningful)6
w12_fish33941591.55x52
w02_fish61351212.05x0
w58_fish83882090.22x174
w32_fish121562400.81x1209
w40_fish72592010.74x31

Within-fish sharpness varies 6-35x (p90/p10, robust measure; usable 10 fps frames: fish2 18x, w12_fish3 6.4x, w02_fish6 35x, w58_fish8 11x, w32_fish1 14x, w40_fish7 13x). The sub-frame phase k of the 30 fps grid makes no difference (paired k1/k0, k2/k0 sharpness ratios 0.98-1.05).

A4. Water / colour (hypothesis 4)

The per-frame colour gains of gs_final are small (p10-p90 within 0.94-1.05 per channel for w12_fish3, within 0.97-1.03 for the other five), and the distance correlations above are weak and of mixed sign. The colour cast lives in the canonical colours instead (the models are cyan; e.g. w12_fish3's yellow body is pale green in views.png), i.e. it is a colour-fidelity problem for the turntables (arm C1), not a cause of blur. Red gain vs distance is clearly negative only for w12_fish3 (rho -0.62).

A5. Conclusion: what dominates

Steering for phase B:

B. Datasets

Date: 2026-11-11. The data side of the arms S1, S2, S3, L1, C1 for the 6 fish. Every arm is a NEW folder next to <name>_hf (<root>/fish/<name>_<arm>, root = work/ or work/w<MM>/); _hf, gs_final, the base dirs and their frame_quality.json are only read. Train an arm with the usual command and --fish <name>_<arm> (FISH_WINDOW=<MM> for window fish), e.g. 06_train_gs.py --fish w32_fish1_s2 --fins --deform --iters 4000 --hyp_max_frames 300 --out <tag>.

Held-out exclusion is already applied in every arm folder: the frames within +-0.3 s of a held-out frame (by time, arm_lib.excluded_by_time, same rule as quality_lib.training_exclusion) are not in meta.json / poses.json / frame_filter.json at all, so no trainer flag is needed (checked by 14_arm_data.py --summary: heldout_frames_present = 0 for every arm, see work/quality_exp/datasets.json). Note for trainers: quality_lib.training_exclusion() takes the BASE name (w32_fish1), not the arm folder name; on an arm folder the exclusion is a no-op.

Code

piecefile
arm score, view bins, coverage selection, exclusion by time, dataset subsetting, C1 gainsscripts/arm_lib.py (new)
S1, S3, C1 builders, --summaryscripts/14_arm_data.py --fish ... --arms s1,c1,s3 (new)
S2 (`--select quality --budget N\0), L1 (--like _hf --lossless), --out-suffix, --exclude-heldout, --pose-source like_hf`scripts/04d_hifps.py (defaults unchanged: without the new flags it plans and writes <name>_hf exactly as before; the new flags refuse to run with the default suffix _hf)
frame quality of an arm data set, ranks vs the existing base + _hf poolscripts/12_frame_quality.py --fish ... --dataset _s2 (writes only <name>_s2/frame_quality.json)

Arm score (arm_lib.q_arm, used by S1, S2, S3): q_arm = gate (0.35 q_pose + 0.35 q_sharp + 0.15 q_size + 0.15 q_motion) with gate and q_ of 12_frame_quality.py. Phase A asked for selection by pose quality as much as by sharpness, so pose fit gets the same weight as sharpness (12's score: 0.40 sharpness / 0.25 pose). A first version with 0.40 pose / 0.25 sharpness chose the same S2 windows for w12_fish3, so the choice is not sensitive there. The view enters as a constraint, not as a score term: view bins = 8 azimuth bins of 45 deg (centred on head-on) x 3 elevation bands (< -30, -30..30, > 30 deg) of the camera direction in the fish frame (view_az, view_el of 12, from the poses).

frame_select.json (format agreed with the trainer side): <dataset dir>/frame_select.json = {"note": ..., "frames": {"<id>": weight}}, weight in [0, 1], 0 = drop, a frame missing from the file is dropped when the file is used. Present in _s1 (weight 1 for every kept frame; the folder holds only those frames, so it is redundant there) and _s3 (the soft weights). Not present in _s2, _l1, _c1.

The arms

Numbers

Training frames = valid pose, keep, weight > 0, after the exclusion (what the trainer trains on, before its own every-10th validation split).

fish_hf (B0x)S1S2S3 (weighted)L1 / C1 frames (all, incl. invalid poses)crop res
fish2631316629 (2 short: all usable footage used)629859512
w12_fish3329164329329580256
w02_fish6218109218218347512
w58_fish8552276552552622256
w32_fish1700350700700702256
w40_fish7429214429429541256

S1 vs all _hf training frames (medians; sharpness = native trimmed Laplacian energy, only comparable within a fish):

fishkept / offorced by coveragebinsq_armsharpnessellipsoid fitnative length px
fish2316 / 631164130.73 vs 0.633035 vs 1752 (1.73x)0.825 vs 0.832260 vs 255
w12_fish3164 / 32987100.78 vs 0.6334139 vs 24796 (1.38x)0.839 vs 0.825140 vs 125
w02_fish6109 / 2185870.67 vs 0.563878 vs 1712 (2.27x)0.790 vs 0.759336 vs 333
w58_fish8276 / 55214270.70 vs 0.6265 vs 42 (1.56x)0.847 vs 0.840166 vs 160
w32_fish1350 / 70017660.57 vs 0.53642 vs 486 (1.32x)0.843 vs 0.828153 vs 148
w40_fish7214 / 42911090.53 vs 0.461424 vs 1106 (1.29x)0.796 vs 0.774111 vs 107

S2 vs _hf (medians over the usable frames of each 30 fps data set, frame_quality.json; for _hf this includes its frames near held-out frames; the 10 fps columns count the usable 10 fps frames each data set draws on):

fishwindows (coverage) -> runsusable 10 fps: S2 / _hf / allvisits S2 / _hfview bins S2 / _hf / allsharpness S2 vs _hf (30 fps)fitlength pxspeed px/s
fish231 (7) -> 15214 / 208 / 2147 / 612 / 11 / 121755 vs 2052 (0.86x)0.831 vs 0.839254 vs 259163 vs 171
w12_fish312 (6) -> 12122 / 112 / 2667 / 313 / 10 / 1516173 vs 20578 (0.79x)0.828 vs 0.832138 vs 123102 vs 113
w02_fish614 (3) -> 1678 / 75 / 807 / 69 / 7 / 91819 vs 4061 (0.45x)0.761 vs 0.772326 vs 289654 vs 577
w58_fish821 (6) -> 18197 / 182 / 2556 / 27 / 7 / 757 vs 42 (1.36x)0.842 vs 0.843161 vs 15875 vs 55
w32_fish124 (6) -> 24263 / 233 / 190412 / 18 / 6 / 8837 vs 485 (1.72x)0.815 vs 0.829138 vs 1479.7 vs 12.6
w40_fish718 (5) -> 17153 / 140 / 1845 / 39 / 9 / 91372 vs 1291 (1.06x)0.769 vs 0.777111 vs 10822 vs 25

Reading: S2 has real room only where the fish has many usable frames outside the production segments: w32_fish1 (263 of 1904 usable 10 fps frames, 12 visits instead of 1, 1.72x sharper), w58_fish8 (6 visits instead of 2, 1.36x sharper) and to a small degree w40_fish7 (1.06x). For fish2 the _hf segments already cover all usable frames, so S2 is the same footage cut into windows (0.86x sharpness at 30 fps; 629 instead of 631 training frames: 2 short because every usable 10 fps frame is already used). w02_fish6 uses 78 of its 80 usable 10 fps frames either way and ends up blurrier at 30 fps (0.45x: different in-between frames around the same anchors), w12_fish3 trades sharpness (0.79x) for more visits (7 vs 3), view bins (13 vs 10) and size: its production segments are its sharpest part (Phase A, A3). S2's runs are short (1-2 s windows, 12-24 runs), so temporal smoothness and the pose-hypothesis chains see shorter chains than in _hf.

C1 gains (R, G, B) and L1:

fishC1 gains RGBfull grey-world gains (not used)clip guard scalefish px newly clippedL1 framesL1 MAD vs JPEG (levels)
fish21.43, 0.90, 0.901.55, 0.87, 0.871.000.00 %8592.09
w12_fish31.60, 0.72, 0.762.19, 0.80, 0.860.851.56 %5802.19
w02_fish61.46, 0.89, 0.891.60, 0.86, 0.861.000.21 %3472.03
w58_fish81.93, 0.85, 0.902.24, 0.80, 0.861.000.00 %6222.05
w32_fish11.54, 0.88, 0.901.71, 0.85, 0.871.000.04 %7022.10
w40_fish71.53, 0.88, 0.921.69, 0.84, 0.891.000.04 %5412.16

Smoke test

All 36 runs passed (6 fish x S1, S2, S3, S3 with --filter_file frame_filter_s3.json, L1, C1): 06_train_gs.py --fish <name>_<arm> --fins --deform --iters 50 --out _smoke (FISH_WINDOW for window fish) with the working-tree trainer of 2026-11-11 ~12:00-14:00 (which the trainer agent was editing at the time). Deviation: all runs except the first two (w58_fish8 S1, S2) added --tto_steps 10, because with the GPU shared by other jobs the default 150-step final evaluation made a 50-iteration run take 5-10 min; it only shortens the final evaluation. Every run loaded the expected training frames (train + val = the "training frames" above; the trainer's valid-frame count additionally includes keep = false frames), all plates and soft alphas, and wrote a model; the _smoke / _smoke_ff output folders were deleted afterwards. frame_filter_s3.json was picked up (mean weight 0.59-0.63 vs 0.91-1.0 with frame_filter.json). Logs: work/quality_exp/logs/smoke_A.log, smoke_B.log (+ .json).

Plates usable for the composite loss (bg_ok of 04c, frames with a valid pose):

fish_hf-based (S1 / L1 / C1)S2 / S3
fish2503 / 631498 / 629
w12_fish3110 / 329188 / 329
w02_fish6185 / 218186 / 219
w58_fish8436 / 555437 / 555
w32_fish1652 / 700381 / 702
w40_fish7338 / 504251 / 438

S2 of w32_fish1 (and less so w40_fish7) has clearly fewer frames with a usable plate: its 1 s windows are spread over 12 short visits where 04c's local plate is worse (frames without bg_ok are trained with the masked loss, see 06's docstring). This is a confound of the S2 arm to keep in mind.

Reproduce

# S2 (GPU: SAM + ViTMatte, 6-35 min per fish), per fish with its FISH_WINDOW
python 04d_hifps.py --fish w32_fish1 --out-suffix _s2 --select quality --exclude-heldout --pose-source like_hf
# L1 (CPU, < 1 min per fish)
python 04d_hifps.py --fish w32_fish1 --out-suffix _l1 --lossless --like _hf --exclude-heldout
# S3 needs the S2 frame quality; S1 / C1 only need _hf (no FISH_WINDOW needed)
python 12_frame_quality.py --fish w32_fish1 --dataset _s2
python 14_arm_data.py --fish fish2 w12_fish3 w02_fish6 w58_fish8 w32_fish1 w40_fish7 --arms s1,c1,s3 --summary

B. Trainer options

Date: 2026-11-11. New options of scripts/06_train_gs.py (with gs_model.py, gs_render.py) for the arms. All are off by default; the default run is unchanged (checked below). Tests: scripts/tests/quality_opts_test.py (unit, eval), synthetic data from scripts/tests/make_synth_fish.py (new flags --motion_blur N --exposure e, --water; defaults unchanged, no extra RNG draws).

optionwhat it does
--frame_select [FILE]reads <dataset dir>/frame_select.json (or FILE, absolute or relative to the data set) = {"frames": {"<id>": weight}}. Weight 0 or missing = not trained (split unselected in poses_refined.json). Weights multiply the per-frame loss together with the frame_filter weight and the robust weight; they are normalised to mean 1 over the training frames (after the internal val split, the exclusion and the frame filter). The internal every-10th val split is unchanged.
--exclude_frames SPECids never trained and not used as internal val (split excluded). SPEC = inline JSON list "[12,13]", a JSON file with a list (or {"frames": [...]}), a heldout.json of 13_heldout.py (exclude.hf for 30 fps data, exclude.base for 10 fps), or auto = quality_lib.training_exclusion(<base name>) with the base name taken from --fish (w32_fish1_hf, w32_fish1_s2 -> w32_fish1; auto:<name> to give it). The arm folders of section "B. Datasets" already contain no excluded frames, so the flag is a no-op there; it is needed for _hf runs (B0x, B1, M1, C2).
--waterunderwater image formation for the fish pixels (SeaThru-NeRF / WaterSplatting style, premultiplied): I_fish = J exp(-bD z) + alpha Binf (1 - exp(-bB z)), z = scale x expected camera depth of the render (gsplat RGB+ED, detached: colour must not move geometry), learnable global per-RGB bD, bB (> 0, log), Binf (0..1, logit) and scale (m per fish half-length, log, weak prior --lambda_water_scale to --water_scale0 0.1: only b x scale is identifiable). The gaussian colours are then the restored colours J. Compositing over the plate is unchanged (I = I_fish + (1 - alpha) plate; the plate already contains the water), alpha is unchanged. Init: bD = bB = 0.1/m, Binf = median background colour of the crops. Per-frame colour gain / bias (which could absorb the per-frame attenuation and backscatter exactly) are shrunk during training: --water_gain shrink (default, L2 x --water_gain_mult 10), fixed (identity) or free; the held-out test-time fit keeps the normal colour fit. Applied everywhere a render is compared with an image (training, robust scores, pose-hypothesis scoring, TTO). Water params: lr_water 1e-2, optimised after the warm-up. Saved as water in model.pt, poses_refined.json and train_log.json final.water (with bD_per_unit, bB_per_unit).
--motion_blur Nevery TRAINING render is the mean of N renders at times t + s e dt / 2, s in linspace(-1, 1, N), dt = frame interval (1/fps), e = learnable global exposure fraction (init --exposure0 0.5, sigmoid, --lr_exposure 1e-2, from --mb_start, default end of warm-up). Sub-frame states: --mb_motion fit (default): constant-velocity fit of the frame's motion from the refined poses, bending and caudal parameters of the training frames of its segment within +---mb_window 3 intervals (omega = sum tau Log(R_j R_i^T) / sum tau^2, v = sum tau (t_j - t_i) / sum tau^2, likewise bending / caudal; neighbours > --mb_max_deg 30 deg away are skipped), applied to the frame's own (differentiable) pose: R(tau) = Exp(tau omega) R_i, t_i + tau v. --mb_motion nearest: SLERP + lerp toward the previous / next refined training frame (the literal plan; see the test, it is too noisy). Densification uses the sum of the sub-render screen gradients. Held-out TTO, eval and all outputs render sharp (N = 1). Cost ~N x render time (N = 5: 2.4x wall time on _synth_blur_pl, GPU shared with another run). Saved as motion_blur {n, exposure, frame_interval_s} in model.pt / poses_refined.json / train_log.json.

Rendering a water model elsewhere: gs_model.water_observe(rgb, alpha, depth, bD, bB, Binf, scale) with depth from gs_render.render_ex(..., depth=True) (info["depth"]) or render_batch(..., depth=True). 07_render.py: turntables show the restored colours, sequence.mp4 the observed ones (--seq_water restored to switch). 10_fish_videos.py: FishModel.render(..., observed=True) applies the water; the tracking overlay (render at a real frame's pose) uses it, turntables / poster / swim stay restored. tests/quality_eval.py does not apply the water model yet (not my file): for a fair held-out comparison of a --water model it must render as observed (water from model.pt / poses_refined.json, water_observe with the render depth) before the colour fit and the metrics; otherwise its per-frame colour fit has to absorb the whole water effect and the restored colours are scored against the water-cast frames. Canonical views (views.png) of a water model are restored colours by construction.

Default behaviour unchanged

Same command at HEAD (copies of the three HEAD files) and with the new code (2000 iterations; gsplat training is not bit-deterministic, so HEAD was run twice to get the noise level):

data / commandruntrain PSNRval TTO PSNRval mask PSNRval IoUval comp PSNRgaussiansrot err train (deg)loss @500
_synth (default flags)HEAD 133.3334.8329.650.983-277623.130.0908
HEAD 233.3234.8629.700.983-278663.080.0908
new33.2734.7829.610.983-277083.110.0908
_synth_fins_4d --fins --deform --hyp_max_frames 300HEAD 125.8228.2227.150.95930.24173404.780.0551
HEAD 225.9028.4027.220.96030.02173384.810.0551
new25.8428.2527.280.95630.04173834.800.0551

The new code is within the HEAD-to-HEAD spread everywhere, with the same loss at iteration 500 and the same pose-repair counts. By construction the default path makes the same calls (the only changed expressions, fw != 1 instead of fw < 1 and filter weight x 1.0, are identical while all weights are <= 1).

Synthetic tests

quality_opts_test.py unit: SO(3) log / exp round trip (max error 1.5e-11) and a finite gradient at 0, pose interpolation end points / half way / extrapolation, water limits (z = 0 -> J, z -> inf -> alpha Binf), WaterModel round trip, render depth (plane at z = 3 exact in gsplat and torch, rgb / alpha unchanged with depth=True, render_batch = render_ex, backends agree), frame_select / exclusion parsing: all pass.

Smoke runs: --frame_select (weights 2 / 1 / 0) + --exclude_frames (6 ids) on _synth: 6 excluded, 19 unselected, 128 trained, effective weights odd / even frames 1.28 / 0.64 (ratio 2, mean 1); --eval_only --water reloads the water model; --fins --deform --water --motion_blur 3 on _synth_fins_4d (30 fps, caudal) runs; on this blur-free, water-free data e falls to 0.17 and bD x scale to ~0.002-0.003, as it should. Real-data smoke on w12_fish3_hf (--fins --deform --exclude_frames auto --water --motion_blur 3, 1500 iterations): the exclusion resolves to 134 of the data set's frames (train 417 -> 298, internal val 46 -> 31), 277 / 20 / 1 training frames with two / one / no temporal neighbour, peak GPU memory 0.61 GB, 788 s (GPU shared); learned e = 0.27, bD x scale = (.017, .009, .010), bB x scale = (.004, .006, .007), Binf = (.09, .44, .34) (red attenuated most, as expected). A smoke test only, not a result (1500 iterations, no comparison run).

Evaluation (quality_opts_test.py eval): on every 2nd training frame (77 frames) the model is rendered SHARP and RESTORED (no water, gain 1, bias 0) at its refined pose and compared with the GT model at the GT pose (no blur / water / gain), inside the GT mask: PSNR, mean colour error (levels), gradient-energy ratio render / GT (quality_lib.sharpness), LPIPS on the fish box over grey. 3000 iterations, default trainer otherwise. Outputs: work/quality_exp/trainer_opts_tests/eval_*.json and contact sheets eval_*.jpg (crop | GT | models).

Motion blur (make_synth_fish.py --motion_blur 9 --exposure 0.5 [--plates]: each frame = mean of 9 renders of the analytic trajectory over half the frame interval; at 10 fps the fish moves roughly 9-13 px during the exposure in the 256 px crops, estimated from the trajectory speed):

datamodelPSNR vs sharp GTcolour errsharpness ratioLPIPSlearned e (GT 0.5)
_synth (no blur, 2000 it., reference)default26.386.40.820.031-
_synth_blur_pl, --bg_compositebase22.2011.90.440.148-
--motion_blur 526.107.30.740.0480.418
_synth_blur (masked loss, no plates)base22.0612.00.440.105-
--motion_blur 5 --mb_motion nearest22.0612.00.440.0990.06 (collapsed)
--motion_blur 5 (fit), e learnablekilled at it. 1500, e = 0.085 (collapsing)
--motion_blur 5 --mb_start 0.6killed at it. 2000, e = 0.20 (collapsing)
--motion_blur 5 --lr_exposure 0 (e fixed 0.5)21.6910.70.850.0740.5 (fixed)

With the plate-composite loss (the production --fins setting) --motion_blur recovers nearly the sharpness of a model trained on unblurred frames (PSNR 26.1 vs 26.4, LPIPS 0.048 vs 0.031, from 0.148) and learns e = 0.42 for a true 0.5. With the masked loss (no plate) it does not work: the target is crop x hard mask on black, so every blur streak that leaves the SAM mask is penalised and the exposure collapses to ~0 (also with a late start); with e fixed at the true value the model is sharper but grows dark rims at the silhouette (PSNR 21.7). Frames with bg_ok false use the masked loss in fins mode and may pull e down. Use --motion_blur only with --bg_composite / --fins. The nearest interpolation (literal plan) failed even before that: one noisy neighbour pose per side is not a usable velocity at the pose noise of this data (refined rotation error ~2-3 deg vs ~3 deg of motion per frame); the windowed fit is the default.

Water (make_synth_fish.py --water --plates: per fish unit bD = (0.08, 0.03, 0.02), bB = (0.06, 0.05, 0.05), Binf = (0.10, 0.42, 0.50), distance 7-18 fish units; the fish turns cyan as in the real models), --bg_composite:

modelPSNR restored vs GT Jcolour errLPIPSsharpnessobserved fit PSNRval TTO PSNRbD x scale (GT .08 .03 .02)bB x scale (GT .06 .05 .05)Binf (GT .10 .42 .50)
base (per-frame gain / bias only)12.9842.20.2570.3929.8234.73---
--water (gain shrink)24.1612.70.0480.8630.9736.11.074 .034 .022.031 .025 .025.15 .65 .77
--water --water_gain fixed24.3012.40.0500.8130.5536.00.070 .032 .020.021 .025 .026.17 .63 .75

The attenuation is recovered within ~10 %; backscatter coefficient and veiling light trade off against each other (at these distances Binf (1 - exp(-bB z)) ~ Binf bB z: the products .0047/.016/.019 vs GT .006/.021/.025), so the restored colours are still a little pale (mean restored RGB .54 .57 .46 vs GT .57 .51 .42). The baseline's canonical colours are the observed (water-cast) ones. The water model also fits the observed frames better (+1.1 dB) and its gradient-energy ratio is higher mostly because the restored colours have more contrast. On real data the distance range of a fish is smaller and the gain shrink matters more (identifiability); treat the learned values with care.

Notes for the arm runs

B. Review before the arm runs (adversarial check, 2026-11-11)

Scope: fairness of the evaluation, the arm data sets, the trainer options. Scripts used for the checks are one-off (not committed); the fixes are in tests/quality_eval.py, quality_lib.py, 04d_hifps.py, 14_arm_data.py.

Checked, no defect found

Defects found and fixed

fishsharp_ratio B0+ noise 3 levels+ noise 6 levelssharp_match B0+ noise 6blur 1 px
fish20.1190.1420.2020.2790.2810.240
w12_fish30.3980.4030.4160.3660.3660.336
w02_fish60.5950.5990.6250.7290.7270.670
w58_fish80.1800.8022.5940.1260.1250.127
w32_fish10.1310.2490.7040.0320.0340.035
w40_fish70.0330.1080.3740.0150.0100.018

(medians over the held-out frames; the photo itself + 3-level noise scores sharp_match 0.997-0.999.) A 3DGS model with speckle / floaters inside the fish could therefore "improve" the sharpness ratio without any real detail. New metrics in quality_eval.py: sharp_match = sum(grad render . grad photo) / sum |grad photo|^2 inside the eroded mask after a sigma 1.5 px pre-blur of both images (only detail in the right place counts; noise cannot raise it) and sharp_ratio_pb (energy ratio after the same pre-blur, less noise-sensitive). Decision rule: a sharp_ratio gain counts only if sharp_match (and LPIPS) do not get worse. For the dark, low-texture fish (w58_fish8, w32_fish1, w40_fish7) sharp_match is near 0 already for B0 (the photo's in-mask gradients are mostly grain and the render's detail is not in register), so there it is a weak signal; LPIPS stays primary.

fish / data settraining frameswith plate (bg_ok)sharpnessellipsoid fitlength pxruns
w32_fish1 _hf (B0x)7006524860.8281481
w32_fish1 _s2700379837 (1.72x)0.81513824
w32_fish1 _s2p700694676 (1.39x)0.83614121
w40_fish7 _hf (B0x)42927511060.7741073
w40_fish7 _s24292481372 (1.24x)0.76911117
w40_fish7 _s2p4293021167 (1.06x)0.77010514

S2p keeps most of S2's sharpness gain for w32_fish1 with more plates than _hf; for w40_fish7 the sharpness advantage over _hf mostly disappears once plates are required (its sharper visits are the ones without a usable plate). Exclusion re-checked (0 frames near a held-out frame in _s2p / _s3p). _s3p = _s2p with the S3 weights (mean 0.626). Build: FISH_WINDOW=32 04d_hifps.py --fish w32_fish1 --out-suffix _s2p --select quality --exclude-heldout --pose-source like_hf --sel-plate-factor 0.2, 12_frame_quality.py --fish w32_fish1 --dataset _s2p, 14_arm_data.py --fish w32_fish1 --arms s3 --s3_from s2p --s3_to s3p (same for w40_fish7 with window 40). Recommendation: run S2 / S3 on _s2p / _s3p for these two fish (or both variants: the difference isolates the plate effect).

Remaining caveats (not fixed)

C. Results: arm matrix (2026-11-11/12)

Setup

Noise floor first

gsplat training is not deterministic. Running the identical B0x command again (B0x2) changes the held-out LPIPS by up to 0.0034, the sharpness ratio by up to x1.14 and masked PSNR by up to 0.15 dB (3 fish). w32_fish1's B0 is a fourth near-rerun: it shares 97 % of B0x's training frames and differs by +0.0018 LPIPS. By the plan's rule a rerun "wins" on 2 of 3 fish, and w32_fish1's rerun is "significantly" worse by the paired Wilcoxon test (p < 0.001, 20 % of frames better). The paired test only covers the sampling of held-out frames, not the training randomness, so per-fish p-values overstate significance. Effects below about 0.0035 LPIPS or x1.15 sharpness on one fish are noise. The table "Beyond the training noise floor" below counts only changes above the largest rerun difference. With 3 reruns that is still a lenient threshold.

Contamination (B0 vs B0x)

On the three fish whose held-out frames were mostly inside gs_final's training window (fish2 16/16, w02_fish6 10/12, w12_fish3 9/24), B0 beats its fair retrain B0x by 0.011-0.020 LPIPS and 0.86-1.35 dB masked PSNR. On the others the gap is 0.004 / 0.2 dB or less. fish2's B0 also had 10000 iterations. Phase A's B0 numbers were optimistic by more than any arm gains, so all comparisons below are against B0x.

Per arm

Visual check (contact sheets and canonical / turntable views)

I looked at compare.png and compare_views.png of all six fish (plus extra sheets with B0x2 / S1r / C2):

Tables (work/quality_exp/matrix/results.md, generated by tests/quality_matrix_report.py)

Held-out set and protocol of section A (identical for every arm). "win" = the plan's rule vs B0x: LPIPS (fish box, mean) lower AND sharpness ratio (median) higher AND sharp_match (median) not lower AND masked PSNR not more than 0.3 dB lower; adopt at >= 4/6 fish.

Per fish x arm (held-out set; PSNR / LPIPS / IoU = means, sharpness = medians)
fisharmPSNR fullPSNR maskLPIPS fishsharp ratiosharp matchIoUflank view grad Egaussianstrain min (job wall / optimisation; 4 jobs share the GPU)contam.
fish2B024.1421.310.16940.1190.2790.89712.5126908- / 6216/16
fish2B0x23.6820.450.18890.0770.0950.8998.152010532 / 240/16
fish2B123.5720.450.17810.1520.1630.89222.623364690 / 820/16
fish2S123.5820.440.17080.2070.2420.89114.192285037 / 310/16
fish2S1r23.6020.450.18070.1030.1800.8959.462234232 / 230/16
fish2S223.6320.450.18900.0850.1040.8997.002002733 / 250/16
fish2S323.6520.500.18650.0850.1520.8966.781989430 / 230/16
fish2C123.6620.420.19070.0860.1190.8995.632017431 / 240/16
fish2C223.6520.340.19810.0680.1060.8965.571892833 / 250/16
fish2M123.6220.340.18800.0950.0900.8996.822067150 / 420/16
fish2L123.6220.400.19160.0750.1270.8976.462005830 / 230/16
w12_fish3B021.8517.150.21420.3980.3660.88559.3246076- / 189/24
w12_fish3B0x21.0216.170.23040.4310.2380.87756.464666226 / 220/24
w12_fish3B0x221.0116.210.22700.4700.2560.88170.244637925 / 210/24
w12_fish3B120.8015.880.22510.4760.2320.87447.714522778 / 740/24
w12_fish3S120.5115.540.22570.6890.2130.872157.124621219 / 170/24
w12_fish3S221.0616.160.21940.4870.2390.88690.354672928 / 230/24
w12_fish3S321.1616.300.21350.5200.2440.875101.374661525 / 220/24
w12_fish3C121.0316.200.22690.4410.2030.87462.774677726 / 220/24
w12_fish3C220.8515.990.23410.4920.2260.878119.534575828 / 240/24
w12_fish3M120.8916.030.22860.5640.2110.88297.824680843 / 390/24
w12_fish3L121.0416.240.22270.4980.2540.88370.264670224 / 210/24
w02_fish6B024.2717.930.10390.5950.7290.872280.0224350- / 1710/12
w02_fish6B0x23.2016.580.11490.6250.6490.872283.272509622 / 190/12
w02_fish6B123.3416.800.11970.6310.6660.866280.793816366 / 630/12
w02_fish6S123.2716.360.11300.6930.7320.872323.452994417 / 160/12
w02_fish6S1r22.9216.100.11780.6590.6870.865268.602793122 / 190/12
w02_fish6S223.3616.800.11600.5810.6490.869278.402695923 / 210/12
w02_fish6S323.3616.700.11650.5920.6700.875285.412577022 / 190/12
w02_fish6C123.2016.580.11890.6190.6420.867280.322609522 / 190/12
w02_fish6C223.2016.600.11770.5960.6350.874284.212543725 / 220/12
w02_fish6M123.1016.460.12510.6810.6180.873299.552630561 / 570/12
w02_fish6L123.1616.560.12300.6440.6450.868272.912550132 / 280/12
w58_fish8B025.7925.660.11630.1720.1260.9190.1319978- / 185/23
w58_fish8B0x25.6925.450.12000.1790.1130.9190.162026230 / 230/23
w58_fish8B0x225.7125.480.11960.1960.1270.9190.142036429 / 220/23
w58_fish8B125.7625.680.11590.2560.1440.9150.162676984 / 770/23
w58_fish8S125.6725.370.11760.2610.1130.9160.112113523 / 200/23
w58_fish8S1r25.8025.740.11470.1810.1500.9160.132052426 / 210/23
w58_fish8S225.8626.080.11600.1500.1600.9210.092143730 / 230/23
w58_fish8S325.8626.230.11420.1470.1720.9230.092160729 / 230/23
w58_fish8C125.7625.540.11670.2060.1280.9200.152021129 / 230/23
w58_fish8C225.6625.430.12160.2610.1310.9200.551959231 / 250/23
w58_fish8M125.6825.410.11810.2230.1220.9190.382044347 / 410/23
w32_fish1B022.2218.680.15760.1300.0320.9026.7022914- / 231/40
w32_fish1B0x22.2118.720.15580.1210.0390.9000.292303136 / 270/40
w32_fish1B0x222.1518.570.15860.1380.0350.9010.302316331 / 230/40
w32_fish1B122.1818.570.15030.1860.0490.8980.503696184 / 760/40
w32_fish1S122.2418.700.15190.2010.0470.90312.212415726 / 220/40
w32_fish1S1r22.2018.610.15320.1180.0350.90212.722406624 / 170/40
w32_fish1S222.6319.020.15310.0890.0410.9050.322223833 / 250/40
w32_fish1S322.7219.040.15030.0770.0500.9090.322215432 / 240/40
w32_fish1C122.1618.660.15860.1270.0480.9000.352339931 / 230/40
w32_fish1C222.2018.670.15670.1090.0400.9027.122242734 / 260/40
w32_fish1M122.1818.690.15840.1330.0400.8990.322349151 / 420/40
w40_fish7B020.6317.850.19830.0330.0150.8700.6318485- / 2310/13
w40_fish7B0x20.5117.760.20120.0330.0230.8670.771773331 / 260/13
w40_fish7B120.7417.850.19810.1410.0310.8712.512617674 / 690/13
w40_fish7S120.3517.620.20580.1650.0300.8643.172210023 / 200/13
w40_fish7S220.6217.940.20050.0460.0230.8711.001847627 / 230/13
w40_fish7S320.5517.930.20310.0700.0180.8701.011926427 / 220/13
w40_fish7C120.4917.650.20300.0390.0230.8661.101829227 / 220/13
w40_fish7C220.5417.810.20450.0390.0280.8681.251756329 / 240/13
w40_fish7M120.4617.680.20360.0360.0280.8650.911801245 / 400/13
Deltas vs B0x (arm - B0x); LPIPS paired: share of held-out frames better, Wilcoxon p
fisharmdPSNR fulldPSNR maskdLPIPSframes better (p)sharp ratio xd sharp matchdIoUflank grad E xwin
fish2B0+0.46+0.86-0.019588% (0.000)1.550.184-0.0021.53yes
fish2B1-0.11+0.00-0.010888% (0.002)1.970.069-0.0072.78yes
fish2S1-0.10-0.01-0.018181% (0.001)2.690.147-0.0081.74yes
fish2S1r-0.08-0.00-0.008281% (0.006)1.340.085-0.0041.16yes
fish2S2-0.05+0.00+0.000169% (0.404)1.100.009-0.0000.86no
fish2S3-0.03+0.05-0.002569% (0.130)1.110.057-0.0020.83yes
fish2C1-0.02-0.03+0.001831% (0.051)1.120.025+0.0000.69no
fish2C2-0.03-0.11+0.00926% (0.000)0.880.011-0.0030.68no
fish2M1-0.06-0.10-0.000962% (0.375)1.24-0.005+0.0000.84no
fish2L1-0.06-0.05+0.002731% (0.029)0.970.032-0.0020.79no
w12_fish3B0+0.84+0.98-0.016279% (0.002)0.920.129+0.0081.05no
w12_fish3B0x2-0.00+0.03-0.003458% (0.252)1.090.018+0.0041.24yes
w12_fish3B1-0.21-0.29-0.005362% (0.089)1.10-0.006-0.0030.85no
w12_fish3S1-0.51-0.63-0.004662% (0.178)1.60-0.025-0.0052.78no
w12_fish3S2+0.05-0.02-0.011062% (0.049)1.130.002+0.0091.60yes
w12_fish3S3+0.15+0.13-0.016983% (0.001)1.210.006-0.0011.80yes
w12_fish3C1+0.02+0.02-0.003554% (0.218)1.02-0.035-0.0031.11no
w12_fish3C2-0.16-0.19+0.003733% (0.069)1.14-0.012+0.0022.12no
w12_fish3M1-0.12-0.14-0.001758% (0.456)1.31-0.026+0.0061.73no
w12_fish3L1+0.02+0.06-0.007679% (0.002)1.160.017+0.0061.24yes
w02_fish6B0+1.07+1.35-0.010975% (0.021)0.950.080+0.0000.99no
w02_fish6B1+0.14+0.22+0.004867% (0.910)1.010.018-0.0060.99no
w02_fish6S1+0.08-0.22-0.001958% (0.677)1.110.084+0.0001.14yes
w02_fish6S1r-0.27-0.48+0.003017% (0.064)1.050.038-0.0070.95no
w02_fish6S2+0.17+0.22+0.001158% (0.791)0.930.001-0.0030.98no
w02_fish6S3+0.16+0.12+0.001750% (0.733)0.950.021+0.0031.01no
w02_fish6C1-0.00-0.00+0.004150% (0.470)0.99-0.006-0.0050.99no
w02_fish6C2+0.00+0.02+0.002842% (0.301)0.95-0.014+0.0021.00no
w02_fish6M1-0.09-0.12+0.01028% (0.001)1.09-0.031+0.0011.06no
w02_fish6L1-0.04-0.02+0.008117% (0.007)1.03-0.004-0.0040.96no
w58_fish8B0+0.10+0.21-0.003791% (0.000)0.960.013+0.0000.81no
w58_fish8B0x2+0.01+0.03-0.000457% (0.377)1.090.014+0.0000.88yes
w58_fish8B1+0.07+0.24-0.004283% (0.001)1.430.031-0.0030.97yes
w58_fish8S1-0.02-0.08-0.002457% (0.111)1.460.000-0.0030.66yes
w58_fish8S1r+0.11+0.29-0.005387% (0.000)1.010.037-0.0030.78yes
w58_fish8S2+0.17+0.64-0.004061% (0.016)0.840.046+0.0020.52no
w58_fish8S3+0.17+0.78-0.005865% (0.048)0.820.059+0.0040.56no
w58_fish8C1+0.06+0.10-0.003378% (0.002)1.150.015+0.0020.91yes
w58_fish8C2-0.03-0.02+0.001626% (0.052)1.460.018+0.0013.34no
w58_fish8M1-0.01-0.03-0.002078% (0.000)1.250.009-0.0002.31yes
w32_fish1B0+0.01-0.04+0.001840% (0.118)1.08-0.007+0.00222.81no
w32_fish1B0x2-0.06-0.15+0.002820% (0.000)1.14-0.004+0.0011.03no
w32_fish1B1-0.03-0.15-0.005580% (0.000)1.540.010-0.0021.71yes
w32_fish1S1+0.03-0.02-0.003980% (0.004)1.660.008+0.00341.61yes
w32_fish1S1r-0.01-0.11-0.002662% (0.039)0.97-0.005+0.00243.34no
w32_fish1S2+0.42+0.30-0.002765% (0.094)0.730.002+0.0051.10no
w32_fish1S3+0.51+0.32-0.005568% (0.006)0.640.011+0.0091.10no
w32_fish1C1-0.05-0.06+0.002825% (0.001)1.050.009+0.0001.20no
w32_fish1C2-0.01-0.05+0.000930% (0.044)0.900.001+0.00224.27no
w32_fish1M1-0.03-0.02+0.002640% (0.027)1.100.001-0.0011.10no
w40_fish7B0+0.12+0.09-0.002954% (0.588)1.00-0.008+0.0030.82no
w40_fish7B1+0.23+0.09-0.003077% (0.127)4.320.007+0.0043.27yes
w40_fish7S1-0.17-0.15+0.004631% (0.305)5.050.007-0.0034.13no
w40_fish7S2+0.11+0.17-0.000738% (0.305)1.410.000+0.0041.30yes
w40_fish7S3+0.04+0.17+0.001946% (0.305)2.15-0.005+0.0031.32no
w40_fish7C1-0.02-0.11+0.001846% (0.168)1.19-0.000-0.0011.43no
w40_fish7C2+0.03+0.05+0.003315% (0.003)1.180.004+0.0011.63no
w40_fish7M1-0.05-0.08+0.002415% (0.017)1.100.005-0.0021.18no
Decision rule per arm (wins = LPIPS lower AND sharp ratio higher AND sharp match not lower AND PSNR mask >= -0.3 dB, vs B0x)
armfishwinsLPIPS bettersharp bettermatch okPSNR okLPIPS sig. better / worsemean dLPIPSmean dPSNR maskmean dPSNR fullsharp ratio x (geo)adopt (>= 4/6)won on
B06152464 / 0-0.0086+0.57+0.431.06nofish2
B0x23223230 / 1-0.0003-0.03-0.021.11now12_fish3, w58_fish8
B16456563 / 0-0.0040+0.02+0.011.66yesfish2, w58_fish8, w32_fish1, w40_fish7
S16456552 / 0-0.0044-0.18-0.121.97yesfish2, w02_fish6, w58_fish8, w32_fish1
S1r4233333 / 0-0.0033-0.07-0.061.09nofish2, w58_fish8
S26243662 / 0-0.0028+0.22+0.141.00now12_fish3, w40_fish7
S36243563 / 0-0.0045+0.26+0.171.06nofish2, w12_fish3
C16125361 / 1+0.0006-0.01-0.001.08now58_fish8
C26003460 / 3+0.0036-0.05-0.031.07no
M16136361 / 3+0.0018-0.08-0.061.18now58_fish8
L13112231 / 2+0.0011-0.00-0.031.05now12_fish3
Beyond the training noise floor (B0x2 = B0x rerun on 3 fish: max |dLPIPS| 0.0034, max sharp-ratio factor 1.14, max |dPSNR mask| 0.15 dB)
armfishLPIPS better by > noiseLPIPS worse by > noisesharp ratio x > noiseclear wins (both + match ok + PSNR ok)
B064011
B0x230000
B164143
S163152
S1r42011
S262010
S363021
C161120
C260220
M160130
L131111
Learned image-formation parameters (M1 exposure fraction; C2 water per fish half-length)
fisharmexposurebD x scale (RGB)bB x scale (RGB)Binf (RGB)
fish2C2-0.001 0.002 0.0010.007 0.017 0.0170.196 0.821 0.802
fish2M10.405---
w12_fish3C2-0.021 0.006 0.0080.001 0.005 0.0060.032 0.354 0.315
w12_fish3M10.242---
w02_fish6C2-0.002 0.002 0.0020.003 0.006 0.0090.065 0.498 0.533
w02_fish6M10.240---
w58_fish8C2-0.001 0.004 0.0030.009 0.013 0.0130.161 0.761 0.739
w58_fish8M10.411---
w32_fish1C2-0.001 0.002 0.0020.005 0.009 0.0080.230 0.838 0.770
w32_fish1M10.997---
w40_fish7C2-0.021 0.005 0.0050.008 0.006 0.0060.240 0.697 0.610
w40_fish7M10.396---

Recommendation (phase C)

Review D (below) re-checked these conclusions and gives the go / no-go per change for the rollout (D5).

Not done / caveats

Reproduce (from scripts/, venv python): python -u run_quality_matrix.py --slots 4 (B0x2 / S1r via work/quality_exp/matrix/control.json {"add": ["w12_fish3:B0x2", ..., "fish2:S1r", ...]} after python tests/make_s1r_select.py --fish fish2 w02_fish6 w58_fish8 w32_fish1), then python tests/quality_matrix_report.py.

D. Adversarial review of the results (2026-11-12)

A second, independent check of section C before the rollout decision. Script: scripts/tests/quality_review_d.py (reads only metrics.json, train_log.json, meta.json and heldout.json; no GPU).

D1. Re-derived and checked: no defect

D2. Confound: S2 / S3 train next to held-out frames that _hf never came near

The held-out set is drawn from all visits (section A). _hf covers 1-3 visits; S2 / S3 draw from all of them. So the S2 data put training frames close in time (beyond the 0.3 s exclusion) to held-out frames whose visit _hf does not contain at all:

fishheld-out frames with a training frame within 3 s, B0x -> S2/S3median distance to the nearest training frame
w58_fish85 -> 22 of 23304 s -> 0.7 s
w32_fish12 -> 16 of 4094 s -> 4.2 s
w12_fish37 -> 11 of 2415.8 s -> 3.5 s
fish2, w02_fish6, w40_fish7unchanged (all near)unchanged

These are exactly the three fish where S2 / S3 beat the noise floor. Per held-out frame, dLPIPS vs B0x split by proximity (3 s radius, pooled over fish):

armnewly near (arm has a training frame within 3 s, B0x not)far from bothnear both
S3n 39, -0.0098 (77 % better)n 34, -0.0110 (76 %)n 51, +0.0007 (53 %)
S2n 39, -0.0039 (62 %)n 34, -0.0094 (76 %)n 51, +0.0007 (51 %)
B1-n 73, -0.0057 (78 %)n 55, -0.0033 (75 %)
B0x2 (rerun)-n 73, +0.0010 (36 %)n 14, -0.0034 (64 %)

How to read this:

D3. Corrections and additions to section C

fishlongest run, _hf -> S2
w32_fish124.0 s -> 3.4 s (9.4 s with _s2p)
w58_fish816.1 s -> 6.6 s
w40_fish714.4 s -> 6.5 s (10.0 s with _s2p)
w12_fish39.5 s -> 5.6 s

S2 also needs the SAM + matting stage per fish (6-35 min of GPU each) and, for some fish, the plate-aware variant.

D4. What stands

D5. Go / no-go per change for the gs_v2 rollout

changeverdictcondition / reason
B1 (--iters 12000)GOThe only gain that holds independently of visit coverage (D2, D4). Cost 3x training (66-90 min per fish with 4 jobs on the GPU). Watch: w02_fish6-like small fish got worse; artifacts get sharper (w32_fish1). Raise --max_gauss for fish near the 50000 cap.
S3 (all-visits data + soft weights)NO-GO for the rollout now; phase C test onlyFails the plan's rule (2/6). Its mean gain is partly visit coverage (D2). It shortens the tracking / swim clips and needs the S2 data stage per fish. Re-test as B1 + S3 vs B1 with 2 seeds, scoring only held-out frames that both data sets cover equally (or far from both). Adopt only if it still wins and the videos are kept on _hf segments.
S2NO-GOSuperseded by S3 (same frames).
S1 (hard halving)NO-GOView holes (w12_fish3 -0.63 dB masked PSNR); half of its gain is the fewer-frames effect, which B1 gives without the holes.
C1 (plate white balance)NO-GONo effect.
C2 (water model)NO-GOLPIPS worse on 6/6; parameters not identifiable.
M1 (motion blur)NO-GOLPIPS worse on 3/6, sharp_match lower, 1.7x cost.
L1 (lossless crops)NO-GONo consistent effect (3 fish).

E. Phase C and rollout: gs_v2 on all 44 fish (2026-11-12)

This is the "rollout" step of the plan (phase C). Section D was already taken by the review, so it is E here.

E1. Recipe

Only B1 got a GO in D5, so there was nothing to combine. The v2 recipe is B1 plus the watch-out from D5, a higher gaussian cap:

06_train_gs.py --fish <name>_hf --fins --deform --iters 12000 --hyp_max_frames 300 [--res 384 for 512 px crops] --max_gauss 100000 --out gs_v2

Everything else is as in gs_final: the same data set (<name>_hf), seed and trainer. S3 was not combined (NO-GO in D5), and C1 / C2 / M1 / L1 were not used. There is no --water, so the turntables show the normal gaussian colours.

E2. Verification on the 6 fish (arm v2 = q_v2, with the held-out exclusion)

run_quality_matrix.py --arms v2 --state work/quality_exp/matrix/state_v2.json (same protocol as section C: --exclude_frames auto, 0 contaminated held-out frames). Report: tests/quality_matrix_report.py --arms B0,B0x,B0x2,B1,v2 --suffix _v2 -> work/quality_exp/matrix/results_v2.{md,json}, work/quality_exp/<fish>/compare_v2.png / compare_views_v2.png (real | B0 | B0x | B1 | v2).

fishdLPIPS vs B0x: B1 / v2frames better (v2)sharpness x raw: B1 / v2sharpness x pre-blurred: B1 / v2dPSNR mask: B1 / v2gaussians B1 / v2rule win: B1 / v2
fish2-0.0108 / -0.011281 %1.97 / 1.951.28 / 1.55+0.00 / +0.0133.6k / 33.9kyes / yes
w12_fish3-0.0053 / -0.005471 %1.10 / 1.071.03 / 1.19-0.29 / -0.1845.2k (cap) / 86.4kno / yes
w02_fish6+0.0048 / +0.001375 %1.01 / 0.980.97 / 0.96+0.22 / +0.2338.2k / 38.5kno / no
w58_fish8-0.0042 / -0.003378 %1.43 / 1.331.23 / 1.11+0.24 / +0.2826.8k / 26.9kyes / yes
w32_fish1-0.0055 / -0.006490 %1.54 / 1.451.23 / 1.14-0.15 / -0.0837.0k / 36.9kyes / yes
w40_fish7-0.0030 / -0.001254 %4.32 / 3.922.00 / 1.93+0.09 / +0.1626.2k / 26.6kyes / no
allmean -0.0040 / -0.0044geo 1.66 / 1.58geo 1.25 / 1.28+0.02 / +0.074/6 / 4/6

<!-- E3+ (rollout) filled in after the 44-fish run -->

Results report docs/RESULTS.md

Results: every fish of the video, reconstructed

Final production run on 2026-11-10 (13:15 to 20:15, one RTX 4090, at most 4 GPU jobs at once). Method: see docs/method_choice.md. Each fish gets 30 fps data from 04d_hifps.py, capped at 24 s of footage. It is then trained with 4D Gaussian splatting and the fin preset (06_train_gs.py --fins --deform --iters 4000 --hyp_max_frames 300 --out gs_final), and its videos come from 10_fish_videos.py.

Where to look:

Reproduce: cd scripts; ..\.venv\Scripts\python -u run_production.py --slots 4 (resumable: it skips steps whose outputs exist), then ..\.venv\Scripts\python make_results.py to rebuild the table below and work/gallery.html.

All fish

Column notes:

<!-- table -->

#fishwindowseen (video time, 10 fps visits)frames 10 fps / 30 fpsspecies top-1 (prob, confidence)methodheld-out PSNR full / maskedIoUreviewoutputs
1fish148:0048:17-49:20, 49:58-50:47, 50:49-50:50 (+7 more)1728 / 721Cephalopholis hemistiktos (Yellowfin hind) 68%, medium30 fps fins+4D25.3 / 24.30.926fairturntable turntable_swim tracking poster
2fish248:0048:02-48:12, 48:23-48:29, 49:41-49:47 (+2 more)359 / 1146Balistapus undulatus (Orange-lined triggerfish) 100%, high30 fps fins+4D, 10k iters25.4 / 20.30.889goodturntable turntable_swim tracking poster
3fish348:0048:09-48:12, 51:26-51:28, 51:57-51:59 (+25 more)910 / 739Chromis dimidiata (Chocolate-dip chromis) 99%, high30 fps fins+4D, 10k iters28.0 / 21.00.884goodturntable turntable_swim tracking poster
4w02_fish102:0002:46-02:50, 04:16-04:19, 05:12-05:17 (+9 more)461 / 724Halichoeres hortulanus (Checkerboard wrasse) 100%, high30 fps fins+4D24.9 / 18.20.883goodturntable turntable_swim tracking poster
5w02_fish202:0002:22-02:23, 02:31-02:33, 02:42-02:45 (+13 more)611 / 523Halichoeres hortulanus (Checkerboard wrasse) 99%, high30 fps fins+4D27.8 / 20.10.899goodturntable turntable_swim tracking poster
6w02_fish402:0005:01-05:03, 08:09-08:14, 09:03-09:10 (+4 more)355 / 715Balistapus undulatus (Orange-lined triggerfish) 100%, high30 fps fins+4D26.6 / 22.20.879goodturntable turntable_swim tracking poster
7w02_fish602:0005:43-05:45, 05:55-05:57, 06:58-07:03 (+3 more)166 / 513Labroides dimidiatus (Bluestreak cleaner wrasse) 100%, high30 fps fins+4D24.7 / 16.80.881goodturntable turntable_swim tracking poster
8w02_fish702:0003:22-03:24, 03:28-03:31, 07:37-07:40 (+9 more)355 / 720Chromis dimidiata (Chocolate-dip chromis) 97%, high30 fps fins+4D25.8 / 20.40.895goodturntable turntable_swim tracking poster
9w02_fish802:0007:10-07:12, 07:27-07:32, 08:21-08:30 (+2 more)206 / 619Oxycheilinus digramma (Cheeklined wrasse) 67%, medium30 fps fins+4D30.2 / 25.50.929fair, species doubtfulturntable turntable_swim tracking poster
10w02_fish902:0002:34-02:37, 05:15-05:17, 06:28-06:30 (+7 more)225 / 395Oxycheilinus digramma (Cheeklined wrasse) 36%, low30 fps fins+4D26.3 / 20.50.856fair, species doubtfulturntable turntable_swim tracking poster
11w02_fish1002:0002:56-02:58, 03:02-03:05, 04:54-04:56 (+5 more)161 / 445Oxycheilinus digramma (Cheeklined wrasse) 41%, low30 fps fins+4D30.7 / 23.40.880fair, species doubtfulturntable turntable_swim tracking poster
12w12_fish112:0012:59-13:24, 13:39-14:03, 14:54-15:23 (+5 more)1776 / 721Cephalopholis hemistiktos (Yellowfin hind) 86%, high30 fps fins+4D24.2 / 21.30.822fairturntable turntable_swim tracking poster
13w12_fish212:0013:23-13:25, 13:43-13:46, 13:53-14:05 (+7 more)709 / 677Halichoeres hortulanus (Checkerboard wrasse) 100%, high30 fps fins+4D24.7 / 17.20.873goodturntable turntable_swim tracking poster
14w12_fish312:0014:12-14:14, 14:17-14:25, 14:30-14:32 (+9 more)550 / 720Chaetodon paucifasciatus (Eritrean butterflyfish) 100%, high30 fps fins+4D21.7 / 17.10.872goodturntable turntable_swim tracking poster
15w12_fish512:0012:05-12:07, 13:11-13:14, 16:04-16:06 (+9 more)220 / 498Chromis dimidiata (Chocolate-dip chromis) 98%, high30 fps fins+4D26.5 / 19.60.870goodturntable turntable_swim tracking poster
16w12_fish912:0012:23-12:25, 12:30-12:36, 13:22-13:24 (+11 more)392 / 640Oxycheilinus digramma (Cheeklined wrasse) 36%, low30 fps fins+4D29.1 / 22.10.888fair, species doubtfulturntable turntable_swim tracking poster
17w22_fish122:0023:18-23:31, 24:09-24:13, 24:16-24:21 (+13 more)1592 / 721Cephalopholis hemistiktos (Yellowfin hind) 83%, high30 fps fins+4D24.0 / 22.20.775poorturntable turntable_swim tracking poster
18w22_fish222:0025:02-25:03, 28:13-28:25, 29:28-29:31 (+3 more)265 / 704Balistapus undulatus (Orange-lined triggerfish) 98%, high30 fps fins+4D26.7 / 21.40.881goodturntable turntable_swim tracking poster
19w22_fish322:0025:23-25:26, 26:10-26:12, 26:18-26:20 (+8 more)218 / 422Chromis dimidiata (Chocolate-dip chromis) 92%, high30 fps fins+4D27.3 / 18.40.821goodturntable turntable_swim tracking poster
20w22_fish422:0029:04-29:11, 29:50-30:02185 / 722Chaetodon paucifasciatus (Eritrean butterflyfish) 100%, high30 fps fins+4D22.7 / 16.80.879goodturntable turntable_swim tracking poster
21w32_fish132:0033:00-33:06, 33:38-33:54, 34:21-34:39 (+9 more)2172 / 721Cephalopholis hemistiktos (Yellowfin hind) 62%, medium30 fps fins+4D23.0 / 23.20.884fairturntable turntable_swim tracking poster
22w32_fish332:0032:26-32:28, 34:53-34:55, 35:10-35:20 (+3 more)211 / 636Epinephelus fasciatus (Blacktip grouper) 60%, low30 fps fins+4D29.1 / 23.00.850fair, species doubtfulturntable turntable_swim tracking poster
23w32_fish432:0032:08-32:10, 33:12-33:14, 34:22-34:26 (+8 more)194 / 395Chromis dimidiata (Chocolate-dip chromis) 91%, high30 fps fins+4D26.9 / 19.90.840goodturntable turntable_swim tracking poster
24w32_fish532:0037:43-38:02189 / 592Cephalopholis hemistiktos (Yellowfin hind) 95%, high30 fps fins+4D22.4 / 23.50.843poorturntable turntable_swim tracking poster
25w40_fish140:0040:08-40:11, 40:42-40:51, 41:00-41:00 (+8 more)3259 / 721Cephalopholis hemistiktos (Yellowfin hind) 85%, high30 fps fins+4D24.1 / 22.20.830fairturntable turntable_swim tracking poster
26w40_fish240:0042:07-42:10, 42:33-42:39, 43:15-43:22 (+8 more)708 / 723Balistapus undulatus (Orange-lined triggerfish) 100%, high30 fps fins+4D26.6 / 20.70.874goodturntable turntable_swim tracking poster
27w40_fish340:0040:00-40:02, 40:20-40:42, 40:44-40:50 (+1 more)341 / 722Cephalopholis hemistiktos (Yellowfin hind) 74%, medium30 fps fins+4D32.4 / 30.50.842fairturntable turntable_swim tracking poster
28w40_fish440:0040:01-40:05, 40:10-40:12, 40:16-40:22 (+11 more)475 / 619Chromis dimidiata (Chocolate-dip chromis) 99%, high30 fps fins+4D26.2 / 19.20.848goodturntable turntable_swim tracking poster
29w40_fish540:0041:25-41:26, 41:42-41:44, 42:01-42:05 (+11 more)393 / 722Epinephelus fasciatus (Blacktip grouper) 62%, low30 fps fins+4D26.7 / 22.20.886fair, species doubtfulturntable turntable_swim tracking poster
30w40_fish640:0041:56-42:08, 42:11-42:11, 42:14-42:28230 / 719Chaetodon paucifasciatus (Eritrean butterflyfish) 93%, high30 fps fins+4D25.1 / 18.70.864goodturntable turntable_swim tracking poster
31w40_fish740:0040:27-40:36, 40:38-40:41, 42:10-42:31263 / 723Cephalopholis hemistiktos (Yellowfin hind) 73%, medium30 fps fins+4D19.7 / 22.40.834poorturntable turntable_swim tracking poster
32w58_fish158:0058:08-58:12, 59:04-59:08, 61:20-61:30 (+6 more)698 / 722Cephalopholis hemistiktos (Yellowfin hind) 70%, medium30 fps fins+4D22.2 / 21.80.855poorturntable turntable_swim tracking poster
33w58_fish258:0059:40-59:41, 59:45-60:06203 / 658Cephalopholis hemistiktos (Yellowfin hind) 61%, medium30 fps fins+4D23.5 / 22.30.887fairturntable turntable_swim tracking poster
34w58_fish358:0058:45-58:46, 58:49-58:54, 59:20-59:25 (+12 more)990 / 1192Cephalopholis miniata (Coral hind) 93%, high30 fps fins+4D26.1 / 21.00.906fairturntable turntable_swim tracking poster
35w58_fish558:0062:36-63:25450 / 712Cephalopholis hemistiktos (Yellowfin hind) 59%, medium30 fps fins+4D21.0 / 21.60.833poorturntable turntable_swim tracking poster
36w58_fish658:0058:37-58:39, 58:43-58:47, 59:02-59:07 (+4 more)304 / 717Balistapus undulatus (Orange-lined triggerfish) 98%, high30 fps fins+4D30.1 / 23.80.901goodturntable turntable_swim tracking poster
37w58_fish758:0058:10-58:20, 59:02-59:07, 59:12-59:17 (+13 more)666 / 655Chromis dimidiata (Chocolate-dip chromis) 99%, high30 fps fins+4D27.6 / 20.50.882goodturntable turntable_swim tracking poster
38w58_fish858:0058:22-58:26, 58:52-59:00, 59:03-59:10 (+3 more)395 / 713Aethaloperca rogaa (Redmouth grouper) 83%, high30 fps fins+4D26.5 / 26.20.914goodturntable turntable_swim tracking poster
39w58_fish958:0058:35-58:38, 58:46-58:48, 58:54-58:57 (+12 more)478 / 490Halichoeres hortulanus (Checkerboard wrasse) 100%, high30 fps fins+4D28.8 / 20.50.913fairturntable turntable_swim tracking poster
40w58_fish1058:0058:29-58:29, 59:01-59:04, 61:00-61:02 (+11 more)332 / 706Oxycheilinus digramma (Cheeklined wrasse) 46%, low30 fps fins+4D27.1 / 20.90.890fair, species doubtfulturntable turntable_swim tracking poster
41w58_fish1158:0058:31-58:33, 58:38-58:41, 58:45-58:48 (+8 more)307 / 681Halichoeres hortulanus (Checkerboard wrasse) 99%, high30 fps fins+4D27.8 / 18.40.892goodturntable turntable_swim tracking poster
42w68_fish168:0068:00-68:12, 68:29-68:31, 68:34-68:36 (+13 more)469 / 595Chromis dimidiata (Chocolate-dip chromis) 100%, high30 fps fins+4D26.5 / 19.40.874goodturntable turntable_swim tracking poster
43w68_fish368:0068:03-68:06, 68:32-68:34, 68:36-68:39 (+5 more)209 / 668Halichoeres hortulanus (Checkerboard wrasse) 98%, high30 fps fins+4D29.0 / 22.40.936goodturntable turntable_swim tracking poster
44w68_fish568:0069:53-69:58, 70:29-70:32, 70:45-70:47 (+1 more)149 / 439Aethaloperca rogaa (Redmouth grouper) 83%, high30 fps fins+4D26.1 / 24.20.897goodturntable turntable_swim tracking poster

<!-- /table -->

Discussion

What works

Failure modes and caveats (honest list)

Their 3D bodies look plausible from the side but are flat and featureless. The model at the pose looks slightly thinner than the real fish, and the spots are lost.

Review

The review step (2026-11-10) checked every row of the table against the files on disk and looked at the outputs. The grades and notes are in docs/review_notes.json; make_results.py reads that file for the review column and the gallery.

Checks against the files (all passed):

What I looked at: all 44 posters, frames from the tracking, turntable and swim videos of 8 fish (fish2, w02_fish8, w12_fish3, w22_fish1, w32_fish1, w40_fish5, w40_fish7, w58_fish3), the 30 fps crop sheets of w40_fish7 and w32_fish1, and turntable tiles of the weakest hinds.

Grades:

Doubtful species labels (7):

The species pages of work/site show the classifier output without these flags.

Best results to look at first:

Method choice (held-out comparison) docs/method_choice.md

Method choice for the final production

Decision (2026-11-10): 4D Gaussian splatting with the fin preset on 30 fps data (06_train_gs.py --fins --deform on the <name>_hf crops of 04d_hifps.py), shortened to 4000 iterations with --hyp_max_frames 300, and 04d capped at 24 s of 30 fps footage per fish (--max-total-hard-s 24, new option). The output folder is gs_final. fish1 is kept.

Candidates

nameflagsdatawhat it adds
gs--legacy-like baseline (sym, pose/colour opt)10 fpsbinary SAM mask, black background
gs_robustdefault robust mode (hypothesis repair, MAD weights, occlusion-tolerant loss, frame filter)10 fpsrobustness to toward/away flips, foreign fish, coral occluders
gs_robust_4drobust + --deform10 fps (or 30 fps for the ablation)per-frame body bending
gs_finsrobust + --fins (plate compositing, fin sheet)10 fpstranslucent fins via compositing over the 04c background plate
gs_fins_4drobust + --fins --deform (+ caudal articulation)30 fps (*_hf, 04d) or 10 fpsfins + body bending + tail articulation

Held-out metrics

Every 10th valid frame is held out; per-frame pose / colour are re-fitted on it (TTO) and the model is frozen. Full = PSNR of the render composited over the plate on the whole crop, masked = PSNR on the SAM mask, IoU against the SAM mask, near-fish = PSNR in the band just outside the mask (where fins live). Rows of one block use the same held-out frames. Sources: work/fish/<f>/eval_cmp/compare.json (round 3b), the review re-evaluation work/fish/<f>_hf/eval_rev/ and the train logs of the runs made for this decision (dec_*, logs in work/logs_decide/).

10 fps data (held-out 10 fps frames)

fishrunitersfullmaskedIoUnear-fish
fish2 (triggerfish)gs1200023.017.80.88423.8
gs_robust1200022.818.00.88423.3
gs_robust_4d1200023.718.40.91524.7
gs_fins1200024.118.70.87224.8
fish3 (chromis)gs2805824.217.20.87127.8
gs_robust1400024.117.30.86827.6
gs_robust_4d1200025.018.20.90528.6
gs_fins1400026.118.50.82928.5
fins + deform, 10 fps (dec_fins_4d_x10_6k, new)600026.419.00.86029.3
fish1 (grouper)gs4000024.26 (black-bg PSNR)-0.86-
gs_robust2000024.63 (black-bg PSNR); full 20.918.10.8620.8

30 fps data (held-out 30 fps frames of fish2_hf / fish3_hf)

fishrunitersfullmaskedIoUnear-fish
fish2_hf10 fps gs-23.216.80.86224.0
10 fps gs_robust_4d-23.817.40.88724.9
10 fps gs_fins-24.318.10.85324.9
gs_robust_4d trained at 30 fps (no-fins ablation), composite re-fit (fair)1000023.918.20.88624.6
gs_fins_4d1000025.420.30.88925.8
gs_fins_4d, production preset (dec_final_test)400025.0519.90.88725.6
fish3_hf10 fps gs-25.218.00.85828.1
10 fps gs_robust_4d-26.019.00.88628.7
10 fps gs_fins-26.719.60.83928.3
gs_robust_4d at 30 fps (ablation), composite re-fit (fair)1000027.220.10.89029.4
gs_fins_4d1000028.021.00.88429.9
gs_fins_4d (dec_fins_4d_6k)600027.820.80.88329.7
w58_fish3_hf (coral hind, window 58)production preset (gs_final, end-to-end test of the template)400026.121.00.90627.1

Notes on fairness: the round-3b table re-fitted each model with its own loss (composite for the fins models, masked for the others), which flatters the fins models. The "fair" rows re-fit the no-fins 30 fps ablation with the composite loss too: the fins advantage is then +0.8 dB full / +0.95 dB masked on fish3_hf and +1.5 / +2.1 dB on fish2_hf. The 10 fps baselines inside the 30 fps blocks were not re-fitted that way (their gap is larger than the fair one). Near-fish values depend on the far_r0 / far_r1 training values; all rows here use the defaults.

Visual judgement (grey turntables, composited validation grids)

Runtime (RTX 4090, two jobs sharing the GPU)

stepmeasured
04d on a window fish (w58_fish3, 1192 frames at 30 fps, 256 px crops)extract 6 s, SAM 142 s, background / soft alpha (04c adapter) 1209 s (~1 crop/s), ~25 min total
--fins --deform, 6000 iters, default hypothesis repair (fish3_hf)1879 s training; 3 repair rounds of ~230 s each (37 % of the time)
production preset (4000 iters, --hyp_max_frames 300, --res 384, fish2_hf)1030 s training (+ ~3 min final evaluation), 2 repair rounds of ~125 s
production preset, w58_fish3_hf (1024 valid poses, 256 px), alone on the GPU1015 s training + ~4 min final evaluation

Expected production cost per new fish: 04d with the 24 s cap (~720 frames) ~14 min, training ~20 min. For the 42 fish to train (fish1 + 41 window fish; fish2 / fish3 reuse their existing 30 fps models; w58_fish3 is already done) that is about 24 GPU-job-hours; with 2 concurrent jobs about 12 h, with 3-4 jobs ~8 h if the GPU throughput scales (it is ~90 % busy with two jobs, so expect less than linear scaling). The data preparation can run in one slot while the other slots train. If this is too slow, use the knobs in that order: drop to --iters 3000, lower the cap to --max-total-hard-s 18, then train the low-priority fish (below) with the 10 fps fallback (no 04d).

Why this choice

Exact command lines

Run from scripts/ with the venv python; window fish need FISH_WINDOW=<window minute> (the fish's window in work/fish_index.json; default-window fish fish1-3 run without it).

cd scripts
# 1) 30 fps data (once per fish; several fish of one window may share one call)
$env:FISH_WINDOW='58'
..\.venv\Scripts\python -u 04d_hifps.py --fish w58_fish5 w58_fish6 --max-total-hard-s 24
# 2) training (add --res 384 when work/w58/fish/<name>_hf/meta.json has crop_res 512)
..\.venv\Scripts\python -u 06_train_gs.py --fish w58_fish5_hf --fins --deform --iters 4000 --hyp_max_frames 300 --out gs_final
# 3) videos (species.json is taken from the base fish dir)
..\.venv\Scripts\python -u 10_fish_videos.py --fish w58_fish5_hf --model gs_final --window 58
Remove-Item Env:FISH_WINDOW

Template: 06_train_gs.py --fish <name>_hf --fins --deform --iters 4000 --hyp_max_frames 300 [--res 384] --out gs_final

Special cases:

Priority order (train the fish with the best material first, so a time-out hits the weakest): species-confident, large fish first; last the drab-wrasse groups that may mix individuals (w02_fish8, w02_fish9, w02_fish10, w12_fish9, w58_fish10) and the heavily coral-occluded fish (w22_fish3, w40_fish7, w12_fish5, w32_fish4, w32_fish5).

Known limits of the chosen method

Generated by scripts/11_site.py · simple gallery: gallery.html