33Co-located cameras are visualized (e.g. in DIVE) by warping one modality
44onto another with a plane homography. This module derives that homography
55for each non-reference camera at a station straight from the calibrated
6- camera models -- no scene points or hand-clicked correspondences. It
7- writes one ``<camera>_to_<reference_camera>_registration.json`` per
8- non-reference camera (matching the registration gif naming):
9-
10- {"camera": "<camera folder>",
11- "reference_camera": "<reference camera folder>",
12- "camera_to_reference": <3x3>,
13- "reference_to_camera": <3x3>}
14-
15- ``camera_to_reference`` maps a pixel in the camera's image to the
16- reference (EO) camera's pixel seeing the same scene point, and
17- ``reference_to_camera`` is its inverse. The cameras are rigidly
18- co-located, so the pixel map is a homography to within a fraction of a
19- pixel; it is for rendering, not an accuracy metric (use the registration
20- gifs for that).
6+ camera models -- no scene points or hand-clicked correspondences -- and
7+ writes one DIVE camera-registration file per station
8+ (``<station>_registration.json``):
9+
10+ {"type": "dive-camera-registration",
11+ "version": 1,
12+ "source": {"model": "kamera-v3", "flight": "<flight>"},
13+ "pairs": [{"left": "eo", "right": "ir",
14+ "points": [[xl, yl, xr, yr], ...],
15+ "leftToRight": <3x3>, "rightToLeft": <3x3>,
16+ "transformType": "homography"}, ...]}
17+
18+ ``left`` is always the station's reference camera under its DIVE name
19+ (rgb -> "eo"), and ``leftToRight`` maps a left pixel to the right
20+ camera's pixel seeing the same scene point. ``points`` are exact
21+ correspondences under the homography, sampled on a grid over the right
22+ image, so a consumer can refit its own transform type. The cameras are
23+ rigidly co-located, so the pixel map is a homography to within a
24+ fraction of a pixel; it is for rendering, not an accuracy metric (use
25+ the registration gifs for that).
2126"""
2227
2328import json
@@ -80,17 +85,40 @@ def pixel_homography(
8085 return h
8186
8287
88+ # DIVE's camera names where they differ from KAMERA modality names.
89+ _DIVE_CAMERA_NAMES = {"rgb" : "eo" }
90+
91+
92+ def _grid_correspondences (
93+ cam_to_ref : np .ndarray , width : int , height : int , n : int = 3
94+ ) -> List [List [float ]]:
95+ """Exact [x_left, y_left, x_right, y_right] correspondences under the
96+ homography, on an ``n x n`` grid over the right (non-reference) image.
97+ The right footprint sits inside the reference view for co-located
98+ KAMERA stations, so gridding the right image keeps both ends in frame.
99+ """
100+ x = np .linspace (0 , width - 1 , n )
101+ y = np .linspace (0 , height - 1 , n )
102+ X , Y = np .meshgrid (x , y )
103+ right = np .vstack ([X .ravel (), Y .ravel (), np .ones (X .size )])
104+ left = cam_to_ref @ right
105+ left = left [:2 ] / left [2 ]
106+ return np .round (np .vstack ([left , right [:2 ]]).T , 2 ).tolist ()
107+
108+
83109def write_registration_homographies (
84110 models : Dict [str , StandardCamera ],
85111 save_dir : str ,
86112 reference_modality : str = "rgb" ,
113+ flight : str = "" ,
114+ source_model : str = "kamera-v3" ,
87115) -> List [str ]:
88- """Write one ``< camera>_to_<reference_camera>_registration. json`` per
89- non-reference camera under ``save_dir``.
116+ """Write one DIVE camera-registration json per station under
117+ ``save_dir`` (``<station>_registration.json``) .
90118
91119 ``models`` maps camera folder (e.g. ``21deg_N56RF_center_rgb``) to its
92- StandardCamera. Cameras are grouped by station; each non-reference
93- camera gets its own file with the homography to its station's
120+ StandardCamera. Cameras are grouped by station; each station's file
121+ holds one ``pairs`` entry per non-reference camera, left = the
94122 reference (EO) camera. Returns the paths written.
95123 """
96124 by_station : Dict [str , Dict [str , str ]] = {}
@@ -108,7 +136,9 @@ def write_registration_homographies(
108136 )
109137 continue
110138 ref = models [ref_folder ]
139+ left = _DIVE_CAMERA_NAMES .get (reference_modality , reference_modality )
111140
141+ pairs = []
112142 for modality , folder in sorted (folders .items ()):
113143 if modality == reference_modality :
114144 continue
@@ -117,21 +147,39 @@ def write_registration_homographies(
117147 except ValueError as e :
118148 print (f"[yellow]Station { station } { modality } : { e } ; skipping pair." )
119149 continue
120- os .makedirs (save_dir , exist_ok = True )
121- out_path = os .path .join (
122- save_dir , f"{ folder } _to_{ ref_folder } _registration.json"
150+ ref_to_cam = np .linalg .inv (cam_to_ref )
151+ ref_to_cam /= ref_to_cam [2 , 2 ]
152+ cam = models [folder ]
153+ pairs .append (
154+ {
155+ "left" : left ,
156+ "right" : _DIVE_CAMERA_NAMES .get (modality , modality ),
157+ "points" : _grid_correspondences (
158+ cam_to_ref , cam .width , cam .height
159+ ),
160+ "leftToRight" : ref_to_cam .tolist (),
161+ "rightToLeft" : cam_to_ref .tolist (),
162+ "transformType" : "homography" ,
163+ }
164+ )
165+ if not pairs :
166+ continue
167+
168+ name = KameraCameraName .parse (ref_folder )
169+ station_slug = f"{ name .prefix } _{ name .channel } " if name .prefix else name .channel
170+ os .makedirs (save_dir , exist_ok = True )
171+ out_path = os .path .join (save_dir , f"{ station_slug } _registration.json" )
172+ with open (out_path , "w" ) as f :
173+ json .dump (
174+ {
175+ "type" : "dive-camera-registration" ,
176+ "version" : 1 ,
177+ "source" : {"model" : source_model , "flight" : flight },
178+ "pairs" : pairs ,
179+ },
180+ f ,
181+ indent = 2 ,
123182 )
124- with open (out_path , "w" ) as f :
125- json .dump (
126- {
127- "camera" : folder ,
128- "reference_camera" : ref_folder ,
129- "camera_to_reference" : cam_to_ref .tolist (),
130- "reference_to_camera" : np .linalg .inv (cam_to_ref ).tolist (),
131- },
132- f ,
133- indent = 2 ,
134- )
135- written .append (out_path )
183+ written .append (out_path )
136184
137185 return written
0 commit comments