@@ -328,6 +328,150 @@ def subplot_fit_dirty_images(
328328 save_figure (fig , path = output_path , filename = "fit_dirty_images" , format = output_format )
329329
330330
331+ def _to_native_np_interf (array ):
332+ """Convert an autoarray Array2D to a plain numpy 2D array."""
333+ try :
334+ mask = array .mask
335+ slim = np .asarray (array .array )
336+ native = np .zeros (mask .shape_native )
337+ native [~ np .asarray (mask )] = slim
338+ return native
339+ except AttributeError :
340+ arr = np .asarray (array )
341+ return arr if arr .ndim == 2 else arr
342+
343+
344+ def _quick_imshow_interf (ax , array_2d , title , extent , cmap , vmin = None , vmax = None ):
345+ """Minimal imshow for quick-update panels."""
346+ if array_2d is None :
347+ ax .axis ("off" )
348+ return
349+ ax .imshow (
350+ array_2d , cmap = cmap , vmin = vmin , vmax = vmax ,
351+ extent = extent , aspect = "auto" , origin = "lower" ,
352+ )
353+ ax .set_title (title , fontsize = 8 )
354+ ax .set_xticks ([])
355+ ax .set_yticks ([])
356+
357+
358+ def subplot_fit_quick (
359+ fit ,
360+ output_path : Optional [str ] = None ,
361+ output_format : str = None ,
362+ colormap : Optional [str ] = None ,
363+ title_prefix : str = None ,
364+ ):
365+ """
366+ Produce a 6-panel quick-update subplot for an interferometer fit.
367+
368+ Arranges the following panels in a 2 × 3 grid:
369+
370+ * Dirty Image (data)
371+ * Dirty Model Image
372+ * Dirty Normalised Residual Map
373+ * Visibility Normalised Residual (Real) vs UV distance
374+ * Visibility Normalised Residual (Imag) vs UV distance
375+ * Source plane image / reconstruction
376+
377+ Uses raw ``imshow`` and ``scatter`` calls on pre-converted numpy
378+ arrays for sub-second rendering.
379+ """
380+ import matplotlib .pyplot as plt
381+
382+ # Pre-convert dirty images to numpy 2D (each is an inverse FFT)
383+ dirty_image = _to_native_np_interf (fit .dirty_image )
384+ dirty_model = _to_native_np_interf (fit .dirty_model_image )
385+ dirty_norm_resid = _to_native_np_interf (fit .dirty_normalized_residual_map )
386+
387+ extent = fit .dataset .real_space_mask .geometry .extent
388+
389+ if colormap is None :
390+ try :
391+ from autoarray .plot .utils import _default_colormap
392+ colormap = _default_colormap ()
393+ except Exception :
394+ colormap = "default"
395+
396+ _pf = (lambda t : f"{ title_prefix .rstrip ()} { t } " ) if title_prefix else (lambda t : t )
397+ fig , axes = plt .subplots (2 , 3 , figsize = (12 , 8 ))
398+ axes_flat = list (axes .flatten ())
399+
400+ # Top row: Dirty Image, Dirty Model Image, Dirty Normalized Residual
401+ _quick_imshow_interf (axes_flat [0 ], dirty_image , _pf ("Dirty Image" ), extent , colormap )
402+ _quick_imshow_interf (axes_flat [1 ], dirty_model , _pf ("Dirty Model Image" ), extent , colormap )
403+
404+ finite = dirty_norm_resid [np .isfinite (dirty_norm_resid )]
405+ abs_max = float (np .max (np .abs (finite ))) if len (finite ) > 0 else 1.0
406+ _quick_imshow_interf (
407+ axes_flat [2 ], dirty_norm_resid , _pf ("Dirty Norm Residual" ),
408+ extent , colormap , vmin = - abs_max , vmax = abs_max ,
409+ )
410+
411+ # Bottom row: Visibility residuals (Real/Imag scatter) + Source Plane
412+ norm_resid_vis = np .asarray (fit .normalized_residual_map )
413+ uv_dist = np .asarray (fit .dataset .uv_distances ) / 1e3
414+
415+ ax_real = axes_flat [3 ]
416+ ax_real .scatter (uv_dist , np .real (norm_resid_vis ), s = 0.5 , alpha = 0.3 , c = "k" , rasterized = True )
417+ ax_real .set_title (_pf ("Vis Norm Resid (Real)" ), fontsize = 8 )
418+ ax_real .set_xlabel ("UV dist (kλ)" , fontsize = 7 )
419+ ax_real .set_ylabel ("σ" , fontsize = 7 )
420+ ax_real .tick_params (labelsize = 6 )
421+
422+ ax_imag = axes_flat [4 ]
423+ ax_imag .scatter (uv_dist , np .imag (norm_resid_vis ), s = 0.5 , alpha = 0.3 , c = "k" , rasterized = True )
424+ ax_imag .set_title (_pf ("Vis Norm Resid (Imag)" ), fontsize = 8 )
425+ ax_imag .set_xlabel ("UV dist (kλ)" , fontsize = 7 )
426+ ax_imag .set_ylabel ("σ" , fontsize = 7 )
427+ ax_imag .tick_params (labelsize = 6 )
428+
429+ # Source plane: parametric → small grid, pixelized → plot_mapper
430+ tracer_viz = fit .tracer_linear_light_profiles_to_light_profiles
431+ final_plane_index = len (tracer_viz .planes ) - 1
432+ source_galaxies = tracer_viz .planes [final_plane_index ]
433+ has_pixelization = any (
434+ hasattr (g , "pixelization" ) and g .pixelization is not None
435+ for g in source_galaxies
436+ )
437+
438+ if not has_pixelization :
439+ try :
440+ rs_mask = fit .dataset .real_space_mask
441+ quick_grid = aa .Grid2D .uniform (
442+ shape_native = (50 , 50 ),
443+ pixel_scales = rs_mask .pixel_scales ,
444+ origin = rs_mask .origin ,
445+ )
446+ source_img = plane_image_from (
447+ galaxies = source_galaxies , grid = quick_grid ,
448+ zoom_to_brightest = False ,
449+ )
450+ src_np = _to_native_np_interf (source_img )
451+ _quick_imshow_interf (
452+ axes_flat [5 ], src_np , _pf ("Source Plane" ),
453+ quick_grid .geometry .extent , colormap ,
454+ )
455+ except Exception :
456+ axes_flat [5 ].axis ("off" )
457+ else :
458+ try :
459+ inversion = fit .inversion
460+ mapper_list = inversion .cls_list_from (cls = Mapper )
461+ mapper = mapper_list [final_plane_index - 1 ] if final_plane_index > 0 else mapper_list [0 ]
462+ pixel_values = inversion .reconstruction_dict [mapper ]
463+ plot_mapper (
464+ mapper , solution_vector = pixel_values , ax = axes_flat [5 ],
465+ title = _pf ("Source Reconstruction" ), colormap = colormap ,
466+ zoom_to_brightest = False ,
467+ )
468+ except Exception :
469+ axes_flat [5 ].axis ("off" )
470+
471+ fig .tight_layout (pad = 0.5 )
472+ save_figure (fig , path = output_path , filename = "fit_quick" , format = output_format , dpi = 100 )
473+
474+
331475def subplot_fit_interferometer_combined (
332476 fit_list ,
333477 output_path : Optional [str ] = None ,
0 commit comments