Creating custom transformations¶
The transformations and interpolation frameworks are intentionally written in an open-ended manner so that you can write your own transformations for your particular data.
To write your own transformer class, you should inherit from the abstract Transformer and implement a number of methods:
_calculate_transformed_calculate_nativecalculate_transformed_bbox
Additionally, the base Transformer allows arbitary coordinate names, so it is often helpful to override the __init__ method in order to specify the expected coordinate names.
So to get started, let’s define a transformer to go from an arbitrary 2d coordinate system with coordinate axes b and c to 3D cartesian coordinates and being by overriding __init__:
from yt_xarray.transformations import Transformer
class MyTransformer(Transformer):
def __init__(self):
native_coords = ('b', 'c')
transformed_coords = ('x', 'y', 'z')
super().__init__(native_coords, transformed_coords)
Now let’s define _calculate_transformed to describe the function x, y, z = f(b, c). _calculate_transformed must conform to a number of requirmements. First, _calculate_transformed must accept a **coords argument. That **coords keyword dictionary is guaranteed to have entries keyed by the native_coords tuple (validation is taken care by methods in the abstract class). _calculate_transformed must then return the coordinates in the transformed coordinate system.
We’ll do something competely arbitrary here…
def _calculate_transformed(self, **coords):
b = coords['b']
c = coords['c']
x = b * 2
y = c * 4
z = np.sqrt(x**2 + y**2)
return x, y, z
Now, add on _calculate_native, which in this exmaple will go from (x, y, z) to (b, c).
def _calculate_native(self, **coords):
x = coords['x']
y = coords['y']
b = x / 2.0
c = y / 4.0
return b, c
And finally, calculate_transformed_bbox must provide a method for calculating the bounding range of coordinates in the transformed coordinate given bounds in the native coordinate system. In this arbitrary coordinate system, we can simply call the method’s to_transformed at the bounds of the native range to get the bounding box in the transformed system:
def calculate_transformed_bbox(self, bbox_dict):
b_min_max = bbox_dict['b']
c_min_max = bbox_dict['c']
xmin, ymin, zmin = self.to_transformed(b=b_min_max[0],
c=c_min_max[0])
xmax, ymax, zmax = self.to_transformed(b=b_min_max[1],
c=c_min_max[1])
Putting it all together:
[4]:
import numpy as np
from yt_xarray.transformations import Transformer
class MyTransformer(Transformer):
def __init__(self):
native_coords = ('b', 'c')
transformed_coords = ('x', 'y', 'z')
super().__init__(native_coords, transformed_coords)
def _calculate_transformed(self, **coords):
b = coords['b']
c = coords['c']
x = b * 2.
y = c * 4.
z = np.sqrt(x**2 + y**2)
return x, y, z
def _calculate_native(self, **coords):
x = coords['x']
y = coords['y']
b = x / 2.0
c = y / 4.0
return b, c
def calculate_transformed_bbox(self, bbox_dict):
b_min_max = bbox_dict['b']
c_min_max = bbox_dict['c']
xmin, ymin, zmin = self.to_transformed(b=b_min_max[0],
c=c_min_max[0])
xmax, ymax, zmax = self.to_transformed(b=b_min_max[1],
c=c_min_max[1])
our transformer is now available to use!
[5]:
mtf = MyTransformer()
[6]:
x, y, z = mtf.to_transformed(b=0,c=0)
x, y, z
[6]:
(0.0, 0.0, 0.0)
[7]:
mtf.to_native(x=x, y=y, z=z)
[7]:
(0.0, 0.0)
[8]:
x, y, z = mtf.to_transformed(b=0.5,c=10.)
print(x, y, z)
mtf.to_native(x=x, y=y, z=z)
1.0 40.0 40.01249804748511
[8]:
(0.5, 10.0)
Additionally, as long as your custom transformer transforms to and from 3D cartesian coordinates and if the “native” coordinates match an xarray dataset field’s dimensions, you can hand off your custom transformer to ``build_interpolated_cartesian_ds`` and build a yt cartesian dataset that reads and interpolates from an arbitrary coordinate system!