Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
write = write_sdds


__all__ = [read, SddsFile, write, __version__]
__all__ = [read, SddsFile, write, __version__]
26 changes: 16 additions & 10 deletions sdds/classes.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
ENDIAN = {'little': '<', 'big': '>'}

NUMTYPES = {"float": "f", "double": "d", "short": "i2",
"long": "i4", "llong": "i8", "char": "i1", "boolean": "i1",
"string": "s"}
"long": "i4", "llong": "i8", "char": "c", "boolean": "i1",
"string": "s", "character": "c"}
NUMTYPES_ascii = {"float": "%f", "double": "%e", "short": "%i",
"long": "%i", "llong": "%i", "char": "%s", "boolean": "%i",
"string": "%s", "character": "%s"}
NUMTYPES_SIZES = {"float": 4, "double": 8, "short": 2,
"long": 4, "llong": 8, "char": 1, "boolean": 1}
"long": 4, "llong": 8, "char": 1, "character": 1, "boolean": 1,"string": 1}
NUMTYPES_CAST = {"float": float, "double": float, "short": int,
"long": int, "llong": int, "char": str, "boolean": int}
"long": int, "llong": int, "char": str, "character": str, "boolean": int, "string": str}


def get_dtype_str(type_: str, endianness: str = 'big', length: int = None):
Expand Down Expand Up @@ -111,14 +114,11 @@ def __init__(self, name: str, type_: str, **kwargs) -> None:
self.type = type_
type_hints = get_type_hints(self)
for argname in kwargs:
assert hasattr(self, argname),\
f"Unknown name {argname} for data type "\
f"{self.__class__.__name__}"
assert hasattr(self, argname), f"Unknown name {argname} for data type {self.__class__.__name__}"
# The type of the parameter can be resolved from the type hint
type_hint = type_hints[argname]
if hasattr(type_hint, "__args__"): # For the Optional[...] types
type_hint = next(t for t in type_hint.__args__
if not isinstance(t, type(None)))
type_hint = next(t for t in type_hint.__args__ if not isinstance(t, type(None)))
setattr(self, argname, type_hint(kwargs[argname]))

def __repr__(self):
Expand Down Expand Up @@ -200,17 +200,23 @@ class SddsFile:
val = sdds_file.values["name"]
# The definitions and values can also be accessed like:
def_, val = sdds_file["name"]
npages = number of pages
mode = "ascii" or "binary"
"""
version: str # This should always be "SDDS1"
description: Optional[Description]
definitions: Dict[str, Definition]
values: Dict[str, Any]
npages: int
mode: str

def __init__(self, version: str, description: Optional[Description],
definitions_list: List[Definition],
values_list: List[Any]) -> None:
values_list: List[Any], npages: int, mode: str = "binary") -> None:
self.version = version
self.description = description
self.npages = npages
self.mode = mode
self.definitions = {definition.name: definition for definition in definitions_list}
self.values = {definition.name: value for definition, value
in zip(definitions_list, values_list)}
Expand Down
94 changes: 75 additions & 19 deletions sdds/reader.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
It provides a high-level function to read SDDS files in different formats, and a series of helpers.
"""
import pathlib
import shlex
import struct
import sys
from typing import IO, Any, List, Optional, Generator, Dict, Union, Tuple, Callable, Type

import numpy as np

from sdds.classes import (SddsFile, Column, Parameter, Definition, Array, Data, Description,
ENCODING, NUMTYPES_CAST, NUMTYPES_SIZES, get_dtype_str)
from sdds.classes import (
SddsFile, Column, Parameter, Definition, Array, Data, Description,
ENCODING, NUMTYPES_CAST, NUMTYPES_SIZES, get_dtype_str,
)


def read_sdds(file_path: Union[pathlib.Path, str], endianness: str = None) -> SddsFile:
Expand All @@ -39,8 +42,8 @@ def read_sdds(file_path: Union[pathlib.Path, str], endianness: str = None) -> Sd
endianness = _get_endianness(inbytes)
version, definition_list, description, data = _read_header(inbytes)
data_list = _read_data(data, definition_list, inbytes, endianness)

return SddsFile(version, description, definition_list, data_list)
npages = len(data_list)
return SddsFile(version, description, definition_list, data_list, npages, data.mode)


##############################################################################
Expand Down Expand Up @@ -144,10 +147,7 @@ def _read_bin_array(inbytes: IO[bytes], definition: Array, endianness: str) -> A
dims, total_len = _read_bin_array_len(inbytes, definition.dimensions, endianness)

