Skip to content
Snippets Groups Projects
oceano.py 12.3 KiB
Newer Older
Jacques Grelet's avatar
Jacques Grelet committed
#!/usr/bin/env python

import argparse
import sys
import PySimpleGUI as sg
import toml
import logging
from profile import Profile
from trajectory import Trajectory
from configparser import ConfigParser
import distutils.util as du
from physical_parameter import Roscop
dict_type = {'PROFILE': ['CTD', 'BTL','XBT','LADCP'], 'TRAJECTORY': ['TSG','MTO']}

# typeInstrument is a dictionary as key: files extension
typeInstrument = {'CTD': ('cnv', 'CNV'), 'XBT': (
    'EDF', 'edf'), 'LADCP': ('lad', 'LAD'), 'TSG': ('colcor','COLCOR'),
    'BTL': ('btl', 'BTL')}
#variables_1D = ['TIME', 'LATITUDE', 'LONGITUDE','BATH']
ti = typeInstrument  # an alias
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
filesBrowsePosition_row = 2
filesBrowsePosition_column = 1
# initialize filename use to save GUI configuration
configfile = 'oceano.cfg'
# default file name for ROSCOP csv
defaultRoscop = 'code_roscop.csv'

# example, PIRATA cruises:
# FR30:
# python oceano.py /m/PIRATA-FR30/data-processing/CTD/data/cnv/d*.cnv -i CTD -k PRES TEMP PSAL DOX2 FLU2 -r code_roscop.csv

def processArgs():
    parser = argparse.ArgumentParser(
        description='This program read multiple ASCII file, extract physical parameter \
                    following ROSCOP codification at the given column, fill arrays, write header file ',
        usage='\npython oceano.py data/CTD/cnv/dfr2900[1-3].cnv -i CTD -d\n'
        'python oceano.py data/CTD/cnv/dfr2900[1-3].cnv -i CTD -k PRES TEMP PSAL DOX2 DENS\n'
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        'python oceano.py data/CTD/cnv/dfr29*.cnv -i CTD -d\n'
        'python oceano.py data/XBT/T7_0000*.EDF -i XBT -k DEPTH TEMP SVEL\n'
Jacques Grelet's avatar
Jacques Grelet committed
        'python oceano.py data/LADCP/*.lad -i LADCP -k DEPTH EWCT NSCT\n'
        'python oceano.py data/CTD/btl/fr290*.btl -i BTL -k BOTL PRES DEPTH ETDD TE01 TE02 PSA1 PSA2 DO11 DO12 DO21 DO22 FLU2'
        epilog='J. Grelet IRD US191 - March 2019 / Feb 2020')
    parser.add_argument('-d', '--debug', help='display debug informations',
                        action='store_true')
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
    parser.add_argument('--demo', nargs='?', choices=ti.keys(),
                        help='specify the commande line for instrument, eg CTD, XBT, TSG, LADCP')
    parser.add_argument('-c', '--config', help="toml configuration file, (default: %(default)s)",
                        default='config.toml')
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
    parser.add_argument('-i', '--instrument', nargs='?', choices=ti.keys(),
                        help='specify the instrument that produce files, eg CTD, XBT, TSG, LADCP')
    parser.add_argument('-r', '--roscop', nargs='?',
                        help='specify location and ROSCOP file name, (default: code_roscop.csv)')
    parser.add_argument('-k', '--keys', nargs='+',
                        help='display dictionary for key(s), (default: %(default)s)')
    parser.add_argument('-g', '--gui', action='store_true',
                        help='use GUI interface')
Jacques Grelet's avatar
Jacques Grelet committed
    parser.add_argument('--database', help='save sqlite3 database in test.db instead of memory',
                        action='store_true')
    # type=argparse.FileType('r') don't work with under DOS 
    parser.add_argument('files', nargs='*',
                        help='ASCII file(s) to parse')                        
    return parser

# TODOS:
# DEPTH is missing
# file name is not clear at startup
# if no file selected, don't leave the program


