I am currently converting a dxf drawing that I made, to a pdf drawing using the function described here: Python converting DXF files to PDF or PNG or JPEG. (I am also putting the code below)
The problem is, that when I convert to the pdf, the code automatically scales the drawing to make it fit to a certain size. Now I need to either turn this off, or have a way of knowing what the scaling factor that it used was.
The complete code is as follows:
import matplotlib.pyplot as plt
import ezdxf
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
# import wx
import glob
import re
class DXF2IMG(object):
default_img_format = '.png'
default_img_res = 300
def convert_dxf2img(self, names, img_format=default_img_format, img_res=default_img_res):
for name in names:
doc = ezdxf.readfile(name)
msp = doc.modelspace()
# Recommended: audit & repair DXF document before rendering
auditor = doc.audit()
# The auditor.errors attribute stores severe errors,
# which *may* raise exceptions when rendering.
if len(auditor.errors) != 0:
raise exception("The DXF document is damaged and can't be converted!")
else :
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
ctx.set_current_layout(msp)
ctx.current_layout.set_colors(bg='#FFFFFF')
out = MatplotlibBackend(ax)
Frontend(ctx, out).draw_layout(msp, finalize=True)
img_name = re.findall("(\S+)\.",name) # select the image name that is the same as the dxf file name
first_param = ''.join(img_name) + img_format #concatenate list and string
fig.savefig(first_param, dpi=img_res)
if __name__ == '__main__':
first = DXF2IMG()
first.convert_dxf2img(['test.DXF'],img_format='.pdf')
From the github discussion thread: https://github.com/mozman/ezdxf/discussions/357
This can be solved in a way that isn't specific to ezdxf by carefully setting the figure size before saving. Matplotlib is quite complex when it comes to measurements. I have a solution which seems to work well but there may be slight inaccuracies since the calculations are done using floating point numbers but at the end of the day pixels are a discrete measurement so it's probably possible to be at least 1 pixel off. There are probably lots of other things like line widths which have an effect. ... you can calculate the desired figure size by specifying a desired units_to_pixels conversion factor and scaling the figure size so that the data spans the correct number of pixels. This assumes that the figure aspect ratio is already correct as my solution uses the same scale factor for width and height.
There's an extended workaround at the page I linked. Rather than copy-paste it here, I think the whole response is worth reading.
Related
UPDATE: I tried increasing size in the chess.svg.board and it somehow cleared all the rendering issues at size = 900 1800
I tried using the svglib and reportlab to make .png files from .svg, and here is how the code looks:
import sys
import chess.svg
import chess
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
board = chess.Board("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR")
drawing = chess.svg.board(board, size=350)
f = open('file.svg', 'w')
f.write(drawing)
drawing = svg2rlg("file.svg")
renderPM.drawToFile(drawing, "file.png", fmt="png")
If you try to open file.png there is a lot of missing parts of the image, which i guess are rendering issues. How can you fix this?
Sidenote: also getting a lot of 'x_order_2: colinear!' messages when running this on a discord bot, but I am not sure if this affects anything yet.
THIS!! I am having the same error with the same libraries... I didn't find a solution but just a workaround which probably won't help too much in your case, where the shapes generating the bands are not very sparse vertically.
I'll try playing with the file dimensions too, but so far this is what I got. Note that my svg consists of black shapes on a white background (hence the 255 - x in the following code)
Since the appearance of the bands is extremely random, and processing the same file several times in a row produces different results, I decided to take advantage of randomness: what I do is I export the same svg a few times into different pngs, import them all into a list and then only take those pixels that are white in all the exported images, something like:
images_files = [my_convert_function(svgfile=file, index=i) for i in range(3)]
images = [255 - imageio.imread(x) for x in images_files]
result = reduce(lambda a,b: a & b, images)
imageio.imwrite(<your filename here>, result)
[os.remove(x) for x in images_files]
where my_convert_function contains your same svg2rlg and renderPM.drawToFile, and returns the name of the png file being written. The index 'i' is to save several copies of the same png with different names.
It's some very crude code but I hope it can help other people with the same issue
The format parameter has to be in uppercase
renderPM.drawToFile(drawing, "file.png", fmt="PNG")
I am trying to create a pipeline in which I first render an image using the blender python API (I am using Blender 2.90) and then perform some image processing in python. I want to fetch the image directly from blender without first writing the rendered image to disk and then loading it again. I ran the following code within the blender GUI to do so:
import bpy
import numpy as np
import PIL.Image as Image
from skimage.util import img_as_ubyte
resolution_x = 512
resolution_y = 512
# render settings
scene = bpy.context.scene
scene.render.engine = 'BLENDER_EEVEE'
scene.render.resolution_x = resolution_x
scene.render.resolution_y = resolution_y
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = "path/to/good_image.png"
# create Viewer Layer in Compositor
scene.use_nodes = True
tree = scene.node_tree
nodes = tree.nodes
links = tree.links
for node in nodes:
nodes.remove(node)
render_layer_node = nodes.new('CompositorNodeRLayers')
viewer_node = nodes.new('CompositorNodeViewer')
links.new(viewer_node.inputs[0], render_layer_node.outputs[0])
# render scene and get pixels from Viewer Node
bpy.ops.render.render(write_still=True)
pixels = bpy.data.images['Viewer Node'].pixels
# do some processing and save
img = np.flip(img_as_ubyte(np.array(pixels[:]).reshape((resolution_y, resolution_x, 4))), axis=0)
Image.fromarray(img).save("path/to/bad_image.png")
Problem: The image I get from the Viewer Node is much darker (bad image) than the image saved in the conventional way (good image). Does anyone have an idea why this happens and how to fix it? Does blender maybe treat pixel values differently than I expect?
Some additional information:
Before conversion to uint8, the values of the alpha channel within the dark image are 1.0 (as they actually should be). Background values in the dark image are not 0.0 or negative (as one might guess from appearance), but 0.05...
What I tried:
I thought that pixels might be scaled within range -1 to 1, so I rescaled the pixels to range 0 to 1 before transforming to uint8... Did not lead to the correct image either :(
It's because the image that you get from the Viewer Node is the one "straight from compositing" before color management takes place. You can have a look at the documentation here: this image is still in the linear space.
Your good_image.png on the other hand is obtained after transformation into the "Display Space" (see diagram in the doc). Hence it was transformed into a log-space, maybe gamma-corrected, etc.
Finally, you can get an image that is close to (but slightly different though) to the good image from the viewer node by calling bpy.data.images['Viewer Node'].save_render(filepath) instead, but there is no way to directly extract the color-managed version without rendering to a file first. You can probably do it yourself by adding PyOpenColorIO to your script and applying the color management from this module.
I'm looking for a library that enables to "create pictures" (or even videos) with the following functions:
Accepting picture inputs
Resizing said inputs to fit given template / scheme
Positioning the pictures in pre-set up layers or coordinates
A rather schematic approach to look at this:
whereas the red spots are supposed to represent e.g. text, picture (or if possible video) elements.
The end goal would be to give the .py script multiple input pictures and the .py creating a finished version like mentioned above.
Solutions I tried were looking into Python PIL, but I wasn't able to find what I was looking for.
Yes, it is possible to do this with Python.
The library you are looking for is OpenCV([https://opencv.org][1]/).
Some basic OpenCV python tutorials (https://docs.opencv.org/master/d9/df8/tutorial_root.html).
1) You can use imread() function to read images from files.
2) You can use resize() function to resize the images.
3) You can create a empty master numpy array matching the size and depth(color depth) of the black rectangle in the figure you have shown, resize your image and copy the contents into the empty array starting from the position you want.
Below is a sample code which does something close to what you might need, you can modify this to suit your actual needs. (Since your requirements are not clear I have written the code like this so that it can at least guide you.)
import numpy as np
import cv2
import matplotlib.pyplot as plt
# You can store most of these values in another file and load them.
# You can modify this to set the dimensions of the background image.
BG_IMAGE_WIDTH = 100
BG_IMAGE_HEIGHT = 100
BG_IMAGE_COLOR_DEPTH = 3
# This will act as the black bounding box you have shown in your figure.
# You can also load another image instead of creating empty background image.
empty_background_image = np.zeros(
(BG_IMAGE_HEIGHT, BG_IMAGE_WIDTH, BG_IMAGE_COLOR_DEPTH),
dtype=np.int
)
# Loading an image.
# This will be copied later into one of those red boxes you have shown.
IMAGE_PATH = "./image1.jpg"
foreground_image = cv2.imread(IMAGE_PATH)
# Setting the resize target and top left position with respect to bg image.
X_POS = 4
Y_POS = 10
RESIZE_TARGET_WIDTH = 30
RESIZE_TARGET_HEIGHT = 30
# Resizing
foreground_image= cv2.resize(
src=foreground_image,
dsize=(RESIZE_TARGET_WIDTH, RESIZE_TARGET_HEIGHT),
)
# Copying this into background image
empty_background_image[
Y_POS: Y_POS + RESIZE_TARGET_HEIGHT,
X_POS: X_POS + RESIZE_TARGET_WIDTH
] = foreground_image
plt.imshow(empty_background_image)
plt.show()
I need to have a report in PDF with a lot of plots. Most of them will be created with matplotlib within a loop, but I would need also to include pandas plots and dataframes (the whole view) and seaborn plots. Right now I have explored the following solutions:
PythonTex. I have already used it for other projects, but it would consume a lot of time because you have to write \pythontexprint for each plot you want to display.
Use savefig command in every iteration of the loop and save all the plots as image for inserting all in Latex later. That would be very time consuming choice too. Other option is with that command save the plots as pdf and then merge all the pdfs. That would create an ugly report since the plots are not going to fit the whole page.
Use RStudio with reticulate for creating a Markdown report. The problem here is that I would need to learn reticulate functionality, thus spending time.
As far as I know, PyPDF does not fit my needs.
Create a jupyter notebook and then try to export it to a PDF. Once again, I do not know how to use jupyter notebook and I read that I would have to convert first to html and then to pdf.
Solutions from here: Generating Reports with Python: PDF or HTML to PDF However, the question is from three years ago and it might better options nowadays.
So my question is the following: is there any easy and quick way of getting all those plots (if it is along the code which generates them even better) in a PDF with a decent aspect?
My recommendation would be to use matplotlibs savefig to a BytesIO buffer (or save buffers to a list or similar data structure for 100). Then you can use those image buffers to insert the image into a pdf using a library like reportlab (website here and docs here). I regularly use this approach to create PowerPoint documents using python-pptx library but also verified it via PDF with reportlab. reportlab library is very powerful and a bit "low level" so there might be a little learning curve getting started but it surely meets your needs. There is a simple getting started tutorial here. reportlab is BSD license and available on pip and conda.
Anyways my code snippet looks like this.
Sorry its a bit long but my code has some helper functions to print text and dummy images. You should be able to copy/paste it directly.
The code will yield a PDF that looks like this
import io
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
import numpy as np
import matplotlib.pyplot as plt
def plot_hist():
""" Create a sample histogram plot and return a bytesio buffer with plot
Returns
-------
BytesIO : in memory buffer with plot image, can be passed to reportlab or elsewhere
"""
# from https://matplotlib.org/gallery/lines_bars_and_markers/scatter_masked.html#sphx-glr-gallery-lines-bars-and-markers-scatter-masked-py
plt.figure(figsize=(7, 2.25))
N = 100
r0 = 0.6
x = 0.9 * np.random.rand(N)
y = 0.9 * np.random.rand(N)
area = (20 * np.random.rand(N))**2 # 0 to 10 point radii
c = np.sqrt(area)
r = np.sqrt(x * x + y * y)
area1 = np.ma.masked_where(r < r0, area)
area2 = np.ma.masked_where(r >= r0, area)
plt.scatter(x, y, s=area1, marker='^', c=c)
plt.scatter(x, y, s=area2, marker='o', c=c)
# Show the boundary between the regions:
theta = np.arange(0, np.pi / 2, 0.01)
plt.plot(r0 * np.cos(theta), r0 * np.sin(theta))
# create buffer and save image to buffer
# dpi should match the dpi of your PDF, I think 300 is typical otherwise it won't pretty well
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=300)
buf.seek(0)
# you'll want to close the figure once its saved to buffer
plt.close()
return buf
def add_text(text, style="Normal", fontsize=12):
""" Adds text with some spacing around it to PDF report
Parameters
----------
text : str
The string to print to PDF
style : str
The reportlab style
fontsize : int
The fontsize for the text
"""
Story.append(Spacer(1, 12))
ptext = "<font size={}>{}</font>".format(fontsize, text)
Story.append(Paragraph(ptext, styles[style]))
Story.append(Spacer(1, 12))
# Use basic styles and the SimpleDocTemplate to get started with reportlab
styles=getSampleStyleSheet()
doc = SimpleDocTemplate("form_letter.pdf",pagesize=letter,
rightMargin=inch/2,leftMargin=inch/2,
topMargin=72,bottomMargin=18)
# The "story" just holds "instructions" on how to build the PDF
Story=[]
add_text("My Report", style="Heading1", fontsize=24)
# See plot_hist for information on how to get BytesIO object of matplotlib plot
# This code uses reportlab Image function to add and valid PIL input to the report
image_buffer1 = plot_hist()
im = Image(image_buffer1, 7*inch, 2.25*inch)
Story.append(im)
add_text("This text explains something about the chart.")
image_buffer2 = plot_hist()
im = Image(image_buffer2, 7*inch, 2.25*inch)
Story.append(im)
add_text("This text explains something else about another chart chart.")
# This command will actually build the PDF
doc.build(Story)
# should close open buffers, can use a "with" statement in python to do this for you
# if that works better
image_buffer1.close()
image_buffer2.close()
I need to print "Wheel Tags" from python. Wheel tags will have images, lines, and text.
The Python tutorial has two paragraphs about creating postscript files with the image lib. After reading it I still do not know how to lay out the data. I was hoping some one might have samples of how to layout the images, text and lines?
Thanks for any help.
See http://effbot.org/imagingbook/psdraw.htm
Note that:
the PSDraw module does not appear to have been actively maintained since 2005; I would guess that most of the effort has been redirected into supporting the PDF format instead. You might be happier using pypdf instead;
it has comments like '# FIXME: incomplete' and 'NOT YET IMPLEMENTED' in the source
it does not appear to have any method of setting the page size - which as I recall means it defaults to A4 (8.26 x 11.69 inches)
all measurements are in points, at 72 points per inch.
You will need to do something like:
import Image
import PSDraw
# fns for measurement conversion
PTS = lambda x: 1.00 * x # points
INS = lambda x: 72.00 * x # inches-to-points
CMS = lambda x: 28.35 * x # centimeters-to-points
outputFile = 'myfilename.ps'
outputFileTitle = 'Wheel Tag 36147'
myf = open(outputFile,'w')
ps = PSDraw.PSDraw(myf)
ps.begin_document(outputFileTitle)
ps is now a PSDraw object which will write PostScript to the specified file, and the document header has been written - you are ready to start drawing stuff.
To add an image:
im = Image.open("myimage.jpg")
box = ( # bounding-box for positioning on page
INS(1), # left
INS(1), # top
INS(3), # right
INS(3) # bottom
)
dpi = 300 # desired on-page resolution
ps.image(box, im, dpi)
To add text:
ps.setfont("Helvetica", PTS(12)) # PostScript fonts only -
# must be one which your printer has available
loc = ( # where to put the text?
INS(1), # horizontal value - I do not know whether it is left- or middle-aligned
INS(3.25) # vertical value - I do not know whether it is top- or bottom-aligned
)
ps.text(loc, "Here is some text")
To add a line:
lineFrom = ( INS(4), INS(1) )
lineTo = ( INS(4), INS(9) )
ps.line( lineFrom, lineTo )
... and I don't see any options for changing stroke weight.
When you are finished, you have to close the file off like:
ps.end_document()
myf.close()
Edit: I was doing a bit of reading on setting stroke weights, and I ran across a different module, psfile: http://seehuhn.de/pages/psfile#sec:2.0.0 The module itself looks pretty minimal - he's writing a lot of raw postscript - but it should give you a better idea of what's going on behind the scenes.
I would recommend the open source library Reportlab for this sort of task.
It is very simple to use and outputs directly to PDF format.
A very simple example from the official documentation:
from reportlab.pdfgen import canvas
def hello(c):
c.drawString(100,100,"Hello World")
c = canvas.Canvas("hello.pdf")
hello(c)
c.showPage()
c.save()
As long as PIL is installed, it is also very easy to add images to your page:
canvas.drawImage(self, image, x,y, width=None,height=None,mask=None)
where "image" is either a PIL Image object, or the filename of the image you wish to use.
Plenty of examples in the documentation also.