Skip to content

Commit

Permalink
Merge branch 'develop' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
DrRobsAT committed Apr 9, 2024
2 parents 88589cd + 21ba783 commit 6119e3f
Show file tree
Hide file tree
Showing 13 changed files with 1,199 additions and 275 deletions.
148 changes: 95 additions & 53 deletions pyceps/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"""
Command line interface for pyEPmap
"""

import argparse
import os
import traceback
import shutil
Expand All @@ -29,6 +29,7 @@
import logging
import tempfile
from typing import Tuple
import xml.etree.ElementTree as ET

from pyceps.fileio.cartoio import CartoStudy
from pyceps.fileio.precisionio import PrecisionStudy
Expand Down Expand Up @@ -78,19 +79,10 @@ def get_args():

parser = ArgumentParser(
prog='pyceps',
# add_help=False,
# formatter_class=RawTextHelpFormatter,
)

# configure clinical mapping system from which to import
system = parser.add_argument_group('Clinical Mapping System')
system.add_argument('--system',
required=True,
type=str.upper,
choices=['CARTO', 'PRECISION'],
help='Specify the clinical mapping system that '
'recorded the study.'
)

# specify location of data
# Note: subsequent arguments are mutually exclusive. The user must enter
# either one!
Expand All @@ -101,14 +93,15 @@ def get_args():
type=str,
default=None,
help='Import study from EAM repository.\n'
'Specify path to folder or to ZIP file containing study data.'
'Specify path to folder or to ZIP file containing study data.\n'
'IMPORTANT: use --system to specify EAM system!'
)
load.add_argument(
'--pkl-file',
'--study-file',
type=str,
default=None,
help='Load study from previously created .pkl file.\n'
'If study root saved in PKL file does not point to a valid '
help='Load study from previously created pyCEPS file.\n'
'If study root saved in pyCEPS file does not point to a valid '
'location, it can be set with --change-root'
)

Expand All @@ -121,9 +114,9 @@ def get_args():
const='ALL',
help='Convenience function to export complete EAM data set.\n'
'When importing from EAM repository all available maps are '
'loaded. When importing from PKL file all maps currently in PKL '
'are exported or --import-map might be used to import additional '
'map(s) before export.\n'
'loaded. When importing from pyCEPS file all maps currently in '
'the file are exported or --import-map might be used to import '
'additional map(s) before export.\n'
'Alternatively a specific map name can be given.\n'
'Calls all functions under Advanced Exports with default values.'
)
Expand All @@ -132,9 +125,9 @@ def get_args():
type=str,
nargs='?',
const='DEFAULT',
help='Save study as PKL file.\n'
help='Save study as pyCEPS file.\n'
'Default location is folder above study root, default name is '
'study name e.g. <study_root>/../<study_name>.pkl\n'
'study name e.g. <study_root>/../<study_name>.pyceps\n'
'Custom location and file name can be given alternatively.'
)

Expand Down Expand Up @@ -170,7 +163,7 @@ def get_args():
'surface labels are included.\n'
'Note: surface maps can also be exported as .dat with '
'"--dump-surface-map".\n'
'Default: <study_root>/../<map>.[pts,elem,vtk]'
'Default: <study_root>/../<map>.surf.[pts,elem,vtk]'
)
aio.add_argument(
'--dump-point-data',
Expand All @@ -179,9 +172,11 @@ def get_args():
'openCARP format.\n'
'For each mapping point following data are exported: unipolar '
'voltages (UNI), bipolar voltages (BIP) and local activation '
'time (LAT), impedance (IMP, if available), and contact force ('
'FRC, if available).'
'Default: <study_root>/../<map>.ptdata.UNI.pc.dat'
'time (LAT), point identifier (NAME), time stamps for '
'annotations (LAT, REF), window of interest (WOI_START, '
'WOI_END), impedance (IMP, if available), and contact force '
'(FRC, if available).\n'
'Default: <study_root>/../<map>.ptdata.<parameter>.pc.dat'
)
aio.add_argument(
'--dump-point-ecgs',
Expand All @@ -191,21 +186,21 @@ def get_args():
choices=['I', 'II', 'III',
'aVR', 'aVL', 'aVF',
'V1', 'V2', 'V3', 'V4', 'V5', 'V6'],
help='Export ecg traces for all valid points associated '
help='Export ECG traces for all valid points associated '
'with current "--map" to IGB.\n'
'Dimension: Nx2500 (valid recorded points x ms)\n'
'Note: Requires valid study root!\n'
'If no traces are specified, all surface ECGs are '
'exported by default.\n'
'Default: <study_root>/../<map>.ecg.V1.pc.igb'
'Default: <study_root>/../<map>.ecg.<lead>.pc.igb'
)
aio.add_argument(
'--dump-point-egms',
action='store_true',
help='Export point EGMs for all valid points associated '
'with current "--map".\n'
help='Export point EGM traces for unipolar, bipolar and reference '
'channels for all valid points associated with current "--map".\n'
'Dimension: Nx2500 (valid recorded nodes x ms)\n'
'Default: e.g. <study_root>/../<map>.egm.BIP.is.igb'
'Default: e.g. <study_root>/../<map>.egm.<lead>.pc.igb'
)
aio.add_argument(
'--dump-map-ecgs',
Expand All @@ -218,7 +213,7 @@ def get_args():
'--dump-surface-maps',
action='store_true',
help='Export surface maps associated with current "--map" to DAT.\n'
'Default: <study_root>/../<map>.map.BIP.dat'
'Default: <study_root>/../<map>.map.<parameter>.dat'
)
aio.add_argument(
'--dump-lesions',
Expand All @@ -235,13 +230,13 @@ def get_args():
help='Change location of EAM data repository.\n'
'Parent directory of "--study-xml" for Carto3 or folder for '
'Precision. Can be absolute path to folder or ZIP file.\n'
'Used only when data is loaded from PKL file.'
'Used only when data is loaded from pyCEPS file.'
)
misc.add_argument(
'--egm-from-pos',
action='store_true',
help='Retrieve EGM channel names from recording positions.\n'
'This will add coordinates for the second unipolar EGM channel.\n'
help='Retrieve EGM channel names from recording positions during '
'import.\n'
'Note: Requires valid study root!'
)
misc.add_argument(
Expand All @@ -257,8 +252,29 @@ def get_args():
default='cp1252',
help='Set encoding for file imports. Default: "cp1252".'
)
misc.add_argument('--password',
type=str,
default='',
help='Password for protected archives.'
)

