@@ -183,6 +183,14 @@ def _assign_axes(
183
183
"""
184
184
Obtain xarray axes names, origin, scale and convert into ImageJ Axis. Supports both
185
185
DefaultLinearAxis and the newer EnumeratedAxis.
186
+
187
+ Note that, in many cases, there are small discrepancies between the coordinates.
188
+ This can either be actually within the data, or it can be from floating point math
189
+ errors. In this case, we delegate to numpy.isclose to tell us whether our
190
+ coordinates are linear or not. If our coordinates are nonlinear, and the
191
+ EnumeratedAxis type is available, we will use it. Otherwise, this function
192
+ returns a DefaultLinearAxis.
193
+
186
194
:param xarr: xarray that holds the data.
187
195
:return: A list of ImageJ Axis with the specified origin and scale.
188
196
"""
@@ -191,41 +199,37 @@ def _assign_axes(
191
199
axis_str = _convert_dim (dim , "java" )
192
200
ax_type = jc .Axes .get (axis_str )
193
201
ax_num = _get_axis_num (xarr , dim )
194
- coords_arr = xarr .coords [dim ].to_numpy ()
202
+ coords_arr = xarr .coords [dim ].to_numpy (). astype ( np . double )
195
203
196
- # check if coords/scale is numeric
197
- if _is_numeric_scale (coords_arr ):
198
- doub_coords = [jc .Double (np .double (x )) for x in xarr .coords [dim ]]
199
- else :
204
+ # coerce numeric scale
205
+ if not _is_numeric_scale (coords_arr ):
200
206
_logger .warning (
201
207
f"The { ax_type .label } axis is non-numeric and is translated "
202
208
"to a linear index."
203
209
)
204
- doub_coords = [
205
- jc .Double (np .double (x )) for x in np .arrange (len (xarr .coords [dim ]))
206
- ]
207
-
208
- # assign calibrated axis type -- checks for imagej metadata
209
- if "imagej" in xarr .attrs .keys ():
210
- ij_dim = _convert_dim (dim , "java" )
211
- if ij_dim + "_cal_axis_type" in xarr .attrs ["imagej" ].keys ():
212
- scale_type = xarr .attrs ["imagej" ][ij_dim + "_cal_axis_type" ]
213
- if scale_type == "linear" :
214
- jaxis = _get_linear_axis (ax_type , sj .to_java (doub_coords ))
215
- if scale_type == "enumerated" :
216
- try :
217
- EnumeratedAxis = _get_enumerated_axis ()
218
- except (JException , TypeError ):
219
- EnumeratedAxis = None
220
- if EnumeratedAxis is not None :
221
- jaxis = EnumeratedAxis (ax_type , sj .to_java (doub_coords ))
222
- else :
223
- jaxis = _get_linear_axis (ax_type , sj .to_java (doub_coords ))
210
+ coords_arr = [np .double (x ) for x in np .arrange (len (xarr .coords [dim ]))]
211
+
212
+ # check scale linearity
213
+ diffs = np .diff (coords_arr )
214
+ linear : bool = diffs .size and np .all (np .isclose (diffs , diffs [0 ]))
215
+
216
+ # For non-linear scales, use EnumeratedAxis
217
+ try :
218
+ EnumeratedAxis = sj .jimport ("net.imagej.axis.EnumeratedAxis" )
219
+ except (JException , TypeError ):
220
+ EnumeratedAxis = None
221
+ # If we can use EnumeratedAxis for a nonlinear scale, then use it
222
+ if not linear and EnumeratedAxis :
223
+ j_coords = [jc .Double (x ) for x in coords_arr ]
224
+ axes [ax_num ] = EnumeratedAxis (ax_type , sj .to_java (j_coords ))
225
+ # Otherwise, use DefaultLinearAxis
224
226
else :
225
- # default to DefaultLinearAxis always if no `scale_type` key in attr
226
- jaxis = _get_linear_axis (ax_type , sj .to_java (doub_coords ))
227
-
228
- axes [ax_num ] = jaxis
227
+ DefaultLinearAxis = sj .jimport ("net.imagej.axis.DefaultLinearAxis" )
228
+ scale = coords_arr [1 ] - coords_arr [0 ] if len (coords_arr ) > 1 else 1
229
+ origin = coords_arr [0 ] if len (coords_arr ) > 0 else 0
230
+ axes [ax_num ] = DefaultLinearAxis (
231
+ ax_type , jc .Double (scale ), jc .Double (origin )
232
+ )
229
233
230
234
return axes
231
235
@@ -280,27 +284,6 @@ def _get_axes_coords(
280
284
return coords
281
285
282
286
283
- def _get_scale (axis ):
284
- """
285
- Get the scale of an axis, assuming it is linear and so the scale is simply
286
- second - first coordinate.
287
-
288
- :param axis: A 1D list like entry accessible with indexing, which contains the
289
- axis coordinates
290
- :return: The scale for this axis or None if it is a non-numeric scale.
291
- """
292
- try :
293
- # HACK: This axis length check is a work around for singleton dimensions.
294
- # You can't calculate the slope of a singleton dimension.
295
- # This section will be removed when axis-scale-logic is merged.
296
- if len (axis ) <= 1 :
297
- return 1
298
- else :
299
- return axis .values [1 ] - axis .values [0 ]
300
- except TypeError :
301
- return None
302
-
303
-
304
287
def _is_numeric_scale (coords_array : np .ndarray ) -> bool :
305
288
"""
306
289
Checks if the coordinates array of the given axis is numeric.
@@ -311,29 +294,6 @@ def _is_numeric_scale(coords_array: np.ndarray) -> bool:
311
294
return np .issubdtype (coords_array .dtype , np .number )
312
295
313
296
314
- def _get_enumerated_axis ():
315
- """Get EnumeratedAxis.
316
-
317
- EnumeratedAxis is only in releases later than March 2020. If using
318
- an older version of ImageJ without EnumeratedAxis, use
319
- _get_linear_axis() instead.
320
- """
321
- return sj .jimport ("net.imagej.axis.EnumeratedAxis" )
322
-
323
-
324
- def _get_linear_axis (axis_type : "jc.AxisType" , values ):
325
- """Get linear axis.
326
-
327
- This is used if no EnumeratedAxis is found. If EnumeratedAxis
328
- is available, use _get_enumerated_axis() instead.
329
- """
330
- DefaultLinearAxis = sj .jimport ("net.imagej.axis.DefaultLinearAxis" )
331
- origin = values [0 ]
332
- scale = values [1 ] - values [0 ]
333
- axis = DefaultLinearAxis (axis_type , scale , origin )
334
- return axis
335
-
336
-
337
297
def _dataset_to_imgplus (rai : "jc.RandomAccessibleInterval" ) -> "jc.ImgPlus" :
338
298
"""Get an ImgPlus from a Dataset.
339
299
@@ -483,30 +443,3 @@ def _to_ijdim(key: str) -> str:
483
443
return ijdims [key ]
484
444
else :
485
445
return key
486
-
487
-
488
- def _cal_axis_type_to_str (key ) -> str :
489
- """
490
- Convert a CalibratedAxis type (e.g. net.imagej.axis.DefaultLinearAxis) to
491
- a string.
492
- """
493
- cal_axis_types = {
494
- jc .ChapmanRichardsAxis : "ChapmanRichardsAxis" ,
495
- jc .DefaultLinearAxis : "DefaultLinearAxis" ,
496
- jc .EnumeratedAxis : "EnumeratedAxis" ,
497
- jc .ExponentialAxis : "ExponentialAxis" ,
498
- jc .ExponentialRecoveryAxis : "ExponentialRecoveryAxis" ,
499
- jc .GammaVariateAxis : "GammaVariateAxis" ,
500
- jc .GaussianAxis : "GaussianAxis" ,
501
- jc .IdentityAxis : "IdentityAxis" ,
502
- jc .InverseRodbardAxis : "InverseRodbardAxis" ,
503
- jc .LogLinearAxis : "LogLinearAxis" ,
504
- jc .PolynomialAxis : "PolynomialAxis" ,
505
- jc .PowerAxis : "PowerAxis" ,
506
- jc .RodbardAxis : "RodbardAxis" ,
507
- }
508
-
509
- if key .__class__ in cal_axis_types :
510
- return cal_axis_types [key .__class__ ]
511
- else :
512
- return "unknown"
0 commit comments