Getting RealSense depth frame in ROS - python

I have a drone in a Gazebo environment with a RealSense d435 camera on it. My plan is to use YOLO to find the center of an object of interest, and then find the depth of that point from the depth image. I heard that the depth camera outputs an image where the depth values are encoded in the RGB values. When further looking this up online, I found that there is a pyrealsense2 library that has functions for everything I need.
The implementations I've seen online need you to create a pyrealsense.pipeline() and get your frames from that. The issue is this seems to only work if you have a RealSense camera connected to your computer. Since mine exists in the Gazebo environment, I need a way to get and use the depth frame in a ROS callback. How would I do this? Any pointers would be greatly appreciated

yeah, you can do this with help of a ROS subscriber as follows (most of the code was taken from here):
import rospy
from sensor_msgs.msg import Image as msg_Image
from cv_bridge import CvBridge, CvBridgeError
import sys
import os
class ImageListener:
def __init__(self, topic):
self.topic = topic
self.bridge = CvBridge()
self.sub = rospy.Subscriber(topic, msg_Image, self.imageDepthCallback)
def imageDepthCallback(self, data):
try:
cv_image = self.bridge.imgmsg_to_cv2(data, data.encoding)
pix = (data.width/2, data.height/2)
sys.stdout.write('%s: Depth at center(%d, %d): %f(mm)\r' % (self.topic, pix[0], pix[1], cv_image[pix[1], pix[0]]))
sys.stdout.flush()
except CvBridgeError as e:
print(e)
return
if __name__ == '__main__':
rospy.init_node("depth_image_processor")
topic = '/camera/depth/image_rect_raw' # check the depth image topic in your Gazebo environmemt and replace this with your
listener = ImageListener(topic)
rospy.spin()
Note: To install CvBridge, you may follow the instructions below:
sudo apt-get install ros-(ROS version name)-cv-bridge
sudo apt-get install ros-(ROS version name)-vision-opencv
More information: http://wiki.ros.org/cv_bridge

To complement the above answer, be careful to the depth/image_rect_raw topic. Very likely you actually want the raw image, which you can obtain from /camera/aligned_depth_to_color/image_raw.
This topic may not be active by default, for instance on d435i you should toggle the align_depth:=true option:
roslaunch realsense2_camera rs_camera.launch align_depth:=true
See https://github.com/IntelRealSense/realsense-ros#usage-instructions for more info.

Related

Mismatched protocol version in packet Error: lost sync or rosserial_python is from different ros release than the rosserial client