main_args, _ = parser.parse_known_args()

return parser.parse_args()
# configure clinical mapping system from which to import
conditional_parser = argparse.ArgumentParser(parents=[parser],
add_help=False
)
if main_args.study_repository:
system = conditional_parser.add_argument_group('Clinical Mapping System')
system.add_argument('--system',
required=main_args.study_repository,
type=str.upper,
choices=['CARTO', 'PRECISION'],
help='Specify the clinical mapping system that '
'recorded the study.'
)

return conditional_parser.parse_args()


def configure_logger(log_level: str) -> Tuple[int, str]:
Expand Down Expand Up @@ -304,33 +320,53 @@ def load_study(args):
logger.info('importing {} study'.format(args.system))
if args.system == 'CARTO':
study = CartoStudy(args.study_repository,
pwd=args.password,
encoding=args.encoding)
study.import_study()
elif args.system == 'PRECISION':
study = PrecisionStudy(args.study_repository,
pwd=args.password,
encoding=args.encoding)
else:
raise KeyError('unknown EAM system specified!')

else:
study_pkg = os.path.abspath(args.pkl_file)
logger.info('loading {} study PKL'.format(args.system))
study_file = os.path.abspath(args.study_file)
logger.info('loading study from file {}'.format(study_file))

# read in study from a pkl file
# Note: both inputs are mutually exclusive
if not study_pkg.lower().endswith(('.pkl', '.pkl.gz')):
if not study_file.lower().endswith('.pyceps'):
# supposedly reading the carto folder directly
study_pkg += '.pkl'
study_file += '.pyceps'

if not os.path.isfile(study_pkg):
raise FileNotFoundError('could not find {}'.format(study_pkg))
if not os.path.isfile(study_file):
raise FileNotFoundError('could not find {}'.format(study_file))

# update argument in case of later usage
args.study_pkg = study_pkg
args.study_file = study_file

# now we can load the object
if args.system == 'CARTO':
study = CartoStudy.load(study_pkg, root=args.change_root)
elif args.system == 'PRECISION':
study = PrecisionStudy.load(study_pkg, root=args.change_root)
try:
with open(study_file) as fid:
xml_root = ET.parse(fid).getroot()
except ET.ParseError:
logger.warning('unknown file format, aborting!')
return None, args

# get EAM system
system = xml_root.get('system')
repo = xml_root.find('Repository')

if system.lower() == 'carto3':
study = CartoStudy(study_repo=repo.get('base'),
pwd=args.password,
encoding=repo.get('encoding'))
study.load(study_file, repo_path=args.change_root)

elif args.system == 'precision':
study = PrecisionStudy.load(study_file, root=args.change_root)

else:
raise KeyError('unknown EAM system specified!')
# TODO: notification if root has changed (needed to process unsaved changes)
Expand Down Expand Up @@ -420,10 +456,10 @@ def execute_commands(args):
if import_maps:
logger.info('need to import map(s): {}'.format(import_maps))

if args.pkl_file and not study.is_root_valid():
if args.study_file and not study.is_root_valid():
logger.warning('a valid study root is necessary to import maps!')
else:
study.import_maps(study.mapNames,
study.import_maps(import_maps,
egm_names_from_pos=args.egm_from_pos)
# import lesion data for all loaded maps
for map_name in study.maps.keys():
Expand Down Expand Up @@ -477,25 +513,31 @@ def execute_commands(args):

# save study
if not args.save_study and data_changed:
logger.debug('unsaved changes found!')
user_input = input('There are unsaved changes, save them now? [Y/N] ')
# input validation
if user_input.lower() in ('y', 'yes'):
logger.debug('user selected to save changes')
args.save_study = 'DEFAULT'
elif user_input.lower() in ('n', 'no'):
logger.debug('user selected to continue without saving changes')
else:
logger.warning('Unknown user input {}'.format(user_input))

pkl_loc = ''
pyceps_loc = ''
if args.save_study:
pkl_loc = study.save(None if args.save_study == 'DEFAULT' else
args.save_study)
pyceps_loc = study.save(None if args.save_study == 'DEFAULT' else
args.save_study)

# redirect log file
log_file = pkl_loc.replace('.pkl', '_import.log') if pkl_loc else (
os.path.join(
if pyceps_loc:
base_file, _ = os.path.splitext(pyceps_loc)
log_file = base_file + '_import.log'
else:
log_file = os.path.join(
study.build_export_basename(''),
study.name + '_import.log'
)
)

return study, log_file

Expand Down
2 changes: 1 addition & 1 deletion pyceps/datatypes/cartotypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def __init__(self,
session_index=None,
channel_id=None,
tag_index_status=None,
coordinates=np.full(3, np.nan, dtype=float),
coordinates=np.full(3, np.nan, dtype=np.float32),
avg_force=np.nan,
fti=np.nan,
max_power=np.nan,
Expand Down
Loading

0 comments on commit 6119e3f

Please sign in to comment.