def createHeatmapChart(matrix, xLabels, yLabels, scopes = [0.2, 0.4, 0.6, 0.8, 1], recSize = 20, colorBG = (255, 255, 255), colorFG = (0, 0, 0), colorChartBG = (230, 230, 230), colorGrid = (100, 100, 100), font = ImageFont.load_default()): """Creates a heatmap chart for the given values in the matrix labeled with the labels given in xDict and yDict TODO: options: - scopes - list of values that describe the scopes for the matrix values, e.g. scopes = [0.2, 0.4, 0.6, 0.8, 1] (default) - font - font used - recSize - size of a single indicator rectangle in the heatmap - colorBG - background color of the chart as rgb value (default: (255, 255, 255)) - colorFG - foreground color of the chart as rgb value (default: (0, 0, 0)) - colorChartBG - background color of the chart as rgb value (default: (230, 230, 230)) - colorGrid - color of the grid as rgb value (dafault: (100, 100, 100)); if set to "None" no grid will be drawn """ #===init=== #margin to left and bottom margin = 5 #maximal identifier length maxIdLength = computeMaxIdLength(set(xLabels+yLabels), font) #size of the img maxX = margin + maxIdLength + ((len(xLabels)+2) * recSize) maxY = margin + maxIdLength + (len(yLabels)+2) * recSize #lower left corner fo the chart offset = (margin+maxIdLength+4, maxY-maxIdLength-margin) #===create img=== img = Image.new("RGB", (maxX, maxY), colorBG) draw = ImageDraw.Draw(img) #draw chart background draw.rectangle((offset, (offset[0]+((len(xLabels)+1)*recSize), offset[1]-((len(yLabels)+1)*recSize))), fill=colorChartBG) #draw heatmap for row in xrange(len(matrix)): for col in xrange(len(matrix[0])): pos = (offset[0]+col*recSize, offset[1]-row*recSize, offset[0]+(col+1)*recSize, offset[1]-(row+1)*recSize) fill = getColorForScope(matrix[row][col], scopes, colorChartBG) draw.rectangle(pos, fill=fill) #draw grid #x-axis for i in xrange(len(yLabels)+1): yPos = offset[1]-((i+1)*recSize) draw.line(((offset[0], yPos), (offset[0]+recSize*(len(xLabels)+1), yPos)), fill=colorGrid) #y-axis for i in xrange(len(xLabels)+1): draw.line(((offset[0]+(i+1)*recSize, offset[1]), (offset[0]+(i+1)*recSize, offset[1]-recSize*(len(yLabels)+1))), fill=colorGrid) #draw axes #x-axis draw.line((offset, (offset[0]+recSize*(len(xLabels)+1), offset[1])), fill=colorFG) for i in xrange(len(xLabels)): draw.line(((offset[0]+recSize/2+i*recSize, offset[1]-2),(offset[0]+recSize/2+i*recSize, offset[1]+2)), fill=colorFG) #y-axis draw.line((offset, (offset[0], offset[1]-recSize*(len(yLabels)+1))), fill=colorFG) for i in xrange(len(yLabels)): draw.line(((offset[0]-2, offset[1]-(recSize/2+i*recSize)),(offset[0]+2, offset[1]-(recSize/2+i*recSize))), fill=colorFG) #draw ids for i in xrange(len(yLabels)): name = yLabels[i] pos = i draw.text((margin, offset[1]-((pos+1)*recSize)), name, font=font, fill=colorFG) for i in xrange(len(xLabels)): name = xLabels[i] pos = i img.paste(rotText(name, font=font, colorBG=colorBG, colorFG=colorFG), (offset[0]+(recSize/5)+((pos)*recSize), offset[1]+2)) #clean up del draw #return heatmap chart img return img
def addIds(img, idList, strLengthList, font=None): """Adds ids to the given image and returns it. Options: font - ImageFont => if no font is set, 16pt Arial is used """ #check preconditions if type(idList) != type([]) or type(strLengthList) != type([]): raise NoValidArgumentError, 'idList and strLengthList must be of type list []' elif len(idList) != len(strLengthList): raise AssertionError, 'idList and strLengthList must have the same size' elif len(idList)<2 or len(strLengthList)<2: raise AssertionError, 'idList and strLengthList must have a length > 2' #if no font is set use default font (Arial) if not font: font = ImageFont.load_default() #compute textsize for used font textsize = font.getsize("00") marginSpace = 10 maxIdLength = computeMaxIdLength(set(idList), font) #create new Image (containing the ids + the old img with dotplots) if len(idList) == 2: x = img.size[0] + textsize[0] + (marginSpace*2) if x<(maxIdLength+(2*marginSpace)+textsize[0]): x = maxIdLength+(2*marginSpace)+textsize[0] #add extra space below for id legend y = img.size[1] + textsize[1] + (marginSpace*2) + ((marginSpace+textsize[1])*2) offset = (textsize[0]+marginSpace, textsize[1]+marginSpace) else: #add extra space for ids x = img.size[0] + textsize[0] + maxIdLength + (marginSpace*2) y = img.size[1] + textsize[1] + (marginSpace*2) offset = (textsize[0]+maxIdLength+marginSpace, textsize[1]+marginSpace) newImg = Image.new("L", (x, y), 255) #create a new draw instance on the new img draw = ImageDraw.Draw(newImg) #special case: two ids if len(idList) == 2: #first id is above draw.text((textsize[0]+marginSpace+(strLengthList[0]/2), marginSpace/2), '1', font=font) #second id is to the left draw.text((marginSpace/2, textsize[1]+marginSpace+(strLengthList[1]/3)), '2', font=font) #draw id legend below draw.text((marginSpace, offset[1]+img.size[1]+marginSpace), "1 "+idList[0], font=font) draw.text((marginSpace, offset[1]+img.size[1]+(marginSpace*2)+textsize[1]), "2 "+idList[1], font=font) #more than 2 ids else: for n in xrange(0, len(strLengthList)): if n == 0: l = 0 elif n == 1: l = strLengthList[0] else : l = reduce(lambda x, y: x+y, [strLengthList[i] for i in xrange(0, n)]) xspace = l + strLengthList[n]/2 yspace = l + strLengthList[n]/3 #first id is above draw.text((offset[0]+xspace, marginSpace/2), str(n+1), font=font) #second id is to the left draw.text((marginSpace/2, offset[1]+yspace), str(n+1)+" "+idList[n], font=font)#str(n+1), font=font) #add borders draw.rectangle([(offset[0]-2, offset[1]-2), (offset[0]+img.size[0]+1, offset[1]+img.size[1]+1)], fill=150) #paste image with dotplot on "id img" newImg.paste(img, (offset[0]-1, offset[1]-1)) #release drawing instance del draw #return img return newImg
def resultsToTorc(resultList, colored=False): """Takes the result and returns an image showing a Torc indicating the similarity relations of the compared texts in the results. A Torc is a kind of overview which allows the user to recognize the similarity relations between different texts. Therefore all texts are arranged on a circle. For each relation of similarity between two texts a connecting line is drawn. """ #check preconditions if type(resultList) != type([]): raise NoValidArgumentError, 'Input must be of type list' elif len(resultList) == 0: return None else: for result in resultList: if type(result) != type(PlagResult()): raise NoValidArgumentError, 'Input list should only contain values of type PlagResult.' #1. get all identifiers of the results idSet = set() for result in resultList: for id in result.getIdentifier(): idSet.add(id) idSet = list(idSet) idSet.sort() #2. create a circle with a size depending on the number of identifier font = ImageFont.load_default() freespace = computeMaxIdLength(idSet, font) margin = 10 radius = computeRadius( len(idSet)) # computes radius depending on number of ids xM = freespace + radius + margin #middle x pos of circle yM = freespace + radius + margin #middle y pos of circle img = Image.new('RGB', (2 * xM, 2 * yM), (255, 255, 255)) draw = ImageDraw.Draw(img) draw.arc((freespace + margin, freespace + margin, freespace + margin + (2 * radius), freespace + margin + (2 * radius)), 0, 360, fill=(150, 150, 150)) #3. arrange the ids along the circle and save the coordinates for each id distToNextId = 360 / len(idSet) angles = range(0, 360, distToNextId) idPosDict = {} for idNr in xrange(0, len(idSet)): # x = xM + r * cos phi und y = yM + r * sin phi pos = (xM + (radius * cos(radians(angles[idNr]))), yM + (radius * sin(radians(angles[idNr])))) idPosDict.setdefault(idSet[idNr], pos) # use a truetype font and draw the id names for id in idPosDict: draw.text(computeFontPos(font, draw, str(id), idPosDict.get(id), xM, yM), str(id), font=font, fill=(0, 0, 0)) #4. walk through the results and plot the similarity relations as lines between the Ids if colored: #TODO: Params von aussen eingeben? clusters = getClusters(resultList, onlyPositives=False, onlyNonZeroSimilarities=False) for result in resultList: if result.isSuspectPlagiarism(): ids = result.getIdentifier() if colored: color = getColorForScope( getClusterNr(ids[0], ids[1], clusters), range(len(clusters))) else: color = (0, 0, 0) draw.line([idPosDict.get(ids[0]), idPosDict.get(ids[1])], fill=color) del draw #free draw instance #5. return the image return img
def addIds(img, idList, strLengthList, font=None): """Adds ids to the given image and returns it. Options: font - ImageFont => if no font is set, 16pt Arial is used """ #check preconditions if type(idList) != type([]) or type(strLengthList) != type([]): raise NoValidArgumentError, 'idList and strLengthList must be of type list []' elif len(idList) != len(strLengthList): raise AssertionError, 'idList and strLengthList must have the same size' elif len(idList) < 2 or len(strLengthList) < 2: raise AssertionError, 'idList and strLengthList must have a length > 2' #if no font is set use default font (Arial) if not font: font = ImageFont.load_default() #compute textsize for used font textsize = font.getsize("00") marginSpace = 10 maxIdLength = computeMaxIdLength(set(idList), font) #create new Image (containing the ids + the old img with dotplots) if len(idList) == 2: x = img.size[0] + textsize[0] + (marginSpace * 2) if x < (maxIdLength + (2 * marginSpace) + textsize[0]): x = maxIdLength + (2 * marginSpace) + textsize[0] #add extra space below for id legend y = img.size[1] + textsize[1] + (marginSpace * 2) + ( (marginSpace + textsize[1]) * 2) offset = (textsize[0] + marginSpace, textsize[1] + marginSpace) else: #add extra space for ids x = img.size[0] + textsize[0] + maxIdLength + (marginSpace * 2) y = img.size[1] + textsize[1] + (marginSpace * 2) offset = (textsize[0] + maxIdLength + marginSpace, textsize[1] + marginSpace) newImg = Image.new("L", (x, y), 255) #create a new draw instance on the new img draw = ImageDraw.Draw(newImg) #special case: two ids if len(idList) == 2: #first id is above draw.text((textsize[0] + marginSpace + (strLengthList[0] / 2), marginSpace / 2), '1', font=font) #second id is to the left draw.text((marginSpace / 2, textsize[1] + marginSpace + (strLengthList[1] / 3)), '2', font=font) #draw id legend below draw.text((marginSpace, offset[1] + img.size[1] + marginSpace), "1 " + idList[0], font=font) draw.text((marginSpace, offset[1] + img.size[1] + (marginSpace * 2) + textsize[1]), "2 " + idList[1], font=font) #more than 2 ids else: for n in xrange(0, len(strLengthList)): if n == 0: l = 0 elif n == 1: l = strLengthList[0] else: l = reduce(lambda x, y: x + y, [strLengthList[i] for i in xrange(0, n)]) xspace = l + strLengthList[n] / 2 yspace = l + strLengthList[n] / 3 #first id is above draw.text((offset[0] + xspace, marginSpace / 2), str(n + 1), font=font) #second id is to the left draw.text((marginSpace / 2, offset[1] + yspace), str(n + 1) + " " + idList[n], font=font) #str(n+1), font=font) #add borders draw.rectangle( [(offset[0] - 2, offset[1] - 2), (offset[0] + img.size[0] + 1, offset[1] + img.size[1] + 1)], fill=150) #paste image with dotplot on "id img" newImg.paste(img, (offset[0] - 1, offset[1] - 1)) #release drawing instance del draw #return img return newImg
def resultsToTorc(resultList, colored=False): """Takes the result and returns an image showing a Torc indicating the similarity relations of the compared texts in the results. A Torc is a kind of overview which allows the user to recognize the similarity relations between different texts. Therefore all texts are arranged on a circle. For each relation of similarity between two texts a connecting line is drawn. """ #check preconditions if type(resultList) != type([]): raise NoValidArgumentError, 'Input must be of type list' elif len(resultList) == 0: return None else: for result in resultList: if type(result) != type(PlagResult()): raise NoValidArgumentError, 'Input list should only contain values of type PlagResult.' #1. get all identifiers of the results idSet = set() for result in resultList: for id in result.getIdentifier(): idSet.add(id) idSet = list(idSet) idSet.sort() #2. create a circle with a size depending on the number of identifier font = ImageFont.load_default() freespace = computeMaxIdLength(idSet, font) margin = 10 radius = computeRadius(len(idSet)) # computes radius depending on number of ids xM = freespace + radius + margin #middle x pos of circle yM = freespace + radius + margin #middle y pos of circle img = Image.new('RGB', (2*xM, 2*yM), (255, 255, 255)) draw = ImageDraw.Draw(img) draw.arc((freespace+margin, freespace+margin, freespace+margin+(2*radius), freespace+margin+(2*radius)), 0, 360, fill = (150, 150, 150)) #3. arrange the ids along the circle and save the coordinates for each id distToNextId = 360 / len(idSet) angles = range(0, 360, distToNextId) idPosDict = {} for idNr in xrange(0, len(idSet)): # x = xM + r * cos phi und y = yM + r * sin phi pos = (xM + (radius * cos(radians(angles[idNr]))), yM + (radius * sin(radians(angles[idNr])))) idPosDict.setdefault(idSet[idNr], pos) # use a truetype font and draw the id names for id in idPosDict: draw.text(computeFontPos(font, draw, str(id), idPosDict.get(id), xM, yM), str(id), font=font, fill = (0, 0, 0)) #4. walk through the results and plot the similarity relations as lines between the Ids if colored: #TODO: Params von aussen eingeben? clusters = getClusters(resultList, onlyPositives=False, onlyNonZeroSimilarities=False) for result in resultList: if result.isSuspectPlagiarism(): ids = result.getIdentifier() if colored: color = getColorForScope(getClusterNr(ids[0], ids[1], clusters), range(len(clusters))) else: color = (0,0,0) draw.line([idPosDict.get(ids[0]), idPosDict.get(ids[1])], fill = color) del draw #free draw instance #5. return the image return img