python reportlab barcode code128 size - python-3.x

i want to create a label (57*32mm) with a code128 barcode.
Its all fine but the barcode is to small =(
How can i make this barcode bigger ?
from reportlab.graphics.barcode import code128
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
c = canvas.Canvas("test.pdf")
c.setPageSize((57*mm,32*mm))
barcode = code128.Code128("123456789")
barcode.drawOn(c, 2*mm, 20*mm)
c.showPage()
c.save()

By looking up the source code for reportlab on GitHub, I found out that the barcode object has a width property.
By simply using
canvas.saveState()
canvas.translate(2*cm, 3*cm) # bottom left corner of the barcode
canvas.scale(15*cm / barcode.width, 2*cm / barcode.height) # resize (15 cm and 2 cm)
barcode.drawOn(canvas, 0, 0)
canvas.restoreState()
You can obtain the obtain of the exact size you desire.

The total width of the barcode is bar_width * total_char_widths + quiet space
So.. the correct barWidth can be determined by
from reportlab.graphics.barcode import code128
final_size = 100 # arbitrary
# setting barWidth to 1
initial_width = .1
barcode128 = code128.Code128(barcode_value, humanReadable=True, barWidth=initial_width,
barHeight=1)
# creates the barcode, computes the total size
barcode128._calculate()
# the quiet space before and after the barcode
quiet = barcode128.lquiet + barcode128.rquiet
# total_wid = barWidth*charWid + quiet_space
# char_wid = (total_width - quiet) / bar_width
char_width = (barcode128._width - quiet) / barcode128.barWidth
# now that we have the char width we can calculate the bar width
bar_width = (final_size - quiet) / char_width
# set the new bar width
barcode128.barWidth = bar_width
# re-calculate
barcode128._calculate()
# draw the barcode on the canvas
wid, hgt = barcode128._width, barcode128._height
x_pos = y_pos = final_size # arbitrary
barcode128.drawOn(your_canvas, x_pos, y_pos)

You can set the barcode size by barHeight and barWidth:
barcode = code128.Code128("123456789",barHeight=.9*inch,barWidth = 1.2)

I finaly found the answer to that with other bits of non-working snipet of code
from reportlab.graphics.barcode import code128
from reportlab.lib.units import mm, inch, cm, pica
from reportlab.pdfgen import canvas
code = "asdasda" #remove this if in function
c = canvas.Canvas(f"{code}.pdf")
page_width = 550 # page width
# add specific unit here as x= num*unit
# pica,mm and no unit works, I don't know why the 2 other don't
page_height = 200 # page height
# add specific unit here as x= num*unit
# pica,mm and no unit works, I don't know why the 2 other don't
margin_y = 10 # top/bottom margin
# add specific unit here as x= num*unit
# pica,mm and no unit works, I don't know why the 2 other don't
bar_height = page_height - (margin_y * 2) # barcode line height
bar_width = page_width / (11 * len(str(code)) + 55) # barcode individual width has the formula
# page width / (11*string_length) + 55 ##(I also saw +35 but in my test it was not working)
c.setPageSize((page_width, page_height)) # set page to said mesure
humanReadable = True # with or without text
barcode = code128.Code128(code,
barHeight=bar_height,
barWidth=bar_width,
humanReadable=humanReadable)
drawon_x = 0 # x value for drawing already has a margin (not like Y) bar with formula account for that
if humanReadable:
drawon_y = page_height - margin_y - bar_height # if text reduce bar height to hace the correct value
else:
drawon_y = page_height - bar_height # set draw point to the top of the page - the height of the drawn barcode
barcode.drawOn(c, drawon_x, drawon_y) # do the drawing
c.save() # save pdf
if you want multiple separate barcode pas a list of code like this
def createSinglePDFBarcode(code):
#code from above here
if __name__ == "__main__":
import random
import string
num = 5
# Generate {num} random numbers between 10 and 30
# printing uppercase
letters = string.ascii_uppercase
randomSList =[] #list of code you want as barcode
for x in range(num):
randomSList.append(''.join(random.choice(letters) for i in range(10)))
for x in randomSList: # for each code make a barcode
createSinglePDFBarcode(x)

Related

Rasterising only selected area of a CAD DXF file

Given a DXF file (2D CAD drawing), is it somehow possible to rasterise only part of it? Preferably in Python's ezdxf. By the part of it, I mean the selected rectangular area, not a single layer.
Background: I'm struggling to rasterise quite a big DXF file with decent DPI in a reasonable time, so I thought that maybe there's a way to speed up the process by parallelising rasterising different parts of the drawing. I'm using ezdxf with matplotlib backend.
This solution renders the DXF file in 4 tiles including filtering the DXF entities outside the rendering area. But the calculation of the bounding boxes is also costly and the entities in the overlapping area are rendered multiple times, this means this solution takes longer as a single-pass rendering. But it shows the concept. The images fit perfect together the space is left to show that this are 4 images:
import matplotlib.pyplot as plt
import random
import ezdxf
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
from ezdxf import bbox
from ezdxf.math import BoundingBox2d
COLORS = list(range(1, 7))
DPI = 300
WIDTH = 400
HEIGHT = 200
LEFT = 0
BOTTOM = 0
doc = ezdxf.new()
msp = doc.modelspace()
def random_points(count):
for _ in range(count):
yield WIDTH * random.random(), HEIGHT * random.random()
for s, e in zip(random_points(100), random_points(100)):
msp.add_line(s, e, dxfattribs={"color": random.choice(COLORS)})
# detecting the drawing extents by ezdxf can take along time for big files!
cache = bbox.Cache() # reuse bounding boxes for entity filtering
rect = bbox.extents(msp, cache=cache)
WIDTH = rect.size.x
HEIGHT = rect.size.y
LEFT = rect.extmin.x
BOTTOM = rect.extmin.y
VIEWPORT_X = [LEFT, LEFT + WIDTH / 2, LEFT, LEFT + WIDTH / 2]
VIEWPORT_Y = [BOTTOM, BOTTOM, BOTTOM + HEIGHT / 2, BOTTOM + HEIGHT / 2]
ctx = RenderContext(doc)
for quarter in [0, 1, 2, 3]:
# setup drawing add-on:
fig = plt.figure(dpi=300)
ax = fig.add_axes([0, 0, 1, 1])
out = MatplotlibBackend(ax)
# calculate and set render borders:
left = VIEWPORT_X[quarter]
bottom = VIEWPORT_Y[quarter]
ax.set_xlim(left, left + WIDTH / 2)
ax.set_ylim(bottom, bottom + HEIGHT / 2)
# set entities outside of the rendering area invisible:
# Bounding box calculation can be very costly, especially for deep nested
# block references! If you did the extents calculation and reuse the cache
# you already have paid the price:
render_area = BoundingBox2d(
[(left, bottom), (left + WIDTH / 2, bottom + HEIGHT / 2)])
for entity in msp:
entity_bbox = bbox.extents([entity], cache=cache)
if render_area.intersect(entity_bbox):
entity.dxf.invisible = 0
else:
entity.dxf.invisible = 1
# finalizing invokes auto-scaling!
Frontend(ctx, out).draw_layout(msp, finalize=False)
# set output size in inches
# width = 6 in x 300 dpi = 1800 px
# height = 3 in x 300 dpi = 900 px
fig.set_size_inches(6, 3, forward=True)
filename = f"lines{quarter}.png"
print(f'saving to "{filename}"')
fig.savefig(filename, dpi=300)
plt.close(fig)
The draw_layout() method has an argument filter_func to specify a function which accepts a DXF entity as argument and returns True or False to render or ignore this entity. This would be an alternative to filter the entities outside of the rendering area without altering the DXF content.
UPDATE: a refined example can be found at github

Count total number of pixels for each color

I am trying to calculate the number of pixels corresponding to each of the color for the segmented image
Following is the code that I have written.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
from ast import literal_eval
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
exit("This script requires the PIL module. Install with pip install Pillow")
try:
import webcolors
except ImportError:
exit("This script uses webcolors for displaying named colors. Install with pip install webcolors")
def count_pixels(filename):
"""
Returns a count of pixels per a unique color
Args:
filename (str): the image to count the number of pixels of
Returns:
a key-value pairing of the rgb color value and the number of times the color was present in the image
"""
color_count = {}
with Image.open(filename) as image:
width, height = image.size
rgb_image = image.convert('RGB')
# iterate through each pixel in the image and keep a count per unique color
for x in range(width):
for y in range(height):
rgb = rgb_image.getpixel((x, y))
if rgb in color_count:
color_count[rgb] += 1
else:
color_count[rgb] = 1
return color_count
def create_legend_image(filename, colors, title, ignore_color):
"""
Create an image of color swatches paired with a color name and number
Args:
filename (str): the name of the legend image file
colors (dict): a key-value pairing of the color name and count
title (str): a title for the legend image
ignore_color (tuple): do not add this color to the legend image
Returns:
None
"""
margin = 10
rect_width = 25
rect_outline_width = 2
font_size = 20
img_width = 250
img_height = len(colors) * (rect_width + rect_outline_width + margin) + (0 if title is None else font_size)
legend_img = Image.new("RGBA", (img_width, img_height), "white")
draw = ImageDraw.Draw(legend_img)
font = ImageFont.truetype("arialbd.ttf", font_size)
# draw title for legend if applicable
text_height = 0
if title is not None:
_, text_height = draw.textsize(title, font=font)
draw.text((0, 0), title, font=font, fill="black")
color_index = 1
for color, count in colors.items():
if color == ignore_color:
continue
try:
# convert RGB color to a human readable color if applicable
color_name = webcolors.rgb_to_name(color)
except ValueError:
color_name = color
# draw square for color legend
y0 = rect_width * (color_index - 1) + margin * color_index + text_height
y1 = rect_width * color_index + margin * color_index + text_height
draw.rectangle([(0, y0), (rect_width, y1)], fill=color, outline="black", width=2)
# draw color name next and pixel count for legend colors
draw.text((rect_width + margin, y0), "{}: {}".format(color_name, count), font=font, fill="black")
color_index += 1
legend_img.save(filename, mode="w")
def main():
parser = argparse.ArgumentParser(description='Calculates the sum of pixels per a color')
parser.add_argument('image', nargs='?', default='.', help='The image to sum the pixels per a color of')
parser.add_argument('-i', '--ignore-color', help='Skip counting pixels of this color')
parser.add_argument('-t', '--title', help='Title for the image legend')
parser.add_argument('-l', '--legend-image', help='Generate an image with color swatches paired with the pixel count')
args = parser.parse_args()
ignore_color = literal_eval(args.ignore_color) if args.ignore_color is not None else None
color_count = count_pixels(args.image)
if args.legend_image is not None:
create_legend_image(args.legend_image, color_count, args.title, ignore_color)
# outputs pixel color count to console
print('Pixel Count per Unique Color:')
print('-' * 30)
color_index = 1
for color, count in color_count.items():
if color == ignore_color:
continue
try:
# convert RGB color to a human readable color if applicable
color_name = webcolors.rgb_to_name(color)
except ValueError:
color_name = color
print('{}.) {}: {}'.format(color_index, color_name, count))
color_index += 1
# Display the total number of pixels not ignored
print('-' * 30)
print('\t{} pixels'.format(sum(color_count[color] for color in color_count if color != ignore_color)))
if __name__ == '__main__':
main()
When I test my code, I am not getting the accurate result which indicates total number of pixels for each of the unique colors represented in the picture i.e. (Red, Yellow and Black), Can you suggest me some alternative approach to calculate the total number of pixels in a segmented image
Solution using OpenCV for import.
# Load image
img = cv2.imread("UMN9c.png")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Flatten image into rows = number of pixels, columns = 3 (RGB)
img = img.reshape(-1, img.shape[2])
# Count number of unique colors
unique_colors, unique_count = np.unique(img, axis=0, return_counts=True)
# Print info
print(unique_colors)
print(unique_count)
print("Total number of unique colors: ", unique_colors.shape[0])
This is the simplest way to get the colours and their corresponding counts:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
# Open image and ensure RGB
im = Image.open('UMN9c.png').convert('RGB')
# Make into Numpy array
na = np.array(im)
# Get colours and corresponding counts
colours, counts = np.unique(na.reshape(-1,3), axis=0, return_counts=1)
print(colours, counts)
Sample Outputs
[[ 37 36 36]
[226 44 11]
[228 239 80]] [255169 1059 5916]
If you don't want to write any Python code, you can just use ImageMagick in the Terminal
magick -verbose UMN9c.png -format %c histogram:info:-
Output
UMN9c.png PNG 512x512 512x512+0+0 8-bit sRGB 3572B 0.020u 0:00.008
255169: (37,36,36,255) #252424FF srgba(37,36,36,1)
1059: (226,44,11,255) #E22C0BFF srgba(226,44,11,1)
5916: (228,239,80,255) #E4EF50FF srgba(228,239,80,1)
UMN9c.png=>info:- PNG 512x512 512x512+0+0 8-bit sRGB 3572B 0.320u 0:00.166
I'm not sure if outright speed is an issue for you. If so, there is a significantly faster method using np.dot() at the end of this answer.

Python OpenCV template matching gives poor results

I am trying to build a tool that would recognize poker cards from an online site.
I thought the task would be trivial.
Harvest all possible cards, paste them in one image, get an "unknown" card, run template matching on all_cards_image, harvest the points where it is matched, compare it to a dictionary with (x,y) = "Ah" and voila.
But template matching gives such hit and miss results, some single cards are not recognized at all, some single cards are recognized as 4 of hearts, 5 of hearts and 6 of hearts simultaneously. 8 of clubs is recognized as 8 of spades and so on. When no card is present then it matches the whole template image.
I could not find threshold that would lead to satisfactory results.
I am attaching the whole code as well as the images.
It is clear to me that I could go the route of recognizing only rank of the card, maybe on black and white image and masking the suits for color (blue, green, red, black) and then matching them afterwards. I was just hoping there is a simple template matching solution without jumping through hoops.
First image with all the cards (all_cards.jpg) in code:
Second image (random screenshot) with all other details changed to white so as not to make the image too big, named "Screenshot_to_test_card_recognition_reduced.png" in code:
import cv2
import numpy as np
import os
# defining constants for positions, card width and height in a screenshot
CARD_HEIGHT = 59
CARD_WIDTH = 90
HORIZONTAL_SPACE = 5
HORIZONTAL_OFFSET_2 = 1463
VERTICAL_OFFSET_2 = 1047
FIRST_CARD_X = 955
FIRST_CARD_Y = 370
FLOPS_STARTING_POINTS = [(FIRST_CARD_X, FIRST_CARD_Y), (FIRST_CARD_X + HORIZONTAL_OFFSET_2, FIRST_CARD_Y), \
(FIRST_CARD_X, FIRST_CARD_Y + VERTICAL_OFFSET_2), \
(FIRST_CARD_X + HORIZONTAL_OFFSET_2, FIRST_CARD_Y + VERTICAL_OFFSET_2)]
# show image function
def show_image(img, title = "Unnamed"):
cv2.imshow(title, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# INSERT FOLDER WITH FILES
saved_folder = "D:\\WinPython 32 3.4\\WinPython-32bit-3.4.4.6Qt5\\notebooks\\Named cards"
template_image = os.path.join(saved_folder, 'all_cards.jpg')
screenshot = os.path.join(saved_folder, 'Screenshot_to_test_card_recognition_reduced.png')
img_rgb = cv2.imread(screenshot)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
CORRECTION_FOR_SEARCH_WIDTH = 0 #60
CORRECTION_FOR_SEARCH_HEIGTH = 0 #20
# LOOP THROUGH ALL FIVE CARDS FOR EACH FLOP ON 4 TABLES
for point in FLOPS_STARTING_POINTS:
flop_starting_x, flop_starting_y = point
for card in range (0,5):
start_x = flop_starting_x + ((HORIZONTAL_SPACE) * card) + ((CARD_WIDTH) * card)
end_x = start_x + CARD_WIDTH - CORRECTION_FOR_SEARCH_WIDTH
start_y = flop_starting_y
end_y = start_y + CARD_HEIGHT - CORRECTION_FOR_SEARCH_HEIGTH
template = img_gray[start_y:end_y,start_x:end_x]
w, h = template.shape[::-1]
show_image(template)
all_cards_image = cv2.imread(template_image)
grey_all_cards_image = cv2.cvtColor(all_cards_image, cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(grey_all_cards_image,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.85
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(all_cards_image, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
print(loc)
show_image(all_cards_image)

Automatically resizing text with window size in tkinter calculator

I have been working on a tkinter calculator in Python 3.8, with basic buttons and an entry field. I want the text in the buttons and entry field to increase (or decrease) size with the window automatically, in proportion with the buttons themselves -- have yet to work on the entry field font sizes -- and, despite trying for a while, have failed. At certain sizes, the font collapses to the minimum size (see code below) or starts phasing rapidly. It also collapses to minimum size when the window is moved.
# Default font size
fontsize = tkFont.Font(size=11)
def font_change(event):
# Base size
normal_width = 418
normal_height = 295
# Screen
screen_width = event.width
screen_height = event.height
# Get percentage of screen size from Base size
percentage_width = screen_width / (normal_width / 100)
percentage_height = screen_height / (normal_height / 100)
# Make a scaling factor
scale_factor = ((percentage_width + percentage_height) / 2) / 100
# Set the fontsize based on scale_factor,
# if the fontsize is less than minimum_size
# it is set to the minimum size
# font_size is the variable to store actual size
minimum_size = 8
if scale_factor > minimum_size/18:
font_size = int(18 * scale_factor)
else:
font_size = minimum_size
fontsize.configure(size = font_size)
I bind the function to an event:
root.bind("<Configure>", font_change)
Example of a button,
decimal = Button(
root,
text=".",
command=lambda: press("."),
font = fontsize,
height = 2,
width=7)
decimal.grid(row=6, column=2, sticky=NW + NE + SW + SE)
It would be appreciated if someone can help me out.
I constructed a test GUI with two Labels. root.bind( "&LT;Configure&GT;", func ) is fired each time root or any of it's children resize. Code below. When the Labels fire the configure event the font is minimised. Which fires the configure event again, even smaller etc.
import tkinter as tk
from tkinter import font
root = tk.Tk()
root.geometry( "200x100" )
fontsize = font.Font( size = 11 )
tk.Label( root, text = 'Test', font = fontsize ).grid( padx = 5, pady = 5 )
tk.Label( root, text = 'Another Test', font = fontsize ).grid( padx = 5, pady = 5 )
def font_change(event):
print( event.widget, event ) # See what is happening
# Base size
normal_width = 200
normal_height = 100
# Screen
screen_width = event.width
screen_height = event.height
print( event.widget, event )
# Get percentage of screen size from Base size
percentage_width = screen_width / (normal_width / 100)
percentage_height = screen_height / (normal_height / 100)
minimum_size = 8
# Make a scaling factor
scale_factor = ((percentage_width + percentage_height) / 2) / 100
# Set the fontsize based on scale_factor,
# if the fontsize is less than minimum_size
# it is set to the minimum size
# font_size is the variable to store actual size
if scale_factor > minimum_size/18:
font_size = int(18 * scale_factor)
else:
font_size = minimum_size
fontsize.configure( size = font_size )
root.bind( '<Configure>', font_change )
root.mainloop()
One option is to replace the print statement in the above code with
if event.widget != root:
return None # Jump out of the function if the widget firing configure isn't root
Another option would be to read the root width and height in the font_change function, not take the width and height from event.

Mapping pixel value to temperature value on a thermal image

I have a thermal image (with a color bar) from an IR camera. My goal is to get the temperature of any point by clicking on it.
I have already written a script that retrieves the RBG values of any pixel by right-clicking on it.
I figure that using the max and min temperatures of the color bar, I can map pixel values to temperature values.
Is this possible or is there a better way to approach this?
Thank you very much.
from PIL import Image
import cv2
from win32api import GetSystemMetrics
counter = 0
max_value = input('Max Temp Value: ')
min_value = input('Min Temp Value: ')
def mouse_callback(event, x, y, flags, params): # Tracks the pixel the mouse it hovering on. When right click it prints the pixel location and its RBG values.
global counter
if event == 2:
counter += 1
r, g, b = rgb_img.getpixel((x, y))
print(f'{counter}: {[x, y]} value {r} {g} {b}')
else:
print([x, y], end='\t\r', flush=True)
path_image = 'colors.jpg'
img = cv2.imread(path_image)
im = Image.open(path_image)
rgb_img = im.convert('RGB')
width = GetSystemMetrics(0)
height = GetSystemMetrics(1)
scale_width = width / im.size[0]
scale_height = height / im.size[1]
scale = min(scale_width, scale_height)
window_width = int((im.size[0] * scale) * 0.5)
window_height = int((im.size[1] * scale) * 0.5)
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.resizeWindow('image', window_width, window_height)
cv2.setMouseCallback('image', mouse_callback)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Resources