Klayout save image issue.

Hi sir,
I meet with a Klayout save image issue.
as below , I make some dimention mark and save the image to some of png file .
I can make dimention mark (ruler) and output the file.
But , when I using the ruby code as below , I will get picture only the mark as picture 1.
May I have your help to check what is issue?


(only a ruler mark, no any pattern)


(it is corrce one , when I output picture by manual)

layoutView = RBA::Application::instance.main_window.current_view.active_cellview.layout
layout_view = RBA::Application.instance.main_window.current_view
cell_view = layout_view.active_cellview
current_cell_name=cell_view.cell_name
app = RBA::Application.instance
mw = RBA::Application::instance.main_window
view = mw.current_view
view.clear_annotations
cell_list_A = Array.new()
fields = Array.new()
each_cell = layoutView.each_cell

each_cell.each do |xxxx|
if xxxx.name.match('[Ff][Ii][Ee][Ll][Dd]') && (xxxx.name.match("PA") || xxxx.name.match("PB")) then
fields << xxxx
end
end

fields.each do |cell_in_field|
cell_in_field.each_inst.each do |yyy|
if yyy.cell.name.match("Dark") then
cell(yyy.cell.name)
output_file1 = "D:\\#{cell_in_field.name}.png"
layout_view = RBA::Application.instance.main_window.current_view
cell_view = layout_view.active_cellview
app = RBA::Application.instance
mw = RBA::Application::instance.main_window
view = mw.current_view
view.clear_annotations

shift=5000
dark=input(93,0)
part_A=dark.xor(dark.extents)
pointx=Array.new
pointy=Array.new
part_A.each do |xx|
points=xx.to_s.gsub("(","").gsub(")","").split(";")
points.each do |yy|
x=yy.split(",")[0].to_f
y=yy.split(",")[1].to_f
pointx << x
pointy << y
  end
end

if yyy.cell.name.match("PA") then
p1x=part_A.bbox.p1.to_s.split(",")[0].to_f
p1y=0
p2x=part_A.bbox.p2.to_s.split(",")[0].to_f
p2y=pointy.uniq.sort[-2].to_f

ant = RBA::Annotation::new
ant.p1 = RBA::DPoint::new(p2x-shift,p1y)
ant.p2 = RBA::DPoint::new(p2x-shift,p2y)
ant.main_position,ant.outline,ant.style =3,0,RBA::Annotation::StyleArrowBoth
view.insert_annotation(ant)
end

if yyy.cell.name.match("PB") then
p1x=part_A.bbox.p1.to_s.split(",")[0].to_f
p1y=pointy.uniq.sort[0].to_f + 10
p2x=part_A.bbox.p2.to_s.split(",")[0].to_f
p2y=pointy.uniq.sort[-1].to_f

ant = RBA::Annotation::new
ant.p1 = RBA::DPoint::new(p1x,p1y)
ant.p2 = RBA::DPoint::new(p2x,p1y)
ant.main_position,ant.outline,ant.style =3,0,RBA::Annotation::StyleArrowBoth
#view.insert_annotation(ant)

ant = RBA::Annotation::new
ant.p1 = RBA::DPoint::new(p2x-shift,p1y)
ant.p2 = RBA::DPoint::new(p2x-shift,p2y)
ant.main_position,ant.outline,ant.style =3,0,RBA::Annotation::StyleArrowBoth
view.insert_annotation(ant)
end

cell = layoutView.cell_by_name(cell_in_field.name)
cv = RBA::CellView::active
cv.cell_name =cell_in_field.name
view.max_hier
rect_drawn1 = RBA::DBox::new(-34000.0, 0, 34000.0, -26700.0)
view.zoom_box(rect_drawn1)
view.save_image("#{output_file1}", 1000,500)
end
end
end

