Skip to content

Commit 7800910

Browse files
authored
Merge pull request #176 from punch-mission/fix-NFI_imax-distortion
Fix nfi imax distortion
2 parents ae61520 + 415e607 commit 7800910

File tree

7 files changed

+468
-141
lines changed

7 files changed

+468
-141
lines changed

docs/source/example.ipynb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"solpolpy is invoked using a simple interface:`output = sp.resolve(input_data, out_system)` where the `input_data` parameter is an appropriate NDCollection (a collection of [SunPy NDCubes](https://docs.sunpy.org/projects/ndcube/en/stable/)), such as a triplet of polarized images. `out_system` is the desired output polarization state (MZP, BtBr, Stokes, BpB, Bp3, or Bthp). It has a utility function to load data into the expected NDCollection format called `load_data`. There is also a plotting utility to plot any collection called `plot_collection`. \n",
1414
"\n",
1515
"## Polarization systems handled by solpolpy\n",
16-
"\n",
17-
"- Minus, Zero, Plus (MZP): Triplet of images taken at -60°, 0°, and +60° polarizing angles.\n",
16+
"- Minus, Zero, Plus in solar frame (mzpsolar): Triplet of images taken at -60°, 0°, and +60° polarizing angles with a reference angle set to solar frame.\n",
17+
"- Minus, Zero, Plus in instrument frame (mzpinstru): Triplet of images taken at -60°, 0°, and +60° polarizing angles with a reference angle set to instrument frame.\n",
1818
"- Tangential and Radial Brightness (BtBr): Pair of images with polarization along the tangential and radial direction with respect to the Sun respectively.\n",
1919
"- Stokes (Stokes): Total (unpolarized) brightness (I), polarized brightness along vertical and horizontal axes (Q) and polarized brightness along ±45° (U) .\n",
2020
"- Brightness, polarized Brightness (BpB): Total (unpolarized) brightness and ‘excess polarized’ brightness images pair respectively.\n",
@@ -150,7 +150,9 @@
150150
"metadata": {
151151
"collapsed": false
152152
},
153-
"source": "In this case, we can see they are angle measurements (in degrees). If we want to know more, we can look at the appropriate cube:"
153+
"source": [
154+
"In this case, we can see they are angle measurements (in degrees). If we want to know more, we can look at the appropriate cube:"
155+
]
154156
},
155157
{
156158
"cell_type": "code",
@@ -160,7 +162,9 @@
160162
"start_time": "2024-08-06T14:26:55.020121Z"
161163
}
162164
},
163-
"source": "stereo_collection['240.0 deg']",
165+
"source": [
166+
"stereo_collection['240.0 deg']"
167+
],
164168
"outputs": [
165169
{
166170
"data": {
@@ -197,7 +201,9 @@
197201
"start_time": "2024-08-06T14:26:55.023786Z"
198202
}
199203
},
200-
"source": "stereo_collection['240.0 deg'].meta['POLAR']",
204+
"source": [
205+
"stereo_collection['240.0 deg'].meta['POLAR']"
206+
],
201207
"outputs": [
202208
{
203209
"data": {
@@ -350,7 +356,9 @@
350356
"start_time": "2024-08-06T14:26:56.857685Z"
351357
}
352358
},
353-
"source": "output_bpb = resolve(stereo_collection, 'bpb')",
359+
"source": [
360+
"output_bpb = resolve(stereo_collection, 'bpb')"
361+
],
354362
"outputs": [],
355363
"execution_count": 8
356364
},
@@ -521,7 +529,9 @@
521529
"start_time": "2024-08-06T14:26:58.564677Z"
522530
}
523531
},
524-
"source": "output_stokes = resolve(output_bpb, 'stokes')",
532+
"source": [
533+
"output_stokes = resolve(output_bpb, 'stokes')"
534+
],
525535
"outputs": [],
526536
"execution_count": 13
527537
},

