|
37 | 37 |
|
38 | 38 | from nifreeze.data.base import BaseDataset, _cmp, _data_repr
|
39 | 39 |
|
| 40 | +DEFAULT_CLIP_PERCENTILE = 75 |
| 41 | +"""Upper percentile threshold for intensity clipping.""" |
| 42 | + |
| 43 | +DEFAULT_MIN_S0 = 1e-5 |
| 44 | +"""Minimum value when considering the :math:`S_{0}` DWI signal.""" |
| 45 | + |
| 46 | +DEFAULT_MAX_S0 = 1.0 |
| 47 | +"""Maximum value when considering the :math:`S_{0}` DWI signal.""" |
| 48 | + |
| 49 | +DEFAULT_LOWB_THRESHOLD = 50 |
| 50 | +"""The lower bound for the b-value so that the orientation is considered a DW volume.""" |
| 51 | + |
| 52 | +DEFAULT_HIGHB_THRESHOLD = 8000 |
| 53 | +"""A b-value cap for DWI data.""" |
| 54 | + |
| 55 | +DEFAULT_NUM_BINS = 15 |
| 56 | +"""Number of bins to classify b-values.""" |
| 57 | + |
| 58 | +DEFAULT_MULTISHELL_BIN_COUNT_THR = 7 |
| 59 | +"""Default bin count to consider a multishell scheme.""" |
| 60 | + |
| 61 | +DTI_MIN_ORIENTATIONS = 6 |
| 62 | +"""Minimum number of nonzero b-values in a DWI dataset.""" |
| 63 | + |
40 | 64 |
|
41 | 65 | @attr.s(slots=True)
|
42 | 66 | class DWI(BaseDataset):
|
@@ -221,7 +245,7 @@ def load(
|
221 | 245 | bvec_file: Path | str | None = None,
|
222 | 246 | bval_file: Path | str | None = None,
|
223 | 247 | b0_file: Path | str | None = None,
|
224 |
| - b0_thres: float = 50.0, |
| 248 | + b0_thres: float = DEFAULT_LOWB_THRESHOLD, |
225 | 249 | ) -> DWI:
|
226 | 250 | """
|
227 | 251 | Load DWI data and construct a DWI object.
|
@@ -337,3 +361,87 @@ def load(
|
337 | 361 | dwi_obj.brainmask = np.asanyarray(mask_img.dataobj, dtype=bool)
|
338 | 362 |
|
339 | 363 | return dwi_obj
|
| 364 | + |
| 365 | + |
| 366 | +def find_shelling_scheme( |
| 367 | + bvals, |
| 368 | + num_bins=DEFAULT_NUM_BINS, |
| 369 | + multishell_nonempty_bin_count_thr=DEFAULT_MULTISHELL_BIN_COUNT_THR, |
| 370 | + bval_cap=DEFAULT_HIGHB_THRESHOLD, |
| 371 | +): |
| 372 | + """ |
| 373 | + Find the shelling scheme on the given b-values. |
| 374 | +
|
| 375 | + Computes the histogram of the b-values according to ``num_bins`` |
| 376 | + and depending on the nonempty bin count, classify the shelling scheme |
| 377 | + as single-shell if they are 2 (low-b and a shell); multi-shell if they are |
| 378 | + below the ``multishell_nonempty_bin_count_thr`` value; and DSI otherwise. |
| 379 | +
|
| 380 | + Parameters |
| 381 | + ---------- |
| 382 | + bvals : :obj:`list` or :obj:`~numpy.ndarray` |
| 383 | + List or array of b-values. |
| 384 | + num_bins : :obj:`int`, optional |
| 385 | + Number of bins. |
| 386 | + multishell_nonempty_bin_count_thr : :obj:`int`, optional |
| 387 | + Bin count to consider a multi-shell scheme. |
| 388 | +
|
| 389 | + Returns |
| 390 | + ------- |
| 391 | + scheme : :obj:`str` |
| 392 | + Shelling scheme. |
| 393 | + bval_groups : :obj:`list` |
| 394 | + List of grouped b-values. |
| 395 | + bval_estimated : :obj:`list` |
| 396 | + List of 'estimated' b-values as the median value of each b-value group. |
| 397 | +
|
| 398 | + """ |
| 399 | + |
| 400 | + # Bin the b-values: use -1 as the lower bound to be able to appropriately |
| 401 | + # include b0 values |
| 402 | + hist, bin_edges = np.histogram(bvals, bins=num_bins, range=(-1, min(max(bvals), bval_cap))) |
| 403 | + |
| 404 | + # Collect values in each bin |
| 405 | + bval_groups = [] |
| 406 | + bval_estimated = [] |
| 407 | + for lower, upper in zip(bin_edges[:-1], bin_edges[1:], strict=False): |
| 408 | + # Add only if a nonempty b-values mask |
| 409 | + if (mask := (bvals > lower) & (bvals <= upper)).sum(): |
| 410 | + bval_groups.append(bvals[mask]) |
| 411 | + bval_estimated.append(np.median(bvals[mask])) |
| 412 | + |
| 413 | + nonempty_bins = len(bval_groups) |
| 414 | + |
| 415 | + if nonempty_bins < 2: |
| 416 | + raise ValueError("DWI must have at least one high-b shell") |
| 417 | + |
| 418 | + if nonempty_bins == 2: |
| 419 | + scheme = "single-shell" |
| 420 | + elif nonempty_bins < multishell_nonempty_bin_count_thr: |
| 421 | + scheme = "multi-shell" |
| 422 | + else: |
| 423 | + scheme = "DSI" |
| 424 | + |
| 425 | + return scheme, bval_groups, bval_estimated |
| 426 | + |
| 427 | + |
| 428 | +def _rasb2dipy(gradient): |
| 429 | + import warnings |
| 430 | + |
| 431 | + gradient = np.asanyarray(gradient) |
| 432 | + if gradient.ndim == 1: |
| 433 | + if gradient.size != 4: |
| 434 | + raise ValueError("Missing gradient information.") |
| 435 | + gradient = gradient[..., np.newaxis] |
| 436 | + |
| 437 | + if gradient.shape[0] != 4: |
| 438 | + gradient = gradient.T |
| 439 | + elif gradient.shape == (4, 4): |
| 440 | + print("Warning: make sure gradient information is not transposed!") |
| 441 | + |
| 442 | + with warnings.catch_warnings(): |
| 443 | + from dipy.core.gradients import gradient_table |
| 444 | + |
| 445 | + warnings.filterwarnings("ignore", category=UserWarning) |
| 446 | + retval = gradient_table(gradient[3, :], bvecs=gradient[:3, :].T) |
| 447 | + return retval |
0 commit comments