Source code for phys2cvr.utils

#!/usr/bin/env python3
"""
Utils functions for phys2cvr.

Attributes
----------
LGR :
    Logger
"""

import datetime
import logging
import os
import sys
from os.path import exists
from pathlib import Path

import numpy as np

LGR = logging.getLogger(__name__)
LGR.setLevel(logging.INFO)


[docs] def if_declared_force_type(var, dtype, varname='an input variable', silent=False): """ Make sure `var` is of type `dtype`. Parameters ---------- var : str, int, or float Variable to change type of dtype : str Type to change `var` to varname : str, optional The name of the variable silent : bool, optional If True, don't return any message Returns ------- int, float, str, list, or var The given `var` in the given `dtype`, or `var` if '' or None Raises ------ NotImplementedError If dtype is not 'int', 'float', 'str', or 'list' """ if not var: return var converters = { 'int': int, 'float': float, 'str': str, 'list': lambda v: v if isinstance(v, list) else [v], } if dtype not in converters: raise NotImplementedError(f'Type {dtype} not supported') tmpvar = converters[dtype](var) if not silent and type(tmpvar) is not type(var): name = f'variable {varname}' if varname != 'an input variable' else varname LGR.warning(f'Changing type of {name} from {type(var)} to {dtype}') return tmpvar
[docs] def check_ext(all_ext, fname, scan=False, remove=False): """Check which extension a file has, and possibly remove it. Parameters ---------- all_ext : list All possible extensions to check within. fname : str or os.PathLike The filename to check. scan : bool, optional Scan the given path to see if there is a file with that extension If True and no path declared, check if fname has a path, if not scan '.' If False, don't scan any folder. remove : bool, optional Remove the extension from fname if it has one. Returns ------- obj_return : Uses a list to return variable amount of options. has_ext : boolean True if the extension is found, false otherwise. fname : str or os.PathLike If ``remove`` is True, return (extensionless) fname. ext : str If both ``remove`` and ``has_ext`` are True, returns also found extension. """ all_ext = if_declared_force_type(all_ext, 'list', silent=True) ext = ''.join(Path(fname).suffixes) LGR.debug(f'{fname} ends with extension {ext}') has_ext = ext.lower() in all_ext if not has_ext and scan: all_ext = ( all_ext + [e.upper() for e in all_ext] + [e.capitalize() for e in all_ext] ) for ext in all_ext: if exists(f'{fname}{ext}'): fname = f'{fname}{ext}' LGR.warning(f'Found {fname}{ext}, using it as input henceforth') has_ext = True break ext = '' if not has_ext else ext obj_return = [has_ext] if remove: obj_return += [ fname[: -len(ext)], None if ext == '' else ext, ] else: obj_return += [fname] return obj_return[:]
[docs] def check_nifti_dim(fname, data, dim=4): """ Remove extra dimensions. Parameters ---------- fname : str The name of the file representing `data` data : np.ndarray The data which dimensionality needs to be checked dim : int, optional The amount of dimensions expected/desired in the data. Returns ------- np.ndarray If `len(data.shape)` = `dim`, returns data. If `len(data.shape)` > `dim`, returns a version of data without the dimensions above `dim`. Raises ------ ValueError If `data` has less dimensions than `dim` """ if data.ndim < dim: raise ValueError( f'A {dim}D nifti file is required, but {fname} has {data.ndim}D. ' 'Please check the input file.' ) if data.ndim > dim: LGR.warning(f'{fname} has more than {dim} dimensions. Removing D > {dim}.') for ax in range(dim, data.ndim): data = np.delete(data, np.s_[1:], axis=ax) return np.squeeze(data)
[docs] def check_array_dim(fname, data, shape=None): """Check dimensions of a matrix. For future 3D implementation, check MIPLabCH/nigsp's check_array_dim. Parameters ---------- fname : str The name of the file representing ``data``. data : np.ndarray The data which dimensionality needs to be checked. shape : None | ``'square'`` | ``'rectangle'`` Shape of matrix, if empty, skip shape check. Returns ------- np.ndarray If ``data.ndim = 2``, returns data. If ``data.ndim = 1`` and ``shape == 'rectangle'``, returns data with added empty axis. Raises ------ NotImplementedError If ``data`` has more than 2 dimensions. ValueError If ``data`` is empty If ``shape == 'square'`` and ``data`` dimensions have different lengths. """ data = data.squeeze() LGR.info('Checking data shape.') if data.size == 0: raise ValueError(f'{fname} is empty!') if data.ndim > 2: raise NotImplementedError( f'Only matrices up to 2D are supported, but given matrix is {data.ndim}D.' ) if shape is not None: if data.ndim == 1 and shape == 'rectangle': data = data[..., np.newaxis] LGR.warning( f'Rectangular matrix required, but {fname} is a vector. ' 'Adding empty dimension.' ) if shape == 'square' and data.shape[0] != data.shape[1]: raise ValueError( f'Square matrix required, but {fname} matrix has shape {data.shape}.' ) return data
[docs] def save_bash_call(fname, outdir): """ Save the bash call into file `p2d_call.sh`. Parameters ---------- fname : str or path Name of or path to functional file outdir : str or path Output directory """ arg_str = ' '.join(sys.argv[1:]) call_str = f'phys2cvr {arg_str}' if outdir: outdir = os.path.abspath(outdir) else: outdir = os.path.join(os.path.split(fname)[0], 'phys2cvr') log_path = os.path.join(outdir, 'logs') os.makedirs(log_path, exist_ok=True) isotime = datetime.datetime.now().strftime('%Y-%m-%dT%H%M%S') _, fname, _ = check_ext('.nii.gz', os.path.basename(fname), remove=True) f = open(os.path.join(log_path, f'p2c_call_{fname}_{isotime}.sh'), 'a') f.write(f'#!bin/bash \n{call_str}') f.close()
""" Copyright 2021-2026, Stefano Moia & phys2cvr contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """