How to use the rasterize function to render a specified area of a GDS file?

assuming a pixel physical size of 20x20 nm, and how to achieve high-precision rendering?

Comments

  • edited December 2024

    Here is some code. I hope the comments are self-explaining:


    # Illustration of how to use Region.rasterize # This code takes the shapes from layer 66/20 # of the current cell and rasterizes a region # 0,1 .. 80,210 (µm) with a resultion of 100nm cell = pya.CellView.active().cell ly = cell.layout() layer = ly.layer(66, 20) # box to render in µm box_to_render = pya.DBox(0, 10, 80, 210) # pixel size in µm pixel_size = pya.DVector(0.1, 0.1) # creates a Region object with the shapes to render # using "box_to_render" to confine the search for shapes shapes_to_render = pya.Region(cell.begin_shapes_rec_touching(layer, box_to_render)) # Performs the rasterization # The result will be a 2d array in "areas" # where the value is the covered area per pixel # in square database units. um_to_dbu = pya.DCplxTrans(ly.dbu).inverted() box_to_render_dbu = um_to_dbu * box_to_render pixel_size_dbu = um_to_dbu * pixel_size nx = round(box_to_render_dbu.width() / pixel_size_dbu.x) ny = round(box_to_render_dbu.height() / pixel_size_dbu.y) # gives the areas covered in units of square data base units # (see comments below about "merged") areas = shapes_to_render.merged().rasterize(box_to_render_dbu.p1, pixel_size_dbu, nx, ny) # for illustration, write as grayscale image pixels = pya.PixelBuffer(len(areas[0]), len(areas)) amax = float(pixel_size_dbu.x * pixel_size_dbu.y) y = len(areas) for arow in areas: x = 0 y -= 1 for a in arow: value = int(round(255.0 * a / amax)) rgb = value * 0x10101 pixels.set_pixel(x, y, rgb) x += 1 pixels.write_png("/tmp/discussion_2630.png")

    A small test case of mine rendered a 800x2000 pixel image in less than a second:

    There is a caveat: the rasterize function will count overlapping shapes twice, hence the area may be larger than the maximum expected area. The "merged()" method will take care of that, by computing the merged polygons which are free of overlaps. If you pick a too complex region, for example a SRAM block, this method may need a lot of memory.

    If the box to render is small, there is little overhead however.

  • Thank you very much for your reply, I really appreciate it. If I need to render multiple layers, how can I do that? Perhaps the rasterized result can be converted to a grayscale image using the following code?

    ## Create a grayscale image array
    height = len(areas)
    width = len(areas[0])
    gray_array = np.zeros((height, width), dtype=np.uint8)
    amax = float(pixel_size_dbu.x * pixel_size_dbu.y)
    
    amax = float(pixel_size_dbu.x * pixel_size_dbu.y)
    
    area_values = np.array(areas).astype(float)
    gray_array = np.clip(255.0 * area_values / amax, 0, 255).astype(np.uint8)
    
    ## Reverse Y-axis
    gray_array = np.flipud(gray_array)
    
    cv2.imwrite('output_gray_image_cv2.png', gray_array)
    
  • Maybe, I don't know what "cv2" is. But I think basically the same happens than in my sample. But more efficient I guess.

    I you want to render multiple layers, you just need a loop. In my sample case, this line gives the layer to render:

    layer = ly.layer(66, 20)
    

    So you can call the procedure below with different values of "layer" and different output files of course.

    Matthias

  • Hi Matthias, thanks for your response. I'm trying to merge multiple layers with the following code, not sure if it's okay?

    layers_to_render = [(10, 7), (11, 7)] 
    
    all_shapes_to_render = pya.Region()
    
    for layer_tuple in layers_to_render:
        layer = ly.layer(layer_tuple[0], layer_tuple[1])
    
        shapes_to_render = pya.Region(cell.begin_shapes_rec_touching(layer, box_to_render)).merged()
    
        all_shapes_to_render += shapes_to_render
    
    
    um_to_dbu = pya.DCplxTrans(layout.dbu).inverted()
    box_to_render_dbu = um_to_dbu * box_to_render
    pixel_size_dbu = um_to_dbu * pixel_size
    
    
    nx = round(box_to_render_dbu.width() / pixel_size_dbu.x)
    ny = round(box_to_render_dbu.height() / pixel_size_dbu.y)
    areas = shapes_to_render.merged().rasterize(box_to_render_dbu.p1, pixel_size_dbu, nx, ny)
    
  • Yes, I think that's okay.

    You can skip the first merge here:

    # was:
        shapes_to_render = pya.Region(cell.begin_shapes_rec_touching(layer, box_to_render)).merged()
    # can be:
        shapes_to_render = pya.Region(cell.begin_shapes_rec_touching(layer, box_to_render))
    

    because the final "merge" before "rasterize" will do it anyway.

    Matthias

  • Okay, thank you very much.

  • @Matthias @leo_cy
    Hello, Happy New Year!
    I used the rasterize() function to convert GDS to BMP. My goal is to split the GDS image into fields of 408.24 [um], with the output image being 1080x1080, and achieving a resolution of 0.378 microns per pixel.
    However... almost all areas (except for two specific points I marked) became two pixels wide instead of the one-pixel width I set. I’m currently working on finding the cause of this issue.
    Heres is my python ,GDS and Bmp Output.

  • Perhaps the parameters of your function are set incorrectly? Maybe you can try 0.378。pixel_size = pya.DVector(0.378, 0.378)

  • @leo_cy Yes,I tried.
    box_to_render = pya.DBox(0, 0, 408.24,408.24)
    aligned_origin = pya.Point(
    round(box_to_render.p1.x / 0.378) * 0.378,
    round(box_to_render.p1.y / 0.378) * 0.378
    )
    pixel_size = pya.DVector(0.378,0.378)

    But Still.....don't work. 0.378um Convert into 2px (I hope is 1px)

    Here is my picture and Python Script

Sign In or Register to comment.