solpolpy/core.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Core transformation functions for solpolpy."""
2+
import copy
23
import typing as t
34

45
import astropy.units as u
@@ -12,7 +13,7 @@
1213
from solpolpy.errors import UnsupportedTransformationError
1314
from solpolpy.instruments import load_data
1415
from solpolpy.transforms import SYSTEM_REQUIRED_KEYS, System, transform_graph
15-
from solpolpy.util import extract_crota_from_wcs
16+
from solpolpy.util import apply_distortion_shift, extract_crota_from_wcs
1617

1718

1819
@u.quantity_input
@@ -33,8 +34,9 @@ def resolve(input_data: list[str] | NDCollection,
3334
The polarization state you want to convert your input dataframes to.
3435
Must be one of the following strings:
3536
36-
- "mzp": Triplet of images taken at -60°, 0°, and +60° polarizing angles.
37-
- "btbr": Pair of images with polarization along the tangential and radial direction with respect to the Sun respectively.
37+
- "mzpsolar": Triplet of images taken at -60°, 0°, and +60° polarizing angles with a reference angle set to solar frame.
38+
- "mzpinstru": Triplet of images taken at -60°, 0°, and +60° polarizing angles with a reference angle set to instrument frame.
39+
- "btbr": A Pair of images with polarization along the tangential and radial direction with respect to the Sun respectively.
3840
- "stokes": Total brightness ("I"), polarized brightness along vertical and horizontal axes (Q) and polarized brightness along ±45° (U) .
3941
- "bpb": Total brightness and ‘excess polarized’ brightness images pair respectively.
4042
- "bp3": Analogous to Stokes I, Q and U, but rotates around the Sun instead of a fixed frame of reference of the instrument.
@@ -74,7 +76,7 @@ def resolve(input_data: list[str] | NDCollection,
7476
raise ValueError("Out angles must be specified for this transform.")
7577

7678
if imax_effect:
77-
if input_kind == "mzp":
79+
if input_kind in ('mzpsolar', 'mzpinstru'):
7880
input_data = resolve_imax_effect(input_data)
7981
else:
8082
msg = "IMAX effect applies only for transformations starting from MZP."
@@ -125,6 +127,9 @@ def determine_input_kind(input_data: NDCollection) -> System:
125127
raise ValueError(msg)
126128

127129
for valid_kind, param_set in SYSTEM_REQUIRED_KEYS.items():
130+
if valid_kind in [System.mzpinstru, System.mzpsolar] and param_set == input_keys:
131+
polarref_value = input_data['Z'].meta.get("POLARREF", "solar").lower()
132+
return System.mzpinstru if polarref_value == "instrument" else System.mzpsolar
128133
if valid_kind != System.npol and param_set == input_keys:
129134
return valid_kind
130135
try:
@@ -225,20 +230,25 @@ def generate_imax_matrix(array_shape: (int, int), cumulative_offset: u.deg, wcs:
225230
raise ValueError(msg)
226231

227232
thmzp = [-60, 0, 60] * u.degree + cumulative_offset
233+
# Define the A matrix
234+
mat_a = np.empty((array_shape[0], array_shape[1], 3, 3))
228235

229-
long_extent, lat_extent = wcs.wcs.cdelt[0]*array_shape[0], wcs.wcs.cdelt[1]*array_shape[1]
230-
long_arr, lat_arr = np.meshgrid(np.linspace(-long_extent/2, long_extent/2, array_shape[0]),
231-
np.linspace(-lat_extent/2, lat_extent, array_shape[1]))
236+
long_extent, lat_extent = wcs.wcs.cdelt[0] * array_shape[0], wcs.wcs.cdelt[1] * array_shape[1]
237+
long_arr, lat_arr = np.meshgrid(np.linspace(-long_extent / 2, long_extent / 2, array_shape[0]),
238+
np.linspace(-lat_extent / 2, lat_extent, array_shape[1]))
232239

233240
# Foreshortening (IMAX) effect on polarizer angle
234241
phi_m = np.arctan2(np.tan(thmzp[0]) * np.cos(long_arr * u.degree), np.cos(lat_arr * u.degree)).to(u.degree)
235242
phi_z = np.arctan2(np.tan(thmzp[1]) * np.cos(long_arr * u.degree), np.cos(lat_arr * u.degree)).to(u.degree)
236243
phi_p = np.arctan2(np.tan(thmzp[2]) * np.cos(long_arr * u.degree), np.cos(lat_arr * u.degree)).to(u.degree)
237244

238-
phi = np.stack([phi_m, phi_z, phi_p])
245+
# Apply distortion to IMAX matrix
246+
if wcs.has_distortion:
247+
phi_m = apply_distortion_shift(phi_m, wcs)
248+
phi_z = apply_distortion_shift(phi_z, wcs)
249+
phi_p = apply_distortion_shift(phi_p, wcs)
239250

240-
# Define the A matrix
241-
mat_a = np.empty((array_shape[0], array_shape[1], 3, 3))
251+
phi = np.stack([phi_m, phi_z, phi_p])
242252

243253
for i in range(3):
244254
for j in range(3):
@@ -263,10 +273,15 @@ def resolve_imax_effect(input_data: NDCollection) -> NDCollection:
263273
"""
264274
data_shape = _determine_image_shape(input_data)
265275
data_mzp_camera = np.zeros([data_shape[0], data_shape[1], 3, 1])
276+
input_keys = list(input_data.keys())
266277