Comments

  • by the way, how to remove the ration mark (1000um) icon in my picture?

  • edited July 2023

    Hi jiunnweiyeh,

    I think there are some possible reasons causing shapes are not shown correctly in the screenshot

    Desired cell is not set to new top
    show cell with "cellName" as the new top before taking screenshot

        layoutView      = RBA::Application.instance.main_window.current_view         
        cellView        = layoutView.active_cellview() 
        layout          = cellView.layout
        cellView.cell   = layout.cell(cellName)
    

    Cell or layer is hidden
    show all layers, cell, and hier

        layoutView.max_hier
        layoutView.show_all_cells
        layoutView.clear_annotations
        layoutView.each_layer do |layerProperties|
            layerProperties.visible = true
        end
    

    Screenshot without scale bar
    For the show/hide screenshot scale bar I didn't find any setting in Doc that provides control to this.
    A workaround that provides screenshots without a scale bar
    1. Increase the zoom box by 10% of the box height.
    2. Get a screenshot with 10% extra pixels in the height direction
    3. Use QImage.copy function to get the image with 10% pixels trimmed away from the bottom
    4. Save the image

    Code example

    def customScreenShot(cellName, savePath, imageWidth, imageHeight, zoomBox = None, annotationsLines = [], scaleBar = true)
        layoutView      = RBA::Application.instance.main_window.current_view         
        cellView        = layoutView.active_cellview() 
        layout          = cellView.layout
    
        #show everything and clear annotations, cell, layer and hier.
        layoutView.max_hier
        layoutView.show_all_cells
        layoutView.clear_annotations
        layoutView.each_layer do |layerProperties|
            layerProperties.visible = true
        end
    
        #move to cell and add annotations
        cellView.cell = layout.cell(cellName)
        annotationsLines.each do |line|
            annotation    = RBA::Annotation.new()
            annotation.p1 = RBA::DPoint.new(line[0], line[1])
            annotation.p2 = RBA::DPoint.new(line[2], line[3])
            layoutView.insert_annotation(annotation)
        end        
    
        if scaleBar then
            #save screen shot normally
            layoutView.zoom_box(RBA::DBox::new(*zoomBox))
            layoutView.save_image("#{savePath}#{cellName}.png", imageWidth,imageHeight)
    
        else
            #increase zoomBox by 10%, get image width 10% height
            #crop away extra 10% of the image what with scale bar
            #save the crop image
    
            cropPercent   = 0.1
            scaledPercent = 1 + cropPercent   
            boxHeight     = (zoomBox[3] - zoomBox[1])
            cropZoomBox   = [zoomBox[0], zoomBox[1] - boxHeight * cropPercent, zoomBox[2], zoomBox[3]]
    
            layoutView.zoom_box(RBA::DBox::new(*cropZoomBox))
            screenImage  = layoutView.get_image(imageWidth, imageHeight * scaledPercent)
            cropImage    = screenImage.copy( 0, 0, imageWidth, imageHeight)
            cropImage.save("#{savePath}#{cellName}_noScaleBar.png")
        end
    
    end
    

    The function above can be used in this way.
    this takes two images from cell "A" and "B" with and without the scale bar respectively.

    savePath    = "D:\\"
    imageWidth  = 500
    imageHeight = 500
    
    customScreenShot(
        "A", savePath, imageWidth, imageHeight, 
        zoomBox          = [-130, -130, 130, 130], 
        annotationsLines = [[0,0,25,25], [25,25, 25, 50]], 
        scaleBar         = true
    )
    
    customScreenShot(
        "A", savePath, imageWidth, imageHeight, 
        zoomBox          = [-130, -130, 130, 130], 
        annotationsLines = [[0,0,25,25], [25,25, 25, 50]], 
        scaleBar         = false
    )
    
    customScreenShot(
        "B", savePath, imageWidth, imageHeight, 
        zoomBox          = [-250, -250, 250, 250], 
        annotationsLines = [[0,0,55,55], [55,55, 0, 0]], 
        scaleBar         = true
    )
    
    customScreenShot(
        "B", savePath, imageWidth, imageHeight, 
        zoomBox          = [-250, -250, 250, 250], 
        annotationsLines = [[0,0,55,55], [55,55, 0, 0]], 
        scaleBar         = false
    )
    
  • edited July 2023

    @RawrRanger Many thanks for your efforts to support this case!

    The magic code for disabling the scale bar (ruler) is:

    layoutView.set_config("grid-show-ruler", "false")
    

    You can basically set every property that is available in the "klayoutrc" file (in C:\Users\you\KLayout or ~/.klayout). To set the ruler color to red for example, use this:

    layoutView.set_config("grid-ruler-color", "#ff0000")
    

    Best regards,

    Matthias

  • Hi Matthias,
    I can follow your code to disable the scale bar.
    But I still meet with save image issue even I make it view.max_hier
    and enable all the layers what I want to shown in picture.
    Can you help for that?
    Thank.s

    layoutView = RBA::Application::instance.main_window.current_view.active_cellview.layout
    layout_view = RBA::Application.instance.main_window.current_view
    layout_view.set_config("grid-show-ruler", "false")
    cell_view = layout_view.active_cellview
    current_cell_name=cell_view.cell_name
    app = RBA::Application.instance
    mw = RBA::Application::instance.main_window
    view = mw.current_view
    view.clear_annotations
    cell_list_A = Array.new()
    fields = Array.new()
    each_cell = layoutView.each_cell
    
    each_cell.each do |xxxx|
    if xxxx.name.match('[Ff][Ii][Ee][Ll][Dd]') && (xxxx.name.match("PA") || xxxx.name.match("PB")) then
    fields << xxxx
    end
    end
    
    fields.each do |cell_in_field|
    cell_in_field.each_inst.each do |yyy|
    if yyy.cell.name.match("Dark") then
    cell(yyy.cell.name)
    output_file1 = "D:\\#{cell_in_field.name}.png"
    layout_view = RBA::Application.instance.main_window.current_view
    cell_view = layout_view.active_cellview
    app = RBA::Application.instance
    mw = RBA::Application::instance.main_window
    view = mw.current_view
    view.clear_annotations
    
    shift=5000
    dark=input(93,0)
    part_A=dark.xor(dark.extents)
    pointx=Array.new
    pointy=Array.new
    part_A.each do |xx|
    points=xx.to_s.gsub("(","").gsub(")","").split(";")
    points.each do |yy|
    x=yy.split(",")[0].to_f
    y=yy.split(",")[1].to_f
    pointx << x
    pointy << y
      end
    end
    
    if yyy.cell.name.match("PA") then
    p1x=part_A.bbox.p1.to_s.split(",")[0].to_f
    p1y=0
    p2x=part_A.bbox.p2.to_s.split(",")[0].to_f
    p2y=pointy.uniq.sort[-2].to_f
    
    ant = RBA::Annotation::new
    ant.p1 = RBA::DPoint::new(p2x-shift,p1y)
    ant.p2 = RBA::DPoint::new(p2x-shift,p2y)
    ant.main_position,ant.outline,ant.style =3,0,RBA::Annotation::StyleArrowBoth
    view.insert_annotation(ant)
    end
    
    if yyy.cell.name.match("PB") then
    p1x=part_A.bbox.p1.to_s.split(",")[0].to_f
    p1y=pointy.uniq.sort[0].to_f + 10
    p2x=part_A.bbox.p2.to_s.split(",")[0].to_f
    p2y=pointy.uniq.sort[-1].to_f
    
    ant = RBA::Annotation::new
    ant.p1 = RBA::DPoint::new(p1x,p1y)
    ant.p2 = RBA::DPoint::new(p2x,p1y)
    ant.main_position,ant.outline,ant.style =3,0,RBA::Annotation::StyleArrowBoth
    #view.insert_annotation(ant)
    
    ant = RBA::Annotation::new
    ant.p1 = RBA::DPoint::new(p2x-shift,p1y)
    ant.p2 = RBA::DPoint::new(p2x-shift,p2y)
    ant.main_position,ant.outline,ant.style =3,0,RBA::Annotation::StyleArrowBoth
    view.insert_annotation(ant)
    end
    
    cell = layoutView.cell_by_name(cell_in_field.name)
    cv = RBA::CellView::active
    cv.cell_name =cell_in_field.name
    view.max_hier
    rect_drawn1 = RBA::DBox::new(-34000.0, 0, 34000.0, -26700.0)
    view.zoom_box(rect_drawn1)
    view.save_image("#{output_file1}", 1000,500)
    end
    end
    end
    
  • edited July 2023

    Hi jiunnweiyeh,

    I don't know whether I read you code correctly or not, but my best guess is you are trying to do a automatic measuring the inner dimension of cell with a border layer (93, 0) and take the screenshot.

    Because I don't have the gds that is suitable for debug, so I create a mockup for this test.

    Test case and result:

    Flow of process
    1. filter with cells that is a sub-cell of Field_XXX_PA/PB which it's cell name looks like DARK_YYY_PA/PB.
    2. get the bound box of the "hole" of layer (93, 0)
    3. add measurement rulers with offset
    4. set cell to top, zoom to area, and take screenshot

    (1) Filter Cell names
    by calling this function it returns a array that contains cell that is placed under "FIELD" cells and it's name looks like DARK_YYY_PA/PB.

    #1. filter all cells with Fleld and PA/PB in cellname
    #2. check sub-cells in "field" cell, if with DARK and PA/PB in cell name add to array
    #3. return a arraay with unique cell names
    def filterCells()
        layout        = RBA::Application.instance.main_window.current_view.active_cellview().layout     
        cellNameArray = Array.new
    
        layout.each_cell do |eachCell|
            cellName = eachCell.name
            if cellName.upcase.match("FIELD") && (cellName.upcase.match("PA") || cellName.upcase.match("PB")) then
                eachCell.each_inst do |eachInst|
                    instCellName = eachInst.cell.name
                    if instCellName.upcase.match("DARK") && (cellName.upcase.match("PA") || cellName.upcase.match("PB")) then
                        cellNameArray << instCellName
                    end
                end
            end
        end
        return cellNameArray.uniq.sort
    end
    

    (2) Get the boundbox of Hole in layer
    after aquire cel name list by filterCells()
    we can feed name into this founction and it returns a bound box of the "hole" (partA in your code)
    in your case layer and data type should be (93, 0)

    #1. get cell by name
    #2. get shapes in that cell
    #3. get holes in those shapes
    #4. return the bound box for hole
    def cellLayerHoleDBox(cellName, layer, datatype)
        layout        = RBA::Application.instance.main_window.current_view.active_cellview().layout    
        cell          = layout.cell(cellName)
        cellLayer     = layout.layer(layer, datatype)
        cellShapesReg = RBA::Region::new(cell.begin_shapes_rec(cellLayer))
        partAReg      = cellShapesReg.holes
        return RBA::DBox.new(partAReg.bbox * layout.dbu)
    end
    

    (4a) Get screen shot
    take screen shot and can apply custom ruler object
    rulers only accept "annotation object"

    #1. show cell as new top and show all layer, cells and clear annotations
    #2. add annotations
    #3. zoom to box 
    #4. save image to file
    def customScreenShot(cellName, savePath, imageWidth, imageHeight, rulers = [], zoomBox = None, scaleBar = false)
        layoutView    = RBA::Application.instance.main_window.current_view         
        cellView      = layoutView.active_cellview() 
        layout        = cellView.layout
        rulerSetting  = layoutView.get_config("grid-show-ruler")
        cellView.cell = layout.cell(cellName)
    
        layoutView.max_hier
        layoutView.show_all_cells
        layoutView.clear_annotations
    
        layoutView.each_layer do |layerProperties|
            layerProperties.visible = true
        end
    
        rulers.each do |ruler|
            layoutView.insert_annotation(ruler)
        end     
        layoutView.set_config("grid-show-ruler", scaleBar.to_s)
        layoutView.zoom_box(RBA::DBox::new(*zoomBox))
        screenImage  = layoutView.save_image(savePath, imageWidth, imageHeight)
        layoutView.set_config("grid-show-ruler", rulerSetting.to_s)
    end
    

    (4b)Custom "Extend"ruler
    a ruler that with two end extended and can adjust extension value

    def extendedRuler(line = [], extension = 3500, rulerOffset = 200)
        layoutView  = RBA::Application.instance.main_window.current_view      
        x1, y1      = line[0], line[1]
        x2, y2      = line[2], line[3]
        dx, dy      = (x2 - x1), (y2 - y1)
        dl          = ((dx ** 2) + (dy ** 2)) ** (0.5)
        rulerOffset = (extension.abs > rulerOffset.abs) ? (extension.abs - rulerOffset.abs) * (extension <=> 0) : extension
        x1_e, y1_e  = x1 + extension   * (dy/dl), y1 - extension   * (dx/dl)
        x2_e, y2_e  = x2 + extension   * (dy/dl), y2 - extension   * (dx/dl)
        x1_r, y1_r  = x1 + rulerOffset * (dy/dl), y1 - rulerOffset * (dx/dl)
        x2_r, y2_r  = x2 + rulerOffset * (dy/dl), y2 - rulerOffset * (dx/dl)
    
        ruler       = RBA::Annotation.new()
        bar1        = RBA::Annotation.new()
        bar2        = RBA::Annotation.new()
        ruler.p1    = RBA::DPoint.new(x1_r, y1_r)
        ruler.p2    = RBA::DPoint.new(x2_r, y2_r)
        bar1.p1     = RBA::DPoint.new(x1,   y1  )
        bar1.p2     = RBA::DPoint.new(x1_e, y1_e)
        bar2.p1     = RBA::DPoint.new(x2,   y2  )
        bar2.p2     = RBA::DPoint.new(x2_e, y2_e)
    
        ruler.main_position = 3
        ruler.outline       = 0
        ruler.style         = RBA::Annotation::StyleArrowBoth
        bar1.style          = RBA::Annotation::StyleLine
        bar2.style          = RBA::Annotation::StyleLine
        bar1.fmt            = ""
        bar2.fmt            = ""
    
        return [ruler, bar1, bar2]
    end
    

    (3) A test case DEMO
    the test case can be run with attached field_test.GDS file.
    the result is shown as the image above, full code and test files is included in zip

    #1. loop through cell name list provided by filterCells() function
    #2. get layer hole bound box by cellLayerHoleDBox() function
    #3. creat annotation line by bound box
    #4. take screenshot using customScreenShot() function
    imgW, imgH    = 1000, 500
    zoomBox       = [-34000.0, 0, 34000.0, -26700.0]
    
    cellNameArray = filterCells() 
    cellNameArray.each do |cellName|
        hDBox    = cellLayerHoleDBox(cellName, 2, 0)
        savePath = "D:\\#{cellName}.png"
        rulers = []
    
        if cellName.match("PA") then
            rulers = [
                *normalRuler([hDBox.p1.x, hDBox.p1.y, hDBox.p2.x, hDBox.p2.y])
            ]
        end
    
        if cellName.match("PB") then
            rulers = [
                *extendedRuler([hDBox.p1.x, hDBox.p1.y, hDBox.p2.x, hDBox.p2.y], -6500),
                *extendedRuler([hDBox.p1.x, hDBox.p1.y, hDBox.p1.x, hDBox.p2.y], 36000),
                *extendedRuler([hDBox.p1.x, hDBox.p2.y, hDBox.p2.x, hDBox.p2.y], 20000)
            ]
        end
        customScreenShot(cellName, savePath, imgW, imgH, rulers, zoomBox, false)        
    end
    
  • edited July 2023

    Hi jiunnweiyeh,

    I run a quick test with your code with my test gds, and I remove line by line and found that if save_image fountion is placed inside "each_inst" loop, then it might not generate image correctly.

    Currently I am not quite sure why this is the case, but following two snippet of code provides such comparison.

    image captured by following code

    (1) the on the left shows no shape was captured with code labeled with "this not work"
    (2) the on the right side did capture shapes successfully, but to me this two piece of code should behaves the same

    mw         = RBA::Application::instance.main_window
    layoutView = mw.current_view 
    cellView   = layoutView.active_cellview
    layout     = cellView.layout
    filePath   = "D:\\"
    
    #this not work, capture inside "each_inst" loop
    layout.cell("TOP").each_inst do |inst|
        cellView.cell = layout.cell("TOP")
        layoutView.zoom_box(RBA::DBox::new(-34000.0, 0, 34000.0, -26700.0))
        layoutView.save_image("#{filePath}[notwork]TOP.png", 1000,500)
        break
    end
    
    #this works, capture inside "each_cell" loop
    layout.each_cell do |cell|
        cellView.cell = layout.cell("TOP")
        layoutView.zoom_box(RBA::DBox::new(-34000.0, 0, 34000.0, -26700.0))
        layoutView.save_image("#{filePath}[work]TOP.png", 1000,500)
        break
    end
    
  • Hi RawrRanger
    Yes , it is what the issue I means.
    if save_image code been placed in "each_inst" loop or not,
    I will get differnece image result.
    I will base on your code to modify some of part for my solution.
    Thanks very much for your help.

  • edited July 2023

    Dear all,

    thanks for this fruitful discussion, specifically to @RawrRanger providing the code example that nicely reproduces the problem.

    I debugged the issue and frankly, it's a feature. Namely during iteration of the instances, the layout is put into "updates disabled" mode to prevent changes made inside the loop from spoiling the iterator with potentially catastrophic and hard to detect bugs.

    A side effect of that is that save_images will not work as it finds a hierarchy "under construction".

    Bottom line: don't use "save_image" while iterating over the instances.

    The solution is fairly trivial: instead of doing "save_image" from inside the "each_inst" iteration loop, collect the instances first and then iterate over them.

    Instead of:

    layout.cell("TOP").each_inst do |inst|
      ...
    end
    

    Use:

    instances = layout.cell("TOP").each_inst.to_a
    instances.each do |inst|
      ...
    end
    

    In my case this worked.

    Kind regards,

    Matthias

  • Hi Matthias
    Thanks for your help , I will try to modify my code.

Sign In or Register to comment.