if definition.type == "string":
len_type: str = "long"\
if not hasattr(definition, "modifier")\
else {"u1": "char", "i2": "short"}\
.get(definition.modifier, "long")
len_type: str = {"u1": "char", "i2": "short"}.get(getattr(definition, "modifier", None), "long")
str_array = []
for _ in range(total_len):
str_len = int(_read_bin_numeric(inbytes, len_type, 1, endianness))
Expand Down Expand Up @@ -183,6 +183,7 @@ def _read_string(inbytes: IO[bytes], str_len: int, endianness: str) -> str:
##############################################################################

def _read_data_ascii(definitions: List[Definition], inbytes: IO[bytes]) -> List[Any]:
# TODO: Some simplifications (jdilly)
def _ascii_generator(ascii_text):
for line in ascii_text:
yield line
Expand All @@ -194,21 +195,51 @@ def _ascii_generator(ascii_text):

# Get the generator for the text
ascii_gen = _ascii_generator(ascii_text)


pages = []
npages = 0
try:
while True: # loop over pages
page = _read_ascii_page(ascii_gen, definitions)

if npages == 0: # quite sloppy but it works :-); jzemella @ 2022-08-25
pages = page
elif npages == 1:
pages = [[l1, l2] for l1, l2 in zip(pages, page)]
else:
t = [l1.append(l2) for l1, l2 in zip(pages, page)]
npages = npages+1
except:
t = 1
return pages


def _read_ascii_page(ascii_gen, definitions):
# Dict of function to call for each type of tag: array, parameter
functs_dict = {Parameter: _read_ascii_parameter,
Array: _read_ascii_array
Array: _read_ascii_array,
Column: _read_ascii_column,
Data: _read_ascii_data
}

# Iterate through every parameters and arrays in the file
data = []
cnt = 0 # add counter to get the column definition at once
flag_col = 1 # sicne all columns are read at once the _read_ascii_column function is only called once per page
########
for definition in definitions:
def_tag = definition.__class__

# Call the function handling the tag we're on
# Change the current line according to the tag and dimensions
value = functs_dict[def_tag](ascii_gen, definition)
data.append(value)
if Column != def_tag:
value = functs_dict[def_tag](ascii_gen, definition)
data.append(value)
elif Column == def_tag and flag_col == 1: # used a if statement to distiguish between parameters,arrays and column mode; jzemlella @ 2022-08-25
values = functs_dict[def_tag](ascii_gen, definitions)
for value in values:
data.append(list(value))
flag_col = 0

return data

Expand All @@ -222,15 +253,16 @@ def _read_ascii_parameter(ascii_gen: Generator[str, None, None],
return definition.fixed_value
if definition.type in NUMTYPES_CAST:
return NUMTYPES_CAST[definition.type](definition.fixed_value)


para = next(ascii_gen)
# No fixed value -> read a line
# Strings can be returned without cast
if definition.type == "string":
return next(ascii_gen)
return para

# For other types, a cast is needed
if definition.type in NUMTYPES_CAST:
return NUMTYPES_CAST[definition.type](next(ascii_gen))
return NUMTYPES_CAST[definition.type](para)

raise TypeError(f"Type {definition.type} for Parameter unsupported")

Expand All @@ -239,12 +271,14 @@ def _read_ascii_array(ascii_gen: Generator[str, None, None],
definition: Array) -> np.ndarray:

# Get the number of elements per dimension
dimensions = next(ascii_gen).split()
dimensions = np.array(dimensions, dtype="int")
dimensions = definition.dimensions # <<-- dimensions parameter used from definition; jzemella @2022-08-25
shape = next(ascii_gen)
shape = shape.split() # <<-- changed dimensions to shape defined in line above array data; jzemella @2022-08-25
shape = np.array(shape, dtype="int")

# Get all the data given by the dimensions
data = []
while len(data) != np.prod(dimensions):
while len(data) != np.prod(shape): # <<-- ; jzemella @2022-08-25
# The values on each line are split by a space
data += next(ascii_gen).strip().split(' ')

Expand All @@ -254,8 +288,30 @@ def _read_ascii_array(ascii_gen: Generator[str, None, None],

# Convert to np.array so that it can be reshaped to reflect the dimensions
data = np.array(data)
data = data.reshape(dimensions)
data = data.reshape(shape) # <<-- ; jzemella @2022-08-25

return data


def _read_ascii_column(ascii_gen: Generator[str, None, None],
definitions: Column) -> List[Any]:

nrow= next(ascii_gen)
nrow=np.array(nrow, dtype="int")

data=[]
for i in range(nrow):
line=next(ascii_gen)
data.append([NUMTYPES_CAST[definition.type]( elem) for definition, elem in zip(definitions,shlex.split(line)) ])
# transpose list
data=list(zip(*data))

return data


def _read_ascii_data():
print('"data mode" not implemented yet')
data = []
return data


Expand Down
Loading