1+ from __future__ import annotations
2+ import numpy as np
3+ from typing import List , Tuple , Union
4+
5+ from autoarray .structures .grids import grid_2d_util
6+
7+
8+ class Zoom2D :
9+
10+ def __init__ (self , mask : Union [np .ndarray , List ]):
11+ """
12+ Derives a zoomed in `Mask2D` object from a `Mask2D` object, which is typically used to visualize 2D arrays
13+ zoomed in to only the unmasked region an analysis is performed on.
14+
15+ A `Mask2D` masks values which are associated with a uniform 2D rectangular grid of pixels, where unmasked
16+ entries (which are `False`) are used in subsequent calculations and masked values (which are `True`) are
17+ omitted (for a full description see the :meth:`Mask2D` class API
18+ documentation <autoarray.mask.mask_2d.Mask2D.__new__>`).
19+
20+ The `Zoom2D` object calculations many different zoomed in qu
21+
22+ Parameters
23+ ----------
24+ mask
25+ The `Mask2D` from which zoomed in `Mask2D` objects are derived.
26+
27+ Examples
28+ --------
29+
30+ .. code-block:: python
31+
32+ import autoarray as aa
33+
34+ mask_2d = aa.Mask2D(
35+ mask=[
36+ [True, True, True, True, True],
37+ [True, False, False, False, True],
38+ [True, False, False, False, True],
39+ [True, False, False, False, True],
40+ [True, True, True, True, True],
41+ ],
42+ pixel_scales=1.0,
43+ )
44+
45+ zoom_2d = aa.Zoom2D(mask=mask_2d)
46+
47+ print(zoom_2d.centre)
48+ """
49+ self .mask = mask
50+
51+ @property
52+ def centre (self ) -> Tuple [float , float ]:
53+ from autoarray .structures .grids .uniform_2d import Grid2D
54+
55+ grid = grid_2d_util .grid_2d_slim_via_mask_from (
56+ mask_2d = np .array (self .mask ),
57+ pixel_scales = self .mask .pixel_scales ,
58+ origin = self .mask .origin ,
59+ )
60+
61+ grid = Grid2D (values = grid , mask = self .mask )
62+
63+ extraction_grid_1d = self .mask .geometry .grid_pixels_2d_from (grid_scaled_2d = grid )
64+ y_pixels_max = np .max (extraction_grid_1d [:, 0 ])
65+ y_pixels_min = np .min (extraction_grid_1d [:, 0 ])
66+ x_pixels_max = np .max (extraction_grid_1d [:, 1 ])
67+ x_pixels_min = np .min (extraction_grid_1d [:, 1 ])
68+
69+ return (
70+ ((y_pixels_max + y_pixels_min - 1.0 ) / 2.0 ),
71+ ((x_pixels_max + x_pixels_min - 1.0 ) / 2.0 ),
72+ )
73+
74+ @property
75+ def offset_pixels (self ) -> Tuple [float , float ]:
76+ if self .mask .pixel_scales is None :
77+ return self .mask .geometry .central_pixel_coordinates
78+
79+ return (
80+ self .centre [0 ] - self .mask .geometry .central_pixel_coordinates [0 ],
81+ self .centre [1 ] - self .mask .geometry .central_pixel_coordinates [1 ],
82+ )
83+
84+ @property
85+ def offset_scaled (self ) -> Tuple [float , float ]:
86+ return (
87+ - self .mask .pixel_scales [0 ] * self .offset_pixels [0 ],
88+ self .mask .pixel_scales [1 ] * self .offset_pixels [1 ],
89+ )
90+
91+ @property
92+ def region (self ) -> List [int ]:
93+ """
94+ The zoomed rectangular region corresponding to the square encompassing all unmasked values. This zoomed
95+ extraction region is a squuare, even if the mask is rectangular.
96+
97+ This is used to zoom in on the region of an image that is used in an analysis for visualization.
98+ """
99+
100+ where = np .array (np .where (np .invert (self .mask .astype ("bool" ))))
101+ y0 , x0 = np .amin (where , axis = 1 )
102+ y1 , x1 = np .amax (where , axis = 1 )
103+
104+ # Have to convert mask to bool for invert function to work.
105+
106+ ylength = y1 - y0
107+ xlength = x1 - x0
108+
109+ if ylength > xlength :
110+ length_difference = ylength - xlength
111+ x1 += int (length_difference / 2.0 )
112+ x0 -= int (length_difference / 2.0 )
113+ elif xlength > ylength :
114+ length_difference = xlength - ylength
115+ y1 += int (length_difference / 2.0 )
116+ y0 -= int (length_difference / 2.0 )
117+
118+ return [y0 , y1 + 1 , x0 , x1 + 1 ]
119+
120+ @property
121+ def shape_native (self ) -> Tuple [int , int ]:
122+ region = self .region
123+ return (region [1 ] - region [0 ], region [3 ] - region [2 ])
124+
125+ @property
126+ def mask_unmasked (self ) -> "Mask2D" :
127+ """
128+ The scaled-grid of (y,x) coordinates of every pixel.
129+
130+ This is defined from the top-left corner, such that the first pixel at location [0, 0] will have a negative x
131+ value y value in scaled units.
132+ """
133+
134+ from autoarray .mask .mask_2d import Mask2D
135+
136+ return Mask2D .all_false (
137+ shape_native = self .shape_native ,
138+ pixel_scales = self .mask .pixel_scales ,
139+ origin = self .offset_scaled ,
140+ )
0 commit comments