I am using a raspberry pi 4 mode B to interface communicate with a Teensy 4.0. I'm using ROS noetic on Ubuntu 20.04. I've flashed the code to the Teensy successfully using platformio. I have a problem, however, when I try and launch the calibration python script for the robot I'm working on. I'm interfacing the raspberry Pi with the Teensy through the GPIO pins (this is after I've flashed the code to the Teensy, I flashed it through usb). When I run the calibration script using roslaunch mini_ros spot_calibration I get the following error:
[servo_calibration-1] process has died [pid 4841, exit code 1, cmd /home/ubuntu/spark_ws/src/spot_mini_mini/mini_ros/src/servo_calibration __name:=servo_calibration __log:=/home/ubuntu/.ros/log/fa068172-32ea-11ec-b45a-513494152363/servo_calibration-1.log].
log file: /home/ubuntu/.ros/log/fa068172-32ea-11ec-b45a-513494152363/servo_calibration-1*.log
[ERROR] [1634874547.846356]: Mismatched protocol version in packet (b'\xf8'): lost sync or rosserial_python is from different ros release than the rosserial client
below is the python code in spot_calibration
#!/usr/bin/env python
"""
DESCRIPTION:
SUBSCRIBERS:
"""
from __future__ import division
import rospy
from mini_ros.srv import CalibServo, CalibServoResponse
from mini_ros.msg import JointPulse
import numpy as np
import sys
import rospkg
rospack = rospkg.RosPack()
sys.path.append(rospack.get_path('mini_ros') + '/../')
sys.path.append('../../')
class ServoCalibrator():
def __init__(self):
rospy.init_node('ServoCalibrator', anonymous=True)
self.serv = rospy.Service('servo_calibrator', CalibServo,
self.calib_service_cb)
self.jp_pub = rospy.Publisher('spot/pulse', JointPulse, queue_size=1)
def calib_service_cb(self, req):
""" Requests a servo to be moved to a certain position
Args: req
Returns: response
"""
try:
jp_msg = JointPulse()
jp_msg.servo_num = req.servo_num
jp_msg.servo_pulse = req.servo_pulse
self.jp_pub.publish(jp_msg)
response = "Servo Command Sent."
except rospy.ROSInterruptException:
response = "FAILED to send Servo Command"
return CalibServoResponse(response)
def main():
""" The main() function. """
srv_calib = ServoCalibrator()
rospy.loginfo(
"Use The servo_calibrator service (Pulse Width Unit is us (nominal ~500-2500))."
)
while not rospy.is_shutdown():
rospy.spin()
if __name__ == '__main__':
try:
main()
except rospy.ROSInterruptException:
pass
The package was supposedly made in ROS Melodic, so that might be why it's throwing the error, but I don't know what to change if the package is melodic exclusive.
The problem is indeed because the roslib package used by rosserial was build for Melodic. As you can guess this error is just caused because of mismatched version ids coming from the arduino side. The best option would be to rebuild ros_lib for Noetic.
To rebuild ros_lib all you have to do is navigate to your sketchbook and run make_libraries.py. Note that this will build up to the currently installed distro, so you need to run this on a machine that has Noetic installed.
cd <sketchbook>/libraries
rm -rf ros_lib
rosrun rosserial_arduino make_libraries.py .
If rebuilding isn't an option you can add the definitions manually. On the arduino side you'll have a ros_lib folder. You have two sub directories you need to change.
The first is ./ros/node_handle.h. At the top of the file you'll see a few protocol versions defined as const uint8_t. Add in the version id for noetic as const uint8_t PROTOCOL_VER6 = 0xfa; then change the line that states what version should be used. const uint8_t PROTOCOL_VER = PROTOCOL_VER6;.
Lastly, you just need to make this same change in ./ros_lib/ros/node_handle.h

How to play video with python-vlc?

I would like to play video with python-vlc module. I wrote the following code. My computer is MacOS Catalina.
#!/usr/bin/env python3
import vlc
p = vlc.MediaPlayer("mediafile.mp4")
p.play()
while True:
pass
But the python3 interpreter threw the following errors.
[00007f89b9661950] caopengllayer vout display error: No drawable-nsobject found!
[00007f89b9661950] macosx vout display error: No drawable-nsobject nor vout_window_t found, passing over.
[00007f89b30530f0] main video output error: video output creation failed
[00007f89b9650c00] main decoder error: failed to create video output
[h264 # 0x7f89b407c000] get_buffer() failed
[h264 # 0x7f89b407c000] thread_get_buffer() failed
[h264 # 0x7f89b407c000] decode_slice_header error
[h264 # 0x7f89b407c000] no frame!
I guessed that this code didn't make a frame displaying the video. It'll be a main cause of this error, I think.
However, I can not come up with a solution of this problem.
Please tell me how to play video with python-vlc module!!
I think the possible reason is, it requires an active hwnd where it can show the video.
So you need an GUI and set hwnd to that player.
Here is my code for Tkinter Window.You can also check out different GUI based example From Github
import vlc
from tkinter import *
root=Tk()
instance=vlc.Instance()
p=instance.media_player_new()
p.set_hwnd(root.winfo_id())
p.set_media(instance.media_new(path_to_media))
p.play()
root.mainloop()
And if you r using Mac ,then as per the example
you should use instaed of this line
p.set_hwnd(root.winfo_id())
try:
libtk = 'libtk%s.dylib' % (Tk.TkVersion,)
prefix = getattr(sys, 'base_prefix', sys.prefix)
libtk = joined(prefix, 'lib', libtk)
dylib = cdll.LoadLibrary(libtk)
# getNSView = dylib.TkMacOSXDrawableView is the
# proper function to call, but that is non-public
# (in Tk source file macosx/TkMacOSXSubwindows.c)
# and dylib.TkMacOSXGetRootControl happens to call
# dylib.TkMacOSXDrawableView and return the NSView
_GetNSView = dylib.TkMacOSXGetRootControl
# C signature: void *_GetNSView(void *drawable) to get
# the Cocoa/Obj-C NSWindow.contentView attribute, the
# drawable NSView object of the (drawable) NSWindow
_GetNSView.restype = c_void_p
_GetNSView.argtypes = c_void_p,
del dylib
except (NameError, OSError): # image or symbol not found
def _GetNSView(unused):
return None
libtk = "N/A"
h = root.winfo_id() # .winfo_visualid()?
# XXX 1) using the videopanel.winfo_id() handle
# causes the video to play in the entire panel on
# macOS, covering the buttons, sliders, etc.
# XXX 2) .winfo_id() to return NSView on macOS?
v= _GetNSView(h)
if v:
p.set_nsobject(v)
else:
p.set_xwindow(h)# plays audio, no video
I think this will work:
import vlc
media = vlc.MediaPlayer("1.mp4")
media.play()
It just takes a media filename for you.
Make sure the video is in the same folder as the script.

How can I publish PIL image binary through ROS without OpenCV?

I'm currently trying to write a ROS Publisher/Subscriber setup that passes image binary opened by PIL. I'd like to not have to use OpenCV due to operating restrictions, and I was wondering if there was a way to do so. This is my current code:
#!/usr/bin/env python
import rospy
from PIL import Image
from sensor_msgs.msg import Image as sensorImage
from rospy.numpy_msg import numpy_msg
import numpy
def talker():
pub = rospy.Publisher('image_stream', numpy_msg(sensorImage), queue_size=10)
rospy.init_node('image_publisher', anonymous=False)
rate = rospy.Rate(0.5)
while not rospy.is_shutdown():
im = numpy.array(Image.open('test.jpg'))
pub.publish(im)
rate.sleep()
if __name__ == '__main__'
try:
talker()
except ROSInterruptException:
pass
which on pub.publish(im) attempt throws:
TypeError: Invalid number of arguments, args should be ['header', 'height', 'width', 'encoding', 'is_bigendian', 'step', 'data'] args are (array([[[***array data here***]]], dtype=uint8),)
How would I transform the image into the right form, or is there a conversion method/different message type that supports just sending raw binary over the ROS connection?
Thanks
Indeed Mark Setchell's answer works perfectly (ignoring the alpha channel in this example):
#!/usr/bin/env python
import rospy
import urllib2 # for downloading an example image
from PIL import Image
from sensor_msgs.msg import Image as SensorImage
import numpy as np
if __name__ == '__main__':
pub = rospy.Publisher('/image', SensorImage, queue_size=10)
rospy.init_node('image_publisher')
im = Image.open(urllib2.urlopen('https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png'))
im = im.convert('RGB')
msg = SensorImage()
msg.header.stamp = rospy.Time.now()
msg.height = im.height
msg.width = im.width
msg.encoding = "rgb8"
msg.is_bigendian = False
msg.step = 3 * im.width
msg.data = np.array(im).tobytes()
pub.publish(msg)
I don't know anything about ROS, but I use PIL a lot, so if someone else knows better, please ping me and I will delete this "best guess" answer.
So, it seems you need to make something like this from a PIL Image. So you need:
'header',
'height',
'width',
'encoding',
'is_bigendian',
'step',
'data'
So, assuming you do this:
im = Image.open('test.jpg')
you should be able to use:
something you'll need to work out
im.height from PIL Image
im.width from PIL Image
probably const std::string RGB8 = "rgb8"
probably irrelevant because data is 8-bit
probably im.width * 3 as it's 3 bytes per pixel RGB
np.array(im).tobytes()
Before anyone marks this answer down, nobody said answers have to be complete - they can just be "hopefully helpful"!
Note that if your input image is PNG format, you should check im.mode and if it is "P" (i.e. palette mode) immediately run:
im = im.convert('RGB')
to make sure it is 3-channel RGB.
Note that if your input image is PNG format and contains an alpha channel, you should change the encoding to "rgba8" and set step = im.width * 4.

svg 2 png without saving [duplicate]

How do I convert an svg to png, in Python? I am storing the svg in an instance of StringIO. Should I use the pyCairo library? How do I write that code?
Here is what I did using cairosvg:
from cairosvg import svg2png
svg_code = """
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="12"/>
<line x1="12" y1="16" x2="12" y2="16"/>
</svg>
"""
svg2png(bytestring=svg_code,write_to='output.png')
And it works like a charm!
See more: cairosvg document
The answer is "pyrsvg" - a Python binding for librsvg.
There is an Ubuntu python-rsvg package providing it. Searching Google for its name is poor because its source code seems to be contained inside the "gnome-python-desktop" Gnome project GIT repository.
I made a minimalist "hello world" that renders SVG to a cairo
surface and writes it to disk:
import cairo
import rsvg
img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 640,480)
ctx = cairo.Context(img)
## handle = rsvg.Handle(<svg filename>)
# or, for in memory SVG data:
handle= rsvg.Handle(None, str(<svg data>))
handle.render_cairo(ctx)
img.write_to_png("svg.png")
Update: as of 2014 the needed package for Fedora Linux distribution is: gnome-python2-rsvg. The above snippet listing still works as-is.
Install Inkscape and call it as command line:
${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -e ${dest_png}
You can also snap specific rectangular area only using parameter -j, e.g. co-ordinate "0:125:451:217"
${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -a ${coordinates} -e ${dest_png}
If you want to show only one object in the SVG file, you can specify the parameter -i with the object id that you have setup in the SVG. It hides everything else.
${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
I'm using Wand-py (an implementation of the Wand wrapper around ImageMagick) to import some pretty advanced SVGs and so far have seen great results! This is all the code it takes:
with wand.image.Image( blob=svg_file.read(), format="svg" ) as image:
png_image = image.make_blob("png")
I just discovered this today, and felt like it was worth sharing for anyone else who might straggle across this answer as it's been a while since most of these questions were answered.
NOTE: Technically in testing I discovered you don't even actually have to pass in the format parameter for ImageMagick, so with wand.image.Image( blob=svg_file.read() ) as image: was all that was really needed.
EDIT: From an attempted edit by qris, here's some helpful code that lets you use ImageMagick with an SVG that has a transparent background:
from wand.api import library
import wand.color
import wand.image
with wand.image.Image() as image:
with wand.color.Color('transparent') as background_color:
library.MagickSetBackgroundColor(image.wand,
background_color.resource)
image.read(blob=svg_file.read(), format="svg")
png_image = image.make_blob("png32")
with open(output_filename, "wb") as out:
out.write(png_image)
I did not find any of the answers satisfactory. All the mentioned libraries have some problem or the other like Cairo dropping support for python 3.6 (they dropped Python 2 support some 3 years ago!). Also, installing the mentioned libraries on the Mac was a pain.
Finally, I found the best solution was svglib + reportlab. Both installed without a hitch using pip and first call to convert from svg to png worked beautifully! Very happy with the solution.
Just 2 commands do the trick:
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
drawing = svg2rlg("my.svg")
renderPM.drawToFile(drawing, "my.png", fmt="PNG")
Are there any limitations with these I should be aware of?
Try this: http://cairosvg.org/
The site says:
CairoSVG is written in pure python and only depends on Pycairo. It is
known to work on Python 2.6 and 2.7.
Update November 25, 2016:
2.0.0 is a new major version, its changelog includes:
Drop Python 2 support
Another solution I've just found here How to render a scaled SVG to a QImage?
from PySide.QtSvg import *
from PySide.QtGui import *
def convertSvgToPng(svgFilepath,pngFilepath,width):
r=QSvgRenderer(svgFilepath)
height=r.defaultSize().height()*width/r.defaultSize().width()
i=QImage(width,height,QImage.Format_ARGB32)
p=QPainter(i)
r.render(p)
i.save(pngFilepath)
p.end()
PySide is easily installed from a binary package in Windows (and I use it for other things so is easy for me).
However, I noticed a few problems when converting country flags from Wikimedia, so perhaps not the most robust svg parser/renderer.
A little extension on the answer of jsbueno:
#!/usr/bin/env python
import cairo
import rsvg
from xml.dom import minidom
def convert_svg_to_png(svg_file, output_file):
# Get the svg files content
with open(svg_file) as f:
svg_data = f.read()
# Get the width / height inside of the SVG
doc = minidom.parse(svg_file)
width = int([path.getAttribute('width') for path
in doc.getElementsByTagName('svg')][0])
height = int([path.getAttribute('height') for path
in doc.getElementsByTagName('svg')][0])
doc.unlink()
# create the png
img = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
ctx = cairo.Context(img)
handler = rsvg.Handle(None, str(svg_data))
handler.render_cairo(ctx)
img.write_to_png(output_file)
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("-f", "--file", dest="svg_file",
help="SVG input file", metavar="FILE")
parser.add_argument("-o", "--output", dest="output", default="svg.png",
help="PNG output file", metavar="FILE")
args = parser.parse_args()
convert_svg_to_png(args.svg_file, args.output)
Here is a another solution without using rsvg(which is currently not available for windows).Only install cairosvg using pip install CairoSVG
svg2png.py
from cairosvg import svg2png
svg_code = open("input.svg", 'rt').read()
svg2png(bytestring=svg_code,write_to='output.png')
SVG scaling and PNG rendering
Using pycairo and librsvg I was able to achieve SVG scaling and rendering to a bitmap. Assuming your SVG is not exactly 256x256 pixels, the desired output, you can read in the SVG to a Cairo context using rsvg and then scale it and write to a PNG.
main.py
import cairo
import rsvg
width = 256
height = 256
svg = rsvg.Handle('cool.svg')
unscaled_width = svg.props.width
unscaled_height = svg.props.height
svg_surface = cairo.SVGSurface(None, width, height)
svg_context = cairo.Context(svg_surface)
svg_context.save()
svg_context.scale(width/unscaled_width, height/unscaled_height)
svg.render_cairo(svg_context)
svg_context.restore()
svg_surface.write_to_png('cool.png')
RSVG C binding
From the Cario website with some minor modification. Also a good example of how to call a C-library from Python
from ctypes import CDLL, POINTER, Structure, byref, util
from ctypes import c_bool, c_byte, c_void_p, c_int, c_double, c_uint32, c_char_p
class _PycairoContext(Structure):
_fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__),
("ctx", c_void_p),
("base", c_void_p)]
class _RsvgProps(Structure):
_fields_ = [("width", c_int), ("height", c_int),
("em", c_double), ("ex", c_double)]
class _GError(Structure):
_fields_ = [("domain", c_uint32), ("code", c_int), ("message", c_char_p)]
def _load_rsvg(rsvg_lib_path=None, gobject_lib_path=None):
if rsvg_lib_path is None:
rsvg_lib_path = util.find_library('rsvg-2')
if gobject_lib_path is None:
gobject_lib_path = util.find_library('gobject-2.0')
l = CDLL(rsvg_lib_path)
g = CDLL(gobject_lib_path)
g.g_type_init()
l.rsvg_handle_new_from_file.argtypes = [c_char_p, POINTER(POINTER(_GError))]
l.rsvg_handle_new_from_file.restype = c_void_p
l.rsvg_handle_render_cairo.argtypes = [c_void_p, c_void_p]
l.rsvg_handle_render_cairo.restype = c_bool
l.rsvg_handle_get_dimensions.argtypes = [c_void_p, POINTER(_RsvgProps)]
return l
_librsvg = _load_rsvg()
class Handle(object):
def __init__(self, path):
lib = _librsvg
err = POINTER(_GError)()
self.handle = lib.rsvg_handle_new_from_file(path.encode(), byref(err))
if self.handle is None:
gerr = err.contents
raise Exception(gerr.message)
self.props = _RsvgProps()
lib.rsvg_handle_get_dimensions(self.handle, byref(self.props))
def get_dimension_data(self):
svgDim = self.RsvgDimensionData()
_librsvg.rsvg_handle_get_dimensions(self.handle, byref(svgDim))
return (svgDim.width, svgDim.height)
def render_cairo(self, ctx):
"""Returns True is drawing succeeded."""
z = _PycairoContext.from_address(id(ctx))
return _librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)
Here is an approach where Inkscape is called by Python.
Note that it suppresses certain crufty output that Inkscape writes to the console (specifically, stderr and stdout) during normal error-free operation. The output is captured in two string variables, out and err.
import subprocess # May want to use subprocess32 instead
cmd_list = [ '/full/path/to/inkscape', '-z',
'--export-png', '/path/to/output.png',
'--export-width', 100,
'--export-height', 100,
'/path/to/input.svg' ]
# Invoke the command. Divert output that normally goes to stdout or stderr.
p = subprocess.Popen( cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
# Below, < out > and < err > are strings or < None >, derived from stdout and stderr.
out, err = p.communicate() # Waits for process to terminate
# Maybe do something with stdout output that is in < out >
# Maybe do something with stderr output that is in < err >
if p.returncode:
raise Exception( 'Inkscape error: ' + (err or '?') )
For example, when running a particular job on my Mac OS system, out ended up being:
Background RRGGBBAA: ffffff00
Area 0:0:339:339 exported to 100 x 100 pixels (72.4584 dpi)
Bitmap saved as: /path/to/output.png
(The input svg file had a size of 339 by 339 pixels.)
Try this python script:
Don't forget to install cairosvg: pip3 install cairosvg
#!/usr/bin/env python3
import os
import cairosvg
for file in os.listdir('.'):
if os.path.isfile(file) and file.endswith(".svg"):
name = file.split('.svg')[0]
cairosvg.svg2png(url=name+'.svg',write_to=name+'.png')
Try using Gtk.Image and Gdk.Pixbuf
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, Gtk
from PIL import Image
image = Gtk.Image()
image.set_from_file("path/to/image.svg")
pb = image.get_pixbuf()
pb.savev("path/to/convented/image.jpeg","jpeg",[],[])
im = Image.open("path/to/convented/image.jpeg")
pix = im.load()
print(pix[1,1])
Actually, I did not want to be dependent of anything else but Python (Cairo, Ink.., etc.)
My requirements were to be as simple as possible, at most, a simple pip install "savior" would suffice, that's why any of those above didn't suit for me.
I came through this (going further than Stackoverflow on the research).
https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/
Looks good, so far. So I share it in case anyone in the same situation.
All the answer's here are great, but I figure I'll mention that I have made a simple library that loads SVG's files as pillow Image instances which can then be exported. It uses inkscape like in blj's answer, but renders to stdout so that no temporary files are made. There's some basic usage stuff in the README.
https://github.com/jlwoolf/pillow-svg
EDIT:
As suggested, here's a brief explanation, since the link could become invalid:
The library uses inkscape's command line interface to convert the image to a png of a specific size or dpi using the python subprocess library. By setting --export-filename to -, inkscape redirects the output to the stdout. The first two lines are discarded, and the remaining output is passed to PIL.Image.open, converting it to pillow image instance.
import subprocess
from PIL import Image
options = ["inkscape", "--export-filename=-", "--export-type=png", "file.svg"]
pipe = subprocess.Popen(options, stdout=subprocess.PIPE)
pipe.stdout.readline()
pipe.stdout.readline()
img = Image.open(pipe.stdout)
From there you can do whatever pillow image operations you need (like export as a jpg, resize, crop, etc).
EDIT 2:
Just added support for skia-python (haven't fully tested it, but seems to work so far). This way you can convert an svg to png with only a single pip install (no need to use inkscape).
Here is an explanation of how the library uses skia-python:
First, the svg file is loaded into a skia.SVGDOM. From there you can grab the SVGDOM's dimensions, using containerSize. Then a skia.Surface of the desired image output size is made. The canvas is scaled to fit the svg to the surface, and then the svg is rendered. From there, an image snapshot can be made, which can then be fed to PIL.Image.open.
import skia
from PIL import Image
skia_stream = skia.Stream.MakeFromFile("file.svg")
skia_svg = skia.SVGDOM.MakeFromStream(skia_stream)
svg_width, svg_height = skia_svg.containerSize()
surface_width, surface_height = 512, 512
surface = skia.Surface(surface_width, surface_height)
with surface as canvas:
canvas.scale(surface_width / svg_width, surface_height / svg_height)
skia_svg.render(canvas)
with io.BytesIO(surface.makeImageSnapshot().encodeToData()) as f:
img = Image.open(f)
img.load()
Edit 3:
I have fleshed out the library much much more. There is a command line utility now for easy svg conversion, along with more documentation explaining usage. Hope it helps!
Posting my code from this StackOverflow answer. It's a workaround to svglib+reportlib not supporting a transparent background and no scaling (see #sarang's answer and #ualter-jr's answer as well as these Github issues on scaling not working and this one on transparency)
This uses pyMuPDF to render an intermediate pdf from reportlab to PNG.
The big advantage is that it doesn't need any external libraries as pymupdf comes with precompiled wheels for Windows, Linux and MacOS.
The whole thing is as easy as
pip install pymupdf svglib
and then executing the following lines
import fitz
from svglib import svglib
from reportlab.graphics import renderPDF
# Convert svg to pdf in memory with svglib+reportlab
# directly rendering to png does not support transparency nor scaling
drawing = svglib.svg2rlg(path="input.svg")
pdf = renderPDF.drawToString(drawing)
# Open pdf with fitz (pyMuPdf) to convert to PNG
doc = fitz.Document(stream=pdf)
pix = doc.load_page(0).get_pixmap(alpha=True, dpi=300)
pix.save("output.png")

Take screenshot in Python on Mac OS X

ImageGrab from PIL would have been ideal. I'm looking for similar functionality, specifically the ability to define the screenshot's bounding box. I've been looking for a library to do so on Mac OS X but haven't had any luck. I also wasn't able to find any sample code to do it (maybe pyobjc?).
While not exactly what you want, in a pinch you might just use:
os.system("screencapture screen.png")
Then open that image with the Image module. I'm sure a better solution exists though.
Here's how to capture and save a screenshot with PyObjC, based on my answer here
You can capture the entire screen, or specify a region to capture. If you don't need to do that, I'd recommend just calling the screencapture command (more features, more robust, and quicker - the initial PyObjC import alone can take around a second)
import Quartz
import LaunchServices
from Cocoa import NSURL
import Quartz.CoreGraphics as CG
def screenshot(path, region = None):
"""region should be a CGRect, something like:
>>> import Quartz.CoreGraphics as CG
>>> region = CG.CGRectMake(0, 0, 100, 100)
>>> sp = ScreenPixel()
>>> sp.capture(region=region)
The default region is CG.CGRectInfinite (captures the full screen)
"""
if region is None:
region = CG.CGRectInfinite
# Create screenshot as CGImage
image = CG.CGWindowListCreateImage(
region,
CG.kCGWindowListOptionOnScreenOnly,
CG.kCGNullWindowID,
CG.kCGWindowImageDefault)
dpi = 72 # FIXME: Should query this from somewhere, e.g for retina displays
url = NSURL.fileURLWithPath_(path)
dest = Quartz.CGImageDestinationCreateWithURL(
url,
LaunchServices.kUTTypePNG, # file type
1, # 1 image in file
None
)
properties = {
Quartz.kCGImagePropertyDPIWidth: dpi,
Quartz.kCGImagePropertyDPIHeight: dpi,
}
# Add the image to the destination, characterizing the image with
# the properties dictionary.
Quartz.CGImageDestinationAddImage(dest, image, properties)
# When all the images (only 1 in this example) are added to the destination,
# finalize the CGImageDestination object.
Quartz.CGImageDestinationFinalize(dest)
if __name__ == '__main__':
# Capture full screen
screenshot("/tmp/testscreenshot_full.png")
# Capture region (100x100 box from top-left)
region = CG.CGRectMake(0, 0, 100, 100)
screenshot("/tmp/testscreenshot_partial.png", region=region)
While I do understand that this thread is close to five years old now, I'm answering this in the hope that it helps people in future.
Here's what worked for me, based on an answer in this thread (credit goes to ponty ) : Take a screenshot via a python script. [Linux]
https://github.com/ponty/pyscreenshot
Install:
easy_install pyscreenshot
Example:
import pyscreenshot
# fullscreen
screenshot=pyscreenshot.grab()
screenshot.show()
# part of the screen
screenshot=pyscreenshot.grab(bbox=(10,10,500,500))
screenshot.show()
# save to file
pyscreenshot.grab_to_file('screenshot.png')
Pillow has since added ImageGrab support for macOS!
However it's not in v2.9 (as of right now the latest) so I just added this file to my local module.
The code is as below:
#
# The Python Imaging Library
# $Id$
#
# screen grabber (macOS and Windows only)
#
# History:
# 2001-04-26 fl created
# 2001-09-17 fl use builtin driver, if present
# 2002-11-19 fl added grabclipboard support
#
# Copyright (c) 2001-2002 by Secret Labs AB
# Copyright (c) 2001-2002 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
from . import Image
import sys
if sys.platform not in ["win32", "darwin"]:
raise ImportError("ImageGrab is macOS and Windows only")
if sys.platform == "win32":
grabber = Image.core.grabscreen
elif sys.platform == "darwin":
import os
import tempfile
import subprocess
def grab(bbox=None):
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp('.png')
os.close(fh)
subprocess.call(['screencapture', '-x', filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
else:
size, data = grabber()
im = Image.frombytes(
"RGB", size, data,
# RGB, 32-bit line padding, origin lower left corner
"raw", "BGR", (size[0]*3 + 3) & -4, -1
)
if bbox:
im = im.crop(bbox)
return im
def grabclipboard():
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp('.jpg')
os.close(fh)
commands = [
"set theFile to (open for access POSIX file \""+filepath+"\" with write permission)",
"try",
"write (the clipboard as JPEG picture) to theFile",
"end try",
"close access theFile"
]
script = ["osascript"]
for command in commands:
script += ["-e", command]
subprocess.call(script)
im = None
if os.stat(filepath).st_size != 0:
im = Image.open(filepath)
im.load()
os.unlink(filepath)
return im
else:
debug = 0 # temporary interface
data = Image.core.grabclipboard(debug)
if isinstance(data, bytes):
from . import BmpImagePlugin
import io
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
return data
from subprocess import call
import time
from time import gmtime, strftime
# Take screenshot every 10 seconds and store in the folder where the
# code file is present on disk. To stop the script press Cmd+Z/C
def take_screen_shot():
# save screen shots where
call(["screencapture", "Screenshot" + strftime("%Y-%m-%d %H:%M:%S", gmtime()) + ".jpg"])
def build_screen_shot_base():
while True:
take_screen_shot()
time.sleep(10)
build_screen_shot_base()
I found that using webkit2png was the most convenient solution for me on OS X.
brew install webkit2png
webkit2png http://stackoverflow.com

Categories

Resources