the image import feature is intended for visualization purposes, that's why no conversion to layout is provided currently.
It is possible to write a Ruby script that extracts information from the images and creates layout from that. The method of extracting layout depends on the application you have. For example, it would be possible to extract a gray level contour as a polygon, although that's more demanding on the algorithmic side.
I'm just looking for a nice method to convert images to gds2 in order to generate Logos for ICs. The image import feature of Klayout looks very good as it is possible to define the pixel size in microns. This is important because the logos shouldn't violate certain design rules.
The only thing which I need in addition is to extract 2 or 3 different colours as polygons or rectangles.
I prepared a sample script that demonstrates how to convert an image to layout.
To install the script, copy the code below to a file with a suffix .rbm, i.e. "image2gds.rbm" into
the installation directory (that is where the klayout executable is).
This script will create a new menu entry in the "Tools" menu: "Image channes to layers".
The function will take the pixel data of all images loaded and convert each pixel into a rectangle in
predefined layers, one per color channel. Each pixel is converted into a rectangle when the respective color value in that
channel is above 50%. Three layers can be specified whose layer and datatype are hard coded in
the "gds2_layers" array around line 17. Currently, layers 1, 2 and 3 are used for red, green and blue
channels respectively. Datatype is 0.
The script should be regarded as an example. I hope I added enough comments to enable you to adjust
the script to your needs.
Best regards,
Matthias
class MenuAction < RBA::Action
def initialize( title, shortcut, &action )
self.title = title
self.shortcut = shortcut
@action = action
end
def triggered
@action.call( self )
end
private
@action
end
$image2gds_handler = MenuAction.new( "Image channels to layers", "" ) do
# Defines the GDS2 layers where the three channels go.
gds2_layers = [
RBA::LayerInfo.new( 1, 0 ), # Red channel (layer, datatype)
RBA::LayerInfo.new( 2, 0 ), # Green channe (layer, datatype)
RBA::LayerInfo.new( 3, 0 ), # Blue channe (layer, datatype)
]
app = RBA::Application.instance
mw = app.main_window
lv = mw.current_view
if lv == nil
raise "No view selected"
end
cv = lv.active_cellview
if !cv.is_valid?
raise "No cell or no layout found"
end
# Create the layers if they do not exist already.
# Prepare a vector (layers) which contains the layer indices for the relevant layers.
layers = []
gds2_layers.each do |l|
layer=-1
cv.layout.layer_indices.each do |li|
if cv.layout.get_info(li).is_equivalent?(l)
layer = li
break
end
end
if layer<0
# layer does not exist yet: create a new layer (both in the layout and the layer list)
layer = cv.layout.insert_layer(l)
lp = RBA::LayerPropertiesNode.new
lp.source_layer = l.layer
lp.source_datatype = l.datatype
lv.init_layer_properties(lp)
lv.insert_layer(lv.end_layers, lp)
end
layers.push(layer)
end
# This call is important to keep the layer list consistent when
# layers have been added or removed:
lv.update_content
# Prepare an array of all images in the view
images = []
lv.each_image { |i| images.push(i) }
# The database unit
dbu = cv.layout.dbu
if images.size > 0
# start transaction for "undo"
lv.transaction( "Image channels to RGB" )
# iterate over all images
images.each do |image|
# This transformation will transform a pixel to the target location of the pixel in the layout
trans = RBA::ICplxTrans.from_dtrans(RBA::DCplxTrans.new(1 / dbu) * image.trans * RBA::DCplxTrans.new(dbu))
# The dimension of one pixel
pw = image.pixel_width / dbu
ph = image.pixel_height / dbu
# iterate over all channels
(0..(layers.size-1)).each do |c|
# That is where the shapes go
shapes = cv.cell.shapes(layers [c])
# Iterate over all rows
(0..(image.height-1)).each do |y|
# Iterate over all columns
(0..(image.width-1)).each do |x|
# Use each channel for a different layer
# d>0.5 selects all pixels with a level >50% in that channel
d = image.get_pixel(x, y, c)
if d>0.5
# Create a polygon corresponding to one pixel
p1 = RBA::DPoint.new(x * pw, y * ph)
p2 = RBA::DPoint.new((x + 1) * pw, (y + 1) * ph)
dbox = RBA::DBox.new(p1, p2)
box = RBA::Box.from_dbox(dbox)
poly = RBA::Polygon.new(box)
shapes.insert(poly.transformed_cplx(trans))
end
end
end
end
end
# commit transaction
lv.commit
end
end
app = RBA::Application.instance
mw = app.main_window
menu = mw.menu
menu.insert_separator("tools_menu.end", "name")
menu.insert_item("tools_menu.end", "image2gds", $image2gds_handler)
I hope that script serves as a preliminary solution.
@Jay: the problem with the .rbm files may be connected with the shell or system you are using. I am also not able to reproduce it on Ubuntu and Windows. However I am interested in feedback from other platforms.
I am using klayout on CentOS 4.7 and if I put the .rbm script in the directory where I start klayout, it works but putting the .rbm script in the same location as klayout binary doesn't work.
I tried to set also RUBYLIB and RUBYPATH w/ the directory containing .rbm files but no changes, seem not to be used.
KLayout derives the installation path form the $0 argument. I learned that shells behave differently and for some shells the absolute path cannot be derived from that argument.
Currently, one workaround is to start KLayout with the full path, i.e.
/use/bin/klayout ...
Maybe you can set an alias for this command, so it's less effort typing.
For the next release I plan to add a more consistent ruby module installation scheme which should avoid those problems.
the idea is to first add an image with "Edit/Add Image", adjust the position and size and then let the script convert that image (or the images) to GDS geometry.
there are numerous vectorizers out on the net. If you need a CAD format, you can try online image to DXF translators (google for "image to dxf converter online").
Your script to import an image to gds looks very good! However, do I need to add this script to the source file, and compile klayout myself? I installed the binary version on my mac pro. Could you possible to run it with the binary version?
The best way I've found is to convert a raster image (eg. PNG, JPG) into vector (PDF) and then into DXF. I found InkScape was the best tool for this - although on MacOS you also have to install XQuartz (an X-windows system) to use it.
To convert your raster image into PDF, I use PoTrace (a command-line tool that is also built into InkScape), or just do it through InkScape itself.
Like this tutorial: http://www.wikihow.com/Convert-Raster-to-Vector
-- see the 2nd "inkscape" method
POTrace (command-line tool): http://potrace.sourceforge.net
Then convert the PDF into DXF - InkScape can also save into DXF (Autocad R14 compatible). I was then able to open this in KLayout (or AutoCad)! Of course, then you can save it as GDSii or any other format KLayout supports.
The code above of December 2009 worked fine for the previous version of KLayout I was using (don't remember which one), but for version 0.25.8., the generated gds structures are scaled by 1/DBU compared to the image. I removed the /DBU or *DBU from your code to make it work as before, but can you tell me why the code behaves differently???
Thank you in advance.
Congratulations with releasing 0.26.: Looking forward to try it soon!!!
I think the old version was something like 0.24.x. Version 0.25 got a more consistent coordinate space system and some methods were deprecated (specifically "transformed_cplx"). That is the reason why the behaviour is different.
Background: in 0.25.x, the "D" shape primitives (DBox, DPolygon, DPath ...) are taken as being in absolute physical units, namely "micrometers". So a DBox with 0,0 .. 10,20 is a 10x20µm box. The integer coordinate types (Box, Polygon, Path ...) are taken as begin in database units. In contrast to 0.24.x you can insert "D" types into a layout. In this case, micrometers are translated into database units internally. This is pretty convenient because you can essentially work (to some extend) with the micrometer-unit objects, hence with physical coordinates.
As a consequence, the transformations had to be made consistent too. The complex transformations now are specific to the input type and output type. So "ICplxTrans" transforms an integer-type object into another integer-type object (i.e. DBU-to-DBU transformation). "DCplxTrans" transforms micrometer-unit objects to the same. "CplxTrans" transformes DBU to micrometer-unit objects. Specifically "CplxTrans::new(dbu)" will perform this scaling. "VCplxTrans" is the inverse type of "CplxTrans" and will convert micrometer-unit objects to DBU unit ones.
Before 0.25.x, the complex transformations were more or less interchangeable and VCplxTrans wasn't existing at all. With 0.25.x and later, ICplxTrans and CplxTrans are very different as they transform into a different unit space. There was only one definition of "transformed_cplx" available and it converted into "D" space always without the intention of doing a unit translation. So this method was inconsistent and I deprecated it. Instead, "transformed" for the Box (Polygon, ...) was enhanced by providing overloads which take "Trans" (simple), "ICplxTrans" for DBU-to-DBU transformation and "CplxTrans" for providing DBU-to-µm transformations. This is the method intended for this purpose.
So the problem is there:
shapes.insert(poly.transformed_cplx(trans))
In the case of the above code, the transformation is ICplxTrans because we like to stay within DBU units. As mentioned "transformed_cplx" is not consistent and will transform into "D" types, hence micrometer units. So implicitly, DBUs are converted to micrometers which is the effect you observe.
The solution is simple: replace "transformed_cplx" by "transformed".
However, with the new, consistent definition, the code can be entirely written in micrometer units. This is very convenient as the image attributes already come in micrometers. This is the new partial code (I hope it's self-explaining):
# The orientation and location of the image
trans = image.trans
# The dimension of one pixel
pw = image.pixel_width
ph = image.pixel_height
# iterate over all channels
(0..(layers.size-1)).each do |c|
# That is where the shapes go
shapes = cv.cell.shapes(layers [c])
# Iterate over all rows
(0..(image.height-1)).each do |y|
# Iterate over all columns
(0..(image.width-1)).each do |x|
# Use each channel for a different layer
# d>0.5 selects all pixels with a level >50% in that channel
d = image.get_pixel(x, y, c)
if d>0.5
# Create a polygon corresponding to one pixel
p1 = RBA::DPoint.new(x * pw, y * ph)
p2 = RBA::DPoint.new((x + 1) * pw, (y + 1) * ph)
dbox = RBA::DBox.new(p1, p2)
shapes.insert(dbox.transformed(trans))
end
end
end
end
I was pointed to the original script and it's no longer working properly (it's from 2009!).
So here is the current version:
action = RBA::Action.new
action.title = "Image channels to layers"
action.on_triggered do
# Defines the GDS2 layers where the three channels go.
gds2_layers = [
RBA::LayerInfo.new( 1, 0 ), # Red channel (layer, datatype)
RBA::LayerInfo.new( 2, 0 ), # Green channe (layer, datatype)
RBA::LayerInfo.new( 3, 0 ), # Blue channe (layer, datatype)
]
app = RBA::Application.instance
mw = app.main_window
lv = mw.current_view
if lv == nil
raise "No view selected"
end
cv = lv.active_cellview
if !cv.is_valid?
raise "No cell or no layout found"
end
# Create the layers if they do not exist already.
# Prepare a vector (layers) which contains the layer indices for the relevant layers.
layers = []
gds2_layers.each do |l|
layer=-1
cv.layout.layer_indices.each do |li|
if cv.layout.get_info(li).is_equivalent?(l)
layer = li
break
end
end
if layer<0
# layer does not exist yet: create a new layer (both in the layout and the layer list)
layer = cv.layout.insert_layer(l)
lp = RBA::LayerPropertiesNode.new
lp.source_layer = l.layer
lp.source_datatype = l.datatype
lv.init_layer_properties(lp)
lv.insert_layer(lv.end_layers, lp)
end
layers.push(layer)
end
# This call is important to keep the layer list consistent when
# layers have been added or removed:
lv.update_content
# Prepare an array of all images in the view
images = []
lv.each_image { |i| images.push(i) }
# The database unit
dbu = cv.layout.dbu
if images.size > 0
# start transaction for "undo"
lv.transaction( "Image channels to RGB" )
# iterate over all images
images.each do |image|
# The dimension of one pixel
pw = image.pixel_width
ph = image.pixel_height
# iterate over all channels
(0..(layers.size-1)).each do |c|
# That is where the shapes go
shapes = cv.cell.shapes(layers [c])
# Iterate over all rows
(0..(image.height-1)).each do |y|
# Iterate over all columns
(0..(image.width-1)).each do |x|
# Use each channel for a different layer
# d>0.5 selects all pixels with a level >50% in that channel
d = image.get_pixel(x, y, c)
if d > image.max_value / 2
# Create a polygon corresponding to one pixel
p1 = RBA::DPoint.new(x * pw, y * ph)
p2 = RBA::DPoint.new((x + 1) * pw, (y + 1) * ph)
dpoly = RBA::DPolygon.new(RBA::DBox.new(p1, p2))
shapes.insert(dpoly.transformed(image.trans))
end
end
end
end
end
# commit transaction
lv.commit
end
end
app = RBA::Application.instance
mw = app.main_window
menu = mw.menu
menu.insert_separator("tools_menu.end", "name")
menu.insert_item("tools_menu.end", "image2gds", action)
It takes an image loading into the layout view with "File/Add Image", creates three layers - 1/0, 2/0, 3/0 and populates them with boxes corresponding to pixels on the R, G and B channels with values > 50%.
It's not too fast, in particular when you run it with the macro IDE open. So please be patient.
Comments
Hi Joe,
the image import feature is intended for visualization purposes, that's why no conversion to layout is provided currently.
It is possible to write a Ruby script that extracts information from the images and creates layout from that. The method of extracting layout depends on the application you have. For example, it would be possible to extract a gray level contour as a polygon, although that's more demanding on the algorithmic side.
What exactly are you looking for?
Best regards,
Matthias
I'm just looking for a nice method to convert images to gds2 in order to generate Logos for ICs. The image import feature of Klayout looks very good as it is possible to define the pixel size in microns. This is important because the logos shouldn't violate certain design rules.
The only thing which I need in addition is to extract 2 or 3 different colours as polygons or rectangles.
Best regards,
Joe
Hi Joe,
I prepared a sample script that demonstrates how to convert an image to layout.
To install the script, copy the code below to a file with a suffix .rbm, i.e. "image2gds.rbm" into
the installation directory (that is where the klayout executable is).
This script will create a new menu entry in the "Tools" menu: "Image channes to layers".
The function will take the pixel data of all images loaded and convert each pixel into a rectangle in
predefined layers, one per color channel. Each pixel is converted into a rectangle when the respective color value in that
channel is above 50%. Three layers can be specified whose layer and datatype are hard coded in
the "gds2_layers" array around line 17. Currently, layers 1, 2 and 3 are used for red, green and blue
channels respectively. Datatype is 0.
The script should be regarded as an example. I hope I added enough comments to enable you to adjust
the script to your needs.
Best regards,
Matthias
Putting the .rbm script in the same location as klayout binary doesn't work. If I put it in the current working directory, it works.
many thanks for your script.
It works pretty fine and it is exactly what I was looking for!
Might be a good idea to include a similar function in a new release of KLayout.
@Jay: It works also in the directory where the KLayout binary is (I'm using the windows version of KLayout at the moment).
Best regards,
Joe
Hi,
I hope that script serves as a preliminary solution.
@Jay: the problem with the .rbm files may be connected with the shell or system you are using. I am also not able to reproduce it on Ubuntu and Windows. However I am interested in feedback from other platforms.
Best regards,
Matthias
I am using klayout on CentOS 4.7 and if I put the .rbm script in the directory where I start klayout, it works but putting the .rbm script in the same location as klayout binary doesn't work.
I tried to set also RUBYLIB and RUBYPATH w/ the directory containing .rbm files but no changes, seem not to be used.
Best Regards,
Joaquim
Hi Joaquim,
KLayout derives the installation path form the $0 argument. I learned that shells behave differently and for some shells the absolute path cannot be derived from that argument.
Currently, one workaround is to start KLayout with the full path, i.e.
Maybe you can set an alias for this command, so it's less effort typing.
For the next release I plan to add a more consistent ruby module installation scheme which should avoid those problems.
Best regards,
Matthias
Hi Joaquim,
in the lastest version 0.19.3 you can set an environment variable $KLAYOUT_PATH which points to the directory containing the .rbm files.
Best regards,
Matthias
it works fine. Thanks.
Best Regards,
Joaquim
Please, can you give more details on its usage ?
Thanks,
OkGuy
Hi,
the idea is to first add an image with "Edit/Add Image", adjust the position and size and then let the script convert that image (or the images) to GDS geometry.
Is that what you are looking for?
Matthias
I'm interesed in converting an image to a polygon, by extracting the gray level as commented above.
Some one to help me?
Thanks
Hi,
there are numerous vectorizers out on the net. If you need a CAD format, you can try online image to DXF translators (google for "image to dxf converter online").
Matthias
can you please past your code that you have modified .
Thank you
Your script to import an image to gds looks very good! However, do I need to add this script to the source file, and compile klayout myself? I installed the binary version on my mac pro. Could you possible to run it with the binary version?
Thank you
Hello,
no, no need to recompile. You'll find a lot of documentation about using and writing scripts here: http://www.klayout.de/doc/programming/index.html.
Matthias
To convert your raster image into PDF, I use PoTrace (a command-line tool that is also built into InkScape), or just do it through InkScape itself.
Like this tutorial: http://www.wikihow.com/Convert-Raster-to-Vector
-- see the 2nd "inkscape" method
POTrace (command-line tool): http://potrace.sourceforge.net
Then convert the PDF into DXF - InkScape can also save into DXF (Autocad R14 compatible). I was then able to open this in KLayout (or AutoCad)! Of course, then you can save it as GDSii or any other format KLayout supports.
Hi all,
I've create a Python Macro script to convert image to Oasis file, along with a simple GUI. You may take a look if interested. https://github.com/zwh42/KLayout_tools/blob/master/image_to_oasis_file/image_to_oasis.py
Thanks
updated path: https://github.com/zwh42/KLayout-PyMacros/tree/master/image_to_oasis_file
Thanks a lot for sharing this! :-)
Matthias
Hi Matthias,
The code above of December 2009 worked fine for the previous version of KLayout I was using (don't remember which one), but for version 0.25.8., the generated gds structures are scaled by 1/DBU compared to the image. I removed the /DBU or *DBU from your code to make it work as before, but can you tell me why the code behaves differently???
Thank you in advance.
Congratulations with releasing 0.26.: Looking forward to try it soon!!!
Cheers,
Tomas
Ok ... simple question but long answer :-)
I think the old version was something like 0.24.x. Version 0.25 got a more consistent coordinate space system and some methods were deprecated (specifically "transformed_cplx"). That is the reason why the behaviour is different.
Background: in 0.25.x, the "D" shape primitives (DBox, DPolygon, DPath ...) are taken as being in absolute physical units, namely "micrometers". So a DBox with 0,0 .. 10,20 is a 10x20µm box. The integer coordinate types (Box, Polygon, Path ...) are taken as begin in database units. In contrast to 0.24.x you can insert "D" types into a layout. In this case, micrometers are translated into database units internally. This is pretty convenient because you can essentially work (to some extend) with the micrometer-unit objects, hence with physical coordinates.
As a consequence, the transformations had to be made consistent too. The complex transformations now are specific to the input type and output type. So "ICplxTrans" transforms an integer-type object into another integer-type object (i.e. DBU-to-DBU transformation). "DCplxTrans" transforms micrometer-unit objects to the same. "CplxTrans" transformes DBU to micrometer-unit objects. Specifically "CplxTrans::new(dbu)" will perform this scaling. "VCplxTrans" is the inverse type of "CplxTrans" and will convert micrometer-unit objects to DBU unit ones.
Before 0.25.x, the complex transformations were more or less interchangeable and VCplxTrans wasn't existing at all. With 0.25.x and later, ICplxTrans and CplxTrans are very different as they transform into a different unit space. There was only one definition of "transformed_cplx" available and it converted into "D" space always without the intention of doing a unit translation. So this method was inconsistent and I deprecated it. Instead, "transformed" for the Box (Polygon, ...) was enhanced by providing overloads which take "Trans" (simple), "ICplxTrans" for DBU-to-DBU transformation and "CplxTrans" for providing DBU-to-µm transformations. This is the method intended for this purpose.
So the problem is there:
In the case of the above code, the transformation is ICplxTrans because we like to stay within DBU units. As mentioned "transformed_cplx" is not consistent and will transform into "D" types, hence micrometer units. So implicitly, DBUs are converted to micrometers which is the effect you observe.
The solution is simple: replace "transformed_cplx" by "transformed".
However, with the new, consistent definition, the code can be entirely written in micrometer units. This is very convenient as the image attributes already come in micrometers. This is the new partial code (I hope it's self-explaining):
Matthias
I was pointed to the original script and it's no longer working properly (it's from 2009!).
So here is the current version:
It takes an image loading into the layout view with "File/Add Image", creates three layers - 1/0, 2/0, 3/0 and populates them with boxes corresponding to pixels on the R, G and B channels with values > 50%.
It's not too fast, in particular when you run it with the macro IDE open. So please be patient.
Matthias