diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index afba6ee..7cf8dc9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -31,16 +31,19 @@ repos: - id: flake8 args: [-j8] additional_dependencies: + - flake8-broken-line - flake8-bugbear - flake8-comprehensions - flake8-debugger + - flake8-isort - flake8-string-format - repo: https://github.com/google/yapf - rev: v0.31.0 + rev: v0.32.0 hooks: - id: yapf args: [-i] + additional_dependencies: [toml] - repo: https://github.com/PyCQA/isort - rev: 5.9.3 + rev: 5.10.1 hooks: - id: isort diff --git a/miutil/fdio.py b/miutil/fdio.py index c551a52..270e7ec 100644 --- a/miutil/fdio.py +++ b/miutil/fdio.py @@ -78,6 +78,7 @@ def extractall(fzip, dest, desc="Extracting"): def nsort(fnames): """Sort a file list, automatically detecting embedded numbers""" + def path2parts(fname): parts = re.split(r"([0-9][0-9.]*e[-+][0-9]+|[0-9]+\.[0-9]+|[0-9]+)", fname) parts[1::2] = map(float, parts[1::2]) diff --git a/miutil/imio/nii.py b/miutil/imio/nii.py index c862968..f5f409b 100644 --- a/miutil/imio/nii.py +++ b/miutil/imio/nii.py @@ -55,7 +55,7 @@ def nii_gzip(imfile, outpath=""): return fout -def getnii(fim, nan_replace=None, output="image"): +def getnii(fim, nan_replace=None, output='image'): """ Get PET image from NIfTI file. Arguments: @@ -71,10 +71,10 @@ def getnii(fim, nan_replace=None, output="image"): """ nim = nib.load(fspath(fim)) - dim = nim.header.get("dim") + dim = nim.header.get('dim') dimno = dim[0] - if output == "image" or output == "all": + if output == 'image' or output == 'all': imr = np.asanyarray(nim.dataobj) # replace NaNs if requested if isinstance(nan_replace, numbers.Number): @@ -86,38 +86,37 @@ def getnii(fim, nan_replace=None, output="image"): # > get orientations from the affine ornt = nib.io_orientation(nim.affine) - trnsp = tuple(2 - np.int8(ornt[:, 0])) + trnsp = tuple(np.int8([ornt[2, 0], ornt[1, 0], ornt[0, 0]])) flip = tuple(np.int8(ornt[:, 1])) # > voxel size - voxsize = nim.header.get("pixdim")[1:nim.header.get("dim")[0] + 1] + voxsize = nim.header.get('pixdim')[1:nim.header.get('dim')[0] + 1] # > rearrange voxel size according to the orientation voxsize = voxsize[np.array(trnsp)] # > dimensions - dims = dim[1:nim.header.get("dim")[0] + 1] + dims = dim[1:nim.header.get('dim')[0] + 1] dims = dims[np.array(trnsp)] - # > flip y-axis and z-axis and then transpose. - # Depends if dynamic (4 dimensions) or static (3 dimensions) - if dimno == 4: + # > flip y-axis and z-axis and then transpose + if dimno == 4: # dynamic imr = np.transpose(imr[::-flip[0], ::-flip[1], ::-flip[2], :], (3,) + trnsp) - elif dimno == 3: + elif dimno == 3: # static imr = np.transpose(imr[::-flip[0], ::-flip[1], ::-flip[2]], trnsp) - if output == "affine" or output == "all": + if output == 'affine' or output == 'all': # A = nim.get_sform() # if not A[:3,:3].any(): # A = nim.get_qform() A = nim.affine - if output == "all": + if output == 'all': out = { - "im": imr, "affine": A, "fim": fim, "dtype": nim.get_data_dtype(), "shape": imr.shape, - "hdr": nim.header, "voxsize": voxsize, "dims": dims, "transpose": trnsp, "flip": flip} - elif output == "image": + 'im': imr, 'affine': A, 'fim': fim, 'dtype': nim.get_data_dtype(), 'shape': imr.shape, + 'hdr': nim.header, 'voxsize': voxsize, 'dims': dims, 'transpose': trnsp, 'flip': flip} + elif output == 'image': out = imr - elif output == "affine": + elif output == 'affine': out = A else: raise NameError("Unrecognised output request!") @@ -154,15 +153,12 @@ def array2nii(im, A, fnii, descrip="", trnsp=None, flip=None, storage_as=None): # >>as obtained from getnii(..., output='all') # > permute the axis order in the image array - if (isinstance(storage_as, dict) and "transpose" in storage_as and "flip" in storage_as): + if (isinstance(storage_as, dict) and 'transpose' in storage_as and 'flip' in storage_as): - trnsp = ( - storage_as["transpose"].index(0), - storage_as["transpose"].index(1), - storage_as["transpose"].index(2), - ) + trnsp = (storage_as['transpose'].index(0), storage_as['transpose'].index(1), + storage_as['transpose'].index(2)) - flip = storage_as["flip"] + flip = storage_as['flip'] if not trnsp: im = im.transpose() @@ -179,10 +175,10 @@ def array2nii(im, A, fnii, descrip="", trnsp=None, flip=None, storage_as=None): res = nib.Nifti1Image(im, A) hdr = res.header - hdr.set_sform(None, code="scanner") - hdr["cal_max"] = np.max(im) # np.percentile(im, 90) # - hdr["cal_min"] = np.min(im) - hdr["descrip"] = descrip + hdr.set_sform(None, code='scanner') + hdr['cal_max'] = np.max(im) # np.percentile(im, 90) # + hdr['cal_min'] = np.min(im) + hdr['descrip'] = descrip nib.save(res, fspath(fnii)) @@ -255,23 +251,23 @@ def niisort(fims, memlim=True): raise ValueError("Input image(s) must be 3D.") out = { - "shape": _nii.shape[::-1], "files": _fims, "sortlist": sortlist, - "dtype": _nii.get_data_dtype(), "N": Nim} + 'shape': _nii.shape[::-1], 'files': _fims, 'sortlist': sortlist, + 'dtype': _nii.get_data_dtype(), 'N': Nim} if memlim and Nfrm > 50: - imdic = getnii(_fims[0], output="all") - affine = imdic["affine"] + imdic = getnii(_fims[0], output='all') + affine = imdic['affine'] else: # get the images into an array _imin = np.zeros((Nfrm,) + _nii.shape[::-1], dtype=_nii.get_data_dtype()) for i in range(Nfrm): if i in sortlist: - imdic = getnii(_fims[i], output="all") - _imin[i, :, :, :] = imdic["im"] - affine = imdic["affine"] - out["im"] = _imin[:Nfrm, :, :, :] + imdic = getnii(_fims[i], output='all') + _imin[i, :, :, :] = imdic['im'] + affine = imdic['affine'] + out['im'] = _imin[:Nfrm, :, :, :] - out["affine"] = affine + out['affine'] = affine return out diff --git a/miutil/plot.py b/miutil/plot.py index 60bd149..83f0b61 100644 --- a/miutil/plot.py +++ b/miutil/plot.py @@ -43,9 +43,10 @@ class imscroll: """ _instances = [] - _SUPPORTED_KEYS = ["control", "shift"] + _SUPPORTED_KEYS = ['control', 'shift'] - def __init__(self, vol, view="t", fig=None, titles=None, order=0, **kwargs): + def __init__(self, vol, view='t', fig=None, titles=None, order=0, sharexy=None, show=False, + **kwargs): """ Scroll through 2D slices of 3D volume(s) using the mouse. Args: @@ -56,6 +57,8 @@ def __init__(self, vol, view="t", fig=None, titles=None, order=0, **kwargs): titles (list): list of strings (overrides `vol.keys()`). order (int): spline interpolation order for line profiles. 0: nearest, 1: bilinear, >2: probably avoid. + sharexy (bool): whether to link zoom across all axes. + show (bool): whether to run `matplotlib.pyplot.show()`. **kwargs: passed to `matplotlib.pyplot.imshow()`. """ if isinstance(vol, str) and path.exists(vol): @@ -79,19 +82,21 @@ def __init__(self, vol, view="t", fig=None, titles=None, order=0, **kwargs): """.format(ndim))) view = view.lower() - if view in ["c", "coronal", "y"]: + if view in ['c', 'coronal', 'y']: vol = [i.transpose(1, 0, 2) for i in vol] - elif view in ["s", "saggital", "x"]: + elif view in ['s', 'saggital', 'x']: vol = [i.transpose(2, 0, 1) for i in vol] # volumes self.titles = titles or [None] * len(vol) self.index_max = min(map(len, vol)) self.index = self.index_max // 2 + if sharexy is None: + sharexy = len({i.shape for i in vol}) == 1 if fig is not None: - self.fig, axs = fig, fig.subplots(1, len(vol)) + self.fig, axs = fig, fig.subplots(1, len(vol), sharex=sharexy, sharey=sharexy) else: - self.fig, axs = plt.subplots(1, len(vol)) + self.fig, axs = plt.subplots(1, len(vol), sharex=sharexy, sharey=sharexy) self.axs = [axs] if len(vol) == 1 else list(axs.flat) for ax, i, t in zip(self.axs, vol, self.titles): ax.imshow(i[self.index], **kwargs) @@ -103,28 +108,30 @@ def __init__(self, vol, view="t", fig=None, titles=None, order=0, **kwargs): self._annotes = [] # event callbacks self.key = {i: False for i in self._SUPPORTED_KEYS} - self.fig.canvas.mpl_connect("scroll_event", self._scroll) - self.fig.canvas.mpl_connect("key_press_event", self._on_key) - self.fig.canvas.mpl_connect("key_release_event", self._off_key) - self.fig.canvas.mpl_connect("button_press_event", self._on_click) + self.fig.canvas.mpl_connect('scroll_event', self._scroll) + self.fig.canvas.mpl_connect('key_press_event', self._on_key) + self.fig.canvas.mpl_connect('key_release_event', self._off_key) + self.fig.canvas.mpl_connect('button_press_event', self._on_click) imscroll._instances.append(self) # prevents gc + if show: + plt.show() @classmethod def clear(cls, self): cls._instances.clear() def _on_key(self, event): - key = {"ctrl": "control"}.get(event.key, event.key) + key = {'ctrl': 'control'}.get(event.key, event.key) if key in self._SUPPORTED_KEYS: self.key[key] = True def _off_key(self, event): - key = {"ctrl": "control"}.get(event.key, event.key) + key = {'ctrl': 'control'}.get(event.key, event.key) if key in self._SUPPORTED_KEYS: self.key[key] = False def _scroll(self, event): - self.set_index(self.index + event.step * (10 if self.key["shift"] else 1)) + self.set_index(self.index + event.step * (10 if self.key['shift'] else 1)) def set_index(self, index): self.index = int(index) % self.index_max @@ -137,7 +144,7 @@ def set_index(self, index): self.fig.canvas.draw() def _on_click(self, event): - if not self.key["control"] or None in (event.xdata, event.ydata): + if not self.key['control'] or None in (event.xdata, event.ydata): return self.picked.append((event.xdata, event.ydata)) if len(self.picked) < 2: @@ -156,20 +163,20 @@ def _on_click(self, event): arr, np.vstack((x, y, np.ones_like(x) * i)), order=self.order, - mode="nearest", + mode='nearest', ) for i in range(event.inaxes.images[0].get_array().shape[-1])] else: - z = ndi.map_coordinates(arr, np.vstack((x, y)), order=self.order, mode="nearest") + z = ndi.map_coordinates(arr, np.vstack((x, y)), order=self.order, mode='nearest') self.picked = [] - self.key["control"] = False + self.key['control'] = False - self._annotes.append(event.inaxes.plot([x0, x1], [y0, y1], "r-")[0]) + self._annotes.append(event.inaxes.plot([x0, x1], [y0, y1], 'r-')[0]) plt.figure() if arr.ndim == 3: - for channel, colour in zip(z, "rgbcmyk"): - plt.plot(x, channel, colour + "-") + for channel, colour in zip(z, 'rgbcmyk'): + plt.plot(x, channel, colour + '-') else: - plt.plot(x, z, "r-") + plt.plot(x, z, 'r-') plt.xlabel("x") plt.ylabel("Intensity") plt.show() diff --git a/pyproject.toml b/pyproject.toml index 4d84990..0055d2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [build-system] requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] -build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "miutil/_dist_ver.py"