Convert Image to gds2 layer

joejoe
edited November 2009 in Layout
Hi!

Is it possible to convert the imported image to a gds2 layer?

Best regards,
Joe

Comments

  • edited November 2009

    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

  • joejoe
    edited November -1
    Hi 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
  • edited December 2009

    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

    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)
    
  • JayJay
    edited November -1
    My experience ...

    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.
  • joejoe
    edited December 2009
    Hi Matthias,

    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
  • edited November -1

    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

  • edited November -1
    Hi,

    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
  • edited November -1

    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.

    /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.

    Best regards,

    Matthias

  • edited November -1

    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

  • edited November -1
    Hi Matthias,

    it works fine. Thanks.

    Best Regards,
    Joaquim
  • edited November -1
    For this routine, I do not see how to load the image, for instance example.bmp
    Please, can you give more details on its usage ?

    Thanks,
    OkGuy
  • edited November -1

    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

  • edited November -1
    Hi,

    I'm interesed in converting an image to a polygon, by extracting the gray level as commented above.

    Some one to help me?

    Thanks
  • edited November -1

    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

  • edited November -1
    Thank you, Matthias. Finally, I have modified the script you attached above, and it works fine :)
  • edited November -1
    Hello @MarryM,

    can you please past your code that you have modified .

    Thank you
  • edited September 2015
    Hi @Matthias,

    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
  • edited November -1

    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

  • edited October 2015
    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.
  • 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

  • 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

  • edited September 2019

    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:

                  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
    

    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:


    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.

    Matthias

Sign In or Register to comment.