Fix more bugs, refined round-trip tests pass now

This commit is contained in:
jaseg 2021-12-30 12:57:53 +01:00
parent cf4957aee4
commit e4941dd5e3
6 changed files with 32 additions and 136 deletions

119
.gitignore vendored
View file

@ -1,115 +1,4 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
gerbonara_test_failures
*.egg-info
__pycache__
.tox

View file

@ -78,6 +78,7 @@ class FileSettings:
value = value.lstrip('+-')
if self.zeros == 'leading':
value = '0'*decimal_digits + value # pad with zeros to ensure we have enough decimals
return float(sign + value[:-decimal_digits] + '.' + value[-decimal_digits:])
else: # no or trailing zero suppression

View file

@ -505,6 +505,7 @@ class GerberParser:
y = self.file_settings.parse_gerber_value(match['y'])
i = self.file_settings.parse_gerber_value(match['i'])
j = self.file_settings.parse_gerber_value(match['j'])
print(f'coord x={x} y={y} i={i} j={j}')
if not (op := match['operation']):
if self.last_operation == 'D01':

View file

@ -8,8 +8,8 @@ import numpy as np
from PIL import Image
class ImageDifference:
def __init__(self, value, ref_path, act_path):
self.value, self.ref_path, self.act_path = value, ref_path, act_path
def __init__(self, value):
self.value = value
def __float__(self):
return float(self.value)
@ -47,34 +47,32 @@ def gbr_to_svg(in_gbr, out_svg, origin=(0, 0), size=(10, 10)):
'-o', str(out_svg), str(in_gbr)]
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def gerber_difference(reference, actual):
def gerber_difference(reference, actual, diff_out=None):
with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\
tempfile.NamedTemporaryFile(suffix='.svg') as ref_svg:
gbr_to_svg(reference, ref_svg.name)
gbr_to_svg(actual, act_svg.name)
diff = svg_difference(ref_svg.name, act_svg.name)
diff.ref_path, diff.act_path = reference, actual
return diff
return svg_difference(ref_svg.name, act_svg.name, diff_out=diff_out)
def svg_difference(reference, actual):
def svg_difference(reference, actual, diff_out=None):
with tempfile.NamedTemporaryFile(suffix='.png') as ref_png,\
tempfile.NamedTemporaryFile(suffix='.png') as act_png:
svg_to_png(reference, ref_png.name)
svg_to_png(actual, act_png.name)
diff = image_difference(ref_png.name, act_png.name)
diff.ref_path, diff.act_path = reference, actual
return diff
return image_difference(ref_png.name, act_png.name, diff_out=diff_out)
def image_difference(reference, actual):
def image_difference(reference, actual, diff_out=None):
ref = np.array(Image.open(reference)).astype(float)
out = np.array(Image.open(actual)).astype(float)
ref, out = ref.mean(axis=2), out.mean(axis=2) # convert to grayscale
delta = np.abs(out - ref).astype(float) / 255
return ImageDifference(delta.mean(), reference, actual)
if diff_out:
Image.fromarray((delta*255).astype(np.uint8), mode='L').save(diff_out)
return ImageDifference(delta.mean()), ImageDifference(delta.max())

View file

@ -12,4 +12,4 @@ Y60000D01*
X50000D01*
Y50000Y50000D01*
G37*
M02*
M02*

View file

@ -25,10 +25,11 @@ def clear_failure_dir(request):
reference_path = lambda reference: Path(__file__).parent / 'resources' / reference
@pytest.fixture
def tmp_gbr(request):
with tempfile.NamedTemporaryFile(suffix='.gbr') as tmp_out_gbr:
def temp_files(request):
with tempfile.NamedTemporaryFile(suffix='.gbr') as tmp_out_gbr,\
tempfile.NamedTemporaryFile(suffix='.png') as tmp_out_png:
yield Path(tmp_out_gbr.name)
yield Path(tmp_out_gbr.name), Path(tmp_out_png.name)
if request.node.rep_call.failed:
module, _, test_name = request.node.nodeid.rpartition('::')
@ -36,9 +37,12 @@ def tmp_gbr(request):
test_name, _, _ext = test_name.partition('.')
test_name = re.sub(r'[^\w\d]', '_', test_name)
fail_dir.mkdir(exist_ok=True)
perm_path = fail_dir / f'failure_{test_name}.gbr'
shutil.copy(tmp_out_gbr.name, perm_path)
print(f'Failing output saved to {perm_path}')
perm_path_gbr = fail_dir / f'failure_{test_name}.gbr'
perm_path_png = fail_dir / f'failure_{test_name}.png'
shutil.copy(tmp_out_gbr.name, perm_path_gbr)
shutil.copy(tmp_out_png.name, perm_path_png)
print(f'Failing output saved to {perm_path_gbr}')
print(f'Difference image saved to {perm_path_png}')
print(f'Reference file is {reference_path(request.node.funcargs["reference"])}')
@pytest.mark.filterwarnings('ignore:Deprecated.*statement found.*:DeprecationWarning')
@ -88,8 +92,11 @@ top_copper.GTL
top_mask.GTS
top_silk.GTO
'''.splitlines() if l ])
def test_round_trip(tmp_gbr, reference):
def test_round_trip(temp_files, reference):
tmp_gbr, tmp_png = temp_files
ref = reference_path(reference)
GerberFile.open(ref).save(tmp_gbr)
assert gerber_difference(ref, tmp_gbr) < 1e-5
mean, max = gerber_difference(ref, tmp_gbr, diff_out=tmp_png)
assert mean < 1e-6
assert max < 0.1