From 43784b4596ebca70aa6266d64af85dc2cc1b12b1 Mon Sep 17 00:00:00 2001 From: Jochen Schmidt Date: Wed, 15 Feb 2023 13:16:59 +0100 Subject: [PATCH 1/3] working conda env --- environment.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 environment.yml diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..fe7c560 --- /dev/null +++ b/environment.yml @@ -0,0 +1,13 @@ +name: slfrank +channels: + - frankong + - main + - conda-forge + - defaults +dependencies: + - cvxpy==1.2.1 + - matplotlib==3.5.3 + - numpy==1.19.5 + - python==3.8.16 + - scipy==1.9.1 + - sigpy==0.1.23 From ec129a8b503de012c5f98becad61e4aa57ceeddc Mon Sep 17 00:00:00 2001 From: Jochen Schmidt Date: Thu, 16 Feb 2023 17:10:24 +0100 Subject: [PATCH 2/3] added cmd line interfacing --- environment.yml | 1 + slr_creation/__init__.py | 0 slr_creation/__main__.py | 93 ++++++++++++++++++++++++++++++++++++++++ slr_creation/options.py | 56 ++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 slr_creation/__init__.py create mode 100644 slr_creation/__main__.py create mode 100644 slr_creation/options.py diff --git a/environment.yml b/environment.yml index fe7c560..4784fb2 100644 --- a/environment.yml +++ b/environment.yml @@ -11,3 +11,4 @@ dependencies: - python==3.8.16 - scipy==1.9.1 - sigpy==0.1.23 + - pandas==1.4.4 diff --git a/slr_creation/__init__.py b/slr_creation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/slr_creation/__main__.py b/slr_creation/__main__.py new file mode 100644 index 0000000..bfd85d3 --- /dev/null +++ b/slr_creation/__main__.py @@ -0,0 +1,93 @@ +""" +script for using slfrank to create slr pulse with set parameters and export with rfpf to interface +eg. in jstmc or emc +_________________ +Jochen Schmidt +15.02.2023 +""" + +import numpy as np +import matplotlib.pyplot as plt +import slfrank +import pathlib as plib +from rf_pulse_files import rfpf +from slr_creation import options + +import logging + + +def main(opts: options.Config): + gamma_Hz = 42577478.518 + bandwidth = gamma_Hz * opts.desiredSliceGrad * 1e-3 * opts.desiredSliceThickness * 1e-3 + if opts.desiredDuration > 0: + duration_in_us = opts.desiredDuration + time_bandwidth = bandwidth * duration_in_us * 1e-6 + else: + time_bandwidth = opts.timeBandwidth + duration_in_us = int(time_bandwidth / bandwidth * 1e6) + + # init object + slr_pulse = rfpf.RF( + name="slfrank_lin_phase_refocus", + duration_in_us=duration_in_us, + bandwidth_in_Hz=bandwidth, + time_bandwidth=time_bandwidth, + num_samples=opts.numSamples + ) + + # set solver + solver = 'PDHG' + pulse_type = opts.pulseType + phase_type = opts.phaseType + + logging.info(f"Generating pulse\n" + f"\t\t\t\t\t__type: {pulse_type} - __phase type {phase_type}") + + # getting length n complex array + pulse_slfrank = slfrank.design_rf( + n=opts.numSamples, tb=time_bandwidth, + ptype=pulse_type, phase=phase_type, + d1=opts.rippleSizes, d2=opts.rippleSizes, + solver=solver, max_iter=opts.maxIter) + + slr_pulse.amplitude = np.real(pulse_slfrank) + slr_pulse.phase = np.angle(pulse_slfrank) + + logging.info(f'SLfRank:\tEnergy={np.sum(np.abs(pulse_slfrank)**2)}\tPeak={np.abs(pulse_slfrank).max()}') + + logging.info("plotting") + fig = slfrank.plot_slr_pulses( + np.full_like(pulse_slfrank, np.nan, dtype=complex), + pulse_slfrank, ptype=pulse_type, phase=phase_type, + omega_range=[-1, 1], tb=time_bandwidth, d1=opts.rippleSizes, d2=opts.rippleSizes) + plt.tight_layout() + plt.show() + + out_path = plib.Path(opts.outputPath).absolute() + plot_file = out_path.joinpath(f'{pulse_type}_{phase_type}.png') + pulse_file = out_path.joinpath(f"slfrank_{pulse_type}_pulse_{phase_type}_phase.pkl") + + logging.info(f"saving plot {plot_file}") + fig.savefig(plot_file, bbox_inches="tight", transparent=True) + + logging.info(f"saving pulse {pulse_file}") + slr_pulse.save(pulse_file) + + logging.info(f"finished") + + +if __name__ == '__main__': + logging.basicConfig(format='%(asctime)s %(levelname)s :: %(name)s -- %(message)s', + datefmt='%I:%M:%S', level=logging.INFO) + + # get cmd line input + parser, args = options.createCommandlineParser() + + logging.info("set parameters") + opts = options.Config.from_cmd_args(args) + + try: + main(opts) + except Exception as e: + logging.error(e) + parser.print_usage() diff --git a/slr_creation/options.py b/slr_creation/options.py new file mode 100644 index 0000000..1040bd0 --- /dev/null +++ b/slr_creation/options.py @@ -0,0 +1,56 @@ +import simple_parsing as sp +import dataclasses as dc +import pathlib as plib + + +@dc.dataclass +class Config(sp.helpers.Serializable): + configFile: str = sp.field(default="", alias=["-c"]) + outputPath: str = sp.field(default="./pulses/", alias=["-o"]) + + numSamples: int = sp.field(default=300, alias=["-n"]) + timeBandwidth: float = sp.field(default=2.75, alias=["-tb"]) + desiredDuration: int = sp.field(default=0, alias=["-dur"]) # us + desiredSliceGrad: float = sp.field(default=35.0, alias=["-sg"]) # [mT/m] + desiredSliceThickness: float = sp.field(default=0.7, alias=["-st"]) # [mm] + rippleSizes: float = sp.field(default=0.01, alias=["-r"]) + pulseType: str = sp.choice("ex", "se", "inv", default="ex", alias=["-pu"]) + phaseType: str = sp.choice("linear", "minimum", default="linear", alias=["-ph"]) + maxIter: int = sp.field(2500, alias=["-mi"]) + + @classmethod + def from_cmd_args(cls, args: sp.ArgumentParser.parse_args): + # create default_dict + default_instance = cls() + instance = cls() + if args.config.ConfigFile: + confPath = plib.Path(args.config.ConfigFile).absolute() + instance = cls.load(confPath) + # might contain defaults + for key, item in default_instance.__dict__.items(): + parsed_arg = args.config.__dict__.get(key) + # if parsed arguments are not defaults + if parsed_arg != default_instance.__dict__.get(key): + # update instance even if changed by config file -> that way prioritize cmd line input + instance.__setattr__(key, parsed_arg) + return instance + + +def createCommandlineParser(): + """ + Build the parser for arguments + Parse the input arguments. + """ + parser = sp.ArgumentParser(prog='slr_slfrank') + parser.add_arguments(Config, dest="config") + args = parser.parse_args() + + return parser, args + + +if __name__ == '__main__': + save_path = plib.Path("./js/default_conf.json").absolute() + save_path.parent.mkdir(parents=True, exist_ok=True) + conf = Config() + conf.save(save_path) + From 7e0797d0d9536562c29bc98edbf3d122965a4d4b Mon Sep 17 00:00:00 2001 From: Jochen Schmidt Date: Thu, 16 Feb 2023 17:27:31 +0100 Subject: [PATCH 3/3] refactoring opts; add display --- slr_creation/__main__.py | 24 ++++++++++++------------ slr_creation/default_conf.json | 1 + slr_creation/options.py | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 slr_creation/default_conf.json diff --git a/slr_creation/__main__.py b/slr_creation/__main__.py index bfd85d3..9e4607b 100644 --- a/slr_creation/__main__.py +++ b/slr_creation/__main__.py @@ -17,21 +17,21 @@ def main(opts: options.Config): + opts.display() gamma_Hz = 42577478.518 bandwidth = gamma_Hz * opts.desiredSliceGrad * 1e-3 * opts.desiredSliceThickness * 1e-3 if opts.desiredDuration > 0: - duration_in_us = opts.desiredDuration - time_bandwidth = bandwidth * duration_in_us * 1e-6 + opts.timeBandwidth = bandwidth * opts.desiredDuration * 1e-6 else: - time_bandwidth = opts.timeBandwidth - duration_in_us = int(time_bandwidth / bandwidth * 1e6) + opts.desiredDuration = int(opts.timeBandwidth / bandwidth * 1e6) + opts.display() # init object slr_pulse = rfpf.RF( name="slfrank_lin_phase_refocus", - duration_in_us=duration_in_us, + duration_in_us=opts.desiredDuration, bandwidth_in_Hz=bandwidth, - time_bandwidth=time_bandwidth, + time_bandwidth=opts.timeBandwidth, num_samples=opts.numSamples ) @@ -40,12 +40,12 @@ def main(opts: options.Config): pulse_type = opts.pulseType phase_type = opts.phaseType - logging.info(f"Generating pulse\n" - f"\t\t\t\t\t__type: {pulse_type} - __phase type {phase_type}") + logging.info(f"Generating pulse" + f"\t\t__type: {pulse_type} \t __phase type {phase_type}") # getting length n complex array pulse_slfrank = slfrank.design_rf( - n=opts.numSamples, tb=time_bandwidth, + n=opts.numSamples, tb=opts.timeBandwidth, ptype=pulse_type, phase=phase_type, d1=opts.rippleSizes, d2=opts.rippleSizes, solver=solver, max_iter=opts.maxIter) @@ -59,7 +59,7 @@ def main(opts: options.Config): fig = slfrank.plot_slr_pulses( np.full_like(pulse_slfrank, np.nan, dtype=complex), pulse_slfrank, ptype=pulse_type, phase=phase_type, - omega_range=[-1, 1], tb=time_bandwidth, d1=opts.rippleSizes, d2=opts.rippleSizes) + omega_range=[-1, 1], tb=opts.timeBandwidth, d1=opts.rippleSizes, d2=opts.rippleSizes) plt.tight_layout() plt.show() @@ -84,10 +84,10 @@ def main(opts: options.Config): parser, args = options.createCommandlineParser() logging.info("set parameters") - opts = options.Config.from_cmd_args(args) + conf_opts = options.Config.from_cmd_args(args) try: - main(opts) + main(conf_opts) except Exception as e: logging.error(e) parser.print_usage() diff --git a/slr_creation/default_conf.json b/slr_creation/default_conf.json new file mode 100644 index 0000000..20a4db8 --- /dev/null +++ b/slr_creation/default_conf.json @@ -0,0 +1 @@ +{"configFile": "", "outputPath": "./pulses/", "numSamples": 300, "timeBandwidth": 2.75, "desiredDuration": 0, "desiredSliceGrad": 35.0, "desiredSliceThickness": 0.7, "rippleSizes": 0.01, "pulseType": "ex", "phaseType": "linear", "maxIter": 2500} \ No newline at end of file diff --git a/slr_creation/options.py b/slr_creation/options.py index 1040bd0..e63de69 100644 --- a/slr_creation/options.py +++ b/slr_creation/options.py @@ -1,6 +1,9 @@ import simple_parsing as sp import dataclasses as dc import pathlib as plib +import logging + +logModule = logging.getLogger(__name__) @dc.dataclass @@ -23,8 +26,8 @@ def from_cmd_args(cls, args: sp.ArgumentParser.parse_args): # create default_dict default_instance = cls() instance = cls() - if args.config.ConfigFile: - confPath = plib.Path(args.config.ConfigFile).absolute() + if args.config.configFile: + confPath = plib.Path(args.config.configFile).absolute() instance = cls.load(confPath) # might contain defaults for key, item in default_instance.__dict__.items(): @@ -35,6 +38,11 @@ def from_cmd_args(cls, args: sp.ArgumentParser.parse_args): instance.__setattr__(key, parsed_arg) return instance + def display(self): + logModule.info(f"Parameters:\n") + for key, value in self.__dict__.items(): + logModule.info(f"\t\t{key}: \t\t {value}") + def createCommandlineParser(): """ @@ -49,8 +57,9 @@ def createCommandlineParser(): if __name__ == '__main__': - save_path = plib.Path("./js/default_conf.json").absolute() + save_path = plib.Path("default_conf.json").absolute() save_path.parent.mkdir(parents=True, exist_ok=True) conf = Config() + conf.display() conf.save(save_path)