Update type annotations and some formatting
This commit is contained in:
parent
4d362f8e09
commit
9d14bf27c6
@ -49,7 +49,7 @@ The goal is to keep this library simple:
|
||||
## Installation
|
||||
|
||||
Requirements:
|
||||
* python >= 3.7 (written and tested with 3.8)
|
||||
* python >= 3.10 (written and tested with 3.11)
|
||||
* numpy
|
||||
|
||||
|
||||
|
@ -1,20 +1,21 @@
|
||||
"""
|
||||
Functionality for encoding/decoding basic datatypes
|
||||
"""
|
||||
from typing import Sequence, IO, List
|
||||
from typing import Sequence, IO
|
||||
import struct
|
||||
from datetime import datetime
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
|
||||
class KlamathError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
"""
|
||||
Parse functions
|
||||
"""
|
||||
#
|
||||
# Parse functions
|
||||
#
|
||||
def parse_bitarray(data: bytes) -> int:
|
||||
if len(data) != 2:
|
||||
raise KlamathError(f'Incorrect bitarray size ({len(data)}). Data is {data!r}.')
|
||||
@ -22,21 +23,21 @@ def parse_bitarray(data: bytes) -> int:
|
||||
return val
|
||||
|
||||
|
||||
def parse_int2(data: bytes) -> numpy.ndarray:
|
||||
def parse_int2(data: bytes) -> NDArray[numpy.int16]:
|
||||
data_len = len(data)
|
||||
if data_len == 0 or (data_len % 2) != 0:
|
||||
raise KlamathError(f'Incorrect int2 size ({len(data)}). Data is {data!r}.')
|
||||
return numpy.frombuffer(data, dtype='>i2', count=data_len // 2)
|
||||
|
||||
|
||||
def parse_int4(data: bytes) -> numpy.ndarray:
|
||||
def parse_int4(data: bytes) -> NDArray[numpy.int32]:
|
||||
data_len = len(data)
|
||||
if data_len == 0 or (data_len % 4) != 0:
|
||||
raise KlamathError(f'Incorrect int4 size ({len(data)}). Data is {data!r}.')
|
||||
return numpy.frombuffer(data, dtype='>i4', count=data_len // 4)
|
||||
|
||||
|
||||
def decode_real8(nums: numpy.ndarray) -> numpy.ndarray:
|
||||
def decode_real8(nums: NDArray[numpy.uint64]) -> NDArray[numpy.float64]:
|
||||
""" Convert GDS REAL8 data to IEEE float64. """
|
||||
nums = nums.astype(numpy.uint64)
|
||||
neg = nums & 0x8000_0000_0000_0000
|
||||
@ -46,7 +47,7 @@ def decode_real8(nums: numpy.ndarray) -> numpy.ndarray:
|
||||
return numpy.ldexp(mant, (4 * (exp - 64) - 56).astype(numpy.int64))
|
||||
|
||||
|
||||
def parse_real8(data: bytes) -> numpy.ndarray:
|
||||
def parse_real8(data: bytes) -> NDArray[numpy.float64]:
|
||||
data_len = len(data)
|
||||
if data_len == 0 or (data_len % 8) != 0:
|
||||
raise KlamathError(f'Incorrect real8 size ({len(data)}). Data is {data!r}.')
|
||||
@ -62,7 +63,7 @@ def parse_ascii(data: bytes) -> bytes:
|
||||
return data
|
||||
|
||||
|
||||
def parse_datetime(data: bytes) -> List[datetime]:
|
||||
def parse_datetime(data: bytes) -> list[datetime]:
|
||||
""" Parse date/time data (12 byte blocks) """
|
||||
if len(data) == 0 or len(data) % 12 != 0:
|
||||
raise KlamathError(f'Incorrect datetime size ({len(data)}). Data is {data!r}.')
|
||||
@ -73,9 +74,9 @@ def parse_datetime(data: bytes) -> List[datetime]:
|
||||
return dts
|
||||
|
||||
|
||||
"""
|
||||
Pack functions
|
||||
"""
|
||||
#
|
||||
# Pack functions
|
||||
#
|
||||
def pack_bitarray(data: int) -> bytes:
|
||||
if data > 65535 or data < 0:
|
||||
raise KlamathError(f'bitarray data out of range: {data}')
|
||||
@ -96,7 +97,7 @@ def pack_int4(data: Sequence[int]) -> bytes:
|
||||
return arr.astype('>i4').tobytes()
|
||||
|
||||
|
||||
def encode_real8(fnums: numpy.ndarray) -> numpy.ndarray:
|
||||
def encode_real8(fnums: NDArray[numpy.float64]) -> NDArray[numpy.uint64]:
|
||||
""" Convert from float64 to GDS REAL8 representation. """
|
||||
# Split the ieee float bitfields
|
||||
ieee = numpy.atleast_1d(fnums.astype(numpy.float64).view(numpy.uint64))
|
||||
@ -151,7 +152,7 @@ def encode_real8(fnums: numpy.ndarray) -> numpy.ndarray:
|
||||
real8[zero] = 0
|
||||
real8[gds_exp < -14] = 0 # number is too small
|
||||
|
||||
return real8
|
||||
return real8.astype(numpy.uint64, copy=False)
|
||||
|
||||
|
||||
def pack_real8(data: Sequence[float]) -> bytes:
|
||||
|
@ -2,11 +2,12 @@
|
||||
Functionality for reading/writing elements (geometry, text labels,
|
||||
structure references) and associated properties.
|
||||
"""
|
||||
from typing import Dict, Tuple, Optional, IO, TypeVar, Type, Union
|
||||
from typing import Optional, IO, TypeVar, Type, Union
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from .basic import KlamathError
|
||||
from .record import Record
|
||||
@ -28,7 +29,7 @@ T = TypeVar('T', bound='Text')
|
||||
X = TypeVar('X', bound='Box')
|
||||
|
||||
|
||||
def read_properties(stream: IO[bytes]) -> Dict[int, bytes]:
|
||||
def read_properties(stream: IO[bytes]) -> dict[int, bytes]:
|
||||
"""
|
||||
Read element properties.
|
||||
|
||||
@ -55,7 +56,7 @@ def read_properties(stream: IO[bytes]) -> Dict[int, bytes]:
|
||||
return properties
|
||||
|
||||
|
||||
def write_properties(stream: IO[bytes], properties: Dict[int, bytes]) -> int:
|
||||
def write_properties(stream: IO[bytes], properties: dict[int, bytes]) -> int:
|
||||
"""
|
||||
Write element properties.
|
||||
|
||||
@ -130,7 +131,7 @@ class Reference(Element):
|
||||
angle_deg: float
|
||||
""" Rotation (degrees counterclockwise) """
|
||||
|
||||
xy: numpy.ndarray
|
||||
xy: NDArray[numpy.int32]
|
||||
"""
|
||||
(For SREF) Location in the parent structure corresponding to the instance's origin (0, 0).
|
||||
(For AREF) 3 locations:
|
||||
@ -143,10 +144,10 @@ class Reference(Element):
|
||||
basis vectors to match it.
|
||||
"""
|
||||
|
||||
colrow: Optional[Union[Tuple[int, int], numpy.ndarray]]
|
||||
colrow: tuple[int, int] | NDArray[numpy.int16] | None
|
||||
""" Number of columns and rows (AREF) or None (SREF) """
|
||||
|
||||
properties: Dict[int, bytes]
|
||||
properties: dict[int, bytes]
|
||||
""" Properties associated with this reference. """
|
||||
|
||||
@classmethod
|
||||
@ -173,8 +174,15 @@ class Reference(Element):
|
||||
size, tag = Record.read_header(stream)
|
||||
xy = XY.read_data(stream, size).reshape(-1, 2)
|
||||
properties = read_properties(stream)
|
||||
return cls(struct_name=struct_name, xy=xy, properties=properties, colrow=colrow,
|
||||
invert_y=invert_y, mag=mag, angle_deg=angle_deg)
|
||||
return cls(
|
||||
struct_name=struct_name,
|
||||
xy=xy,
|
||||
properties=properties,
|
||||
colrow=colrow,
|
||||
invert_y=invert_y,
|
||||
mag=mag,
|
||||
angle_deg=angle_deg,
|
||||
)
|
||||
|
||||
def write(self, stream: IO[bytes]) -> int:
|
||||
b = 0
|
||||
@ -215,13 +223,13 @@ class Boundary(Element):
|
||||
"""
|
||||
__slots__ = ('layer', 'xy', 'properties')
|
||||
|
||||
layer: Tuple[int, int]
|
||||
layer: tuple[int, int]
|
||||
""" (layer, data_type) tuple """
|
||||
|
||||
xy: numpy.ndarray
|
||||
xy: NDArray[numpy.int32]
|
||||
""" Ordered vertices of the shape. First and last points should be identical. """
|
||||
|
||||
properties: Dict[int, bytes]
|
||||
properties: dict[int, bytes]
|
||||
""" Properties for the element. """
|
||||
|
||||
@classmethod
|
||||
@ -252,7 +260,7 @@ class Path(Element):
|
||||
"""
|
||||
__slots__ = ('layer', 'xy', 'properties', 'path_type', 'width', 'extension')
|
||||
|
||||
layer: Tuple[int, int]
|
||||
layer: tuple[int, int]
|
||||
""" (layer, data_type) tuple """
|
||||
|
||||
path_type: int
|
||||
@ -261,13 +269,13 @@ class Path(Element):
|
||||
width: int
|
||||
""" Path width """
|
||||
|
||||
extension: Tuple[int, int]
|
||||
extension: tuple[int, int]
|
||||
""" Extension when using path_type=4. Ignored otherwise. """
|
||||
|
||||
xy: numpy.ndarray
|
||||
xy: NDArray[numpy.int32]
|
||||
""" Path centerline coordinates """
|
||||
|
||||
properties: Dict[int, bytes]
|
||||
properties: dict[int, bytes]
|
||||
""" Properties for the element. """
|
||||
|
||||
@classmethod
|
||||
@ -326,13 +334,13 @@ class Box(Element):
|
||||
"""
|
||||
__slots__ = ('layer', 'xy', 'properties')
|
||||
|
||||
layer: Tuple[int, int]
|
||||
layer: tuple[int, int]
|
||||
""" (layer, box_type) tuple """
|
||||
|
||||
xy: numpy.ndarray
|
||||
xy: NDArray[numpy.int32]
|
||||
""" Box coordinates (5 pairs) """
|
||||
|
||||
properties: Dict[int, bytes]
|
||||
properties: dict[int, bytes]
|
||||
""" Properties for the element. """
|
||||
|
||||
@classmethod
|
||||
@ -360,13 +368,13 @@ class Node(Element):
|
||||
"""
|
||||
__slots__ = ('layer', 'xy', 'properties')
|
||||
|
||||
layer: Tuple[int, int]
|
||||
layer: tuple[int, int]
|
||||
""" (layer, node_type) tuple """
|
||||
|
||||
xy: numpy.ndarray
|
||||
xy: NDArray[numpy.int32]
|
||||
""" 1-50 pairs of coordinates. """
|
||||
|
||||
properties: Dict[int, bytes]
|
||||
properties: dict[int, bytes]
|
||||
""" Properties for the element. """
|
||||
|
||||
@classmethod
|
||||
@ -395,7 +403,7 @@ class Text(Element):
|
||||
__slots__ = ('layer', 'xy', 'properties', 'presentation', 'path_type',
|
||||
'width', 'invert_y', 'mag', 'angle_deg', 'string')
|
||||
|
||||
layer: Tuple[int, int]
|
||||
layer: tuple[int, int]
|
||||
""" (layer, node_type) tuple """
|
||||
|
||||
presentation: int
|
||||
@ -420,13 +428,13 @@ class Text(Element):
|
||||
angle_deg: float
|
||||
""" Rotation (ccw). Default 0. """
|
||||
|
||||
xy: numpy.ndarray
|
||||
xy: NDArray[numpy.int32]
|
||||
""" Position (1 pair only) """
|
||||
|
||||
string: bytes
|
||||
""" Text content """
|
||||
|
||||
properties: Dict[int, bytes]
|
||||
properties: dict[int, bytes]
|
||||
""" Properties for the element. """
|
||||
|
||||
@classmethod
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
File-level read/write functionality.
|
||||
"""
|
||||
from typing import List, Dict, Tuple, Optional, IO, TypeVar, Type, MutableMapping
|
||||
from typing import IO, TypeVar, Type, MutableMapping
|
||||
import io
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
@ -80,7 +80,7 @@ class FileHeader:
|
||||
return b
|
||||
|
||||
|
||||
def scan_structs(stream: IO[bytes]) -> Dict[bytes, int]:
|
||||
def scan_structs(stream: IO[bytes]) -> dict[bytes, int]:
|
||||
"""
|
||||
Scan through a GDS file, building a table of
|
||||
{b'structure_name': byte_offset}.
|
||||
@ -107,7 +107,7 @@ def scan_structs(stream: IO[bytes]) -> Dict[bytes, int]:
|
||||
return positions
|
||||
|
||||
|
||||
def try_read_struct(stream: IO[bytes]) -> Optional[Tuple[bytes, List[Element]]]:
|
||||
def try_read_struct(stream: IO[bytes]) -> tuple[bytes, list[Element]] | None:
|
||||
"""
|
||||
Skip to the next structure and attempt to read it.
|
||||
|
||||
@ -125,9 +125,10 @@ def try_read_struct(stream: IO[bytes]) -> Optional[Tuple[bytes, List[Element]]]:
|
||||
return name, elements
|
||||
|
||||
|
||||
def write_struct(stream: IO[bytes],
|
||||
def write_struct(
|
||||
stream: IO[bytes],
|
||||
name: bytes,
|
||||
elements: List[Element],
|
||||
elements: list[Element],
|
||||
cre_time: datetime = datetime(1900, 1, 1),
|
||||
mod_time: datetime = datetime(1900, 1, 1),
|
||||
) -> int:
|
||||
@ -150,7 +151,7 @@ def write_struct(stream: IO[bytes],
|
||||
return b
|
||||
|
||||
|
||||
def read_elements(stream: IO[bytes]) -> List[Element]:
|
||||
def read_elements(stream: IO[bytes]) -> list[Element]:
|
||||
"""
|
||||
Read elements from the stream until an ENDSTR
|
||||
record is encountered. The ENDSTR record is also
|
||||
@ -162,7 +163,7 @@ def read_elements(stream: IO[bytes]) -> List[Element]:
|
||||
Returns:
|
||||
List of element objects.
|
||||
"""
|
||||
data: List[Element] = []
|
||||
data: list[Element] = []
|
||||
size, tag = Record.read_header(stream)
|
||||
while tag != ENDSTR.tag:
|
||||
if tag == BOUNDARY.tag:
|
||||
@ -186,7 +187,7 @@ def read_elements(stream: IO[bytes]) -> List[Element]:
|
||||
return data
|
||||
|
||||
|
||||
def scan_hierarchy(stream: IO[bytes]) -> Dict[bytes, Dict[bytes, int]]:
|
||||
def scan_hierarchy(stream: IO[bytes]) -> dict[bytes, dict[bytes, int]]:
|
||||
"""
|
||||
Scan through a GDS file, building a table of instance counts
|
||||
`{b'structure_name': {b'ref_name': count}}`.
|
||||
|
@ -1,14 +1,14 @@
|
||||
"""
|
||||
Generic record-level read/write functionality.
|
||||
"""
|
||||
from typing import Optional, Sequence, IO
|
||||
from typing import TypeVar, List, Tuple, ClassVar, Type
|
||||
from typing import Sequence, IO, TypeVar, ClassVar, Type
|
||||
import struct
|
||||
import io
|
||||
from datetime import datetime
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy # type: ignore
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from .basic import KlamathError
|
||||
from .basic import parse_int2, parse_int4, parse_real8, parse_datetime, parse_bitarray
|
||||
@ -27,7 +27,7 @@ def write_record_header(stream: IO[bytes], data_size: int, tag: int) -> int:
|
||||
return stream.write(header)
|
||||
|
||||
|
||||
def read_record_header(stream: IO[bytes]) -> Tuple[int, int]:
|
||||
def read_record_header(stream: IO[bytes]) -> tuple[int, int]:
|
||||
"""
|
||||
Read a record's header (size and tag).
|
||||
Args:
|
||||
@ -58,7 +58,7 @@ R = TypeVar('R', bound='Record')
|
||||
|
||||
class Record(metaclass=ABCMeta):
|
||||
tag: ClassVar[int] = -1
|
||||
expected_size: ClassVar[Optional[int]] = None
|
||||
expected_size: ClassVar[int | None] = None
|
||||
|
||||
@classmethod
|
||||
def check_size(cls, size: int):
|
||||
@ -80,7 +80,7 @@ class Record(metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def read_header(stream: IO[bytes]) -> Tuple[int, int]:
|
||||
def read_header(stream: IO[bytes]) -> tuple[int, int]:
|
||||
return read_record_header(stream)
|
||||
|
||||
@classmethod
|
||||
@ -133,7 +133,7 @@ class Record(metaclass=ABCMeta):
|
||||
|
||||
|
||||
class NoDataRecord(Record):
|
||||
expected_size: ClassVar[Optional[int]] = 0
|
||||
expected_size: ClassVar[int | None] = 0
|
||||
|
||||
@classmethod
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> None:
|
||||
@ -147,7 +147,7 @@ class NoDataRecord(Record):
|
||||
|
||||
|
||||
class BitArrayRecord(Record):
|
||||
expected_size: ClassVar[Optional[int]] = 2
|
||||
expected_size: ClassVar[int | None] = 2
|
||||
|
||||
@classmethod
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> int:
|
||||
@ -160,7 +160,7 @@ class BitArrayRecord(Record):
|
||||
|
||||
class Int2Record(Record):
|
||||
@classmethod
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> numpy.ndarray:
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.int16]:
|
||||
return parse_int2(read(stream, size))
|
||||
|
||||
@classmethod
|
||||
@ -170,7 +170,7 @@ class Int2Record(Record):
|
||||
|
||||
class Int4Record(Record):
|
||||
@classmethod
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> numpy.ndarray:
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.int32]:
|
||||
return parse_int4(read(stream, size))
|
||||
|
||||
@classmethod
|
||||
@ -180,7 +180,7 @@ class Int4Record(Record):
|
||||
|
||||
class Real8Record(Record):
|
||||
@classmethod
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> numpy.ndarray:
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.float64]:
|
||||
return parse_real8(read(stream, size))
|
||||
|
||||
@classmethod
|
||||
@ -200,7 +200,7 @@ class ASCIIRecord(Record):
|
||||
|
||||
class DateTimeRecord(Record):
|
||||
@classmethod
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> List[datetime]:
|
||||
def read_data(cls, stream: IO[bytes], size: int) -> list[datetime]:
|
||||
return parse_datetime(read(stream, size))
|
||||
|
||||
@classmethod
|
||||
|
@ -1,8 +1,8 @@
|
||||
import struct
|
||||
|
||||
import pytest # type: ignore
|
||||
import numpy # type: ignore
|
||||
from numpy.testing import assert_array_equal # type: ignore
|
||||
import numpy
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from .basic import parse_bitarray, parse_int2, parse_int4, parse_real8, parse_ascii
|
||||
from .basic import pack_bitarray, pack_int2, pack_int4, pack_real8, pack_ascii
|
||||
|
Loading…
Reference in New Issue
Block a user