aboutsummaryrefslogtreecommitdiff
path: root/gerbimg.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerbimg.py')
-rwxr-xr-xgerbimg.py185
1 files changed, 185 insertions, 0 deletions
diff --git a/gerbimg.py b/gerbimg.py
new file mode 100755
index 0000000..14b6cb4
--- /dev/null
+++ b/gerbimg.py
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+
+import subprocess
+import zipfile
+import tempfile
+import os.path as path
+import os
+import sys
+import shutil
+import math
+
+import gerber
+from gerber.render import GerberCairoContext
+import numpy as np
+import cv2
+
+def paste_image(target_gerber:str, outline_gerber:str, source_img:np.ndarray, extend_overlay_r_mil:float=12, extend_picture_r_mil:float=2):
+ outline = gerber.loads(outline_gerber)
+ (minx, maxx), (miny, maxy) = outline.bounds
+ grbw, grbh = maxx - minx, maxy - miny
+
+ imgh, imgw = source_img.shape
+ scale = math.ceil(max(imgw/grbw, imgh/grbh)) # scale is in dpi
+
+ target = gerber.loads(target_gerber)
+ (tminx, tmaxx), (tminy, tmaxy) = target.bounds
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ img_file = path.join(tmpdir, 'target.png')
+
+ fg, bg = gerber.render.RenderSettings((1, 1, 1)), gerber.render.RenderSettings((0, 0, 0))
+ ctx = GerberCairoContext(scale=scale)
+ ctx.render_layer(target, settings=fg, bgsettings=bg)
+ ctx.dump(img_file)
+
+ original_img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)
+ r = 1+2*max(1, int(extend_overlay_r_mil/1000 * scale))
+ target_img = cv2.blur(original_img, (r, r))
+ _, target_img = cv2.threshold(target_img, 255//(1+r), 255, cv2.THRESH_BINARY)
+
+ qr = 1+2*max(1, int(extend_picture_r_mil/1000 * scale))
+ # source_img = cv2.blur(source_img, (r, r))
+ # cv2.imwrite('/tmp/03blurred.png', source_img)
+ source_img = source_img[::-1]
+ _, source_img = cv2.threshold(source_img, 127, 255, cv2.THRESH_BINARY)
+ cv2.imwrite('/tmp/06thresh.png', source_img)
+ tgth, tgtw = target_img.shape
+ padded_img = np.zeros(shape=(max(imgh, tgth), max(imgw, tgtw)), dtype=source_img.dtype)
+ padded_img[(tgth-imgh)//2:tgth-((tgth-imgh+1)//2), (tgtw-imgw)//2:tgtw-((tgtw-imgw+1)//2)] = source_img
+
+ cv2.imwrite('/tmp/10padded.png', padded_img)
+ cv2.imwrite('/tmp/20target.png', target_img)
+ out_img = (np.multiply((padded_img/255.0), (target_img/255.0) * -1 + 1) * 255).astype(np.uint8)
+
+ cv2.imwrite('/tmp/30multiplied.png', out_img)
+ cv2.imwrite('/tmp/40vis.png', out_img + original_img)
+
+ plot_contours(out_img, target, offx=(tminx, tminy), scale=scale)
+
+ from gerber.render import rs274x_backend
+ ctx = rs274x_backend.Rs274xContext(target.settings)
+ target.render(ctx)
+ return ctx.dump().getvalue()
+
+
+def plot_contours(img:np.ndarray, layer:gerber.rs274x.GerberFile, offx:tuple, scale:float, debug=lambda *args:None):
+ imgh, imgw = img.shape
+
+ # Extract contours
+ img_cont_out, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_TC89_KCOS)
+
+ aperture = list(layer.apertures)[0]
+
+ # XXX
+ layer.primitives.clear()
+
+ from gerber.primitives import Line, Region
+ debug('offx', offx, 'scale', scale)
+
+ xbias, ybias = offx
+ def map(coord):
+ x, y = coord
+ # FIXME sometimes only ybias is needed
+ return (x/scale, y/scale + ybias)
+ def contour_lines(c):
+ return [ Line(map(start), map(end), aperture, level_polarity='dark', units=layer.settings.units)
+ for start, end in zip(c, np.vstack((c[1:], c[:1]))) ]
+
+ done = []
+ process_stack = [-1]
+ next_process_stack = []
+ is_dark = True
+ while len(done) != len(contours):
+ for i, (_1, _2, _3, parent) in enumerate(hierarchy[0]):
+ if parent in process_stack:
+ contour = contours[i]
+ polarity = 'dark' if is_dark else 'clear'
+ debug('rendering {} with parent {} as {} with {} vertices'.format(i, parent, polarity, len(contour)))
+ debug('process_stack is', process_stack)
+ debug()
+ layer.primitives.append(Region(contour_lines(contour[:,0]), level_polarity=polarity, units=layer.settings.units))
+ next_process_stack.append(i)
+ done.append(i)
+ debug('skipping to next level')
+ process_stack, next_process_stack = next_process_stack, []
+ is_dark = not is_dark
+ debug('done', done)
+
+# Utility foo
+# ===========
+
+def find_gerber_in_dir(dir_path, file_or_ext):
+ lname = path.join(dir_path, file_or_ext)
+ if path.isfile(lname):
+ with open(lname, 'r') as f:
+ return lname, f.read()
+
+ contents = os.listdir(dir_path)
+ for entry in contents:
+ if entry.lower().endswith(file_or_ext.lower()):
+ lname = path.join(dir_path, entry)
+ if not path.isfile(lname):
+ continue
+ with open(lname, 'r') as f:
+ return lname, f.read()
+
+ raise ValueError('Cannot find file or suffix "{}" in dir {}'.format(file_or_ext, dir_path))
+
+def find_gerber_in_zip(zip_path, file_or_ext):
+ with zipfile.ZeipFile(zip_path, 'r') as lezip:
+ nlist = [ item.filename for item in zipin.infolist() ]
+ if file_or_ext in nlist:
+ return file_or_ext, lezip.read(file_or_ext)
+
+ for n in nlist:
+ if n.lower().endswith(file_or_ext.lower()):
+ return n, lezip.read(n)
+
+ raise ValueError('Cannot find file or suffix "{}" in zip {}'.format(file_or_ext, dir_path))
+
+def replace_file_in_zip(zip_path, filename, contents):
+ with tempfile.TemporaryDirectory() as tmpdir:
+ tempname = path.join(tmpdir, 'out.zip')
+ with zipfile.ZipFile(zip_path, 'r') as zipin, zipfile.ZipFile(tempname, 'w') as zipout:
+ for item in zipin.infolist():
+ if item.filename != filename:
+ zipout.writestr(item, zipin.read(item.filename))
+ zipout.writestr(filename, contents)
+ shutil.move(tempname, zip_path)
+
+def paste_image_file(zip_or_dir, target, outline, source_img):
+ if path.isdir(zip_or_dir):
+ tname, target = find_gerber_in_dir(zip_or_dir, target)
+ _, outline = find_gerber_in_dir(zip_or_dir, outline)
+
+ out = paste_image(target, outline, source_img)
+
+ # XXX
+ with open('/tmp/out.GTO', 'w') as f:
+# with open(tname, 'w') as f:
+ f.write(out)
+ elif zipfile.is_zipfile(zip_or_dir):
+ tname, target = find_gerber_in_zip(zip_or_dir, target)
+ _, outline = find_gerber_in_zip(zip_or_dir, outline)
+
+ out = paste_image(target, outline, source_img)
+ replace_file_in_zip(zip_or_dir, tname, out)
+ else:
+ raise ValueError('{} does not look like either a folder or a zip file')
+
+# Command line interface
+# ======================
+
+if __name__ == '__main__':
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-t', '--target', default='.GTO', help='Target layer. Filename or extension in target folder/zip')
+ parser.add_argument('-o', '--outline', default='.GKO', help='Target outline layer. Filename or extension in target folder/zip')
+ parser.add_argument('zip_or_dir', default='.', nargs='?', help='Optional folder or zip with target files')
+ parser.add_argument('source', help='Source image')
+ args = parser.parse_args()
+
+ source_img = cv2.imread(args.source, cv2.IMREAD_GRAYSCALE)
+ paste_image_file(args.zip_or_dir, args.target, args.outline, source_img)
+