Skip to content

Commit

Permalink
[cli] Add initial comand line scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
lucduron committed Nov 3, 2017
1 parent a9a6f01 commit 3f53393
Show file tree
Hide file tree
Showing 16 changed files with 363 additions and 31 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ DOC_PATH=../CNR-Engineering.github.io/PyTelTools

doc: doxygen.config
doxygen $<
export PYTHONPATH=$(shell pwd) && cd cli && python write_cli_usage.py

update_doc: doc
rm -r ${DOC_PATH} && cp -r doc/html/* ${DOC_PATH}
Expand All @@ -37,4 +38,4 @@ test:
clean:
rm -rf doc venv .cache

.PHONY: clean test update_doc venv
.PHONY: clean doc test update_doc venv
Empty file added PyTelTools/__init__.py
Empty file.
51 changes: 32 additions & 19 deletions PyTelTools/slf/Serafin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
"""

import copy
import logging
import numpy as np
import os
import struct

from slf.util import logger
from slf.variable.variables_2d import VARIABLES_2D
from slf.variable.variables_3d import VARIABLES_3D

Expand All @@ -22,9 +22,6 @@
SLF_EIT = 'iso-8859-1'


module_logger = logging.getLogger(__name__)


VARIABLES_ID_2D, VARIABLES_ID_3D = {'fr': {}, 'en': {}}, {'fr': {}, 'en': {}}


Expand All @@ -51,7 +48,7 @@ def __init__(self, message):
"""
super().__init__(message)
self.message = message
module_logger.error('SERAFIN VALIDATION ERROR: %s' % message)
logger.error('SERAFIN VALIDATION ERROR: %s' % message)


class SerafinRequestError(Exception):
Expand All @@ -64,7 +61,7 @@ def __init__(self, message):
"""
super().__init__(message)
self.message = message
module_logger.error('SERAFIN REQUEST ERROR: %s' % message)
logger.error('SERAFIN REQUEST ERROR: %s' % message)


class SerafinHeader:
Expand Down Expand Up @@ -96,7 +93,7 @@ def __init__(self, file, file_size, language):
self.endian = '<'
else:
raise SerafinValidationError('File endianness could not be determined')
module_logger.debug('File is determined to be %s endian' % ('big' if self.endian == '>' else 'litte'))
logger.debug('File is determined to be %s endian' % ('big' if self.endian == '>' else 'litte'))

# Read title
self.title = file.read(72)
Expand All @@ -105,23 +102,23 @@ def __init__(self, file, file_size, language):

try:
data_format = self.file_type.decode(SLF_EIT)
module_logger.debug('The file type is: "%s"' % data_format)
logger.debug('The file type is: "%s"' % data_format)
except UnicodeDecodeError:
raise SerafinValidationError('File type is unreadable: %s' % self.file_type)

if data_format in ('SERAFIND', ' D'):
self._set_as_double_precision()
else:
if data_format not in ('SERAFIN ', 'SERAPHIN', ' '):
module_logger.warning('Format "%s" is unknown and is forced to "SERAFIN"' % data_format)
logger.warning('Format "%s" is unknown and is forced to "SERAFIN"' % data_format)
self.file_type = bytes('SERAFIN', SLF_EIT).ljust(8)
self._set_as_single_precision()

# Read the number of linear and quadratic variables
file.read(4)
self.nb_var = self.unpack_int(file.read(4), 1)[0]
self.nb_var_quadratic = self.unpack_int(file.read(4), 1)[0]
module_logger.debug('The file has %d variables' % self.nb_var)
logger.debug('The file has %d variables' % self.nb_var)
file.read(4)
if self.nb_var_quadratic != 0:
raise SerafinValidationError('The number of quadratic variables is not equal to zero')
Expand Down Expand Up @@ -171,7 +168,7 @@ def __init__(self, file, file_size, language):
raise SerafinValidationError('The number of nodes per element is not equal to 6')
if self.nb_planes < 2:
raise SerafinValidationError('The number of planes is less than 2')
module_logger.debug('The file is determined to be %s' % {True: '2D', False: '3D'}[self.is_2d])
logger.debug('The file is determined to be %s' % {True: '2D', False: '3D'}[self.is_2d])

# determine the number of nodes in 2D
if self.is_2d:
Expand Down Expand Up @@ -207,7 +204,7 @@ def __init__(self, file, file_size, language):

# Deduce the number of frames and test the integer division
self.nb_frames = (self.file_size - self.header_size) // self.frame_size
module_logger.debug('The file has %d frames of size %d bytes' % (self.nb_frames, self.frame_size))
logger.debug('The file has %d frames of size %d bytes' % (self.nb_frames, self.frame_size))

diff_size = self.file_size - self.header_size - self.frame_size * self.nb_frames
# A difference of only one byte is tolerated.
Expand All @@ -222,7 +219,7 @@ def __init__(self, file, file_size, language):
name = var_name.decode(encoding=SLF_EIT).strip()
if name not in var_table:
slf_type = '2D' if self.is_2d else '3D'
module_logger.warning('WARNING: The %s variable name "%s" is not known (lang=%s). '
logger.warning('WARNING: The %s variable name "%s" is not known (lang=%s). '
'The complete name will be used as ID' % (slf_type, name, self.language))
var_id = name
else:
Expand All @@ -242,7 +239,7 @@ def __init__(self, file, file_size, language):
else:
self.ikle_2d = self.ikle.reshape(self.nb_elements, self.nb_nodes_per_elem)

module_logger.debug('Finished reading the header')
logger.debug('Finished reading the header')

def _set_as_single_precision(self):
"""Set Serafin as single precision"""
Expand Down Expand Up @@ -296,6 +293,14 @@ def pack_float(self, *args, nb=1):
fmt = self.endian + str(nb) + self.float_type
return struct.pack(fmt, *args)

def toggle_endianness(self):
"""Toggle original endianness (between big or little endian)"""
if self.endian == '>':
self.endian = '<'
else:
self.endian = '>'
logger.debug('Toggle endianness to %s' % ('big' if self.endian == '>' else 'litte'))

def _set_header_size(self):
"""Set header size"""
nb_ikle_values = self.nb_elements * self.nb_nodes_per_elem
Expand Down Expand Up @@ -484,7 +489,7 @@ def __init__(self, filename, language):
self.header = None
self.time = []
self.file_size = os.path.getsize(self.filename)
module_logger.info('Reading the input file: "%s" of size %d bytes' % (filename, self.file_size))
logger.info('Reading the input file: "%s" of size %d bytes' % (filename, self.file_size))

def read_header(self):
"""!
Expand All @@ -498,14 +503,22 @@ def get_time(self):
"""
if self.header is None:
raise SerafinRequestError('Cannot read time without any header (forgot read_header ?)')
module_logger.debug('Reading the time series from the file')
logger.debug('Reading the time series from the file')
self.file.seek(self.header.header_size, 0)
for i in range(self.header.nb_frames):
self.file.read(4)
self.time.append(self.header.unpack_float(self.file.read(self.header.float_size), 1)[0])
self.file.read(4)
self.file.seek(self.header.frame_size - 8 - self.header.float_size, 1)

def subset_time(self, start, end, ech):
new_time = []
for time_index, time in enumerate(self.time):
if start <= time <= end:
if time_index % ech == 0:
new_time.append((time_index, time))
return new_time

def _get_var_index(self, var_ID):
"""!
@brief Handle data request by variable ID
Expand All @@ -527,7 +540,7 @@ def read_var_in_frame(self, time_index, var_ID):
@param var_ID <str>: variable ID
@return <numpy 1D-array>: values of the variables, of length equal to the number of nodes
"""
module_logger.debug('Reading variable %s at frame %i' % (var_ID, time_index))
logger.debug('Reading variable %s at frame %i' % (var_ID, time_index))
pos_var = self._get_var_index(var_ID)
self.file.seek(self.header.header_size + time_index * self.header.frame_size
+ 8 + self.header.float_size + pos_var * (8 + self.header.float_size * self.header.nb_nodes), 0)
Expand Down Expand Up @@ -568,13 +581,13 @@ class Write(Serafin):
"""
def __init__(self, filename, language):
super().__init__(filename, 'wb', language)
module_logger.info('Writing the output file: "%s"' % filename)
logger.info('Writing the output file: "%s"' % filename)

def __enter__(self):
try:
return Serafin.__enter__(self)
except FileExistsError:
module_logger.error('ERROR: Cannot overwrite existing file')
logger.error('ERROR: Cannot overwrite existing file')
raise FileExistsError('File {} already exists (remove the file or change the option '
'and then re-run the program)'.format(self.filename))

Expand Down
7 changes: 2 additions & 5 deletions PyTelTools/slf/datatypes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from copy import deepcopy
import datetime
import logging
import numpy as np

from slf import Serafin

module_logger = logging.getLogger(__name__)
from slf.util import logger


class SerafinData:
Expand Down Expand Up @@ -43,7 +40,7 @@ def read(self):
year, month, day, hour, minute, second = self.header.date
self.start_time = datetime.datetime(year, month, day, hour, minute, second)
except ValueError:
module_logger.warning('Date seems invalid, replaced by default date.')
logger.warning('Date seems invalid, replaced by default date.')
if self.start_time is None:
self.start_time = datetime.datetime(1900, 1, 1, 0, 0, 0)
self.time_second = list(map(lambda x: datetime.timedelta(seconds=x), self.time))
Expand Down
5 changes: 2 additions & 3 deletions PyTelTools/slf/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
import shapefile

from slf import Serafin
from slf.util import logger
from slf.variables import do_calculation, get_available_variables, get_necessary_equations


module_logger = logging.getLogger(__name__)

# constants
OPERATORS = ['+', '-', '*', '/', '^', 'sqrt', 'sin', 'cos', 'atan']
MAX, MIN, MEAN, ARRIVAL_DURATION, PROJECT, DIFF, REV_DIFF, \
Expand Down Expand Up @@ -67,7 +66,7 @@ def scalars_vectors(known_vars, selected_vars, us_equation=None):
is_2d=True, us_equation=us_equation))
continue
# if the magnitude is not computable, use scalar operation instead
module_logger.warning('The variable %s will be considered to be scalar instead of vector.' % var)
logger.warning('The variable %s will be considered to be scalar instead of vector.' % var)
scalars.append((var, name, unit))
else:
scalars.append((var, name, unit))
Expand Down
10 changes: 10 additions & 0 deletions PyTelTools/slf/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import logging

from conf.settings import LOGGING_LEVEL


logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(handler)
logger.setLevel(LOGGING_LEVEL)
5 changes: 3 additions & 2 deletions PyTelTools/slf/variable/variables_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import numpy as np


# define constants
KARMAN = 0.4
RHO_WATER = 1000.
Expand Down Expand Up @@ -84,8 +85,8 @@ def compute_COMPONENT_X(scalar, x, y):
return np.where(magnitude>0, scalar*x/magnitude, 0)


def compute_COMPONENT_Y(a, x, y):
return compute_COMPONENT_X(a, y, x)
def compute_COMPONENT_Y(scalar, x, y):
return compute_COMPONENT_X(scalar, y, x)


PLUS, MINUS, TIMES, NORM2, NORM2_3D = 1, 2, 3, 4, 104
Expand Down
1 change: 1 addition & 0 deletions PyTelTools/slf/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from slf.variable.variables_2d import get_available_2d_variables, get_necessary_2d_equations, \
get_US_equation, new_variables_from_US
# Beware: `get_US_equation` and `new_variables_from_US` are imported indirectly
from slf.variable.variables_3d import get_available_3d_variables, get_necessary_3d_equations
from slf.variable.variables_utils import do_calculation

Expand Down
92 changes: 92 additions & 0 deletions cli/slf_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/python3
"""
Performs multiple operations on a Serafin file:
- compute and/or remove variables
- mesh transformations (coordinates)
- select frames
"""

import sys
from tqdm import tqdm

from utils.util import logger, MyArgParse
from PyTelTools.geom.transformation import Translation
from PyTelTools.slf import Serafin
from PyTelTools.slf.variables import do_calculations_in_frame, get_necessary_equations


def slf_base(args):
with Serafin.Read(args.in_slf, args.lang) as resin:
resin.read_header()
logger.info(resin.header.summary())
resin.get_time()

output_header = resin.header.copy()
# Shift mesh coordinates if necessary
if args.shift:
output_header.transform_mesh(Translation(args.shift[0], args.shift[1], 0))

# Toogle output file endianness if necessary
if args.toggle_endianness:
output_header.toggle_endianness()

# Remove variables if necessary
output_header.empty_variables()
for var_ID, var_name, var_unit in zip(resin.header.var_IDs, resin.header.var_names, resin.header.var_units):
if args.var2del is not None:
if var_ID not in args.var2del:
output_header.add_variable(var_ID, var_name, var_unit)
else:
output_header.add_variable(var_ID, var_name, var_unit)

# Add new derived variables
if args.var2add is not None:
for var_ID in args.var2add:
if var_ID in output_header.var_IDs:
logger.warn('Variable %s is already present (or asked)' % var_ID)
else:
output_header.add_variable_from_ID(var_ID)

# Convert to single precision
if args.to_single_precision:
if resin.header.is_double_precision():
output_header.to_single_precision()
else:
logger.warn('Input file is already single precision! Argument `--to_single_precision` is ignored')

necessary_equations = get_necessary_equations(resin.header.var_IDs, output_header.var_IDs,
is_2d=resin.header.is_2d)

with Serafin.Write(args.out_slf, args.lang) as resout:
resout.write_header(resin.header)

for time_index, time in tqdm(resin.subset_time(args.start, args.end, args.ech), unit='frame'):
values = do_calculations_in_frame(necessary_equations, resin, time_index, output_header.var_IDs,
output_header.np_float_type, is_2d=output_header.is_2d,
us_equation=None)
resout.write_entire_frame(output_header, time, values)


parser = MyArgParse(description=__doc__, add_args=['in_slf', 'out_slf', 'shift'])

group_var = parser.add_argument_group('Serafin variables (optional)',
'See variables abbrevations on https://github.com/CNR-Engineering/PyTelTools/wiki/Notations-of-variables')
group_var.add_argument('--var2del', nargs='+', help='variable(s) to delete', default=[], metavar=('V1', 'V2'))
group_var.add_argument('--var2add', nargs='+', help='variable(s) to add', default=[], metavar=('V1', 'V2'))

group_temp = parser.add_argument_group('Temporal operations (optional)')
group_temp.add_argument('--ech', type=int, help='frequency sampling of input', default=1)
group_temp.add_argument('--start', type=float, help='minimum time (in seconds)', default=-float('inf'))
group_temp.add_argument('--end', type=float, help='maximum time (in seconds)', default=float('inf'))

parser.add_group_general(['force', 'verbose'])


if __name__ == '__main__':
args = parser.parse_args()

try:
slf_base(args)
except (Serafin.SerafinRequestError, Serafin.SerafinValidationError):
# Message is already reported by slf logger
sys.exit(2)
Loading

0 comments on commit 3f53393

Please sign in to comment.