def defineGUI():
    '''
    function to define and create the graphical interface, written with PySimpleGUI
    '''
    # check if GUI config file exist
    if args.instrument != None:
        instrument_default_value = args.instrument
    else:
        instrument_default_value = 'CTD'

    # get all devices
    devices = list(ti.keys())

    # change look and feel color scheme
    sg.ChangeLookAndFeel('SandyBeach')
    # define a frame layout for each instrument (device) with composite key as PRES_CTD, TEMP_XBT
    for d in devices:
        keys = cfg['split'][d.lower()].keys()
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        # List comprehensions
        frameLayout[d] = [[sg.Checkbox(k, key='{:s}_{:s}'.format(k, d),
                                       tooltip='Select the extract the physical parameter {}'.format(k))] for k in keys]

    # define GUI layout
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
    layout = ([[sg.Text('File(s) to read and convert')],                              # row 0
               [sg.Multiline(size=(40, 5), key='_IN_'),                               # row 1, col 0
                sg.Input(key='_HIDDEN_', visible=False,                               # row 1, col 1
                         enable_events=True),
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
                sg.FilesBrowse(key='_HIDDEN_', initial_folder=None,                   # row 1, col 2
                               tooltip='Choose one or more files')],
               [sg.Combo(list(ti.keys()), enable_events=True, size=(8, 1),          # row 2
                         default_value=instrument_default_value,
                         key='_DEVICE_', tooltip='Select the instrument')],
               [sg.Frame(d, frameLayout[d], key='_FRAME_{:s}'.format(d), visible=True)          # row 3
                for d in devices],
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
               [sg.OK(), sg.CloseButton('Cancel')]])                                  # row 4
    # [sg.CloseButton('Run'), sg.CloseButton('Cancel')]])

    # create a local instance windows used to reload the saved config from file
    window = sg.Window('Oceano converter', layout)
    window.Finalize()
    window.LoadFromDisk(configfile)
def updateFilesBrowseCombo(window, extentions, x, y):
    '''
    special function used to update the FilesBrowseCombo with canvas poisition
    instead of key because the same key is assign to shadow input object
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
    '''
    e = window.Rows[x][y]    # hardcoded
    e.FileTypes = []         # init to empty list
    for ext in extentions:
        e.FileTypes.append(("{} files".format(ext), "*.{}".format(ext)))
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed

    e.initial_folder = 'data/{}'.format(extentions[0])
    window.Finalize

def process_gui():
          # setup the GUI windows Layout
        window = defineGUI()
        device = window.find_element('_DEVICE_').DefaultValue
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        keys = cfg['split'][device.lower()].keys()
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        # can't update combo with FindElement('_HIDDEN_').Update(), we use this function
        # with hardcoded FilesBrowseCombo position
        updateFilesBrowseCombo(window,
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
            ti[device], filesBrowsePosition_column, filesBrowsePosition_row)

        # set the rigth frame for device visible, dosn't work
        #  File "C:\git\python\PySimpleGUI\PySimpleGUI.py", line 2362, in Update
        #  self.TKFrame.pack()
        #  AttributeError: 'NoneType' object has no attribute 'pack'
        # for d in list(ti.keys()):
        #     print(d)
        #     if d == device:
        #         window.FindElement(
        #             '_FRAME_{:s}'.format(d)).Update(visible=True)
        # main GUI loop
        while True:
           # display the main windows
            event, values = window.Read()
            #print(event, values)
            if event == 'Cancel' or event == None:
                raise SystemExit("Cancelling: user exit")

            if event == 'OK':  # end of initialization, process data now
                # values['_HIDDEN_'] is a string with files separated by ';' and Profile need a list
                files = values['_HIDDEN_'].split(';')
                args.files = files

                # test if a or more file are selected
                if not all(args.files):
                    sg.Popup("Cancel", "No filename supplied")
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
                    # raise SystemExit("Cancelling: no filename supplied")
                # you have to go into the bowels of the pygi code, to get the instance of the Combo
                # by the line and column number of the window to update its "fileType" property.
                updateFilesBrowseCombo(window,
                    ti[device], filesBrowsePosition_column, filesBrowsePosition_row)
                # TODOS: reset checkbox
            # update the Multilines instance from FilesBrowse return
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
                window.Element('_IN_').Update(
                    values['_HIDDEN_'].split(';'))
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        # save program configuration
        window.SaveToDisk(configfile)

        # debug return values from GUI
        logging.debug("Event: {}, Values: {}".format(event, values))

        # extract selected parameters (true) from dict values only for selected device
        args.keys = []
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        for k in values.keys():
            if k[0] == '_' or values[k] == False:
            # check if device == device
            if not device in k:
                continue
            else:
                args.keys.append(re.sub('_\w+$', '', k))
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        # process of files start here
        fe = process(args, cfg, device)
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed

        # display result in popup GUI
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        dims = "Dimensions: {} x {}".format(fe.n, fe.m)
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        sg.PopupScrolled('Oceano2python', dims,
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
                         fe.disp(),  size=(80, 40))
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        # It will output to a debug window. Bug ? debug windows xas closed before exiting program
        # print = sg.Print
        # or
        # print = sg.Print(size=(80,40))

        return window, fe, device



