Full Circle THE INDEPENDENT MAGAZINE FOR THE COMMUNITY

PROGRAMMING SERIES SPECIAL EDITION

PYTHONPYTHON

VolumeVolume Ten Parts 54 - 59

Full Circle Magazine is neither ailiated, with nor endorsed by, Ltd. HHOOWW--TTOO Program In Python - Part 54 Written by Greg D. Walters Program In Python - Part 54

anyyears ago, I was dealing numbers), and the other uses What you will learn for each floss color. It seems that M with high blood pressure totally blank aida that you count • Revisitation ofdatabase and XML HSVis the easiestwayto find the issues. My doctor suggested that I stitches from the pattern. The manipulation. “closest” representation of a color do something that allowed me to second is much harderthan the • Revisitation ofTkinter GUI that will match the floss colors. Of concentrate on something fairly first. Go to your favorite fabric programming. Ifyou missed the course, the human eye is the useful, but rather trivial. I dealt store or craft section ofyour local previous articles on this, please ultimate decision maker. Ifyou are with it by trying to do counted mega-mart and you’ll get the idea. referto FCM issues 51 thru 54. not familiar with HSV color cross stitch. It’s creative, focused, • Image manipulation using PIL representations, there is a rather and keeps yourmind occupied on Also a while back, I started (http://pillow.readthedocs.org/en/l complex writeup on Wikipedia at whatyou are doing, notwhatis playing with creating a program atest/). http://en.wikipedia.org/wiki/HSL_a bothering you. I find myselfin that thatwould take an image and • PDF creation using pyFPDF nd_HSV. Itmighthelp, butitmight position again, so I broke outthe convert it into a cross stitch (https://code.google.com/p/pyfpdf make things less clear. hoop and needles and started pattern. One thing lead to another, ). again. and I had to shelve the program for The firstthing we need is an other things. I’ve now dusted off GETTING STARTED XML file that has the DMC floss In case you aren’t familiar with the idea and started anew. colors with a RGB conversion. The counted cross stitch, I’ll give you a The first thing in ourlist oftasks bestone I found isat gross overview ofwhat it is. Cross We will spend the next few is to create the database that will http://sourceforge.net/p/kxstitch/f stitch is a type ofneedlework that articles dealing with this project. It hold the DMC(™) floss colors and eature-requests/9/. The file you uses tiny ‘x’ patterns ofthread that will take a while, since some things reference them to the closest want is dmc.xml. Download it and eventually make up a picture. The are fairly complex and have many approximation to the RGB (Red, putitin a folderthatyou willuse thread is called “floss” and the parts to them. Here is the “game Green, Blue) values that are used in to hold the Python code. fabric that you use is called “aida”. plan”: images on the computer. At the According to Wikipedia, aida is a • Create a database forthe pixel same time, the database will hold Nowwe willbe using apsw special fabric that has tiny squares colors to floss colors. the hexvalue and the HSV(Hue, (below) to do our database that have small holes at regular • Create a GUI using Tkinterforthe Saturation, Value) representations manipulation, which you should intervals that form the squares. application. This facilitates the placement of • Flesh out the application to do # makedb.py # DMC.xml to SQLite database the “x” patterns that make the the manipulation ofthe image files. # For Full Circle Magazine #85 image. There are two types ofcross • Create a PDF file thatwillbe the stitch. One has an image printed on ultimate pattern for the project. import apsw from xml.etree import ElementTree as ET the aida (sortoflike paintby tablename = "DMC"

full circle magazine #85 1 1 contents ^ HOWTO - PYTHON PART 54 already have and ElementTree to get started. We also use a counter do the XMLparsing (which is variable to show that something is 150 included in Python version 2.7+). happening while the parsing and Dusty Rose Ultra VDK database inserts are going on. 171 As always, we start with our 2 imports. In this program, we have Nowthatwe have allourdata, 73 onlythe two. We also setthe name we need to create the SQL insert ofthe table. statement and execute it. Notice the “\” after the word VALUES in The next portion should be the SQL statement. That is a line- def ReadXML(): global connection familiar ifyou have been reading continuation character to make it global cursor the articles for a while. We create a easier for printing here in the fn = 'dmc.xml' function that will read the XML file, magazine. We will be creating the tree = ET.parse(fn) root = tree.getroot() and parse itforus. We then can use database and table in a few cntr = 0 the information to load the moments. for floss in root.findall('floss'): database. A snippet ofthe XML file name = floss.find('name').text desc = floss.find('description').text is shown top right. SQL = "INSERT INTO DMC for colour in floss.findall('color'): (DMC,Description,Red,Green,Bl red = colour.find('red').text ue) VALUES \ We are looking for the green = colour.find('green').text blue = colour.find('blue').text tag for each line ofinformation. To ('%s','%s',%s,%s,%s)" % do this, we use the .findall(‘floss’) (name,desc,red,green,blue) command. Once we have the def OpenDB(): cursor.execute(SQL) global connection information line, we break each tag global cursor (name, description, etc.) into global ucursor separate variables to place into the Now, we print to the terminal global dbname connection = apsw.Connection("floss.db3") database. When it comes to the window that something is going on: cursor = connection.cursor() tag, we use the ucursor = connection.cursor() .floss.findall(‘color’) command to print "Working record geteach value ofRed, Green and {0}".format(cntr) work before, you will notice that since ifyou modifya cursorin the Blue. we are using two cursors this time. middle ofa logic statement, you cntr += 1 The cursor variable is used for the lose everything with the new We start by telling the function Now we create and/or open the “normal” inserts, and later on in command. By using ‘ucursor’, we thatwe will be using the global database in the OpenDB routine the select statement for the can use thatforthe update variables connection and cursor. (bottom right). Ifyou’ve been with update to setthe hexand HSV statements. Other than that, it is We then set the filename ofthe us when we have done database values. We have to use two cursors, our normal OpenDB routine. XML file, parse the XML file, and

full circle magazine #85 1 2 contents ^ HOWTO - PYTHON PART 54

Now that the database is def MakeTables(): created and/or opened, we can set sql = '''CREATE TABLE IF NOT EXISTS DMC up our table (top right). Notice that (pkID INTEGER PRIMARY KEY, DMC INTEGER, Description TEXT, Red INTEGER, Green INTEGER, Blue INTEGER, the SQL statement below uses the HEX TEXT,H INTEGER,S INTEGER,V INTEGER)''' triple quote to allow forthe line to cursor.execute(sql) break neatly for viewing. def rgb2hex(rgb): def EmptyTables(): The EmptyTables routine return '%02x%02x%02x' % rgb sql="DELETE FROM %s" % tablename cursor.execute(sql) (middle right) is there just to make sure thatifwe wantto orneed to cursor variable to hold the data. run the application more than We then step through the returned def rgb2hsv(r, g, b): data, and read the RGB values, and r, g, b = r/255.0, g/255.0, b/255.0 once, we startwith a clean and mx = max(r, g, b) empty table ifit exists. pass them to the rgb2hex function mn = min(r, g, b) as a tuple and to the rgb2hsv df = mx-mn function as three separate values. if mx == mn: IF we were to stop here, we h = 0 would have a reasonable working Once we get the return values, we elif mx == r: database with the DMC color, color use the update SQLcommand to h = (60 * ((g-b)/df) + 360) % 360 match the proper record by using elif mx == g: name and the RGB values h = (60 * ((b-r)/df) + 120) % 360 associated with each. However, as I the primary key (pkID). As I stated elif mx == b: before, we have to use a separate h = (60 * ((r-g)/df) + 240) % 360 alluded to before, it is easierto if mx == 0: pick the closest floss color by using cursor for the update statement. s = 0 the HSVdata. else: The lastthing we do is calleach s = df/mx v = mx We next create the hex value ofthe functions in order to create return int(round(h,0)), int(round(s*100,0)), from the RGB values (middle left). the database, and, atthe end, we int(round(v*100,0)) print “Finished” so the user knows The next function creates the everything is done. created in the same folder where to refresh your memory by looking HSVvalues from the RGB values. I the code and XML file are located. atFCM issues 51 thru 54 where I OpenDB() As always, the full code can be take you through Tkinter. found the algorithm on the MakeTables() internet. You can research it there. EmptyTables() # Just to be found at safe http://pastebin.com/Zegqw3pi. Until nexttime, have a good ReadXML() month. Finally, we create the UpdateDB UpdateDB() function (next page, top left). We print "Finished" Next time, we will workon the use the SELECT* FROM DMC GUI. We use Tkinterforthe GUI, so, I named this program command and use the “standard” in the meantime, you mightwant “MakeDB”. The database should be

full circle magazine #85 1 3 contents ^ HHOOWW--TTOO Program In Python - Part 55 Written by Greg D. Walters Program In Python - Part 55

his is the second in a multi-part https://openclipart.org/detail/1 778 the processing ofthe image. The from Tkinter import * T tutorial on creating a Cross 90/file-folder-by-thebyteman- bottom frame shows the original import tkFileDialog Stitch pattern generator. In the 1 77890. Open itin GIMP, resize it image on the leftand the first part (FCM85), we created a to 30x30 and save itin the same processed image on the right, and import tkCommonDialog directory as the other two files as the side frame displays the colors database containing the DMC™ import tkMessageBox floss colors with their closest RGB “open.”. and floss required. It seems from values. In this part, we will create first glance there is a lot ofwasted import ttk the GUI using Tkinter. We will also Above is a screenshot ofwhat space here, butwhen you see the from PIL import use PIL (Python Imaging Library) the finished GUI will look like. program run, it doesn’t really have Image,ImageTk,ImageOps and PMW (Python Mega Widgets). There are fourmain frames in the that much empty space, once we import Pmw You’ll need to download those GUI. Three on the leftside and one get through the processing libraries and install them before we on the right. When we go through portion. import apsw # Database go too far. ForPIL, go to the Pillow the build widget process, I refer to Access fork at https://github.com/python- them as Top Frame, Middle Frame, Now we are ready to start import math # Math library imaging/Pillow and download the Bottom Frame and Side Frame. The working on the code. Here is our import sys latest version. For PMW, go to top frame deals with the original long list ofimports... image. The middle frame deals with http://pmw.sourceforge.net/ and From the sheernumberof download from there. imports, you can tell this is going to be a long program. In fact, the UI You will also need two image portion ofthe code will be over files. One is a simple grey rectangle 300 lines, including comments. The 500x400 pixels. You can use GIMP “good” news is thatabout200 of or some other image manipulating the lines ofcode deal with the program to create it. Name it Tkinter portion ofthe program, the default.jpg, and place it into your actual GUI itself. The majority of source code directory along with the remaining lines ofcode in this the database. The otheris an portion are stubs for functions image ofa folderforthe open needed for the next article. image button. I gotone from open clipart and searched for the word We’ll create a class to hold all of “folder”. I found a reasonable one our UI processing code (next page, at

full circle magazine #86 1 0 contents ^ HOWTO - PYTHON PART 55 top right). variable and then assign itto a class XStitch: Variable Class (BooleanVar, def __init__(self, master): First, we have the class DoubleVar, IntVar or StringVar). self.picFormats = [ definition and next we have the This will then “track” changes to ('JPEG / JFIF','*.jpg'), ('Portable Network Graphics','*.png'), __init__ function which we pass the the values within the widget values ('CompuServer GIF','*.gif'), TopLevel “root” window into. We so you can access them with the ('Windows Bitmap','*.bmp'), create the TopLevel window in the .get() or .set() methods. In the next ('All File Types *.*','*.*'), ] last four lines ofthe program. lines ofcode, we create the global Within the __init__ function we are variable name, then assign it to the defining all the global variables and proper wrapper class. I put some #------doing some initial assignments comments into the code to try to # Global Definitions before we start the other help you keep trackofwhat we are #------# UI Required functions. The first thing we do is doing. global OriginalFilename create a list ofTuples that hold the OriginalFilename = StringVar() picture file formats that we need As you can see, we are setting a global OriginalColorCount OriginalColorCount = StringVar() when we call the OpenFile dialog. variable called OriginalFilename, global OriginalSize The next two lines below, define which holds the image that we OriginalSize = StringVar() and readythe two image files we want to create the pattern from, just created (open folder GIF file, OriginalColorCount which holds and the grey rectangle –which will the numberofcolors in the original global ComboStitch be used as placeholders for our image file, and OriginalSize which ComboStitch = IntVar() global ComboSize images used to create the pattern. holds the size in pixels ofthe ComboSize = StringVar() original image. As they say on tv... global FabricWidth self.openimage = “BUTWAIT!THERE’SMORE!” FabricWidth = DoubleVar() PhotoImage(file='open.gif') global FabricHeight (bottom right): FabricHeight = DoubleVar() self.DefaultImage global MaxColors =ImageTk.PhotoImage(self.Thum The ComboStitch variable is set MaxColors = IntVar() bnail("default.jpg",450,450)) global BorderSize by a combobox, and handles the BorderSize = DoubleVar() stitch size ofthe aida that you wish Nowwe getinto the global number ofcolors, and BorderSize is to use for your project. The global ProcessedSize definitions (middle right). Ifyou a floating point value that specifies ComboSize variable is also set by a remember, when you use Tkinter, if the amountofunused aida for ProcessedSize = StringVar() combo boxand holds the size of you have a widgetlike a textentry framing. the aida fabric. FabricHeight and global DmcColor boxorcombo boxthatyou wantto FabricWidth are the breakdowns retrieve the information selected global ProcessedColors DmcColor = StringVar() from the aida size. MaxColors is a orentered, you define a global value from an entryboxto setthe ProcessedColors = StringVar()

full circle magazine #86 1 1 contents ^ HOWTO - PYTHON PART 55 The final ‘variable class’ #------variables are used for information #------self.OpenDB() global ShowGrid once we have processed the ShowGrid = True original image to the desired self.MakeMenu(master) global ProcessedImage ProcessedImage = "" parameters. frm = global GridImage self.BuildWidgets(master) GridImage = "" The next set ofglobals is (top global backgroundColor1 self.PlaceWidgets(frm) backgroundColor1 = (120,)*3 right) used for easy access global backgroundColor2 throughout the program. For the backgroundColor2 = (0,)*3 The next portion ofour code most part, they are either obvious global ReadyToProcess (middle right) will set up the menu ReadyToProcess = False by theirname, orwill become bar. I’ve tried to lay it out logically obvious once we use them. There so it will be easy to understand. are three not-so-obvious variables #======here. backgroundColor1 and # BEGIN UI DEFINITION We define a function called backgroundColor2 are tuples that #======MakeMenu, and pass in the def MakeMenu(self,master): are used in the gridding process, TopLevel window. We then define menu = Menu(master) and the ReadyToProcess variable is root.config(menu=menu) the three menu sets we will be used to designate that the original filemenu = Menu(menu, tearoff=0) creating. One for File, one for process = Menu(menu,tearoff=0) image is loaded and everything is Process, and one for Help. help = Menu(menu,tearoff=0) readyto go –justin case the user presses the Process button too menu.add_cascade(label="F early. ile", menu=filemenu) #------# File Menu menu.add_cascade(label="P #------Finally we have assigned all our rocess",menu=process) filemenu.add_command(label="New") globals, and now have the code filemenu.add_command(label="Open", command=self.GetFileName) menu.add_cascade(label="H filemenu.add_command(label="Save", command=self.FileSave) that actually creates the GUI. We elp",menu=help) filemenu.add_separator() open the database, create the filemenu.add_command(label="Exit", command=self.DoExit) menu, setup the widgets, and Nowwe setup the File Nowwe willmake our finally place the widgets into the menu options (bottom right). Open Now we have the Process BuildWidgets function. This is properplaces. Just to give you a willopen ourimage and uses a option and the Help functions where we create all the widgets heads-up, we will be using the Grid function called “GetFileName”. (next page, top right). thatwillbe used on the GUI. geometry placement manager. Save will create the output PDF file More on that later. and uses the FileSave function. We All ofthe options in the menu def add a separatorand finally an Exit bar are also available from various BuildWidgets(self,master): function. buttons within the program.

full circle magazine #86 1 2 contents ^ HOWTO - PYTHON PART 55 self.frame = image=self.openimage, Frame(master,width=900,height #------=850) command=self.GetFileName) # Process Menu #------We start with the function First thing to notice is that this process.add_command(label="All",command=self.Process) #------(bottom right) definition, passing is broken into two lines. You can # Help Menu in the TopLevel window (master) safely place everything on one #------and placing a frame thatholds all line...itis justtoo long to fitinto a help.add_command(label="Help",command=self.ShowHelp) help.add_separator() ofour other widgets. I’ve added 72-character line. We’ll really pay help.add_command(label="About",command=self.ShowAbout) comments to help realize which attention to the parameters we part ofcode deals with which use here. First the parent (frm1), parameter, which tells the system information that will be displayed frame. We’ll deal with the top nextthe width which is setat28. what function to call when the in the widget. We setthese in the frame first. When we use a widgetthathas the button is clicked. __init__ function earlier. One other option oftextoran image, we have thing to mention is that the frame Assuming you remember or to be careful setting the width. Ifit One more thing to lookatis the itself has two parameters you refreshed your memory on Tkinter, will contain text, the width textvariable parameter. This tells might not remember. The Relief it should be fairly straight-forward. parameter is the number of us what variable will hold the parameter sets the border type of Let’s lookat the first label as a characters it will discussion item. hold. Ifitisto # ------Middle Frame ------display an self.frm2 = Frame(self.frame,width=900,height=160,bd=4,relief=GROOVE) self.label1 = image, itwill be self.lbl4 = Label(self.frm2,text="Aida Stitch Size: ") Label(self.frm1,text = self.lbl5 = Label(self.frm2,text="Aida Fabric Size: ") "Original Filename: ") set at the self.TCombobox1 = ttk.Combobox(self.frm2,textvariable=ComboStitch,width=8) number of self.TCombobox1.bind('<>', self.StitchSizeSelect) pixels. Finally, self.TCombobox1['values'] = (7,10,11,12,14,16,18,22) First, we define the name ofthe self.TCombobox2 = ttk.Combobox(self.frm2,textvariable=ComboSize,width = 8) widget (self.label1 =). Next we set we set the self.TCombobox2.bind('<>',self.AidaSizeSelect) that variable to which widget type command self.TCombobox2['values'] = ("12x18","15x18","30") we wantto use; in this case Label. Finally we set the parameters we # ------TOP FRAME ------self.frm1 = Frame(self.frame,width=900,height=100,bd=4,relief=GROOVE) want to apply to that widget self.label1 = Label(self.frm1,text = "Original Filename: ") starting with the parent widget self.entFileName = Entry(self.frm1,width=50,textvariable=OriginalFilename) (self.frm1), and in this case, the self.btnGetFN = Button(self.frm1, width=28, image=self.openimage, command=self.GetFileName) text that will show up in the label. self.label2 = Label(self.frm1,text = "Original Colors: ") Nowlet’s take a momentto lookat self.lblOriginalColorCount = Label(self.frm1,text="",width=10, textvariable=OriginalColorCount) the button self.btnGetFN. self.label3 = Label(self.frm1,text = "Original Size: ") self.lblOriginalSize = Label(self.frm1,text="",width=10, self.btnGetFN = textvariable=OriginalSize) Button(self.frm1, width=28,

full circle magazine #86 1 3 contents ^ HOWTO - PYTHON PART 55 the frame, which in this case is GROOVE, and the bd parameter self.lbl6 = Label(self.frm2,text="Max Colors: ") self.entMaxColors = Entry(self.frm2,textvariable=MaxColors,width=3) sets the border width. Border self.lbl7 = Label(self.frm2,text="Border Size: ") width defaults at0 so ifyou want self.entBorderSize = Entry(self.frm2,textvariable=BorderSize,width = 8) self.frmLine = Frame(self.frm2,width=6,height=80,bd=3,relief="raised") to see the effect, you have to set self.lbl8 = Label(self.frm2,text=" Processed Image Colors: ") the border width (bd is a shortcut). self.lbl9 = Label(self.frm2,text="Processed Image Stitch Count: ") self.lblProcessedColors = Label(self.frm2, width=10,textvariable=ProcessedColors, justify=LEFT) Now we’ll deal with the middle self.lblProcessedSize = Label(self.frm2, width=10, textvariable=ProcessedSize, frame widgets. justify=LEFT) self.btnDoIt = Button(self.frm2,text="Process",width=11,command = self.Process) self.btnShowGrid = Button(self.frm2,text="Hide Grid", width=11, The last 6 lines ofthis section command=self.ShowHideGrid) (previous page, middle right) deal self.btnCreatePDF = Button(self.frm2, text="Create PDF", width=11, with the two combo boxes in the command=self.CreatePDF) UI. Each combo box uses three lines (the way I programmed it to # ------Bottom Frame ------make iteasyto understand). In the self.frm3 = Frame(self.frame,width=450,height=450,bd=4,relief=GROOVE) first line, we set the basic self.lblImageL = Label(self.frm3, image=self.DefaultImage, parameters. The next line, we bind height=400, width=400, borderwidth=2, relief=GROOVE) self.lblImageR = Label(self.frm3, image=self.DefaultImage, height=400, the combobox selection-changed width=400,borderwidth=2, relief=GROOVE) event to the function we have to setup onlythe frame library. It’s really easy to use and StitchSizeSelect, and the last line FabricHeight.set(18) has a listofthe values thatwillbe and two labels which we will use to provides a nice interface to the available for the pulldown. MaxColors.set(50) hold our images. information about the floss that should be used. You can research BorderSize.set(1.0) Everything else above is pretty Finally we deal with the side the ScrolledFrame on your own, “normal” stuff. Now we set our Now we deal with the bottom frame. The side frame will hold a since we still have a lotto cover defaults for the widgets that need frame. This is really simple, since ScrolledFrame from the PMW here. them. Again, we are using the global variables that we set up in #------Side Frame ------the __init__ function and wrapped self.frm4 = Frame(self.frame,width = 300,height=580,bd=4,relief=GROOVE) to the widget variable class. # Create the ScrolledFrame. self.sf = Pmw.ScrolledFrame(self.frm4, labelpos = 'n', label_text = 'Processed Color List', ComboStitch.set(14) usehullsize = 1, hull_width = 300, ComboSize.set("15x18") hull_height = 567,) return self.frame FabricWidth.set(15)

full circle magazine #86 1 4 contents ^ HOWTO - PYTHON PART 55

That’s all for the widgets. Now ROW | Col 0 | Col 1 - Col 6 |Col 7 | Col 9 | Col 10 | we have to place them. As I said ------earlier, we will be using the Grid 0 | Label1 | entFileName |btnGenFN| Label2|lblOriginalColorCount | 1 | | Label3|lblOriginalSize | geometry manager, rather than the ------absolute or pack managers.

The Grid method places the def PlaceWidgets(self,frame): frame.grid(column = 0, row = 0) widgets in (you guessed it) a grid, # ------TOP FRAME ------referenced by row and column self.frm1.grid(column=0,row=0,rowspan=2,sticky="new") designations. I’ll use the top frame self.label1.grid(column=0,row=0,sticky='w') self.entFileName.grid(column=1,row=0,sticky='w',columnspan = 5) as an example (shown top right). self.btnGetFN.grid(column=7,row = 0,sticky='w') self.label2.grid(column=9,row=0,sticky='w',padx=10) First we place the frame. self.lblOriginalColorCount.grid(column=10,row=0,sticky='w') self.label3.grid(column=9,row=1,sticky='w',padx=10,pady=5) self.lblOriginalSize.grid(column=10,row=1,sticky='w') You can see thatwe place the widget by using the {widgetname}.grid command, then # ------MIDDLE FRAME ------the row and column positions. self.frm2.grid(column=0,row=2,rowspan=2,sticky="new") self.lbl4.grid(column=0,row=0,sticky="new",pady=5) Notice that we are telling the entry self.lbl5.grid(column=0,row=1,sticky="new") widget to span 5 columns. Padx self.TCombobox1.grid(column=1,row=0,sticky="new",pady=5) and pady values will place some self.TCombobox2.grid(column=1,row=1,sticky="new") self.lbl6.grid(column=2,row = 0,sticky="new",padx=5,pady=5) extra space on both the right and self.entMaxColors.grid(column=3,row=0,sticky="new",pady=5) left sides (padx) orthe top and self.lbl7.grid(column=2,row=1,sticky='new',padx=5) bottom (pady). The sticky self.entBorderSize.grid(column=3,row=1,sticky='new') self.frmLine.grid(column=4,row=0,rowspan=2,sticky='new',padx=15) parameter is similar to a justify self.lbl8.grid(column=5,row=0,sticky='new',pady=5) command for text. self.lbl9.grid(column=5,row=1,sticky='new') self.lblProcessedColors.grid(column=6,row=0,sticky='w') self.lblProcessedSize.grid(column=6,row=1,sticky='new') The middle frame is a bitmore self.btnDoIt.grid(column=7,row=0,sticky='e',padx=5,pady = 5) complicated, but basically the same self.btnShowGrid.grid(column=7,row=1,sticky='e',padx=5,pady = 5) self.btnCreatePDF.grid(column=8,row=0,rowspan=2,sticky='ew',padx=10) as the top frame. You might notice an extra frame in the middle ofthe vertical line widget, I cheated and code (self.frmLine). This gives us a used a frame with a width of6 nice divider between the options # ------BOTTOM FRAME ------pixels and borderwidth of3, self.frm3.grid(column=0,row=4,sticky="nsew") section and the display section. making itjustlooklike a fatline. self.lblImageL.grid(column=0,row=0,sticky="w") Since there is no horizontal or self.lblImageR.grid(column=1,row=0,sticky="e")

full circle magazine #86 1 5 contents ^ HOWTO - PYTHON PART 55 The bottom frame is simple pass the filename and the width since we have onlythe frame and and heightthatwe wantthe # ------SIDE FRAME ------self.frm4.grid(column=2,row=0,rowspan=12,sticky="new") the two labels to hold the images. thumbnail image to be. self.sf.grid(column=0,row=1) self.sfFrame = self.sf.interior() The side frame is pretty much Since this article is so long, I’m self.lblch1 = Label(self.sfFrame,text=" Original") self.lblch2 = Label(self.sfFrame,text=" DMC") the same thing, except the going to give you a listoffunction self.lblch3 = Label(self.sfFrame,text="Name/Number") ScrolledFrame allows for a frame namesand allyou have to do is self.lblch1.grid(column=0,row=0,sticky='w') to be setto the interiorofthe stub itoutbyusing the pass self.lblch2.grid(column=1,row=0,sticky='w') self.lblch3.grid(column=2,row=0,sticky="w") scrolled frame widget. We then command. We’ll fill them in next create three widgets here and month. I’ll give you the firstone as place them in theirgrids as column an example, but you should already def Thumbnail(self,file,hsize,wsize): headers. We do this since we knowhowto do it. size = hsize,wsize assigned the interior frame for the extpos = file.rfind(".") outfile = file[:extpos] + ".thumbnail" scroll frame here and we have to def GetFileName(self): pass im = Image.open(file) assign the parent (self.sfFrame) im.thumbnail(size) after we have created it. im.save(outfile,"JPEG") For the rest ofthe functions, I’ll return im justgive you the deflines. Be sure That’s all the hard work for now. to include them all in yourcode. test = XStitch(root) Well that’s a lot for this month, At this point, we will create all of but we are finally done. You can the functions that we need to get root.mainloop() You can see, we have a large actually run the program to see the the GUI to run, stubbing mostof amount ofworkto do next month. GUI. them until next month. There are a The first line sets up the root We still have fourmore lines to fewwe willgo ahead and TopLevel window. The next line write to finish up for this month. As always, the code is available complete, but they are fairly short. sets the title on the top line. The This is outofourclass code. on Pastebin at third line instantiates our XStitch http://pastebin.com/XtBawJps. class, and the last line starts the The first function will be the root = Tk() Exit option from the menu bar. It’s main loop thatshows the UI and Next month we will flesh out underthe File menu option. root.title("Cross Stitch gives control over to it. the code. See you then. Pattern Creator")

def DoExit(self): def ShowHelp(self):, def ShowAbout(self):, def OpenDB(self):, def ShowHideGrid(self): sys.exit() def StitchSizeSelect(self,p):, def AidaSizeSelect(self,p):, def Process(self): def CreatePDF(self):, def OriginalInfo(self,file):, def GetColorCount(self,file): The onlyotherone is the def GetHW(self,file):, def GetHW2(self,file):, def GetColors(self,image): Thumbnail function. We need this def Pixelate(self,im,pixelSize):, def ReduceColours(self,ImageName): def MakeLines(self,im,pixelSize):, def MakeLines2(self,im,pixelSize): to fill the grey rectangles into the def Rgb2Hex(self,rgb):, def FillScrolledList(self,filename): labels in the bottom frame. We def GetBestDistance(self,r1,g1,b1):

full circle magazine #86 1 6 contents ^ HHOOWW--TTOO Program In Python - Part 56 Written by Greg D. Walters Program In Python - Part 56

e’ve been working on a left image in the bottom frame. OriginalFilename.set(fileName) W Cross Stitch pattern See the text box to the right. OriginalColorCount.set(self.GetColorCount(fileName)) generator. Last month we did the OriginalSize.set(self.GetHW(fileName)) UI portion, and nowit’s time to do Next we do the ShowHideGrid masterimage=Image.open(fileName) masterimage.thumbnail((400,400)) the code thatdoes the mostofthe function. This simply exchanges self.img = ImageTk.PhotoImage(masterimage) work. Next month we will start two images in the rightimage label self.lblImageL['image'] = self.img working on the PDF file output based on the global variable ReadyToProcess = True portion. ShowGrid. IfFalse, we change the The FileSave menu option will simply call the CreatePDF routine, once it’s finished. text on the show/hide button, then We’ll workon the menu items set the ShowGrid variable to true def FileSave(self): self.CreatePDF() first. The code is shown below. and setthe image to the one with the grid. Otherwise we change the We’ll stub out the ShowHelp and ShowAbout routines with a dialog box saying that The global ReadyToProcess text on the show/hide button to those options are not yet available. variable is used to make sure thatif “Show Grid”, set the ShowGrid def ShowHelp(self): the user presses the Process variable to False and putup the tkMessageBox.showinfo(title="Help",message='Sorry, button, the system doesn’t try to ungridded image. Code is on the but help is not yet available.') process things without anything to next page, top left. def ShowAbout(self): process. We use the tkFileDialog tkMessageBox.showinfo(title="About",message='Sorry, askopenfilename built-in dialog The StitchSizeSelect function is but the About function is not yet available.') routine to get the filename ofthe fired whenever the stitch size We’ve written the OpenDB routine a dozen times before, so you should know what it original image. We then get the combobox is changed. We get the does. number ofcolors in the original value from the combo box and def OpenDB(self): image as wellas the width and assign it to a local variable. global connection height. We save those values and global cursor def StitchSizeSelect(self,p): #------displaythem in the GUI. We then connection = apsw.Connection("floss.db3") open the image and create a selection = ComboStitch.get() cursor = connection.cursor() thumbnail image to display in the

def GetFileName(self): global ReadyToProcess #------fileName = tkFileDialog.askopenfilename(parent=root,filetypes=self.picFormats ,title="Select File to open...")

full circle magazine #87 1 2 contents ^ HOWTO - PYTHON PART 56

def ShowHideGrid(self): def AidaSizeSelect(self,p): global ShowGrid selection = ComboSize.get() #------if selection != "30": if ShowGrid == False: pos = selection.find("x") self.btnShowGrid['text'] = 'Hide Grid' width = int(selection[:pos]) ShowGrid = True height=int(selection[pos+1:]) self.im2=Image.open(self.GridImage) else: self.im2.thumbnail((400,400)) width = 30 self.img3 = ImageTk.PhotoImage(self.im2) height = 30 self.lblImageR['image'] = self.img3 FabricWidth.set(width) else: FabricHeight.set(height) self.btnShowGrid['text'] = 'Show Grid' ShowGrid = False self.im2=Image.open(self.ProcessedImage) # Place image have the grid to do the cross self.im2.thumbnail((400,400)) stitching. self.img3 = ImageTk.PhotoImage(self.im2) self.im2=Image.open(Reduced) self.lblImageR['image'] = self.img3 self.im2.thumbnail((400,400)) self.MakeLines(Reduced,5) self.img3 = self.MakeLines2('output.png', The AidaSizeSelect function is loaded. ImageTk.PhotoImage(self.im2) 50) (top right) is very similar to the StitchSizeSelect function. We set We pixelate the original file to a self.lblImageR['image'] = self.im2 = self.img3 Image.open('output2.png') the FabricWidth and FabricHeight 5x5 pixel matrix This allows us to globals based on the selection on group that5x5 matrixto a single self.ProcessedImage = self.im2.thumbnail((400,400)) the combo box. We also default to color. We then reduce the colors, 'im1.png' self.img3 = 30x30 ifthey select 30. getthe width and heightofthe ImageTk.PhotoImage(self.im2) processed image and set the size The above set ofcode places the processed image into the self.lblImageR['image'] = We have a variable called so the usercan see howbig the self.img3 ReadyToProcess (below) just in resulting image will be. image that will hold the processed case the usertries to run the image. The next set ofcode will self.FillScrolledList('output .png') process function before the image create a grid so thatthe userwill

def Process(self): global ReadyToProcess #------if ReadyToProcess == False: tkMessageBox.showinfo(title="ERROR...",message='You must load an original imaage first.') else: newimage = self.Pixelate(OriginalFilename.get(),5) Reduced = self.ReduceColors(newimage) W,H = self.GetHW2(Reduced) siz = "{0}x{1}".format(W/5,H/5) ProcessedSize.set(siz)

full circle magazine #87 1 3 contents ^ HOWTO - PYTHON PART 56 self.GridImage = def Pixelate(self,im,pixelSize): 'output2.png' image = Image.open(im) self.GetColors(image) We stub the CreatePDF image = image.resize((image.size[0]/pixelSize, image.size[1]/pixelSize), Image.NEAREST) image = image.resize((image.size[0]*pixelSize, image.size[1]*pixelSize), Image.NEAREST) function until we finish the PDF self.GetColors(image) function next month. #image.show() image.save('newimage.png') def CreatePDF(self): return 'newimage.png' tkMessageBox.showinfo(title=" opened image file. Ifyou use im = Image.open(file) The Pixelate function (above) Create PDF",message='Sorry, GetColorCount, you have to pass tmp = but the Create PDF function "{0}x{1}".format(im.size[0],i takes two parameters, image is not yet available.') an unopened file. m.size[1]) filename (im) and the size ofpixels return tmp you want. The workis done bythe The OriginalInfo() routine gets def GetColorCount(self,file): def GetHW2(self,file): image.resize method. I found this and sets variables based on the im = Image.open(file) numColors = im = Image.open(file) routine on the web in a numberof original image format, size and return im.getcolors(1600000) places. In this instance we will be mode. self.colors = im.size[0],im.size[1] len(numColors) passing a pixel size of5, which return self.colors works well for Cross Stitch def OriginalInfo(self,file): GetColors will get the number im = Image.open(file) ofcolors in the passed image file. projects. We also tell the method imFormat = im.format The next two functions return We use 1.6 million colors as the to take the color ofthe nearest imSize = im.size the heightand width ofthe image imMode = im.mode parameter, because the neighbor. This returns a new file in pixels. The difference image.getcolors() routine defaults image, which we save as a file and self.size = imSize between the two is that GetHW to 0 overcolorcount over256. return the filename. self.imformat = imFormat returns a string like 1024x768 and self.immode = imMode GetHW2 returns two integers. def GetColors(self,image): The ReduceColors routine numColors = The GetColorCount function (below) basically uses the def GetHW(self,file): image.getcolors(1600000) uses the .getcolors method to get colors = len(numColors) Image.ADAPTIVE pallet so we can the numberofcolors in the image def ReduceColors(self,ImageName): file. We have to use 1600000 as the #Reduce colors maxcolors parameter because if numcolors=MaxColors.get() image = Image.open(ImageName) the image contains more than 256 output = image.convert('P', palette=Image.ADAPTIVE, colors=numcolors) colors (or whatever is in the x = output.convert("RGB") parameter, the method returns self.GetColors(x) numcolors = x.getcolors() ‘None’. This function is similar to ProcessedColors.set(len(numcolors)) the GetColors function except the x.save('im1.png') GetColors works with an already return 'im1.png'

full circle magazine #87 1 4 contents ^ HOWTO - PYTHON PART 56 geta much smallernumberof be used to get the properfloss colors. colors. We simply create labels to def MakeLines(self,im,pixelSize): global backgroundColor1 hold the colors (visual) and text. #------There are two MakeLines (top image = Image.open(im) pixel = image.load() right) routines. They create the This (next page) is the routine for i in range(0,image.size[0],pixelSize): grid we spoke ofearlier. thatwe use to tryto find the for j in range(0,image.size[1],pixelSize): closest match between the color in for r in range(pixelSize): pixel[i+r,j] = backgroundColor1 Rgb2Hex() returns a hex value the image and the colorin the pixel[i,j+r] = backgroundColor1 ofthe RGB value thatis passed in. database. There are many different image.save('output.png') We will use this to try to compare algorithms on the web that you can def MakeLines2(self,im,pixelSize): the colors in the database with the lookat and try to understand the global backgroundColor2 colors in the image. logic behind it. It gets rather #------complicated. image = Image.open(im) pixel = image.load() def Rgb2Hex(self,rgb): for i in range(0,image.size[0],pixelSize): return '#%02x%02x%02x' % Ok. That’s all for this month. for j in range(0,image.size[1],pixelSize): rgb for r in range(pixelSize): Next time, we will start creating try: The ScrollList (below) on the the PDF output file so the cross pixel[i+r,j] = backgroundColor2 stitcher has something to work pixel[i,j+r] = backgroundColor2 right side holds the colors that will except: pass image.save('output2.png') def FillScrolledList(self,filename): im = Image.open(filename) numColors = im.getcolors() colors = len(numColors) cntr = 1 for c in numColors: hexcolor = self.Rgb2Hex(c[1]) lblColor=Label(self.sfFrame,text=" ",bg=hexcolor,relief=GROOVE) lblColor.grid(row = cntr, column = 0, sticky = 'nsew',padx=10,pady=5) pkID = self.GetBestDistance(c[1][0],c[1][1],c[1][2]) sql = "SELECT * FROM DMC WHERE pkID = {0}".format(pkID) rset = cursor.execute(sql) for r in rset: hexcolor2 = r[6] dmcnum = r[1] colorname = r[2] lblColor2=Label(self.sfFrame,text=" ",bg="#" + hexcolor2,relief=GROOVE) lblColor2.grid(row = cntr,column = 1,sticky = 'w',padx=5,pady=5) lblColor3=Label(self.sfFrame,text = str(dmcnum) + "-" + colorname,justify=LEFT) DmcColor.set(dmcnum) lblColor3.grid(row = cntr, column = 2,sticky = "w",padx=1,pady=5) cntr += 1

full circle magazine #87 1 5 contents ^ HOWTO - PYTHON PART 56 with. def GetBestDistance(self,r1,g1,b1): # dist = math.sqrt(((r1-r2)**2) + ((g1-g2)**2) + ((b1-b2)**2)) As always, the code is available sql = "SELECT * FROM DMC" on PasteBin at rset = cursor.execute(sql) BestDist = 10000.0 http://pastebin.com/DmQ1 GeUx. for r in rset: We will continue in the next month pkID = r[0] orso. I’m facing some surgery soon r2 = r[3] g2 = r[4] so I’m notsure howsoon I willbe b2 = r[5] able to sitforanylong periods of dist = math.sqrt(((r1-r2)**2) + ((g1-g2)**2) + ((b1-b2)**2)) if dist < BestDist: time. Until then, enjoy. BestDist = dist BestpkID = pkID return BestpkID

Greg Walters is owner ofRainyDay Solutions, LLC, a consulting company in Aurora, Colorado, and has been programming since 1972. He enjoys cooking, hiking, music, and spending time with his family. His website is www.thedesignatedgeek.net.

full circle magazine #87 1 6 contents ^ HHOOWW--TTOO Program In Python Pt. 57 Written by Greg Walters Program In Python Pt. 57

CROSS STITCH PATTERN The first line imports the library The origin ofthe page is the upper- border(default)), 1 is border, or a left corner, and the current string ofany or all ofthe following GENERATOR -PART 4 - file. The next creates an instance of the FPDF object. We use the position defaults to 1 cm from the characters: "L","T","B","R" UNDERSTANDINGPYFPDF default values for this example, margin. The margin can be changed • Line is where the current position which are: with the SetMargins function. should go after printing the text. S orry for missing so many • Portrait Values are 0 (to the right), 1 (to the months. I still can’t sit for long • Measure Unit = Millimeters. Before you can actually print beginning ofthe nextline, 2 periods oftime, so this article • Format = A4 any text, you must call (below). Default is 0, and putting 1 might be shorterthan what you are pdf.set_font() to define a font. In is equivalent to putting 0 and used to. Myoriginal plan was to Ifyou need to use ‘US’ the line above, we are defining calling ln() immediatly after. jump right into the PDF output standards, you could do it this way: Arial Bold 16 point. Standard valid • Align allows to centeroralign the portion ofthe program, but there fonts are Arial, Times, Courier, text within the cell. Values are "L" is so much to understand about pdf=FPDF(‘P’,’in’,’Letter) Symbol and ZapfDingbats. (left), "C" (center), "R" (right). this library, I decided to use this • Fill sets the background to be installment as a tutorial on pyfPDF Notice the parameters are Nowwe can printa cellwith the painted (true) or transparent and then tackle the PDF output FPDF(orientation, units, format): pdf.cell() call. A cell is a rectangular (false). Default is false. next time. So let’s get started. • Possible values for orientation are area, possibly framed, which • Link is a url oridentifierreturned “P” for Portrait and “L” for contains some text. Output is at by addlink(). FPDF stands for Free PDF. A Landscape. the current position which is VERYminimal example would be as • Possible values for units are: ‘pt’ specified (40,10 cm) in the above Finally, the document is closed follows: (poiints), ‘mm’ (millimeter), ‘cm’ example. The parameters are: and sent to the file with Output. (centimeter), ‘in’ (inches). The parameters are from fpdf import FPDF • Possible values for format are: pdf.cell(Width, Height, text, fpdf.output(name,dest). If file is border, line, align, fill, pdf = FPDF() ‘A3’, ‘A4’, ‘A5’, ‘Letter’, ‘Legal’ or a link) not specified, the output will be tuple containing the width and sent to the browser. Options for pdf.add_page() height expressed in the unit given Where: destination are "I" (inline to pdf.set_font(‘Arial’,’B’,16) in the unit parameter. • Width is length ofcell. If0, width browser(default)), "F" (local file extends to the right margin. given byname), "D" (to the pdf.cell(40,10,’Hello From browser and force a file download Python’) The third line creates a page to • Heightis the heightofthe cell. enterdata into. Notice a page is • Textis the string oftextyou want with the name passed), and "S" pdf.output(‘example1.pdf’,’F’ not automatically created when we to print. (return the document as a string). ) create the instance ofthe object. • Borderis either0 (no

full circle magazine #91 1 1 contents ^ HOWTO-PROGRAMINPYTHON Since we will be sending our FPDF priorto 1.7, Alpha channel is cross stitch images to the pdffile, not supported. from fpdf import FPDF we will have to understand the class PDF(FPDF): image function. I stole this example (shown def header(this): right) from the pyFPDF tutorial. # Logo - replace with a small png of your own The function is called like this: this.image('img1.png',10,8,33) # Arial bold 15 You have been around long this.set_font('Arial','B',15) pdf.image(name,x=None,y=None, enough thatyou should be able to # Move to the right w=0,h=0,type="",link="") look at the program and this.cell(80) # Title understand whatis going on. Butin This function puts the image. this.cell(30,10,'Title',1,0,'C') this example the line we are # Line break The size itwilltake on the page can REALLY interested in is the fourth this.ln(20) be specified in different ways: line: • Explicit width and height or # Instantiation of inherited class pdf=PDF() • One explicit dimension this.image('img1.png',10,8,33 pdf.alias_nb_pages() ) pdf.add_page() pdf.set_font('Times','',12) Supported formats are JPEG, for i in range(1,41): PNG, and GIF. Ifyou wish to use GIF In this instance, we are calling pdf.cell(0,10,'Printing line number '+str(i),0,1) files, you mustgetthe GD the image function with the pdf.output('example2.pdf','F') extension. filename, the x position ofwhere the picture will go on the page, the For , all flavors are yposition, and the width ofthe allowed: picture. • gray scale • true colours (24 bits) Nowthatyou have a gross • CMYK(32 bits) grasp ofthe library, we will start ourPDF code next time. For PNGs, the following are allowed: Until then, have a good month. • grayscales on atmost8 bits (256 See you soon. levels) Greg Walters is owner ofRainyDay • indexed colors Solutions, LLC, a consulting company • true colors (24 bits) in Aurora, Colorado, and has been programming since 1972. He enjoys cooking, hiking, music, and spending Note: interlacing is not allowed, time with his family. His website is and ifyou are using a version of www.thedesignatedgeek.net.

full circle magazine #91 1 2 contents ^ HHOOWW--TTOO Program in Python - Part 57 Written by Greg D. Walters Program in Python - Part 57

irst, let me thankall the running screaming from the room, I here we are. But first, let’s revert programmer. Almost anyone could, F readers who sent me emails of innocently asked what was wrong. backto 1979 when Apple in a fewhours orso, make sense of hope and wishes fora quick He responded that there was introduced Visicalc. That was the numbers, create charts and graphs, recovery. They were very kind and something wrong with one ofthe first “Free Form Calculation type and share that information with helpful. I also want to thank macros and “the thing just quits in system” to really make a hit in the coworkers. Shortly after that, the Ronnie, our wonderful editor, for the middle ofthe calculations”. As I marketplace. While there were ability to automate some portions his support and patience during whipped out my white cowboy hat, many bugs in the software, the of the spreadsheet through that painful period. I still have I said in mybesthero voice “Don’t world loved the idea and clones Macros and Basic-like embedded issues with sitting for long periods worry citizen. We’ll have you up (bugs and all) began to pop up on languages gave these non- oftime, so this is being done over and running in no time.” Within a other computer systems, like the programmer users even more the course ofa numberofdays, so short while, I discovered the reason Commodore Pet and other Apple power over their destiny. They I hope the continuity that I’m the spreadsheet was competitors (including Microsoft in could get the answers themselves, trying for works. Now on with “the unceremoniously crashing was that 1981 with a program called and pretty charts and graphs as show”… one cell in one of35 workbooks Multiplan). Finally, in 1983, a well, without having to wait in the was getting a divide by zero error company called Lotus queue for I.T. assistance. However, Nottoo long ago, I was walking due to an expected value not being Development Corp. introduced as we all learned from Peter to the time clock and the General entered in another cell in yet Lotus 1-2-3. While very close to Parker’s uncle Ben… Managerofmy “day job” called me another one ofthe 35 workbooks. Visicalc in many aspects, including into his office. Hoping it was just a Let me make this perfectly clear, it the menu structure, it was written WITH GREAT POWER, COMES “how’s itgoing” talk, I wentin and was not my boss’s fault. All he had completely in x86 assembly GREAT RESPONSIBILITY. sat down. He then started the asked forwas a simple wayto get language, which made it very fast, meeting with “I’m having a the higher-up values from the data. and many ofthe bugs ofVisicalc Soon the spreadsheet was problem with my spreadsheet (The previous two sentences have were fixed. Lotus 1-2-3 was so taken into areas that were better program, and was hoping you absolutely nothing to do with the popular that it became a common suited for databases than could help me”. fact that my boss may read this benchmark to test a machine for article! Or maybe it does.) “PC Compatibility”. spreadsheets. We now had workbooks upon workbooks that As my vision darkened and the relied on other workbooks, and if three-note ominous orchestral As I walked backto mywork The advent ofthe Free Form one little number along the way string hits “Da Da DAAAAAAAAA” area, brushing the spurious bits of Calculation systems, allowed the didn’t happen to get updated… that we all know from the horror computer code from my white hat, “normal” person to deal with well, we had the old “house of flicks ofthe 70’s and 80’s rang I realized thatthis would be an numbers in a way that previously cards” effect. through my mind, rather than excellent teaching moment. So, was in the realm ofthe

full circle magazine #95 1 5 contents ^ HOWTO - PYTHON

While I don’t think that every This library allows us to easily read Now download and install XLRD sheet_by_index method to get spreadsheet is evil, there are some data from Excel files (.xls , .xlsx and (https://pypi.python.org/pypi/xlrd). Sheet1 into the first_sheet object. (read this to say ‘many’) that .xlsm) from versions 2.0 onward. We can use it like is shown below. Now we can start getting data. We should have been converted to get the information from the cell databases many years ago. They Let’s create an excel Save the file as example1.py in at position (1,1) which translates just became too large and spreadsheet that we can use to the same folderas the to cell position B2 (it’s Zero based, unwieldy for their own good. If examine the functionality of XLRD. spreadsheet. Since the code is so so cell A1 would be (0,0)). We print someone had just sat down with Either open excel, or openoffice or short, we will simply discuss it the data from there, both what the the programmers and said, “Please libreoffice calc. In the first column here. Ofcourse, the first line cell contains and the value, so we help”, the world would be a kinder, (A), enterthe numbers 1 to 5 going imports the library. Then we create could use itin a calculation ifwe gentler place. down. In the next column (B), enter a function called OpenFile and wish. 6 to 10. Itshould looksomething pass the name (and path ifneeded) Nowas I step down from my like this: ofthe spreadsheet to the function. That was really easy, wasn’t it? soapbox, we come to the real Now, let’s do something a bit more reason for this month’s article. Now we call the useful. Enter the code shown on Every good Python programmer open_workbook method and get the next page (top right) and save should have a wayto deal with back a ‘book’ object. Then we use it as ‘example2.py’. This example spreadsheets in their arsenal of the nsheets attribute to return the will print out the contents ofthe tools. You never know when you number of ACTIVE workbooks. We workbook. will be called upon to pull data can also getthe name ofthe from a spreadsheet and workbooks. In this case, they are Since we already used the first manipulate it. While there are the default. We use the fourlines ofcode in the first many ways to get data from spreadsheets like using CSV files, import xlrd def OpenFile(path): which has its own drawbacks, # Open and read excel file sometimes you need to read and book = xlrd.open_workbook(path) write directly from and to a ‘live’ # Get number of active workbooks print "Number of workbooks: ",book.nsheets spreadsheet. After looking around, # Get the names of those workbooks I settled on a very nice library to print "Workbook names: ",book.sheet_names() access my boss’s problematical first_sheet = book.sheet_by_index(0) Now save the spreadsheet as cell = first_sheet.cell(1,1) spreadsheet. print "Cell at 1,1: ",cell “example1.xls” in the folder you print "Cell Value at 1,1: ",cell.value We will be adding the library will use to save the test code. This way, we won’t have to worry about if __name__ == "__main__": called XLRD, which one might path = "example1.xls" imagine stands for eXceL ReaD. paths. OpenFile(path)

full circle magazine #95 1 6 contents ^ HOWTO - PYTHON example, we’ll skip them. By using the ‘sheet.nrows’ and ‘sheet.ncols’ import xlrd def OpenFile(path): attributes, we get the number of book = xlrd.open_workbook(path) rows and columns. This can be first_sheet = book.sheet_by_index(0) helpful, not only so we know what # Get the number of rows in this workbook rows = first_sheet.nrows we are dealing with; we can write # get the number of columns in this workbook “generic” routines that use those cols = first_sheet.ncols values in our calculations as you print "There are %d rows in this workbook." % rows print "There are %d cols in this workbook." % cols willsee. In fact, we use ‘rows’ in a for r in range(0,rows): for loop to obtain each row’s cells = first_sheet.row_slice(rowx=r,start_colx=0,end_colx=cols) information. print cells if __name__ == "__main__": Notice the line that has path = "example1.xls" OpenFile(path) ‘first_sheet.row_slice’. This gets a block ofcells ofa given row. The syntax is as follows: We’ll do one more example xldate:36588.0] You can use any dates you like. Press any key to continue ... before we end this month’s article. Now let’s re-run our example2.py X = Go to the spreadsheet and in program. Here is the output from Well, that’s not what we first_sheet.row_slice(RowInQu column C put some dates. Here’s mine. expected. It seems that excel holds estion, Start_Column, what my spreadsheet looks like End_Column) dates as a value thatis simply now: There are 5 rows in this formatted for whatever we ask it So we have used the numberof workbook. There are 3 cols in this to. This might be helpful for rows and the numberofcolumns in workbook. sorting and calculations, but, for calculations. The output from our [number:1.0, number:6.0, showing the actual data, this won’t program should look something xldate:41649.0] [number:2.0, number:7.0, do. Luckily, the writers ofthe like this… xldate:42109.0] library already thought of this. [number:3.0, number:8.0, Delete the line that says “print There are 5 rows in this xldate:31587.0] workbook. [number:4.0, number:9.0, cells” and replace it with the code There are 2 cols in this xldate:23284.0] shown below. workbook. [number:5.0, number:10.0, [number:1.0, number:6.0] [number:2.0, number:7.0] for c in cells: [number:3.0, number:8.0] if c.ctype == xlrd.XL_CELL_DATE: [number:4.0, number:9.0] date_value = xlrd.xldate_as_tuple(c.value,book.datemode) [number:5.0, number:10.0] dt = str(date_value[1]) + "/" + str(date_value[2]) + "/" + str(date_value[0]) Press any key to continue . . print dt . else: print c.value

full circle magazine #95 1 7 contents ^ HOWTO - PYTHON

Here, we go through each cell in The source code for the cells list and check the type of example3.py is on pastebin at PYTHON SPECIAL EDITIONS: the cell to see ifitis considered a http://pastebin.com/bWz7beBw. XL_CELL_DATE. If it is, then we convertitto a tuple. Itis stored as Hopefully, I’ll see you next YYYY,MM,DD. We simply pretty it month. up to print it as MM/DD/YYYY. Here is the outputofournew program…

There are 5 rows in this workbook. There are 3 cols in this http://fullcirclemagazine.org/issue-py01 / http://fullcirclemagazine.org/issue-py02/ workbook. 1.0 6.0 1/10/2014 2.0 7.0 4/15/2015 3.0 8.0 6/24/1986 4.0 9.0 9/30/1963 5.0 http://fullcirclemagazine.org/python- http://fullcirclemagazine.org/python- 10.0 special-edition-issue-three/ special-edition-volume-four/ 3/3/2000 Press any key to continue ...

Just for your information, there is a library from the same wonderful people called XLWT, which allows you to write to excel files. There is a wonderful tutorial Greg Walters is owner ofRainyDay Solutions, LLC, a consulting company and documentation on these two in Aurora, Colorado, and has been libraries at http://www.python- programming since 1972. He enjoys cooking, hiking, music, and spending excel.org/. http://fullcirclemagazine.org/python- http://fullcirclemagazine.org/python- time with his family. His website is special-edition-volume-five/ special-edition-volume-six/ www.thedesignatedgeek.net.

full circle magazine #95 1 8 contents ^ HHOOWW--TTOO Program in Python Pt. 59 Written by Greg. D. Walters Program in Python Pt. 59

irst, letme sayHappy100 to will double the frequency ofthe So with the abilityofa guitar F Ronnie and the crew. It’s a vibrations. In the case ofa guitar, makerto come up with theirown privilege to be part ofthis this string length is between the scale length, the spacing ofthe milestone. nut and the saddle. This distance is frets will have to be recalculated. referred to the Scale Length ofthe This is something that luthiers This time I thoughtthatI’d guitar. The half-point that allows (guitar makers) have been dealing share some information on my new for the doubled frequency is fret # with for hundreds ofyears. obsession. I’ve started repairing 12. Ifcorrectly done, just by lightly and building stringed musical placing your finger on the string at In the past, there was a instruments like guitars and this location, you get a pleasing technique called the rule of18 violins. Believe it ornot, there is a tone. There are a few other which involves successively lot ofmath involved in musical positions that this will happen, but dividing the scale length minus the instruments. Today, we will look at the 12th fret should be the perfect offset to the previous fret by 18. some ofthe math involved with location for this doubling, making While this kind ofworked, the the length ofstrings and where the note go up one octave. tones were off, the higher up the the frets should be placed on the fingerboard the player went. These fretboard. Different scale lengths will days, we use a different constant. create different feel and tones. For This constant is 17.817. By using Take a lookat the picture ofthe example, guitars like the Fender this “new” constant, the 12th fret guitar. I annotated various items in Stratocasters® have a scale length or octave is at the exact position to the image. The important things to of25½”, which produces a rich and be halfthe scale length ofthe lookatare the Nutnearthe top of strong bell-like tone. On the other string. the fingerboard, the Frets, the hand, Gibson guitars often use a Bridge nearthe bottom, and the scale length of24¾”. This creates a Now, these calculations are white “line” within the bridge lower string tension which makes easyenough to do bypaperand called the Saddle. The purpose of an easierplaying feel and a warmer pencil or even a simple calculator, the frets is to create a perfect spot tone. Other guitar manufacturers it’s just as easy to create a Python to change the length ofthe string decided that a scale length of25” program to do the calculations for Now, the physics of vibrating to create a note thatis in tune. The makes a clearer tone than either of us in justa second. Once you have strings tells us that ifyou halfthe positions ofthese frets are not the other two “standard” scale the positions, you simply saw a slot vibrating string length ofa arbitrary, but mathematically lengths. for the fret at the correct positions theoretically perfect string, you determined. and then hammerin the frets.

full circle magazine #100 21 contents ^ HOWTO - PYTHON then take that value, divide it by So, let’s take a lookatthe def CalcSpacing(Length,NTF): program. ourconstant (17.817), add backin BridgeToFret = Length-NTF the cumulative distance and then NutToFret = (BridgeToFret/17.817) + NTF return NutToFret We want to create a program return that value to our calling that will prompt for the scale routine. Remember, we could since this is the first calculation. with the conversion in the DoWork length ofthe guitar(orbass), do simply have returned the Otherwise, we pass the last routine. the calculations and then print out calculated value without assigning cumulative length in and it the distances. The calculations and it to a variable name. However, if becomes the result from the ScaleLength = we ever want to inspect the raw_input(“Please enter Scale all returned lengths are all in calculation routine. Finally, we Length of guitar -> “) inches, so all our friends that use calculated values, it’s easier to do print each fret number followed by metric measurements, please add ifwe assign the value before we a formatted version ofthe DoWork(float(ScaleLength)) the proper conversion calculations. return it. cumulative length. You might wonder what good After almost 5 years, you should be this program will do ifyou aren’t able to do this with ease. Now we will make our worker Finally, we have the code that routine. We’ve done this kind of does the prompting for the scale going to build a guitarfrom scratch. It can be valuable when We don’t need to import any thing manytimes in the past. We length. I’m sure you will remember you're looking at buying a used libraries for this so we will start off will pass itthe scale length and it the format for the raw_input guitarortrying to tweak a guitar by defining a couple ofvariables. will loop forup to 24 frets routine, since we have used it so (range(1,25)). Even if your project many times before. Something you with a floating bridge. Also, ifyou ScaleLength = 0 has less than 24 frets, you will have might not remember: that are a guitar player, this might have the correct positions ofall the raw_input always returns a string, been something you didn’t know CumulativeLength = 0 frets you do have. I chose 24 so when we pass itoffto the about guitars. because that’s the maximum of Next we will create a routine DoWork routine, we have to pass it frets for most guitars. When we Ofcourse, the code is available (top right) that will be called as a floating point numberso the get into the loop, we checkthe from pastebin at repeatedly as we “travel down” the routine will work correctly. Of fretnumber(x) and ifitis1, we http://pastebin.com/A2RNECt5. fingerboard. We will pass two course, we could simply pass it as a pass the cumulative length as 0, values into this routine. One is the string, but we would have to deal scale length and the otheris the def DoWork(ScaleLength): cumulative distance from the nut CumulativeLength = 0 to the previous fret. for x in range(1,25): FretNumber = x if FretNumber == 1: In this routine, we take the CumulativeLength = CalcSpacing(ScaleLength,0) scale length, subtract the else: CumulativeLength = CalcSpacing(ScaleLength,CumulativeLength) cumulative distance and assign print(“Fret=%d,NutToFret=%.3f” % (FretNumber,CumulativeLength)) that value to BridgeToFret. We

full circle magazine #100 22 contents ^ Full Circle Team HHOOWW TTOO CCOONNTTRRIIBBUUTTEE Edit or - Ronnie Tucker [email protected] Please note: Webmast er - Lucas Westermann FULL CIRCLE NEEDS YOU! Special editions are A magazine isn't a magazine without articlesand Full Circle is no compiled from originals [email protected] exception. We need your opinions, desktops, stories, how-to's, and may not work with Special Editions - Jonathan Hoskin reviews,and anything else you want to tell your fellow *buntu users. current versions. Send your articles to: [email protected] Editing & Proofreading We are alwayslooking for new articlesto include in Full Circle. For help and advice Mike Kennedy, Gord Campbell, Robert please see the Official Full Circle Style Guide: http://url.fullcirclemagazine.org/75d471 Orsino, Josh Hertel, Bert Jerred, Jim Dyer and Emily Gonyer Send your comments or Linux experiencesto: [email protected] Hardware/software reviews should be sent to: [email protected] Our thanksgo to Canonical,the many Questionsfor Q&A should go to: [email protected] translation teamsaround the world Desktop screensshould be emailed to: [email protected] and Thorsten Wilmsfor the FCM logo. ...or you can visit our site via: fullcirclemagazine.org

Get t ing Full Circle Magazine: For the Full Circle Weekly News: EPUB Format -Most editionshave alink to the epub file on that issuesdownload page.If you have any problems You can keep up to date with the Weekly Newsusing the RSS with the epub file,email: [email protected] feed: http://fullcirclemagazine.org/feed/podcast

Or,if your out and about,you can get the WeeklyNewsvia Issuu - You can read Full Circle online via Issuu: Stitcher Radio (Android/iOS/web): http://issuu.com/fullcirclemagazine. Please share and rate http://www.stitcher.com/s?fid=85347&refid=stpr FCM asit helpsto spread the word about FCM and Ubuntu.

and via TuneIn at: http://tunein.com/radio/Full-Circle-Weekly- Magzst er - You can also read Full Circle online via News-p855064/ Magzster: http://www.magzter.com/publishers/Full-Circle. Please share and rate FCM asit helpsto spread the word about FCM and Ubuntu Linux.

29 contents ^