General overhaul of gdsii read/write functions

- read() and write() now take streams instead of filenames
- readfile() and writefile() were added to handle filenames and can
detect and handle '.gz' suffixed/compressed files.
- write_dose2dtype() and and read_dtype2dose() were removed in favor of
read(use_dtype_as_dose=True) and dose2dtype()
This commit is contained in:
Jan Petykiewicz 2019-04-20 15:29:56 -07:00
parent d6d26b4e46
commit 485a7bc29d

View File

@ -8,11 +8,14 @@ import gdsii.elements
from typing import List, Any, Dict, Tuple
import re
import io
import copy
import numpy
import base64
import struct
import logging
import pathlib
import gzip
from .utils import mangle_name, make_dose_table
from .. import Pattern, SubPattern, GridRepetition, PatternError, Label, Shape
@ -35,7 +38,7 @@ path_cap_map = {0: Path.Cap.Flush,
def write(patterns: Pattern or List[Pattern],
filename: str,
stream: io.BufferedIOBase,
meters_per_unit: float,
logical_units_per_unit: float = 1,
library_name: str = 'masque-gdsii-write',
@ -58,8 +61,8 @@ def write(patterns: Pattern or List[Pattern],
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
prior to calling this function.
:param patterns: A Pattern or list of patterns to write to file. Modified by this function.
:param filename: Filename to write to.
:param patterns: A Pattern or list of patterns to write to file.
:param file: Filename or stream object to write to.
:param meters_per_unit: Written into the GDSII file, meters per (database) length unit.
All distances are assumed to be an integer multiple of this unit, and are stored as such.
:param logical_units_per_unit: Written into the GDSII file. Allows the GDSII to specify a
@ -99,50 +102,29 @@ def write(patterns: Pattern or List[Pattern],
structure += _labels_to_texts(pat.labels)
structure += _subpatterns_to_refs(pat.subpatterns)
with open(filename, mode='wb') as stream:
lib.save(stream)
return
def write_dose2dtype(patterns: Pattern or List[Pattern],
filename: str,
meters_per_unit: float,
def writefile(patterns: List[Pattern] or Pattern,
filename: str or pathlib.Path,
*args,
**kwargs,
) -> List[float]:
):
"""
Write a Pattern or list of patterns to a GDSII file, by first calling
.polygonize() to change the shapes into polygons, and then writing patterns
as GDSII structures, polygons as boundary elements, and subpatterns as structure
references (sref).
Wrapper for gdsii.write() that takes a filename or path instead of a stream.
For each shape,
layer is chosen to be equal to shape.layer if it is an int,
or shape.layer[0] if it is a tuple
datatype is chosen arbitrarily, based on calcualted dose for each shape.
Shapes with equal calcualted dose will have the same datatype.
A list of doses is retured, providing a mapping between datatype
(list index) and dose (list entry).
Note that this function modifies the Pattern(s).
It is often a good idea to run pattern.subpatternize() prior to calling this function,
especially if calling .polygonize() will result in very many vertices.
If you want pattern polygonized with non-default arguments, just call pattern.polygonize()
prior to calling this function.
:param patterns: A Pattern or list of patterns to write to file. Modified by this function.
:param filename: Filename to write to.
:param meters_per_unit: Written into the GDSII file, meters per (database) length unit.
All distances are assumed to be an integer multiple of this unit, and are stored as such.
:param args: passed to masque.file.gdsii.write().
:param kwargs: passed to masque.file.gdsii.write().
:returns: A list of doses, providing a mapping between datatype (int, list index)
and dose (float, list entry).
Will automatically compress the file if it has a .gz suffix.
"""
patterns, dose_vals = dose2dtype(patterns)
write(patterns, filename, meters_per_unit, *args, **kwargs)
return dose_vals
path = pathlib.Path(filename)
if path.suffix == 'gz':
open_func = gzip.open
else:
open_func = open
with open_func(path, mode='wb') as stream:
results = write(patterns, stream, *args, **kwargs)
return results
def dose2dtype(patterns: Pattern or List[Pattern],
@ -219,14 +201,27 @@ def dose2dtype(patterns: Pattern or List[Pattern],
return patterns, list(dose_vals)
def read_dtype2dose(filename: str) -> (List[Pattern], Dict[str, Any]):
def readfile(filename: str or pathlib.Path,
*args,
**kwargs,
) -> (Dict[str, Pattern], Dict[str, Any]):
"""
Alias for read(filename, use_dtype_as_dose=True)
Wrapper for gdsii.read() that takes a filename or path instead of a stream.
Tries to autodetermine file type based on suffixes
"""
return read(filename, use_dtype_as_dose=True)
path = pathlib.Path(filename)
if path.suffix == 'gz':
open_func = gzip.open
else:
open_func = open
with open_func(path, mode='rb') as stream:
results = read(stream, *args, **kwargs)
return results
def read(filename: str,
def read(stream: io.BufferedIOBase,
use_dtype_as_dose: bool = False,
clean_vertices: bool = True,
) -> (Dict[str, Pattern], Dict[str, Any]):
@ -251,7 +246,6 @@ def read(filename: str,
:return: Tuple: (Dict of pattern_name:Patterns generated from GDSII structures, Dict of GDSII library info)
"""
with open(filename, mode='rb') as stream:
lib = gdsii.library.Library.load(stream)
library_info = {'name': lib.name.decode('ASCII'),
@ -532,3 +526,4 @@ def _disambiguate_pattern_names(patterns):
pat.name = encoded_name
used_names.append(suffixed_name)