It would be nice to have a module to be added in the menus to help us to calculate the density of a layer.
Thanks to this module : http://www.klayout.de/useful_scripts.html#cell_bbox.rbm
and this routine : klayout.de/forum/comments.php?DiscussionID=164
But, I don't know how to make a window to select a layer to calculate its density :(
Thanks,
OkGuy
Comments
Hi OkGuy,
here is some derived script that adds some "user interface". It computes the density on the selected layer and asks for the tile dimensions in a input dialog. It will also use the cell's bounding box to derive the tiling area.
This is hopefully some acceptable compromise. "Real" user interface are possible using qtruby or the Qt integration which will come with the next version.
Best regards,
Matthias
I modified it to include it in the tools menu.
However, I did expected a figure:
Layer density = ..% of the selected area
How to calculate the area of the selected layer / the total area ?
Thanks,
OkGuy
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# DESCRIPTION: Compute area of selected shapes
#
# Run the script with
# klayout -rm density_map.rbm ...
# or put the script as "density_map.rbm" into the installation path (on Unix for version <=0.21:
# set $KLAYOUTPATH to the installation folder).
#
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
# $density_map = RBA::Action.new
# $density_map.title = "Density Map"
$density_map = MenuAction.new( "Layer density map", "" ) do
app = RBA::Application.instance
mw = app.main_window
lv = mw.current_view
if !lv || !lv.active_cellview
raise "Multiclip: No view or no layout active"
end
cl = lv.current_layer
if cl.is_null? || cl.current.layer_index < 0
raise "No layer selected to create the density map from"
end
input_layer = cl.current.layer_index
value = RBA::InputDialog.get_string("Tile Size", "Enter the tile size in micron (x,y)", "50,50");
if !value.has_value?
return
end
value = value.value
if value !~ /^\d*(\.\d*)?,\d*(\.\d*)?$/
raise "Expected a tile size (x, y) in micron"
end
va = value.split(/,/)
xw = va[0].to_f
yw = va[1].to_f
if xw < 1.0e-6 || yw < 1.0e-6
raise "Invalid tile size"
end
# Let's start ...
# Set up the environment:
app = RBA::Application.instance
mw = app.main_window
lv = mw.current_view
if !lv || !lv.active_cellview
raise "Density map: No view or no layout active"
end
# obtain the pointers to the layout and cell
lay = lv.active_cellview.layout
top = lv.active_cellview.cell_index
top_name = lay.cell_name(top)
top_bbox = lay.cell(top).bbox
dbu = lay.dbu
bbox = lv.active_cellview.cell.bbox
xmin = bbox.left * dbu
xmax = bbox.right * dbu
ymin = bbox.bottom * dbu
ymax = bbox.top * dbu
# Start to collect the data:
data = []
# proceed row by row
# (for each row we use the multi_clip_into which is more efficient than single clips per window)
nrows = 0
y = ymin
while y < ymax-1e-6
# Prepare a new layout to receive the clips
# Hint: we need to clip all layers (clip does not support clipping of one layer alone currently).
cl = RBA::Layout.new
cl.dbu = dbu
lay.layer_indices.each do |li|
cl.insert_layer_at(li, lay.get_info(li))
end
# Prepare the clip boxes for this row.
# Note: because clip only works with boxes that overlap the cell's bounding box currently, we
# have to operate with a subset of fields to support the general case.
boxes = []
x = xmin
ncolumns = 0
colstart = nil
while x < xmax-1e-6
b = RBA::Box.new((0.5 + x / dbu).floor, (0.5 + y / dbu).floor, (0.5 + (x + xw) / dbu).floor, (0.5 + (y + yw) / dbu).floor);
if b.overlaps?(top_bbox)
colstart ||= ncolumns
boxes.push(b)
end
x += xw
ncolumns += 1
end
columns = []
ncolumns.times { columns.push(0.0) }
$stdout.write "Running row y=#{y}..#{y+yw} "
if colstart
col = colstart
# Actually do the clip
cells = lay.multi_clip_into(top, cl, boxes)
ep = RBA::ShapeProcessor.new
merged = RBA::Shapes.new
# Compute the area within area box
cells.each do |c|
# merge the shapes to polygons and compute the area
ep.merge(cl, cl.cell(c), input_layer, merged, true, 0, false, false)
a = 0
merged.each do |m|
a += m.polygon.area
end
# compute and store the density
a = a * dbu * dbu / (xw * xw)
columns[col] = a
col += 1
$stdout.write((0.5 + a * 100).floor.to_s)
$stdout.write " "
$stdout.flush
end
end
puts ""
columns.each { |d| data.push(d) }
y += yw
nrows += 1
end
if nrows * ncolumns > 0
img = RBA::Image.new(ncolumns, nrows, data)
img.pixel_width = xw
img.pixel_height = yw
img.trans = RBA::DCplxTrans.new(1.0, 0.0, false, RBA::DPoint.new(xmin, ymin))
if $density_map_image_id
lv.replace_image($density_map_image_id, img)
else
lv.insert_image(img)
$density_map_image_id = img.id
end
end
end
app = RBA::Application.instance
mw = app.main_window
menu = mw.menu
menu.insert_item("tools_menu.end", "density_map", $density_map)
Hi OkGuy,
from the explanation you gave initially I thought you might be interested in the density map. At least that is what the forum entry is about. Could you be somewhat specific what exactly you require? Do you need the total density? Do you need the density map at all?
Even if you require the overall density it may be a good idea to compute a density map first and then average over the individual tiles. That avoids memory allocation issues with large layouts because the algorithm works on a tile-by-tile basis. You still need to enter a tile size which should be reasonably small (i.e 500x500 micron max). Please note that the chip dimensions are rounded to the tile size, to the results are most accurate when the tile size is an integer fraction of the chip size.
The code change would be to replace that piece:
by something like:
Regards,
Matthias
Sorry, for my bad explanation.
It works fine, even on a very large dimension.
Thank you very much!
OkGuy
I tried using it.
I get completely black map in all my attempts
When I modify macro to only report the total density, I get a result different than zero which seems to make sense.
So i don't understand why the density map is all black.
Itamar
Hi Itamar,
the script should print out the densities in percent on the console (if you run it in the macro editor for example). What are those numbers? Do they make sense?
If yes, then it's somehow related to the data mapping inside the image.
Matthias
I think the problem is when doing density for via layers which have inherent low density.
When I ran it for poly i got a valid map.
Maybe it's possible to do the color scaling based on relative layer density and not based on fixed 0-100% density
This is the printout for VIA2:
Running row y=0.0..500.0 1 1 1 1 1 1 2 0
Running row y=500.0..1000.0 1 1 0 0 0 0 1 0
Running row y=1000.0..1500.0 1 0 0 1 0 0 0 0
Running row y=1500.0..2000.0 0 0 0 1 0 0 0 0
Running row y=2000.0..2500.0 1 0 0 1 0 0 0 0
Running row y=2500.0..3000.0 1 0 0 1 0 0 0 0
Running row y=3000.0..3500.0 1 0 0 1 0 0 1 0
Running row y=3500.0..4000.0 0 0 0 0 0 0 0 0
This is the printout for poly:
Running row y=0.0..500.0 19 19 31 33 29 34 36 0
Running row y=500.0..1000.0 19 24 20 40 27 45 42 0
Running row y=1000.0..1500.0 41 41 40 26 42 48 41 0
Running row y=1500.0..2000.0 47 51 51 34 51 52 49 0
Running row y=2000.0..2500.0 45 49 49 32 48 49 47 0
Running row y=2500.0..3000.0 46 49 49 33 49 50 47 0
Running row y=3000.0..3500.0 40 40 42 34 47 40 39 0
Running row y=3500.0..4000.0 1 0 0 2 2 0 0 0
Itamar
Sure that's possible. Just exploit the power of Ruby:
Matthias
Thank you for the free platform and the discussion forum.
I have a problem where I need to compute density of different layers and then scale them and merge them into one image. Is this something you think the above code can be modified to?
Sorry I am a beginner with Ruby coding so asking for your help with the development.
Also, when I used the above code, the image is in grey scale, is it possible to convert to visible color scale.
Thanks,
Dheeraj
Something to help you get started.
The line that makes the image is
Look at the class Image under Public Constructors. The above is using the fourth version of the constructor listed there, because it has three arguments: ncolumns, nrows, data.
When you see "double[]" in front of one of the Public Constructor's arguments, it means that value should be an array, which "data" is.
Anyway instead of the fourth version of the Public Constructor, you want one of the other versions where you put red, green, and blue separately. For example for the result to be red you need first to make an array of zeros the same size as your data array, then to create the image with r, g, b arguments:
If you want it to be purple you can do:
To get a red image overlaid on a purple one, just call the first one img1 and the second one img2 or something. I haven't tried this but I guess it should work.
HTH
P.S. This code has been improved slightly and given a GUI: See TRT. After downloading it per the instructions, look under TRT menu > Layout > Density calculation.
Hi David,
thanks a lot - perfect intro :-)
I realize, that the image cannot be saved to PNG directly. If you want that you'll need to traverse the image pixel by pixel and generate a QImage object. This can be saved. I am overhauling the Ruby/Python API currently and I guess that interfaces to QImage and/or PNG/JPG files make some sense.
Thanks, Matthias
TRT code did not work for density calculations though:(
Mathias,
I have one more issue with using the code (just cumbersome).
Currently I am running the code by clicking macros and then execute. However, I would like the "layer density" tab to appear right when I open any layout. Could you suggest how to modify the code to achieve this?
Thanks,
Dheeraj