Not signed in (Sign In)

Vanilla 1.1.4 is a product of Lussumo. More Information: Documentation, Community Support.

    • CommentAuthorokguy
    • CommentTimeApr 15th 2012
     
    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
    • CommentAuthorMatthias
    • CommentTimeApr 17th 2012 edited
     

    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

    $density_map = RBA::Action.new
    $density_map.title = "Density Map"
    $density_map.on_triggered 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_separator("@toolbar.end", "name")
    menu.insert_item("@toolbar.end", "density_map", $density_map)
    
    • CommentAuthorokguy
    • CommentTimeApr 18th 2012
     
    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)
    • CommentAuthorMatthias
    • CommentTimeApr 18th 2012 edited
     

    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:

    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
    

    by something like:

    if nrows * ncolumns > 0
      density = 0
      data.each { |d| density += d }
      density /= (nrows * ncolumns)
      RBA::MessageBox::info("Density", "The total density is #{'%.1f' % (density*100)}%", RBA::MessageBox::b_ok) 
    end
    

    Regards,

    Matthias

    • CommentAuthorokguy
    • CommentTimeApr 19th 2012
     
    Matthias,

    Sorry, for my bad explanation.
    It works fine, even on a very large dimension.

    Thank you very much!
    OkGuy
    • CommentAuthormikamar
    • CommentTimeMay 2nd 2014
     
    Hi-

    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
    • CommentAuthorMatthias
    • CommentTimeMay 2nd 2014
     

    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

    • CommentAuthormikamar
    • CommentTimeMay 6th 2014
     
    Hi 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
    • CommentAuthorMatthias
    • CommentTimeMay 7th 2014 edited
     

    Sure that's possible. Just exploit the power of Ruby:

    # normalize to 0..1
    max = data.max
    max < 1e-10 && raise("Overall density too low")
    data = data.collect { |d| d / max }
    
    # now the original code follows:
    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
    

    Matthias

    • CommentAuthordmohata
    • CommentTimeNov 7th 2016
     
    Hi 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
    • CommentAuthordavidnhutch
    • CommentTimeNov 8th 2016 edited
     

    Something to help you get started.

    The line that makes the image is

    img = RBA::Image.new(ncolumns, nrows, data)
    

    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:

    zeros = data.map{|i| i.map{|j| 0 }} # There's probably a better way to do this... Basically just make an array of zeros
    img = RBA::Image.new(ncolumns, nrows, data, zeros, zeros)
    

    If you want it to be purple you can do:

    halfs = data.map{|i| i.map{|j| j/2.0 }}
    zeros = data.map{|i| i.map{|j| 0 }}
    img = RBA::Image.new(ncolumns, nrows, halfs, zeros, halfs)
    

    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.

    • CommentAuthorMatthias
    • CommentTimeNov 8th 2016
     

    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

    • CommentAuthordmohata
    • CommentTimeApr 26th 2017
     
    Thank you so much David. I was able to change the color:)
    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