267-
satellite_orientation = extract_crota_from_wcs(input_data['M'])
268-
polarizer_difference = [input_data[k].meta['POLAROFF'] if 'POLAROFF' in input_data[k].meta else 0
269-
for k in ['M', 'Z', 'P']] * u.degree
278+
if input_data['M'].meta['POLARREF'].lower() == 'instrument':
279+
satellite_orientation = extract_crota_from_wcs(input_data['M'])
280+
polarizer_difference = [input_data[k].meta['POLAROFF'] if 'POLAROFF' in input_data[k].meta else 0
281+
for k in ['M', 'Z', 'P']] * u.degree
282+
else:
283+
satellite_orientation = 0 * u.degree
284+
polarizer_difference = [0, 0, 0] * u.degree
270285

271286
for i, key in enumerate(["M", "Z", "P"]):
272287
data_mzp_camera[:, :, i, 0] = input_data[key].data
@@ -283,10 +298,21 @@ def resolve_imax_effect(input_data: NDCollection) -> NDCollection:
283298

284299
data_mzp_solar = np.matmul(imax_matrix_inv, data_mzp_camera)
285300

286-
for i, key in enumerate(input_data.keys()):
287-
input_data[key].data[:, :] = data_mzp_solar[:, :, i, 0]
301+
metaM, metaZ, metaP = (copy.copy(input_data[key].meta) for key in ["M", "Z", "P"])
302+
for meta in [metaM, metaZ, metaP]:
303+
meta.update(POLARREF='Solar')
288304

289-
return input_data
305+
cube_list = [
306+
(key, NDCube(data_mzp_solar[:, :, i, 0], wcs=input_data[key].wcs, meta=meta))
307+
for i, (key, meta) in enumerate(zip(["M", "Z", "P"], [metaM, metaZ, metaP]))
308+
]
309+
310+
for p_angle in input_keys:
311+
if p_angle.lower() == "alpha":
312+
cube_list.append(("alpha", NDCube(input_data["alpha"].data * u.radian,
313+
wcs=input_data[input_keys[0]].wcs,
314+
meta=input_data["alpha"].meta)))
315+
return NDCollection(cube_list, meta={}, aligned_axes="all")
290316

291317

292318
def _determine_image_shape(input_collection: NDCollection) -> tuple[int, int]:
@@ -354,8 +380,10 @@ def _compose2(f: t.Callable, g: t.Callable) -> t.Callable:
354380
composed function
355381
356382
"""
383+
357384
def out(*a, **kw):
358385
return f(g(*a, **kw), **kw)
386+
359387
out.uses_alpha = getattr(f, "uses_alpha", False) or getattr(g, "uses_alpha", False)
360388
out.uses_out_angles = getattr(f, "uses_out_angles", False) or getattr(g, "uses_out_angles", False)
361389

0 commit comments

Comments
 (0)