class WaferMap: def __init__(self): self.img = None self.legend = Legend() self.title = Title() self.autosize = App.cfg['image']['autosize'] self.autosize_minpx = App.cfg['image']['autosize_minpx'] self.basewidth = App.cfg['image']['basewidth'] self.baseheight = App.cfg['image']['baseheight'] self.padding = App.cfg['image']['padding'] self.showborder = App.cfg['image']['border'] self.showtitle = App.cfg['title']['show'] self.showlegend = App.cfg['legend']['show'] self.showgrid = App.cfg['axis']['grid'] self.showaxis = App.cfg['axis']['show'] self.showflat = App.cfg['image']['flatline'] self.showtimestamp = App.cfg['image']['timestamp'] self.timestampcolor = App.cfg['image']['timestampcolor'] self.bordercolor = App.cfg['image']['bordercolor'] self.streetcolor = App.cfg['image']['streetcolor'] self.flatcolor = App.cfg['image']['flatcolor'] self.gridcolor = App.cfg['axis']['gridcolor'] self.axiscolor = App.cfg['axis']['color'] self.axisfont = App.cfg['axis']['font'] self.bgcolor = App.cfg['image']['bgcolor'] def buildmap(self, lot, wfr, testset): #--------------------------------------------------------------------------- # Get actual extents... that is number of rows & cols in the wafermap, # If the demodata routine was used, a random number of rows/cols were # generated. #--------------------------------------------------------------------------- min_Y = min(d.Y for d in wfr.dielist) max_Y = max(d.Y for d in wfr.dielist) min_X = min(d.X for d in wfr.dielist) max_X = max(d.X for d in wfr.dielist) col_count = max_X - min_X+1 row_count = max_Y - min_Y+1 log.debug('Die Extents: X = {} to {} Y = {} to {}'.format(min_X, max_X, min_Y, max_Y)) log.debug('Columns (X) = {}, Rows (Y) = {} (Total Die={})'.format(col_count, row_count, len(wfr.dielist))) if self.basewidth >= self.baseheight: sizeRatio = float(self.basewidth) / float(self.baseheight) else: sizeRatio = float(self.baseheight) / float(self.basewidth) log.debug('Size Ratio: {:.2f}, width={}, height={}'.format(sizeRatio, self.basewidth, self.baseheight)) #--------------------------------------------------------------------------- # If image autosize is enabled, the canvas size may be increased to ensure # die are visible. This will force a minimum pixel size of "autosize_minpx". # The original aspect ratio will be maintained. #--------------------------------------------------------------------------- if self.autosize: autominX = (self.autosize_minpx * col_count) + (2 * self.padding) autominY = (self.autosize_minpx * row_count) + (2 * self.padding) log.debug('Autosize Minimum Map Size X={} Y={} at Autopx = {}'.format( autominX, autominY, self.autosize_minpx)) if(autominX > self.basewidth): self.basewidth = autominX self.baseheight = int (float(self.basewidth) * float(sizeRatio)) log.debug('Autosize (autominX > bw) Updated sizes - width={}, height={}, sizeRatio={}'.format( self.basewidth, self.baseheight, float(sizeRatio))) elif (autominY > self.baseheight): self.baseheight = autominY self.basewidth = int (float(self.baseheight) * float(sizeRatio)) log.debug('Autosize (autominY > bh) Updated sizes - width={}, height={}, sizeRatio={}'.format( self.basewidth, self.baseheight, float(sizeRatio))) else: log.debug('Autosize sizing not required, width={}, height={}, autominX={}, autominY={}'.format( self.basewidth, self.baseheight, autominX, autominY)) #Store updated values back in cfg # App.cfg['image']['basewidth'] = bw # App.cfg['image']['baseheight'] = bh # Nominal size of map excluding padding mapX = self.basewidth - (self.padding *2) mapY = self.baseheight - (self.padding *2) log.debug('Map Space (BaseSize - 2*Padding) (X,Y)=({},{}) Padding = {}'.format( mapX, mapY, self.padding)) # Calculate the die's pixel size - width (X) and height (Y) based on map size # divided by the number of rows & columns X_pixels = mapX / col_count #X-diesize in pixels Y_pixels = mapY / row_count #Y diesize in pixels log.debug('Pixels per die: X={}, Y={}'.format(X_pixels, Y_pixels)) #--------------------------------------------------------------------------- # Unless the nominal image size (Canvas size minus padding) happens to be an # exact multiple of the calculated die size in pixels, we will have some # space left on all sides. # Calculate the extra space so we can center the image on the canvas #--------------------------------------------------------------------------- slackX = (mapX - (X_pixels * col_count)) / 2 slackY = (mapY - (Y_pixels * row_count)) / 2 log.debug('Slack: X={}, Y={}'.format(slackX, slackY)) log.debug('Calculated Map Size (excluding slack) - (X,Y) = ({},{})'.format( X_pixels * col_count, Y_pixels * row_count)) # #------------------------------------------------------------------------- # Have the legend calculate its size, it will be rendered later, but we # need to know the space it will take up so we can adjust the canvas size # Actual Map width is then adjusted to allow for legend. #-------------------------------------------------------------------------- actualWidth = self.basewidth if self.showlegend: self.legend.getsize(wfr.dielist, testset) actualWidth += self.legend.width log.debug('Legend Width = {}'.format(self.legend.width)) #-------------------------------------------------------------------------- # baseheight and fontsize adjusted for title #-------------------------------------------------------------------------- # Start with a reasonable guess for the size of the title font and then # autosize it from there. May not be the most efficient way to do # this, but it works for now... #-------------------------------------------------------------------------- actualHeight = self.baseheight titleheight = 0 if self.showtitle: #A guess... title_fontsize = actualWidth//40 titlekeys = self.title.autosize(lot.mir, wfr, title_fontsize, testset) upsearch=False # While title string width at current font size is > canvas width, decrease # font size and re-evaluate if(titlekeys['maxfw'] > actualWidth): while titlekeys['maxfw'] > actualWidth: upsearch=False title_fontsize -= 1 titlekeys = self.title.autosize(lot.mir, wfr, title_fontsize, testset) # print "D:imageWith = {}, fontsize = {}, maxfw={}, height={}".format(actualWidth, title_fontsize, titlekeys['maxfw'], titlekeys['maxfh']) # Otherwise, font is too small, increase it and re-evaluate else: while titlekeys['maxfw'] < actualWidth: upsearch=True title_fontsize += 1 titlekeys = self.title.autosize(lot.mir, wfr, title_fontsize, testset) # print "U:imageWith = {}, fontsize = {}, maxfw={}, height={}".format(actualWidth, title_fontsize, titlekeys['maxfw'], titlekeys['maxfh']) # If we were decreasing font size when the while condition became false, all the titlekeys are properly set, # from the last loop cycle.... but if we were increasing font size, the last loop cycle left the titlekeys # in a state for a larger font, so decrease the font size by one and re-evaluate the titlekeys if(upsearch): title_fontsize -=1 titlekeys = self.title.autosize(lot.mir, wfr, title_fontsize, testset) titleheight = titlekeys['maxfh'] # Increase canvas size to allow for title actualHeight += titleheight log.debug('Title Height = {}, Title Fontsize = {}'.format(titleheight, title_fontsize)) #-------------------------------------------------------------------------- # Adjust Width and Height for axis space #-------------------------------------------------------------------------- # if self.showaxis: # actualWidth += axis_space # actualHeight += axis_space #-------------------------------------------------------------------------- # Get axis space, add in 10 pixels for flat spacing. (the axes are # shifted left or up by 10 pixels when they are drawn to account for the # 10 pixels). #-------------------------------------------------------------------------- axis_space = 0 if self.showaxis: axis_space = maputil.do_axis(self, wfr.dielist, X_pixels, Y_pixels) + 10 log.debug('Axis Space = {}'.format(axis_space)) actualWidth += axis_space actualHeight += axis_space log.debug('Axis space = {}'.format(axis_space)) log.debug('Actual Image Size (X,Y) = ({},{})'.format(actualWidth, actualHeight)) #-------------------------------------------------------------------------- # Create the canvas and draw the wafermap #-------------------------------------------------------------------------- self.img = Image.new('RGB', (actualWidth,actualHeight), self.bgcolor) # Get the drawable object... dr = ImageDraw.Draw(self.img) # map_extents are used for tracking the actual pixel extents of the drawn map which makes # calculating the location to draw the flat & axis labels a bit easier... map_extents = {'minx':65535, 'miny':65535, 'maxx':0, 'maxy':0} # limit flag_size factor to a minimum of 1.0 (or else flag will exceed die boundary) App.cfg['flag']['size_factor'] = max(App.cfg['flag']['size_factor'], 1.0) # if we want the grid, draw it first so it's behind everything else... if self.showgrid: maputil.do_grid(self, wfr.dielist, X_pixels, Y_pixels, self.padding, axis_space, slackX, slackY, titleheight) idx=0 imap = [] #----------------------------------------------------------- # Draw All die loop #----------------------------------------------------------- for eachDie in wfr.dielist: mybin=getattr(eachDie,App.cfg['binmap']) idx+=1 x1 = self.padding + axis_space + ((eachDie.X - min_X) * X_pixels) + slackX y1 = self.padding + axis_space + ((eachDie.Y - min_Y) * Y_pixels) + slackY + titleheight x2 = x1 + X_pixels y2 = y1 + Y_pixels # Draw the die and file with bincolor if eachDie.testset == testset: dr.rectangle([x1,y1,x2,y2], fill=App.cfg['bin'][str(mybin)]['color'], outline=self.streetcolor) imap.append({'Row':eachDie.Y,'Col':eachDie.X,'x':x1,'y':y1,'width':x2-x1,'height':y2-y1}) # Draw any specified symbols, if enabled if App.cfg['enable_symbols']: maputil.do_symbol(dr,x1,y1,x2,y2, False, **App.cfg['bin'][str(mybin)]) # Draw flags if appropriate xystr="{}|{}".format(eachDie.X, eachDie.Y) # Bindict Key if (wfr.diedict[xystr]['flags'] & constants.FLAG_MULTITEST) and App.cfg['flag']['show']: if wfr.diedict[xystr]['flags'] & constants.FLAG_DUPLICATE_BIN: flagcolor= App.cfg['flag']['duplicate_color'] else: flagcolor= App.cfg['flag']['split_color'] flagX = int(float(x2-x1)/float(App.cfg['flag']['size_factor'])) flagY = int(float(y2-y1)/float(App.cfg['flag']['size_factor'])) dr.polygon([(x1,y1+flagY),(x1,y1),(x1+flagX,y1)], fill=flagcolor, outline=(0,0,0)) # track minimum and maximums of actual drawn wafermap. map_extents['minx']= min(map_extents['minx'],x1) map_extents['maxx']= max(map_extents['maxx'],x2) map_extents['miny']= min(map_extents['miny'],y1) map_extents['maxy']= max(map_extents['maxy'],y2) #----------------------------------------------------------- log.debug('Drawn Map Extents = {}'.format(map_extents)) # do_html(imap, actualWidth, actualHeight) #-------------------------------------------------------------------------- # Draw title area #-------------------------------------------------------------------------- if self.showtitle: dr.rectangle([0,0,actualWidth-1,titleheight], fill=self.title.bgcolor, outline=self.bordercolor) self.title.render(self.img, wfr, titlekeys) #-------------------------------------------------------------------------- # Draw legend, border, flat indicator, timestamp, axis labels #-------------------------------------------------------------------------- if self.showlegend: self.legend.render(self.img, self.basewidth + axis_space , self.padding + titleheight) if self.showborder: dr.rectangle([0,0,actualWidth-1,actualHeight-1], fill=None, outline=self.bordercolor) if self.showflat: maputil.do_flat(self, lot.wcr['WF_FLAT'], map_extents, self.flatcolor) if self.showtimestamp: maputil.do_timestamp(self, color=self.timestampcolor) if self.showaxis: maputil.do_axis(self, wfr.dielist, X_pixels, Y_pixels, map_extents, True) return(self.img)