|
11 | 11 | from openlifu.util.units import getunitconversion |
12 | 12 |
|
13 | 13 |
|
| 14 | +# === Tools to work with points === |
14 | 15 | @dataclass |
15 | 16 | class Point: |
16 | 17 | position: np.ndarray = field(default_factory=lambda: np.array([0.0, 0.0, 0.0])) # mm |
@@ -136,3 +137,81 @@ def to_json(self, compact:bool) -> str: |
136 | 137 | return json.dumps(self.to_dict(), separators=(',', ':')) |
137 | 138 | else: |
138 | 139 | return json.dumps(self.to_dict(), indent=4) |
| 140 | + |
| 141 | + |
| 142 | +# === Tools to work with spherical coordinate systems === |
| 143 | + |
| 144 | +def cartesian_to_spherical(x:float,y:float,z:float) -> Tuple[float, float, float]: |
| 145 | + """Convert cartesian coordinates to spherical coordinates |
| 146 | +
|
| 147 | + Args: x, y, z are cartesian coordinates |
| 148 | + Returns: r, theta, phi, where |
| 149 | + r is the radial spherical coordinate, a nonnegative float. |
| 150 | + theta is the polar spherical coordinate, aka the angle off the z-axis, aka the non-azimuthal spherical angle. |
| 151 | + theta is in the range [0,pi]. |
| 152 | + phi is the azimuthal spherical coordinate, in the range [-pi,pi] |
| 153 | +
|
| 154 | + Angles are in radians. |
| 155 | + """ |
| 156 | + return (np.sqrt(x**2+y**2+z**2), np.arctan2(np.sqrt(x**2+y**2),z), np.arctan2(y,x)) |
| 157 | + |
| 158 | +def spherical_to_cartesian(r: float, th:float, ph:float) -> Tuple[float, float, float]: |
| 159 | + """Convert spherical coordinates to cartesian coordinates |
| 160 | +
|
| 161 | + Args: |
| 162 | + r: the radial spherical coordinate |
| 163 | + th: the polar spherical coordinate theta, aka the angle off the z-axis, aka the non-azimuthal spherical angle |
| 164 | + ph: the azimuthal spherical coordinate phi |
| 165 | + Returns the cartesian coordinates x,y,z |
| 166 | +
|
| 167 | + Angles are in radians. |
| 168 | + """ |
| 169 | + return (r*np.sin(th)*np.cos(ph), r*np.sin(th)*np.sin(ph), r*np.cos(th)) |
| 170 | + |
| 171 | +def cartesian_to_spherical_vectorized(p:np.ndarray) -> np.ndarray: |
| 172 | + """Convert cartesian coordinates to spherical coordinates |
| 173 | +
|
| 174 | + Args: |
| 175 | + p: an array of shape (...,3), where the last axis describes point cartesian coordinates x,y,z. |
| 176 | + Returns: An array of shape (...,3), where the last axis describes point spherical coordinates r, theta, phi, where |
| 177 | + r is the radial spherical coordinate, a nonnegative float. |
| 178 | + theta is the polar spherical coordinate, aka the angle off the z-axis, aka the non-azimuthal spherical angle. |
| 179 | + theta is in the range [0,pi]. |
| 180 | + phi is the azimuthal spherical coordinate, in the range [-pi,pi] |
| 181 | +
|
| 182 | + Angles are in radians. |
| 183 | + """ |
| 184 | + return np.stack([ |
| 185 | + np.sqrt((p**2).sum(axis=-1)), |
| 186 | + np.arctan2(np.sqrt((p[...,0:2]**2).sum(axis=-1)),p[...,2]), |
| 187 | + np.arctan2(p[...,1],p[...,0]), |
| 188 | + ], axis=-1) |
| 189 | + |
| 190 | +def spherical_to_cartesian_vectorized(p:np.ndarray) -> np.ndarray: |
| 191 | + """Convert spherical coordinates to cartesian coordinates |
| 192 | +
|
| 193 | + Args: |
| 194 | + p: an array of shape (...,3), where the last axis describes point spherical coordinates r, theta, phi, where: |
| 195 | + r is the radial spherical coordinate |
| 196 | + theta is the polar spherical coordinate, aka the angle off the z-axis, aka the non-azimuthal spherical angle |
| 197 | + phi is the azimuthal spherical coordinate |
| 198 | + Returns the cartesian coordinates x,y,z |
| 199 | +
|
| 200 | + Angles are in radians. |
| 201 | + """ |
| 202 | + return np.stack([ |
| 203 | + p[...,0]*np.sin(p[...,1])*np.cos(p[...,2]), |
| 204 | + p[...,0]*np.sin(p[...,1])*np.sin(p[...,2]), |
| 205 | + p[...,0]*np.cos(p[...,1]), |
| 206 | + ], axis=-1) |
| 207 | + |
| 208 | +def spherical_coordinate_basis(th:float, phi:float) -> np.ndarray: |
| 209 | + """Return normalized spherical coordinate basis at a location with spherical polar and azimuthal coordinates (th, phi). |
| 210 | + The coordinate basis is returned as an array `basis` of shape (3,3), where the rows are the basis vectors, |
| 211 | + in the order r, theta, phi. So `basis[0], basis[1], basis[2]` are the vectors $\\hat{r}$, $\\hat{\\theta}$, $\\hat{\\phi}$. |
| 212 | + Angles are assumed to be provided in radians.""" |
| 213 | + return np.array([ |
| 214 | + [np.sin(th)*np.cos(phi), np.sin(th)*np.sin(phi), np.cos(th)], |
| 215 | + [np.cos(th)*np.cos(phi), np.cos(th)*np.sin(phi), -np.sin(th)], |
| 216 | + [-np.sin(phi), np.cos(phi), 0], |
| 217 | + ]) |
0 commit comments