if __name__ == "__main__":
    '''
    usage:
    > python oceano.py data/CTD/cnv/dfr2900[1-3].cnv -i CTD
    > python oceano.py data/CTD/cnv/dfr29*.cnv -i CTD -k PRES TEMP PSAL DOX2 DENS -d
Jacques Grelet's avatar
Jacques Grelet committed
    > python oceano.py data/CTD/cnv/dfr2900[1-3].cnv -i CTD -k PRES TEMP PSAL DOX2 DENS
    > python oceano.py data/CTD/cnv/dfr29*.cnv -i CTD -d
    > python oceano.py data/XBT/T7_0000*.EDF -i XBT -k DEPTH TEMP SVEL
    > python oceano.py data/LADCP/*.lad -i LADCP - k DEPTH EWCT NSCT
    > python oceano.py data/CTD/btl/fr290*.btl -i BTL -k BOTL PRES DEPTH ETDD TE01 TE02 PSA1 PSA2 DO11 DO12 DO21 DO22 FLU2
    '''
    # recover and process line arguments
    parser = processArgs()
    args = parser.parse_args()

    # set looging mode if debug
    if args.debug:
        logging.basicConfig(
            format='%(levelname)s:%(message)s', level=logging.DEBUG)

    # read config Toml file and get the physical parameter list (Roscop code) for the specified instrument
    cfg = toml.load(args.config)
    # this the select device from command line !
    device = str(args.instrument)  # convert one element list to str

    # get roscop file
    if cfg['global']['codeRoscop'] != None:
        defaultRoscop = Path(cfg['global']['codeRoscop'])
    if args.roscop != None:
        defaultRoscop = args.roscop
    roscop = Roscop(defaultRoscop)
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
    # test arguments from sys.argv, args is never to None with default option set, without option
    # or -g, call GUI
    if args.gui or len(sys.argv) == 1:
        # define graphical interface
        window, fe, device = process_gui()

jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
    else:
        # demo mode, only in command line
        if args.demo != None:
            print('demo mode: {}'.format(args.demo))
        # test if a or more file are selected
        else:
            if args.files == []:
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
                print(
                    'Error, you need to specify one or more files to process !!!', end='\n\n')
                parser.print_help(sys.stderr)
                sys.exit(1)
            
            # work with DOs, Git bash and Linux
            files = []
            for file in args.files:  
                files += glob(file)  
            args.files = files
   
jacques.grelet_ird.fr's avatar
jacques.grelet_ird.fr committed
        if device == 'None':
            print(
                'Error: missing option -i or --instrument, instrument = {}\n'.format(device))
            parser.print_help(sys.stderr)
            sys.exit(1)

    def search_dict(dict_type, instrument):
        for k,i in dict_type.items():
            if instrument in i:
                return(k)

    type = search_dict(dict_type, args.instrument)
    print(type)

    if type == 'PROFILE':
        context = Profile(args.files, roscop, args.keys)
    elif type == 'TRAJECTORY':
        context = Trajectory(args.files, roscop, args.keys)
    else:
        print(f"Invalide type: {type}")
        sys.exit()
        
    context.process(args, cfg, device)

    # write ASCII hdr and data files
    #ascii.writeAscii(cfg, device, fe, r)
    #netcdf_profile.writeNetCDF(cfg, device, fe, r)