| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- from typing import Tuple
- import numpy as np
- from seqgen.pypulseq.opts import Opts
- def calc_ramp(
- k0: np.ndarray,
- k_end: np.ndarray,
- max_grad: np.ndarray = np.zeros(0),
- max_points: int = 500,
- max_slew: np.ndarray = np.zeros(0),
- system: Opts = Opts(),
- ) -> Tuple[np.ndarray, bool]:
- """
- Join the points `k0` and `k_end` in three-dimensional k-space in minimal time, observing the gradient and slew
- limits (`max_grad` and `max_slew` respectively), and the gradient strength `G0` before `k0[:, 1]` and `Gend` after
- `k_end[:, 1]`. In the context of a fixed gradient dwell time this is a discrete problem with an a priori unknown
- number of discretization steps. Therefore this method tries out the optimization with 0 steps, then 1 step, and so
- on, until all conditions can be fulfilled, thus yielding a short connection.
- Parameters
- ----------
- k0 : numpy.ndarray
- Two preceding points in k-space. Shape is `[3, 2]`. From these points, the starting gradient will be calculated.
- k_end : numpy.ndarray
- Two following points in k-space. Shape is `[3, 2]`. From these points, the target gradient will be calculated.
- max_grad : float or array_like, default=0
- Maximum total gradient strength. Either a single value or one value for each coordinate, of shape `[3, 1]`.
- max_points : int, default=500
- Maximum number of k-space points to be used in connecting `k0` and `k_end`.
- max_slew : float or array_like, default=0
- Maximum total slew rate. Either a single value or one value for each coordinate, of shape `[3, 1]`.
- system : Opts, default=Opts()
- System limits.
- Returns
- -------
- k_out : numpy.ndarray
- Connected k-space trajectory.
- success : bool
- Boolean flag indicating if `k0` and `k_end` were successfully joined.
- """
- def __inside_limits(grad, slew):
- if mode == 0:
- grad2 = np.sum(np.square(grad), axis=1)
- slew2 = np.sum(np.square(slew), axis=1)
- ok = np.all(np.max(grad2) <= np.square(max_grad)) and np.all(
- np.max(slew2) <= np.square(max_slew)
- )
- else:
- ok = (np.sum(np.max(np.abs(grad), axis=1) <= max_grad) == 3) and (
- np.sum(np.max(np.abs(slew), axis=1) <= max_slew) == 3
- )
- return ok
- def __joinleft0(k0, k_end, use_points, G0, G_end):
- if use_points == 0:
- G = np.stack((G0, (k_end - k0) / grad_raster, G_end)).T
- S = (G[:, 1:] - G[:, :-1]) / grad_raster
- k_out_left = np.zeros((3, 0))
- success = __inside_limits(G, S)
- return success, k_out_left
- dk = (k_end - k0) / (use_points + 1)
- kopt = k0 + dk
- Gopt = (kopt - k0) / grad_raster
- Sopt = (Gopt - G0) / grad_raster
- okGopt = np.sum(np.square(Gopt)) <= np.square(max_grad)
- okSopt = np.sum(np.square(Sopt)) <= np.square(max_slew)
- if okGopt and okSopt:
- k_left = kopt
- else:
- a = np.multiply(max_grad, grad_raster)
- b = np.multiply(max_slew, grad_raster**2)
- dkprol = G0 * grad_raster
- dkconn = dk - dkprol
- ksl = k0 + dkprol + dkconn / np.linalg.norm(dkconn) * b
- Gsl = (ksl - k0) / grad_raster
- okGsl = np.sum(np.square(Gsl)) <= np.square(max_grad)
- kgl = k0 + np.multiply(dk / np.linalg.norm(dk), a)
- Ggl = (kgl - k0) / grad_raster
- Sgl = (Ggl - G0) / grad_raster
- okSgl = np.sum(np.square(Sgl)) <= np.square(max_slew)
- if okGsl:
- k_left = ksl
- elif okSgl:
- k_left = kgl
- else:
- c = np.linalg.norm(dkprol)
- c1 = np.divide(np.square(a) - np.square(b) + np.square(c), (2 * c))
- h = np.sqrt(np.square(a) - np.square(c1))
- kglsl = k0 + np.multiply(c1, np.divide(dkprol, np.linalg.norm(dkprol)))
- projondkprol = (kgl * dkprol.T) * (dkprol / np.linalg.norm(dkprol))
- hdirection = kgl - projondkprol
- kglsl = kglsl + h * hdirection / np.linalg.norm(hdirection)
- k_left = kglsl
- success, k = __joinright0(
- k_left, k_end, (k_left - k0) / grad_raster, G_end, use_points - 1
- )
- if len(k) != 0:
- if len(k.shape) == 1:
- k = k.reshape((len(k), 1))
- if len(k_left.shape) == 1:
- k_left = k_left.reshape((len(k_left), 1))
- k_out_left = np.hstack((k_left, k))
- else:
- k_out_left = k_left
- return success, k_out_left
- def __joinleft1(k0, k_end, use_points, G0, G_end):
- if use_points == 0:
- G = np.stack((G0, (k_end - k0) / grad_raster, G_end))
- S = (G[:, 1:] - G[:, :-1]) / grad_raster
- k_out_left = np.zeros((3, 0))
- success = __inside_limits(G, S)
- return success, k_out_left
- k_left = np.zeros(3)
- dk = (k_end - k0) / (use_points + 1)
- kopt = k0 + dk
- Gopt = (kopt - k0) / grad_raster
- Sopt = (Gopt - G0) / grad_raster
- okGopt = np.abs(Gopt) <= max_grad
- okSopt = np.abs(Sopt) <= max_slew
- dkprol = G0 * grad_raster
- dkconn = dk - dkprol
- ksl = k0 + dkprol + np.multiply(np.sign(dkconn), max_slew) * grad_raster**2
- Gsl = (ksl - k0) / grad_raster
- okGsl = np.abs(Gsl) <= max_grad
- kgl = k0 + np.multiply(np.sign(dk), max_grad) * grad_raster**2
- Ggl = (kgl - k0) / grad_raster
- Sgl = (Ggl - G0) / grad_raster
- okSgl = np.abs(Sgl) <= max_slew
- for ii in range(3):
- if okGopt[ii] == 1 and okSopt[ii] == 1:
- k_left[ii] = kopt[ii]
- elif okGsl[ii] == 1:
- k_left[ii] = ksl[ii]
- elif okSgl[ii] == 1:
- k_left[ii] = kgl[ii]
- else:
- print("Unknown error")
- success, k = __joinright1(
- k0=k_left,
- k_end=k_end,
- use_points=use_points - 1,
- G0=(k_left - k0) / grad_raster,
- G_end=G_end,
- )
- if len(k) != 0:
- if len(k.shape) == 1:
- k = k.reshape((len(k), 1))
- if len(k_left.shape) == 1:
- k_left = k_left.reshape((len(k_left), 1))
- k_out_left = np.hstack((k_left, k))
- else:
- k_out_left = k_left
- return success, k_out_left
- def __joinright0(k0, k_end, use_points, G0, G_end):
- if use_points == 0:
- G = np.stack((G0, (k_end - k0) / grad_raster, G_end)).T
- S = (G[:, 1:] - G[:, :-1]) / grad_raster
- k_out_right = np.zeros((3, 0))
- success = __inside_limits(G, S)
- return success, k_out_right
- dk = (k0 - k_end) / (use_points + 1)
- kopt = k_end + dk
- Gopt = (k_end - kopt) / grad_raster
- Sopt = (G_end - Gopt) / grad_raster
- okGopt = np.sum(np.square(Gopt)) <= np.square(max_grad)
- okSopt = np.sum(np.square(Sopt)) <= np.square(max_slew)
- if okGopt and okSopt:
- k_right = kopt
- else:
- a = np.multiply(max_grad, grad_raster)
- b = np.multiply(max_slew, grad_raster**2)
- dkprol = -G_end * grad_raster
- dkconn = dk - dkprol
- ksl = k_end + dkprol + dkconn / np.linalg.norm(dkconn) * b
- Gsl = (k_end - ksl) / grad_raster
- okGsl = np.sum(np.square(Gsl)) <= np.square(max_grad)
- kgl = k_end + np.multiply(dk / np.linalg.norm(dk), a)
- Ggl = (k_end - kgl) / grad_raster
- Sgl = (G_end - Ggl) / grad_raster
- okSgl = np.sum(np.square(Sgl)) <= np.square(max_slew)
- if okGsl:
- k_right = ksl
- elif okSgl:
- k_right = kgl
- else:
- c = np.linalg.norm(dkprol)
- c1 = np.divide(np.square(a) - np.square(b) + np.square(c), (2 * c))
- h = np.sqrt(np.square(a) - np.square(c1))
- kglsl = k_end + np.multiply(
- c1, np.divide(dkprol, np.linalg.norm(dkprol))
- )
- projondkprol = (kgl * dkprol.T) * (dkprol / np.linalg.norm(dkprol))
- hdirection = kgl - projondkprol
- kglsl = kglsl + h * hdirection / np.linalg.norm(hdirection)
- k_right = kglsl
- success, k = __joinleft0(
- k0=k0,
- k_end=k_right,
- G0=G0,
- G_end=(k_end - k_right) / grad_raster,
- use_points=use_points - 1,
- )
- if len(k) != 0:
- if len(k.shape) == 1:
- k = k.reshape((len(k), 1))
- if len(k_right.shape) == 1:
- k_right = k_right.reshape((len(k_right), 1))
- k_out_right = np.hstack((k, k_right))
- else:
- k_out_right = k_right
- return success, k_out_right
- def __joinright1(k0, k_end, use_points, G0, G_end):
- if use_points == 0:
- G = np.stack((G0, (k_end - k0) / grad_raster, G_end))
- S = (G[:, 1:] - G[:, :-1]) / grad_raster
- k_out_right = np.zeros((3, 0))
- success = __inside_limits(G, S)
- return success, k_out_right
- k_right = np.zeros(3)
- dk = (k0 - k_end) / (use_points + 1)
- kopt = k_end + dk
- Gopt = (k_end - kopt) / grad_raster
- Sopt = (G_end - Gopt) / grad_raster
- okGopt = np.abs(Gopt) <= max_grad
- okSopt = np.abs(Sopt) <= max_slew
- dkprol = -G_end * grad_raster
- dkconn = dk - dkprol
- ksl = k_end + dkprol + np.multiply(np.sign(dkconn), max_slew) * grad_raster**2
- Gsl = (k_end - ksl) / grad_raster
- okGsl = np.abs(Gsl) <= max_grad
- kgl = k_end + np.multiply(np.sign(dk), max_grad) * grad_raster
- Ggl = (k_end - kgl) / grad_raster
- Sgl = (G_end - Ggl) / grad_raster
- okSgl = np.abs(Sgl) <= max_slew
- for ii in range(3):
- if okGopt[ii] == 1 and okSopt[ii] == 1:
- k_right[ii] = kopt[ii]
- elif okGsl[ii] == 1:
- k_right[ii] = ksl[ii]
- elif okSgl[ii] == 1:
- k_right[ii] = kgl[ii]
- else:
- print("Unknown error")
- success, k = __joinleft1(
- k0=k0,
- k_end=k_right,
- use_points=use_points - 1,
- G0=G0,
- G_end=(k_end - k_right) / grad_raster,
- )
- if len(k) != 0:
- if len(k.shape) == 1:
- k = k.reshape((len(k), 1))
- if len(k_right.shape) == 1:
- k_right = k_right.reshape((len(k_right), 1))
- k_out_right = np.hstack((k, k_right))
- else:
- k_out_right = k_right
- return success, k_out_right
- # =========
- # MAIN FUNCTION
- # =========
- if np.all(np.where(max_grad <= 0)):
- max_grad = [system.max_grad]
- if np.all(np.where(max_slew <= 0)):
- max_slew = [system.max_slew]
- grad_raster = system.grad_raster_time
- if len(max_grad) == 1 and len(max_slew) == 1:
- mode = 0
- elif len(max_grad) == 3 and len(max_slew) == 3:
- mode = 1
- else:
- raise ValueError("Input value max grad or max slew in invalid format.")
- G0 = (k0[:, 1] - k0[:, 0]) / grad_raster
- G_end = (k_end[:, 1] - k_end[:, 0]) / grad_raster
- k0 = k0[:, 1]
- k_end = k_end[:, 0]
- success = 0
- k_out = np.zeros((3, 0))
- use_points = 0
- while success == 0 and use_points <= max_points:
- if mode == 0:
- if np.linalg.norm(G0) > max_grad or np.linalg.norm(G_end) > max_grad:
- break
- success, k_out = __joinleft0(
- k0=k0, k_end=k_end, G0=G0, G_end=G_end, use_points=use_points
- )
- else:
- if np.abs(G0) > np.abs(max_grad) or np.abs(G_end) > np.abs(max_grad):
- break
- success, k_out = __joinleft1(
- k0=k0, k_end=k_end, use_points=use_points, G0=G0, G_end=G_end
- )
- use_points += 1
- return k_out, success
|