Shallow Thoughts : tags : programming

Akkana's Musings on Open Source Computing, Science, and Nature.

Thu, 26 Jun 2014

A Raspberry Pi Night Vision Camera

[Mouse caught on IR camera]

When I built my http://shallowsky.com/blog/hardware/raspberry-pi-motion-camera.html (and part 2), I always had the NoIR camera in the back of my mind. The NoIR is a version of the Pi camera module with the infra-red blocking filter removed, so you can shoot IR photos at night without disturbing nocturnal wildlife (or alerting nocturnal burglars, if that's your target).

After I got the daylight version of the camera working, I ordered a NoIR camera module and plugged it in to my RPi. I snapped some daylight photos with raspstill and verified that it was connected and working; then I waited for nightfall.

In the dark, I set up the camera and put my cup of hot chocolate in front of it. Nothing. I hadn't realized that although CCD cameras are sensitive in the near IR, the wavelengths only slightly longer than visible light, they aren't sensitive anywhere near the IR wavelengths that hot objects emit. For that, you need a special thermal camera. For a near-IR CCD camera like the Pi NoIR, you need an IR light source.

Knowing nothing about IR light sources, I did a search and came up with something called a "Infrared IR 12 Led Illuminator Board Plate for CCTV Security CCD Camera" for about $5. It seemed similar to the light sources used on a few pages I'd found for home-made night vision cameras, so I ordered it. Then I waited, because I stupidly didn't notice until a week and a half later that it was coming from China and wouldn't arrive for three weeks. Always check the shipping time when ordering hardware!

When it finally arrived, it had a tiny 2-pin connector that I couldn't match locally. In the end I bought a package of female-female SchmartBoard jumpers at Radio Shack which were small enough to make decent contact on the light's tiny-gauge power and ground pins. I soldered up a connector that would let me use a a universal power supply, taking a guess that it wanted 12 volts (most of the cheap LED rings for CCD cameras seem to be 12V, though this one came with no documentation at all). I was ready to test.

Testing the IR light

[IR light and NoIR Pi camera]

One problem with buying a cheap IR light with no documentation: how do you tell if your power supply is working? Since the light is completely invisible.

The only way to find out was to check on the Pi. I didn't want to have to run back and forth between the dark room where the camera was set up and the desktop where I was viewing raspistill images. So I started a video stream on the RPi:

$ raspivid -o - -t 9999999 -w 800 -h 600 | cvlc -vvv stream:///dev/stdin --sout '#rtp{sdp=rtsp://:8554/}' :demux=h264

Then, on the desktop: I ran vlc, and opened the network stream:
rtsp://pi:8554/
(I have a "pi" entry in /etc/hosts, but using an IP address also works).

Now I could fiddle with hardware in the dark room while looking through the doorway at the video output on my monitor.

It took some fiddling to get a good connection on that tiny connector ... but eventually I got a black-and-white view of my darkened room, just as I'd expect under IR illumination. I poked some holes in the milk carton and used twist-ties to seccure the light source next to the NoIR camera.

Lights, camera, action

Next problem: mute all the blinkenlights, so my camera wouldn't look like a christmas tree and scare off the nocturnal critters.

The Pi itself has a relatively dim red run light, and it's inside the milk carton so I wasn't too worried about it. But the Pi camera has quite a bright red light that goes on whenever the camera is being used. Even through the thick milk carton bottom, it was glaring and obvious. Fortunately, you can disable the Pi camera light: edit /boot/config.txt and add this line

disable_camera_led=1

My USB wi-fi dongle has a blue light that flickers as it gets traffic. Not super bright, but attention-grabbing. I addressed that issue with a triple thickness of duct tape.

The IR LEDs -- remember those invisible, impossible-to-test LEDs? Well, it turns out that in darkness, they emit a faint but still easily visible glow. Obviously there's nothing I can do about that -- I can't cover the camera's only light source! But it's quite dim, so with any luck it's not spooking away too many animals.

Results, and problems

For most of my daytime testing I'd used a threshold of 30 -- meaning a pixel was considered to have changed if its value differed by more than 30 from the previous photo. That didn't work at all in IR: changes are much more subtle since we're seeing essentially a black-and-white image, and I had to divide by three and use a sensitivity of 10 or 11 if I wanted the camera to trigger at all.

With that change, I did capture some nocturnal visitors, and some early morning ones too. Note the funny colors on the daylight shots: that's why cameras generally have IR-blocking filters if they're not specifically intended for night shots.

[mouse] [rabbit] [rock squirrel] [house finch]

Here are more photos, and larger versions of those: Images from my night-vision camera tests.

But I'm not happy with the setup. For one thing, it has far too many false positives. Maybe one out of ten or fifteen images actually has an animal in it; the rest just triggered because the wind made the leaves blow, or because a shadow moved or the color of the light changed. A simple count of differing pixels is clearly not enough for this task.

Of course, the software could be smarter about things: it could try to identify large blobs that had changed, rather than small changes (blowing leaves) all over the image. I already know SimpleCV runs fine on the Raspberry Pi, and I could try using it to do object detection.

But there's another problem with detection purely through camera images: the Pi is incredibly slow to capture an image. It takes around 20 seconds per cycle; some of that is waiting for the network but I think most of it is the Pi talking to the camera. With quick-moving animals, the animal may well be gone by the time the system has noticed a change. I've caught several images of animal tails disappearing out of the frame, including a quail who visited yesterday morning. Adding smarts like SimpleCV will only make that problem worse.

So I'm going to try another solution: hooking up an infra-red motion detector. I'm already working on setting up tests for that, and should have a report soon. Meanwhile, pure image-based motion detection has been an interesting experiment.

Tags: , , , ,
[ 13:31 Jun 26, 2014    More hardware | permalink to this entry | comments ]

Sat, 21 Jun 2014

Mirror a website using lftp

I'm helping an organization some website work. But I'm not the only one working on the website, and there's no version control. I wanted an easy way to make sure all my files were up-to-date before I start to work on one ... a way to mirror the website, or at least specific directories, to my local disk.

Normally I use rsync -av over ssh to mirror directories, but this website is on a server that only offers ftp access. I've been using ncftp to copy files up one by one, but although ncftp's manual says it has a mirror mode and I found a few web references to that, I couldn't find anything telling me how to activate it.

Making matters worse, there are some large files that I don't need to mirror. The first time I tried to use get * in ncftp to get one directory, it spent 15 minutes trying to download a huge powerpoint file, then stalled and lost the connection. There are some big .doc and .docx files, too. And ncftp doesn't seem to have a way to exclude specific files.

Enter lftp. It has a mirror mode (with documentation, even!) which includes a -X to exclude files matching specified patterns.

lftp includes a -e to pass commands -- like "mirror" -- to it on the command line. But the documentation doesn't say whether you can use more than one command at a time. So it seemed safer to start up an lftp session and pass a series of commands to it.

And that works nicely. Just set up the list of directories you want to mirror, and you can write a nice shell function you can put in your. .zshrc or .bashrc:

sitemirror() {
commands=""
for dir in thisdir thatdir theotherdir
do
  commands="$commands
mirror --only-newer -vvv -X '*.ppt' -X '*.doc*' -X '*.pdf' htdocs/$dir $HOME/web/webmirror/$dir"
done

echo Commands to be run:
echo $commands
echo

lftp <<EOF
open -u 'user,password' ftp.example.com
$commands
bye
EOF
}

Super easy -- all I do is type sitemirror and wait a little. Now I don't have any excuse for not being up to date.

Tags: , ,
[ 12:39 Jun 21, 2014    More tech/web | permalink to this entry | comments ]

Sun, 15 Jun 2014

Vim: Set wrapping and indentation according to file type

Although I use emacs for most of my coding, I use vim quite a lot too, for quick edits, mail messages, and anything I need to edit when logged onto a remote server. In particular, that means editing my procmail spam filter files on the mail server.

The spam rules are mostly lists of regular expression patterns, and they can include long lines, such as:
gift ?card .*(Visa|Walgreen|Applebee|Costco|Starbucks|Whitestrips|free|Wal.?mart|Arby)

My default vim settings for editing text, including line wrap, don't work if get a flood of messages offering McDonald's gift cards and decide I need to add a "|McDonald" on the end of that long line.

Of course, I can type ":set tw=0" to turn off wrapping, but who wants to have to do that every time? Surely vim has a way to adjust settings based on file type or location, like emacs has.

It didn't take long to find an example of Project specific settings on the vim wiki. Thank goodness for the example -- I definitely wouldn't have figured that syntax out just from reading manuals. From there, it was easy to make a few modifications and set textwidth=0 if I'm opening a file in my procmail directory:

" Set wrapping/textwidth according to file location and type
function! SetupEnvironment()
  let l:path = expand('%:p')
  if l:path =~ '/home/akkana/Procmail'
    " When editing spam filters, disable wrapping:
    setlocal textwidth=0
endfunction
autocmd! BufReadPost,BufNewFile * call SetupEnvironment()

Nice! But then I remembered other cases where I want to turn off wrapping. For instance, editing source code in cases where emacs doesn't work so well -- like remote logins over slow connections, or machines where emacs isn't even installed, or when I need to do a lot of global substitutes or repetitive operations. So I'd like to be able to turn off wrapping for source code.

I couldn't find any way to just say "all source code file types" in vim. But I can list the ones I use most often. While I was at it, I threw in a special wrap setting for mail files:

" Set wrapping/textwidth according to file location and type
function! SetupEnvironment()
  let l:path = expand('%:p')
  if l:path =~ '/home/akkana/Procmail'
    " When editing spam filters, disable wrapping:
    setlocal textwidth=0
  elseif (&ft == 'python' || &ft == 'c' || &ft == 'html' || &ft == 'php')
    setlocal textwidth=0
  elseif (&ft == 'mail')
    " Slightly narrower width for mail (and override mutt's override):
    setlocal textwidth=68
  else
    " default textwidth slightly narrower than the default
    setlocal textwidth=70
  endif
endfunction
autocmd! BufReadPost,BufNewFile * call SetupEnvironment()

As long as we're looking at language-specific settings, what about doing language-specific indentation like emacs does? I've always suspected vim must have a way to do that, but it doesn't enable it automatically like emacs does. You need to set three variables, assuming you prefer to use spaces rather than tabs:

" Indent specifically for the current filetype
filetype indent on
" Set indent level to 4, using spaces, not tabs
set expandtab shiftwidth=4

Then you can also use useful commands like << and >> for in- and out-denting blocks of code, or ==, for indenting to the right level. It turns out vim's language indenting isn't all that smart, at least for Python, and gets the wrong answer a lot of them time. You can't rely on it as a syntax checker the way you can with emacs. But it's a lot better than no language-specific indentation.

I will be a much happier vimmer now!

Tags: , ,
[ 11:29 Jun 15, 2014    More linux/editors | permalink to this entry | comments ]

Sun, 11 May 2014

Sonograms in Python

I went to a terrific workshop last week on identifying bird songs. We listened to recordings of songs from some of the trickier local species, and discussed the differences and how to remember them. I'm not a serious birder -- I don't do lists or Big Days or anything like that, and I dislike getting up at 6am just because the birds do -- but I do try to identify birds (as well as mammals, reptiles, rocks, geographic features, and pretty much anything else I see while hiking or just sitting in the yard) and I've always had trouble remembering their songs.

[Sonogram of ruby-crowned kinglet] One of the tools birders use to study bird songs is the sonogram. It's a plot of frequency (on the vertical axis) and intensity (represented by color, red being louder) versus time. Looking at a sonogram you can identify not just how fast a bird trills and whether it calls in groups of three or five, but whether it's buzzy/rattly (a vertical line, lots of frequencies at once) or a purer whistle, and whether each note is ascending or descending.

The class last week included sonograms for the species we studied. But what about other species? The class didn't cover even all the local species I'd like to be able to recognize. I have several collections of bird calls on CD (which I bought to use in combination with my "tweet" script -- yes, the name messes up google searches, but my tweet predates Twitter -- a tweet Python script and tweet in HTML for Android). It would be great to be able to make sonograms from some of those recordings too.

But a search for Linux sonogram turned up nothing useful. Audacity has a histogram visualization mode with lots of options, but none of them seem to result in a usable sonogram, and most discussions I found on the net agreed that it couldn't do it. There's another sound editor program called snd which can do sonograms, but it's fiddly to use and none of the many color schemes produce a sonogram that I found very readable.

Okay, what about python scripts? Surely that's been done?

I had better luck there. Matplotlib's pylab package has a specgram() call that does more or less what I wanted, and here's an example of how to use pylab.specgram(). (That post also has another example using a library called timeside, but timeside's PyPI package doesn't have any dependency information, and after playing the old RPM-chase game installing another dependency, trying it, then installing the next dependency, I gave up.)

The only problem with pylab.specgram() was that it shows the full range of the sound, both in time and frequency. The recordings I was examining can last a minute or more and go up to 20,000 Hz -- and when pylab tries to fit that all on the screen, you end up with a plot where the details are too small to show you anything useful.

You'd think there would be a way for pylab.specgram() to show only part of the spectrum, but that doesn't seem to be. I finally found a Stack Overflow discussion where "edited" gives an excellent rewritten version of pylab.specgram which allows setting minimum and maximum frequency cutoffs. Worked great!

Then I did some fiddling to allow for analyzing only part of the recording -- Python's wave package has no way to read in just the first six seconds of a .wav file, so I had to read in the whole file, read the data into a numpy array, then take a slice representing the seconds of the recording I actually wanted.

But now I can plot nice sonograms of any bird song I want to see, print them out or stick them on my Android device so I can carry them with me.

Update: Oops! I forgot to include a link to the script. Here it is: Sonograms in Python.


Tags: , , ,
[ 09:17 May 11, 2014    More programming | permalink to this entry | comments ]

Sun, 06 Apr 2014

Snow-Hail while preparing for Montreal

Things have been hectic in the last few days before I leave for Montreal with last-minute preparation for our PyCon tutorial, Build your own PiDoorbell - Learn Home Automation with Python next Wednesday.

[Snow-hail coming down on the Piñons] But New Mexico came through on my next-to-last full day with some pretty interesting weather. A windstorm in the afternoon gave way to thunder (but almost no lightning -- I saw maybe one indistinct flash) which gave way to a strange fluffy hail that got gradually bigger until it eventually grew to pea-sized snowballs, big enough and snow enough to capture well in photographs as they came down on the junipers and in the garden.

Then after about twenty minutes the storm stopped the sun came out. And now I'm back to tweaking tutorial slides and thinking about packing while watching the sunset light on the Rio Grande gorge.

But tomorrow I leave it behind and fly to Montreal. See you at PyCon!

Tags: , , , , ,
[ 18:55 Apr 06, 2014    More misc | permalink to this entry | comments ]

Wed, 29 Jan 2014

PyCon Tutorial: Build your own PiDoorbell - Learn Home Automation with Python

[Raspberry Pi from wikipedia] The first batch of hardware has been ordered for Rupa's and my tutorial at PyCon in Montreal this April!

We're presenting Build your own PiDoorbell - Learn Home Automation with Python on the afternoon of Wednesday, April 9.

It'll be a hands-on workshop, where we'll experiment with the Raspberry Pi's GPIO pins and learn how to control simple things like an LED. Then we'll hook up sonar rangefinders to the RPis, and build a little device that can be used to monitor visitors at your front door, birds at your feeder, co-workers standing in front of your monitor while you're away, or just about anything else you can think of.

Participants will bring their own Raspberry Pi computers and power supplies -- attendees of last year's PyCon got them there, but a new Model A can be gotten for $30, and a model B for $40.

We'll provide everything else. We worried that requiring participants to bring a long list of esoteric hardware was just asking for trouble, so we worked a deal with PyCon and they're sponsoring hardware for attendees. Thank you, PyCon! CodeChix is fronting the money for the kits and helping with our travel expenses, thanks to donations from some generous sponsors. We'll be passing out hardware kits and SD cards at the beginning of the workshop, which attendees can take home afterward.

We're also looking for volunteer T/As. The key to a good hardware workshop is having lots of helpers who can make sure everybody's keeping up and nobody's getting lost. We have a few top-notch T/As signed up already, but we can always use more. We can't provide hardware for T/As, but most of it's quite inexpensive if you want to buy your own kit to practice on. And we'll teach you everything you need to know about how get your PiDoorbell up and running -- no need to be an expert at hardware or even at Python, as long as you're interested in learning and in helping other people learn.

This should be a really fun workshop! PyCon tutorial sign-ups just opened recently, so sign up for the tutorial (we do need advance registration so we know how many hardware kits to buy). And if you're going to be at PyCon and are interested in being a T/A, drop me or Rupa a line and we'll get you on the list and get you all the information you need.

See you at PyCon!

Tags: , , , , ,
[ 20:32 Jan 29, 2014    More hardware | permalink to this entry | comments ]

Sat, 28 Dec 2013

Finding filenames in a disorganized directory

I've been scanning a bunch of records with Audacity (using as a guide Carla Schroder's excellent Book of Audacity and a Behringer UCA222 USB audio interface -- audacity doesn't seem able to record properly from the built-in sound card on any laptop I own, while it works fine with the Behringer.

Audacity's user interface isn't great for assembly-line recording of lots of tracks one after the other, especially on a laptop with a trackpad that doesn't work very well, so I wasn't always as organized with directory names as I could have been, and I ended up with a mess. I was periodically backing up the recordings to my desktop, but as I shifted from everything-in-one-directory to an organized system, the two directories got out of sync.

To get them back in sync, I needed a way to answer this question: is every file inside directory A (maybe in some subdirectory of it) also somewhere under subdirectory B? In other words, can I safely delete all of A knowing that anything in it is safely stored in B, even though the directory structures are completely different?

I was hoping for some clever find | xargs way to do it, but came up blank. So eventually I used a little zsh loop: one find to get the list of files to test, then for each of those, another find inside the target directory, then test the exit code of find to see if it found the file. (I'm assuming that if the songname.aup file is there, the songname_data directory is too.)

for fil in $(find AAA/ -name '*.aup'); do
  fil=$(basename $fil)
  find BBB -name $fil >/dev/null
  if [[ $? != 0 ]]; then
    echo $fil is not in BBB
  fi
done

Worked fine. But is there an easier way?

Tags: , , ,
[ 10:36 Dec 28, 2013    More linux/cmdline | permalink to this entry | comments ]

Wed, 11 Dec 2013

Counting syllables in Python

When I wrote recently about my Dactylic dinosaur doggerel, I glossed over a minor problem with my final poem: the rules of double-dactylic doggerel say that the sixth line (or sometimes the seventh) should be a single double-dactyl word -- something like "paleontologist" or "hexasyllabic'ly". I used "dinosaur orchestra" -- two words, which is cheating.

I don't feel too guilty about that. If you read the post, you may recall that the verse was the result of drifting grumpily through an insomniac morning where I would have preferred to be getting back to sleep. Coming up with anything that scans at all is probably good enough.

Still, it bugged me, not being able to think of a double-dactylic word that related somehow to Parasaurolophus. So I vowed that, later that day when I was up and at the computer, I would attempt to find one and rewrite the poem accordingly.

I thought that would be fairly straightforward. Not so much. I thought there would be some utility I could run that would count syllables for me, then I could run /usr/share/dict/words through it, print out all the 6-syllable words, and find one that fit. Turns out there is no such utility.

But Python has a library for everything, doesn't it?

Some searching turned up PyHyphen, which includes some syllable-counting functions. It apparently uses the hyphenation dictionaries that come with LibreOffice.

There's a Debian package for it, python-pyhyphen -- but it doesn't work. First, it depends on another package, hyphen-en-us, but doesn't have that dependency encoded in the package, even as a suggested or recommended package. But even when you install the hyphenated dictionary, it still doesn't work because it doesn't point to the dictionary in the place it was installed. Looks like that problem was reported almost two years ago, bug 627944: python-pyhyphen: doesn't work out-of-the-box with hyphen-* packages. There's a fix there that involves editing two files, /usr/lib/python2.7/dist-packages/hyphen/config.py and /usr/lib/python2.7/dist-packages/hyphen/__init__.py.

Or you can just give up on Debian and pip install pyhyphen, which is a lot easier.

But once you get it working, you find that it's terrible. It was wrong about almost every word I tried. I hope not too many people are relying on this hyphen-en-us dictionary for important documents. Its results seemed nearly random, and I quickly gave up on it for getting a useful list of words around six syllables.

Just for fun, since my count syllables web search turned up quite a few websites claiming that functionality, I tried entering some of my long test words manually. All of the websites I tried were wrong more than half the time, and often they were off by more than two syllables. I don't mind off-by-ones -- I can look at words claiming 5 and 7 syllables while searching for double dactyls -- but if I have to include 4-syllable words as well, I'll never find what I'm looking for.

That discouraged me from using another Python suggestion I'd seen, the nltk (natural language toolkit) package. I've been looking for an excuse to play with nltk, and some day I will, but for this project I was looking for a quick approximate solution, and the nltk examples I found mostly looked like using it would require a bigger time commitment than I was willing to devote to silly poetry. And if none of the dedicated syllable-counting websites or dictionaries got it right, would a big time investment in nltk pay off?

Anyway, by this time I'd wasted more than an hour poking around various libraries and websites for this silly unimportant problem, and I decided that with that kind of time investment, I could probably do better on my own than the official solutions were giving me. Why not basically just count vowels?

So I whipped up a little script, countsyl, that did just that. I gave it a list of vowels, with a few simple rules. Obviously, you can't just say every vowel is a new syllable -- there are too many double vowels and silent letters and such. But you can't say that any run of multiple vowels together counts as one syllable, because sometimes the vowels do count; and you can't make absolute rules like "'e' at the end of a word is always silent", because sometimes it isn't. So I kept both minimum and maximum syllable counts for each word, and printed both.

And much to my surprise, without much tuning at all my silly little script immediately much better results than the hyphenation dictionary or the dedicated websites.

Alas, although it did give me quite a few hexasyllabic words in /usr/share/dict/words, none of them were useful at all for a program on Parasaurolophus. What I really needed was a musical term (since that's what the poem is about). What about a musical dictionary?

I found a list of musical terms on Wikipedia: Glossary of musical terminology, saved it as a local file, ran a few vim substitutes and turned it into a plain list of words. That did a little better, and gave me some possible ideas: (non?)contrapuntally? (something)harmonically? extemporaneously?

But none of them worked out, and by then I'd run out of steam. I gave up and blogged the poem as originally written, with the cheating two-word phrase "dinosaur orchestra", and vowed to write up how to count words in Python -- which I have now done. Quite noncontrapuntally, and definitely not extemporaneously. But at least I have a useful little script next time I want to get an approximate syllable count.

Tags: , , , , ,
[ 17:51 Dec 11, 2013    More programming | permalink to this entry | comments ]

Wed, 13 Nov 2013

Does scrolling output make a program slower? Followup.

Last week I wrote about some tests I'd made to answer the question Does scrolling output make a program slower? My test showed that when running a program that generates lots of output, like an rsync -av, the rsync process will slow way down as it waits for all that output to scroll across whatever terminal client you're using. Hiding the terminal helps a lot if it's an xterm or a Linux console, but doesn't help much with gnome-terminal.

A couple of people asked in the comments about the actual source of the slowdown. Is the original process -- the rsync, or my test script, that's actually producing all that output -- actually blocking waiting for the terminal? Or is it just that the CPU is so busy doing all that font rendering that it has no time to devote to the original program, and that's why it's so much slower?

I found pingu on IRC (thanks to JanC) and the group had a very interesting discussion, during which I ran a series of additional tests.

In the end, I'm convinced that CPU allocation to the original process is not the issue, and that output is indeed blocked waiting for the terminal to display the output. Here's why.

First, I installed a couple of performance meters and looked at the CPU load while rendering. With conky, CPU use went up equally (about 35-40%) on both CPU cores while the test was running. But that didn't tell me anything about which processes were getting all that CPU.

htop was more useful. It showed X first among CPU users, xterm second, and my test script third. However, the test script never got more than 10% of the total CPU during the test; X and xterm took up nearly all the remaining CPU.

Even with the xterm hidden, X and xterm were the top two CPU users. But this time the script, at number 3, got around 30% of the CPU rather than 10%. That still doesn't seem like it could account for the huge difference in speed (the test ran about 7 times faster with xterm hidden); but it's interesting to know that even a hidden xterm will take up that much CPU.

It was also suggested that I try running it to /dev/null, something I definitely should have thought to try before. The test took .55 seconds with its output redirected to /dev/null, and .57 seconds redirected to a file on disk (of course, the kernel would have been buffering, so there was no disk wait involved). For comparison, the test had taken 56 seconds with xterm visible and scrolling, and 8 seconds with xterm hidden.

I also spent a lot of time experimenting with sleeping for various amounts of time between printed lines. With time.sleep(.0001) and xterm visible, the test took 104.71 seconds. With xterm shaded and the same sleep, it took 98.36 seconds, only 6 seconds faster. Redirected to /dev/null but with a .0001 sleep, it took 97.44 sec.

I think this argues for the blocking theory rather than the CPU-bound one: the argument being that the sleep gives the program a chance to wait for the output rather than blocking the whole time. If you figure it's CPU bound, I'm not sure how you'd explain the result.

But a .0001 second sleep probably isn't very accurate anyway -- we were all skeptical that Linux can manage sleep times that small. So I made another set of tests, with a .001 second sleep every 10 lines of output. The results: 65.05 with xterm visible; 63.36 with xterm hidden; 57.12 to /dev/null. That's with a total of 50 seconds of sleeping included (my test prints 500000 lines). So with all that CPU still going toward font rendering, the visible-xterm case still only took 7 seconds longer than the /dev/null case. I think this argues even more strongly that the original test, without the sleep, is blocking, not CPU bound.

But then I realized what the ultimate test should be. What happens when I run the test over an ssh connection, with xterm and X running on my local machine but the actual script running on the remote machine?

The remote machine I used for the ssh tests was a little slower than the machine I used to run the other tests, but that probably doesn't make much difference to the results.

The results? 60.29 sec printing over ssh (LAN) to a visible xterm; 7.24 sec doing the same thing with xterm hidden. Fairly similar to what I'd seen before when the test, xterm and X were all running on the same machine.

Interestingly, the ssh process during the test took 7% of my CPU, almost as much as the python script was getting before, just to transfer all the output lines so xterm could display them.

So I'm convinced now that the performance bottleneck has nothing to do with the process being CPU bound and having all its CPU sucked away by rendering the output, and that the bottleneck is in the process being blocked in writing its output while waiting for the terminal to catch up.

I'd be interested it hear further comments -- are there other interpretations of the results besides mine? I'm also happy to run further tests.

Tags: , , ,
[ 17:19 Nov 13, 2013    More linux | permalink to this entry | comments ]

Fri, 08 Nov 2013

Does scrolling output make a program slower?

While watching my rsync -av messages scroll by during a big backup, I wondered, as I often have, whether that -v (verbose) flag was slowing my backup down.

In other words: when you run a program that prints lots of output, so there's so much output the terminal can't display it all in real-time -- like an rsync -v on lots of small files -- does the program wait ("block") while the terminal catches up?

And if the program does block, can you speed up your backup by hiding the terminal, either by switching to another desktop, or by iconifying or shading the terminal window so it's not visible? Is there any difference among the different ways of hiding the terminal, like switching desktops, iconifying and shading?

Since I've never seen a discussion of that, I decided to test it myself. I wrote a very simple Python program:

import time

start = time.time()

for i in xrange(500000):
    print "Now we have printed", i, "relatively long lines to stdout."

print time.time() - start, "seconds to print", i, "lines."

I ran it under various combinations of visible and invisible terminal. The results were striking. These are rounded to the nearest tenth of a second, in most cases the average of several runs:

Terminal type Seconds
xterm, visible 56.0
xterm, other desktop 8.0
xterm, shaded 8.5
xterm, iconified 8.0
Linux framebuffer, visible 179.1
Linux framebuffer, hidden 3.7
gnome-terminal, visible 56.9
gnome-terminal, other desktop 56.7
gnome-terminal, iconified 56.7
gnome-terminal, shaded 43.8

Discussion:

First, the answer to the original question is clear. If I'm displaying output in an xterm, then hiding it in any way will make a huge difference in how long the program takes to complete.

On the other hand, if you use gnome-terminal instead of xterm, hiding your terminal window won't make much difference. Gnome-terminal is nearly as fast as xterm when it's displaying; but it apparently lacks xterm's smarts about not doing that work when it's hidden. If you use gnome-terminal, you don't get much benefit out of hiding it.

I was surprised how slow the Linux console was (I'm using the framebuffer in the Debian 3.2.0-4-686-pae on Intel graphics). But it's easy to see where that time is going when you watch the output: in xterm, you see lots of blank space as xterm skips drawing lines trying to keep up with the program's output. The framebuffer doesn't do that: it prints and scrolls every line, no matter how far behind it gets.

But equally interesting is how much faster the framebuffer is when it's not visible. (I typed Ctrl-alt-F2, logged in, ran the program, then typed Ctrl-alt-F7 to go back to X while the program ran.) Obviously xterm is doing some background processing that the framebuffer console doesn't need to do. The absolute time difference, less than four seconds, is too small to worry about, but it's interesting anyway.

I would have liked to try it my test a base Linux console, with no framebuffer, but figuring out how to get a distro kernel out of framebuffer mode was a bigger project than I wanted to tackle that afternoon.

I should mention that I wasn't super-scientific about these tests. I avoided doing any heavy work on the machine while the tests were running, but I was still doing light editing (like this article), reading mail and running xchat. The times for multiple runs were quite consistent, so I don't think my light system activity affected the results much.

So there you have it. If you're running an output-intensive program like rsync -av and you care how fast it runs, use either xterm or the console, and leave it hidden most of the time.

Tags: , , ,
[ 15:17 Nov 08, 2013    More linux | permalink to this entry | comments ]

Mon, 07 Oct 2013

Viewing HTML mail messages from Mutt (or other command-line mailers)

Command-line mailers like mutt have one disadvantage: viewing HTML mail with embedded images. Without images, HTML mail is no problem -- run it through lynx, links or w3m. But if you want to see images in place, how do you do it?

Mutt can send a message to a browser like firefox ... but only the textual part of the message. The images don't show up.

That's because mail messages include images, not as separate files, but as attachments within the same file, encoded it a format known as MIME (Multipurpose Internet Mail Extensions). An image link in the HTML, instead of looking like <img src="picture.jpg">., will instead look something like <img src="cid:0635428E-AE25-4FA0-93AC-6B8379300161">. (Apple's Mail.app) or <img src="cid:1.3631871432@web82503.mail.mud.yahoo.com">. (Yahoo's webmail).

CID stands for Content ID, and refers to the ID of the image as it is encoded in MIME inside the image. GUI mail programs, of course, know how to decode this and show the image. Mutt doesn't.

A web search finds a handful of shell scripts that use the munpack program (part of the mpack package on Debian systems) to split off the files; then they use various combinations of sed and awk to try to view those files. Except that none of the scripts I found actually work for messages sent from modern mailers -- they don't decode the CID links properly.

I wasted several hours fiddling with various shell scripts, trying to adjust sed and awk commands to figure out the problem, when I had the usual epiphany that always eventually arises from shell script fiddling: "Wouldn't this be a lot easier in Python?"

Python's email package

Python has a package called email that knows how to list and unpack MIME attachments. Starting from the example near the bottom of that page, it was easy to split off the various attachments and save them in a temp directory. The key is

import email

fp = open(msgfile)
msg = email.message_from_file(fp)
fp.close()

for part in msg.walk():

That left the problem of how to match CIDs with filenames, and rewrite the links in the HTML message accordingly.

The documentation on the email package is a bit unclear, unfortunately. For instance, they don't give any hints what object you'll get when iterating over a message with walk, and if you try it, they're just type 'instance'. So what operations can you expect are legal on them? If you run help(part) in the Python console on one of the parts you get from walk, it's generally class Message, so you can use the Message API, with functions like get_content_type(), get_filename(). and get_payload().

More useful, it has dictionary keys() for the attributes it knows about each attachment. part.keys() gets you a list like

['Content-Type', 
 'Content-Transfer-Encoding',
 'Content-ID',
 'Content-Disposition' ]

So by making a list relating part.get_filename() (with a made-up filename if it doesn't have one already) to part['Content-ID'], I'd have enough information to rewrite those links.

Case-insensitive dictionary matching

But wait! Not so simple. That list is from a Yahoo mail message, but if you try keys() on a part sent by Apple mail, instead if will be 'Content-Id'. Note the lower-case d, Id, instead of the ID that Yahoo used.

Unfortunately, Python doesn't have a way of looking up items in a dictionary with the key being case-sensitive. So I used a loop:

    for k in part.keys():
        if k.lower() == 'content-id':
            print "Content ID is", part[k]

Most mailers seem to put angle brackets around the content id, so that would print things like "Content ID is <14.3631871432@web82503.mail.mud.yahoo.com>". Those angle brackets have to be removed, since the CID links in the HTML file don't have them.

for k in part.keys():
    if k.lower() == 'content-id':
        if part[k].startswith('<') and part[k].endswith('>'):
            part[k] = part[k][1:-1]

But that didn't work -- the angle brackets were still there, even though if I printed part[k][1:-1] it printed without angle brackets. What was up?

Unmutable parts inside email.Message

It turned out that the parts inside an email Message (and maybe the Message itself) are unmutable -- you can't change them. Python doesn't throw an exception; it just doesn't change anything. So I had to make a local copy:

for k in part.keys():
    if k.lower() == 'content-id':
        content_id = part[k]
        if content_id.startswith('<') and content_id.endswith('>'):
            content_id = content_id[1:-1]
and then save content_id, not part[k], in my list of filenames and CIDs.

Then the rest is easy. Assuming I've built up a list called subfiles containing dictionaries with 'filename' and 'Content-Id', I can do the substitution in the HTML source:

    htmlsrc = html_part.get_payload(decode=True)
    for sf in subfiles:
        htmlsrc = re.sub('cid: ?' + sf['Content-Id'],
                         'file://' + sf['filename'],
                         htmlsrc, flags=re.IGNORECASE)

Then all I have to do is hook it up to a key in my .muttrc:

# macro  index  <F10>  "<copy-message>/tmp/mutttmpbox\n<enter><shell-escape>~/bin/viewhtmlmail.py\n" "View HTML in browser"
# macro  pager  <F10>  "<copy-message>/tmp/mutttmpbox\n<enter><shell-escape>~/bin/viewhtmlmail.py\n" "View HTML in browser"

Works nicely! Here's the complete script: viewhtmlmail.

Tags: , , , , ,
[ 11:49 Oct 07, 2013    More tech/email | permalink to this entry | comments ]

Fri, 13 Sep 2013

GIMP menu placeholders

Someone on the gimp-developers list asked whether there was documentation of GIMP's menu hooks.

I wasn't sure what they meant by "hooks", but GIMP menus do have an interesting feature that plug-in writers should know about: placeholders.

Placeholders let you group similar types of actions together. For instance, iever notice that in the image window's File menu, all the things that Open images are grouped together? There's Open, Open as Layers..., Open Location... and Open Recent. And then there's a group of Save actions all grouped together -- Save, Save As..., Save a Copy... and so forth. That's because there's a placeholder for Open in the File menu, and another placeholder for Save.

When you write your own plug-ins, you can take advantage of these placeholders. For instance, I want my Save/Export clean plug-in to show up next to the other Save menu items, not somewhere else down near the bottom of the menu -- so when I register it, I pass menu = "<Image>/File/Save/" so GIMP knows to group it with the other Save actions, even though it's directly in the File menu, not a submenu called Save..

Pretty slick, huh? But how do you know what placeholders are available?

I took a look at the source. In the menus/ subdirectory are all the menu definitions in XML, and they're pretty straightforward. In image-menu.xml you'll see things like <placeholder name="Open">, <placeholder name="Save">

So to get a list of all the menu placeholders, you just need to find all the " grep '<placeholder' menus/*.xml

That's not actually so useful, though, because it doesn't tell you what submenu contains the placeholder. For instance, Acquire is a placeholder but you need to know that it's actually File->Create->Acquire. So let's be a little more clever.

We want to see <menu lines as well as <placeholders, but not <menuitem since those are just individual menu entries. egrep '<(placeholder|menu) will do that. Then pass it through some sed expressions to clean up the output, loop over all the XML files, and I ended up with:

for f in *.xml; do
  echo $f
  egrep '<(placeholder|menu) ' $f | sed -e 's_<placeholder *name="_** _' -e 's_<menu.*name="__' -e 's_"/*>__'
done

It isn't perfect: a few lines still show up that shouldn't -- but it'll get you the list you need. Fortunately the GIMP developers are very good about things like code formatting, so the identation of the file shows which placeholder is inside which submenu.

I only found placeholders in the image window menu, plus a single placeholder, "Outline", in the selection menu popup. I'm a little confused about that menu file: it seems to duplicate the existing Select menu in the image-menu.xml, except that the placeholder items in question -- feather, sharpen, shrink, grow, and border -- are in a placeholder called Outline in selection-menu.xml, but in a placeholder called Modify in image-menu.xml.

Anyway, here's the full list of placeholders, cleaned up for readability. Placeholders are in bold and followed with an asterisk *.

===== image-menu.xml =====
    File
      Create
        Acquire *
      Open *
      Open Recent
        Files *
      Debug
      Save *
      Export *
      Send *
      Info *
    Context
    Edit
      Undo *
      Cut *
      Copy *
      Paste *
      Paste as
      Buffer
      Clear *
      Fill *
      Stroke *
      Preferences *
    Select
      Modify *
    View
    Image
      New *
      Mode
        Color Profile *
      Precision
      Transform
        Flip *
        Rotate *
      Resize *
      Scale *
      Crop *
      Structure *
      Arrange *
      Guides
    Layer
      New *
      Structure *
      Text *
      Stack
        Select *
        Position *
      Mask
        Modify *
        Properties *
        Selection *
      Transparency
        Modify *
        Selection *
      Transform
        Flip *
        Rotate *
      Properties
        Opacity
        Layer Mode
      Resize *
      Scale *
      Crop *
    Colors
      Invert *
      Auto
      Components
      Desaturate
      Map
        Colormap *
      Info
      Modify *
    Tools
    Filters
      Recently Used
        Plug-Ins *
      Blur
        Motion *
      Enhance
      Distorts
      Light and Shadow
        Light *
        Shadow *
        Glass *
      Noise
      Edge-Detect
      Generic
      Combine
      Artistic
      Decor
      Map
      Render
        Clouds
        Nature
        Pattern
      Web
      Animation
        Animators *
      Menus *
      Languages *
      Extensions *
    Menus *
    Windows
      Recently Closed Docks
      Dockable Dialogs
      Images *
      Docks *
    Help
      Programming *

===== selection-menu.xml =====
    Outline *

Tags: ,
[ 19:06 Sep 13, 2013    More gimp | permalink to this entry | comments ]

Wed, 28 Aug 2013

Python scripts for Android

Python on Android. Wouldn't that make so many things so much easier?

I've known for a long time about SL4A, but when I read, a year or two ago, that Google officially disclaimed support for languages other than Java and C and didn't want their employees working on projects like SL4A, I decided it wasn't a good bet.

But recently I heard from someone who had just discovered SL4A and its Python support and talked about it like a going thing. I had an Android scripting problem I really wanted to solve, and decided it was time to take another look.

It turns out SL4A and its Python interpreter are still being maintained, and indeed, I was able to solve my problem that way. But the documentation was scanty at best. So here are some shortcuts.

Getting Python running on Android

How do you install it in the first place? Took me three or four tries: it turns out it's extremely picky about the order in which you do things, and the documentation doesn't warn you about that. Follow these steps:

  1. Enable "Unknown Sources" under Application settings if you haven't already.
  2. Download both sl4a_r6.apk and PythonForAndroid_r4.apk
  3. Install sl4a from the apk. Do not install Python yet.
  4. Find SL4A in Applications and run it. It will say "no matches found" (i.e. no scripts) but that's okay: the important thing is that it creates the directory where the scripts will live, /sdcard/sl4a/scripts, without which PythonForAndroid would fail to install.
  5. Install PythonForAndroid from the apk.
  6. Find Python for Android in Applications and run it. Tap Install. This will install the sample scripts, and you'll be ready to go.

Make a shortcut on the home screen:

You've written a script and it does what you want. But to run it, you have to run SL4A, choose the Python interpreter, scroll around to find the script, tap on it, and indicate whether or not you want to see the console. Way too many steps!

Turns out you can make a shortcut on the home screen to an SL4A script, like this: (thanks to this tip):

This will give you the familiar twin-snake Python icon on your home screen. There doesn't seem to be any way to change this to a different icon.

Wait, what about UI?

Well, that still seems to be a big hole in the whole SL4A model. You can write great scripts that print to the console. You can even do a few specialized things, like popup menus, messages (what the Python Android module calls makeToast()) and notifications. The test.py sample script is a great illustration of how to use all those features, plus a lot more.

But what if you want to show a window, put a few buttons in it, let the user control things? Nobody seems to have thought about that possibility. I mean, it's not "sorry, we haven't had time to implement this", it isn't even mentioned as something someone would want to do on an Android device. Boggle.

The only possibility I've found is that there is apparently a way to use Android's WebView class from Python. I have not tried this yet; when I do, I'll write it up separately.

WebView may not be the best way to do UI. I've spent many hours tearing my hair out over its limitations even when called from Java. But still, it's something. And one very interesting thing about it is that it provides an easy way to call up an HTML page, either local or remote, from an Android home screen icon. So that may be the best reason yet to check out SL4A.

Tags: , ,
[ 22:31 Aug 28, 2013    More programming | permalink to this entry | comments ]

Tue, 20 Aug 2013

Using Google Maps with Python to turn a list of addresses into waypoints

A few days ago I tlaked about how I use making waypoint files for a list of house addresses is OsmAnd. For waypoint files, you need latitude/longitude coordinates, and I was getting those from a web page that used the online Google Maps API to convert an address into latitude and longitude coordinates.

It was pretty cool at first, but pasting every address into the latitude/longitude web page and then pasting the resulting coordinates into the address file, got old, fast. That's exactly the sort of repetitive task that computers are supposed to handle for us.

The lat/lon page used Javascript and the Google Maps API. and I already had a Google Maps API key (they have all sorts of fun APIs for map geeks) ... but I really wanted something that could run locally, reading and converting a local file.

And then I discovered the Python googlemaps package. Exactly what I needed! It's in the Python Package Index, so I installed it with pip install googlemaps. That enabled me to change my waymaker Python script: if the first line of a description wasn't a latitude and longitude, instead it looked for something that might be an address.

Addresses in my data files might be one line or might be two, but since they're all US addresses, I know they'll end with a two-capital-letter state abbreviation and a 5-digit zip code: 2948 W Main St. Anytown, NM 12345. You can find that with a regular expression:

    match = re.search('.*[A-Z]{2}\s+\d{5}$', line)

But first I needed to check whether the first line of the entry was already latitude/longitude coordinates, since I'd already converted some of my files. That uses another regular expression. Python doesn't seem to have a built-in way to search for generic numeric expressions (containing digits, decimal points or +/- symbols) so I made one, since I had to use it twice if I was searching for two numbers with whitespace between them.

    numeric = '[\+\-\d\.]'
    match = re.search('^(%s+)\s+(%s+)$' % (numeric, numeric),
                      line)
(For anyone who wants to quibble, I know the regular expression isn't perfect. For instance, it would match expressions like 23+48..6.1-64.5. Not likely to be a problem in these files, so I didn't tune it further.)

If the script doesn't find coordinates but does find something that looks like an address, it feeds the address into Google Maps and gets the resulting coordinates. That code looks like this:

from googlemaps import GoogleMaps

gmaps = GoogleMaps('YOUR GOOGLE MAPS API KEY HERE')
try:
    lat, lon = gmaps.address_to_latlng(addr)
except googlemaps.GoogleMapsError, e:
    print "Oh, no! Couldn't geocode", addr
    print e

Overall, a nice simple solution made possible with python-googlemaps. The full script is on github: waymaker.

Tags: , , , , , ,
[ 12:24 Aug 20, 2013    More mapping | permalink to this entry | comments ]

Fri, 16 Aug 2013

Offline mapping with lists of waypoints

Dave and I have been doing some exploratory househunting trips, and one of the challenges is how to maintain a list of houses and navigate from location to location. It's basically like geocaching, navigating from one known location to the next.

Sure, there are smartphone apps to do things like "show houses for sale near here" against a Google Maps background. But we didn't want everything, just the few gems we'd picked out ahead of time. And some of the places we're looking are fairly remote -- you can't always count on a consistent signal everywhere as you drive around, let alone a connection fast enough to download map tiles.

Fortunately, I use a wonderful open-source Android program called OsmAnd. It's the best, bar none, at offline mapping: download data files prepared from OpenStreetMap vector data, and you're good to go, even into remote areas with no network connectivity. It's saved our butts more than once exploring remote dirt tracks in the Mojave. And since the maps come from OpenStreetMap, if you find anything wrong with the map, you can fix it.

So the map part is taken care of. What about that list of houses?

Making waypoint files

On the other hand, one of OsmAnd's many cool features is that it can show track logs. I can upload a GPX file from my Garmin, or record a track within OsmAnd, and display the track on OsmAnd's map.

GPX track files can include waypoints. What if I made a GPX file consisting only of waypoints and descriptions for each house?

My husband was already making text files of potentially interesting houses:

404 E David Dr 
Flagstaff, AZ 86001
$355,000
3 Bed 2 Bath
1,673 Sq Ft
0.23 acres
http://blahblah/long_url

2948 W Wilson Dr 
Flagstaff, AZ 86001
$285,000
3 Bed 2 Bath
1,908 Sq Ft
8,000 Sq Ft Lot 
http://blahblah/long_url

... (and so on)
So I just needed to turn those into GPX.

GPX is a fairly straightforward XML format -- I've parsed GPX files for pytopo and for ellie, and generating them from Python should be easier than parsing. But first I needed latitude and longitude coordinates. A quick web search solved that: an excellent page called Find latitude and longitude with Google Maps. You paste the address in and it shows you the location on a map along with latitude and longitude. Thanks to Bernard Vatant at Mondeca!

For each house, I copied the coordinates directly from the page and pasted them into the file. (Though that got old after about the fifth house; I'll write about automating that step in a separate article.)

Then I wrote a script called waymaker that parses a file of coordinates and descriptions and makes waypoint files. Run it like this: waymaker infile.txt outfile.gpx and it will create (or overwrite) a gpx file consisting of those waypoints.

Getting it into OsmAnd

I plugged my Android device into my computer's USB port, mounted it as usb-storage and copied all the GPX files into osmand/tracks (I had to create the tracks subdirectory myself, since I hadn't recorded any tracks. After restarting OsmAnd, it was able to see all the waypoint files.

OsmAnd has a couple of other ways of showing points besides track files. "Favorites" lets you mark a point on the map and save it to various Favorites categories. But although there's a file named favorites.gpx, changes you make to it never show up in the program. Apparently they're cached somewhere else. "POI" (short for Points of Interest) can be uploaded, but only as a .obf OsmAnd file or a .sqlitedb database, and there isn't much documentation on how to create either one. GPX tracks seemed like the easiest solution, and I've been happy with them so far.

Update: I asked on the osmand mailing list; it turns out that on the Favorites screen (Define View, then Favorites) there's a Refresh button that makes osmand re-read favorites.gpx. Works great. It uses pretty much the same format as track files -- I took <wpt></wpt> sequences I'd generated with waymaker and added them to my existing favorites.gpx file, adding appropriate categories. It's nice to have two different ways to display and categorize waypoints within the app.

Using waypoints in OsmAnd

How do you view these waypoints once they're loaded? When you're in OsmAnd's map view, tap the menu button and choose Define View, then GPX track... You'll see a list of all your GPX files; choose the one you want.

You'll be taken back to the map view, at a location and zoom level that shows all your waypoints. Don't panic if you don't see them immediately; sometimes I needed to scroll and zoom around a little before OsmAnd noticed there were waypoints and started drawing them.

Then you can navigate in the usual way. When you get to a waypoint, tap on it to see the description brieftly -- I was happy to find that multiple line descriptions work just fine. Or long-press on it to pop up a persistent description window that will stay up until you dismiss it.

It worked beautifully for our trip, both for houses and for other things like motels and points of interest along the way.

Tags: , , ,
[ 15:58 Aug 16, 2013    More mapping | permalink to this entry | comments ]

Sat, 29 Jun 2013

Teaching Robotics to High School Girls at GetSET

[GetSET Robots and Sensors workshop] Wednesday I taught my "Robotics and Sensors" workshop at the SWE GetSET summer camp.

It was lots of fun, and definitely better than last year. It helped that I had a wonderful set of volunteers helping out -- five women from CodeChix (besides myself), so we had lots of programming expertise, plus a hardware engineer who was wonderfully helpful with debugging circuits. Thanks so much to all the volunteers! You really made the workshop!

We also had a great group of girls -- 14 high school seniors, all smart and motivated, working in teams of two.

How much detail?

One big issue when designing a one-day programming workshop is how much detail to provide in each example, and how much to leave to the students to work out. Different people learn differently. I'm the sort who learns from struggling through a problem, not from simply copying an example, and last year I think I erred too much in that direction, giving minimal information and encouraging the girls to work out the rest. Some of them did fine, but others found it frustrating. In a one-day workshop, if you have to spend too much time working everything out, you might never get to the fun stuff.

So this year I took a different approach. For each new piece of hardware, I gave them one small, but complete, working example, then suggested ways they could develop that. So for the first example (File->Examples->Basic->Blink is everyone's first Arduino exercise), I gave everyone two LEDs and two resistors, and as soon as they got their first LED blinking, I encouraged them to try adding another.

It developed that about half the teams wired their second LED right next to the first one, still on pin 13. Clever! but not what I'd had in mind. So I encouraged them to try moving the second LED to a different pin, like pin 12, and see if they could make one LED turn on while the other one turned off.

Another challenge with workshops is that people work at very different speeds. You have to have projects the fast students can work on to keep them from getting bored while the rest are catching up. So for LEDs, having a box full of extra LEDs helped, and by the time we were ready to move on, they had some great light shows going -- tri-colored blinkers, fast flashers, slow double-blinks.

I had pushbuttons on the tentative agenda but I was pretty sure that we'd skip that part. Pushbuttons are useful but they aren't really all that much fun. You have to worry about details like pull-down resistors and debouncing, too much detail when you have only six hours total. Potentiometers are more rewarding. We went through File->Examples->03.Analog->AnalogInput, and a few teams also tried LED fading with File->Examples->03.Analog->AnalogInOutSerial.

Music

[GetSET Robots and Sensors workshop] But then we moved on to what was really the highlight of the day, piezo speakers. Again, I provided a small working example program to create a rising tone. The Arduino IDE has no good speaker examples built in, so I'd made a short url for my Robots and Sensors workshop page, is.gd/getset, to make it easyto copy/paste code. It took no time at all before their speakers were making noise.

I was afraid they'd just stop there ... but as it turned out, everybody was energized (including me and the other volunteers) by all the funny noises, and without any prompting the girls immediately got to work changing their tones, making them rise faster or slower, or (with some help from volunteers) making them fall instead of rise. Every team had different sounds, and everybody was laughing and having fun as they tweaked their code.

In fact, that happened so fast that we ended up with plenty of time left before lunch. My plan was to do speakers right before lunch because noise is distracting, and after you've done that you can't to concentrate on anything else for a while. So I let them continue to play with the speakers.

I was glad I did. At least three different teams took the initiative to search the web and find sample code for playing music. There were some hitches -- a lot of the code samples needed to be tweaked a bit, from changing the pin where the speaker was plugged in, to downloading an include file of musical notes. One page gave code that didn't compile at all. But it was exciting to watch -- after all, this sort of experimentation and trial-and-error is a big part of what programmers do, and they all eventually got their music projects working.

One thing I learned was that providing a complete working .ino file makes a big difference. Some of the "music on Arduino" pages the girls found provided C functions but no hints as to how to call those functions. (It wasn't obvious to me, either.) Some of my own examples for the afternoon projects were like that, providing code snippets without setup() and loop(), and some teams were at sea, unsure how to create setup() and loop(). Of course I'd explained about setup() and loop() during the initial blink exercise. But considering how much material we covered in such a short time, it's not reasonable to expect everybody to remember details like that. And the Arduino IDE error messages aren't terribly easy to read, especially showing up orange on black in a tiny 3-line space at the bottom of the window.

So, for future workshops, I'll provide complete .ino files for all my own examples, plus a skeleton file with an empty setup() and loop() already there. It's okay to spoon feed basic details like the structure of an .ino file if it gives the students more time to think about the really interesting parts of their project.

Afternoon projects

[Working on the robotic car] After lunch, the afternoon was devoted to projects. Teams could pick anything we had hardware for, work on it throughout the afternoon and present it at the end of the workshop. There were two teams working on robotic cars (sadly, as with so many motor projects, the hardware ended up being too flaky and the cars didn't do much). Other teams worked with sonar rangefinders, light sensors or tilt switches, while some continued to work on their lights and music.

Everybody seemed like they were having a good time, and I'd seen a lot of working (or at least partly working) projects as I walked around during the afternoon, but when it came to present what they'd done, I was a little sad. There was a lot of "Well, I tried this, but I couldn't get it to work, so then I switched to doing this." Of course, trying things and changing course are also part of engineering ... that sentence describes a lot of my own playing with hardware, now that I think of it. But still ... I was sad hearing it.

Notes for next time

So, overall, I was happy with the workshop. I haven't seen the evaluation forms yet, but it sure seemed like everybody was having fun, and I know we volunteers did. What are the points I want to remember for next time?

Thanks again to the great volunteers! I'm looking forward to giving this workshop again.

Tags: , , , ,
[ 20:36 Jun 29, 2013    More education | permalink to this entry | comments ]

Tue, 28 May 2013

A quick URL shortener

For years I've used bookmarklets to shorten URLs. For instance, with is.gd, I set up a bookmark to javascript:document.location='http://is.gd/create.php?longurl='+encodeURIComponent(location.href);, give it a keyword like isgd, and then when I'm on a page I want to paste into Twitter (the only reason I need a URL shortener), I type Ctrl-L (to focus the URL bar) then isgd and hit return. Easy.

But with the latest rev of Firefox (I'm not sure if this started with version 20 or 21), sometimes javascript: links don't work. They just display the javascript source in the URLbar rather than executing it. Lacking a solution to the Firefox problem, I still needed a way of shortening URLs. So I looked into Python solutions.

It turns out there are a few URL shorteners with public web APIs. is.gd is one of them; shorturl.com is another. There are also APIs for bit.ly and goo.gl if you don't mind registering and getting an API key. Given that, it's pretty easy to write a Python script.

Which of course I did: shorturl.

[Python url shortening script] In the browser, I select the URL I want (e.g. by doubleclicking in the URLbar, or by right-clicking and choosing "Copy link location". That puts the URL in the X selection. Then I run the shorturl script, with no arguments. (I have it in my window manager's root menu.)

shorturl reads the X selection and shortens the URL (it tries is.gd first, then shorturl.com if is.gd doesn't work for some reason). Then it pops up a little window showing me both the short URL and the original long one, so I can be sure I shortened the right thing. (One thing I don't like about a lot of the URL services is that they don't tell you the original URL; I only find out later that I tweeted a link to something that wasn't at all the link I intended to share.)

It also copies the short URL into the X selection, so after verifying that the long URL was the one I wanted, I can go straight to my Twitter window (in my case, a Bitlbee tab in my IRC client) and middleclick to paste it.

After I've pasted the short link, I can dismiss the window by typing q. Don't type q too early -- since the python script owns the X selection, you won't be able to paste it anywhere once you've closed the window. (Unless you're running a selection-managing app like klipper.)

I just wish there were some way to use it for Twitter's own shortener, t.co. It's so frustrating that Twitter makes us all shorten URLs to fit in 140 characters just so they can shorten them again with their own service -- in the process removing any way for readers to see where the link will go. Sorry, folks -- nothing I can do about that. Complain to Twitter about why they won't let anyone use t.co directly.

Tags: , ,
[ 12:42 May 28, 2013    More tech/web | permalink to this entry | comments ]

Sat, 25 May 2013

Telling your Raspberry Pi that your terminal is bigger than 24 lines

When I'm working with an embedded Linux box -- a plug computer, or most recently with a Raspberry Pi -- I usually use GNU screen as my terminal program. screen /dev/ttyUSB0 115200 connects to the appropriate USB serial port at the appropriate speed, and then you can log in just as if you were using telnet or ssh.

With one exception: the window size. Typically everything is fine until you use an editor, like vim. Once you fire up an editor, it assumes your terminal window is only 24 lines high, regardless of its actual size. And even after you exit the editor, somehow your window will have been changed so that it scrolls at the 24th line, leaving the bottom of the window empty.

Tracking down why it happens took some hunting. Tthere are lots of different places the screen size can be set. Libraries like curses can ask the terminal its size (but apparently most programs don't). There's a size built into most terminfo entries (specified by the TERM environment variable) -- but it's not clear that gets used very much any more. There are environment variables LINES and COLUMNS, and a lot of programs read those; but they're often unset, and even if they are set, you can't trust them. And setting any of these didn't help -- I could change TERM and LINES and COLUMNS all I wanted, but as soon as I ran vim the terminal would revert to that scrolling-at-24-lines behavior.

In the end it turned out the important setting was the tty setting. You can get a summary of what the tty driver thinks its size is:

% stty size
32 80

But to set it, you use rows and columns rather than size. I discovered I could type stty rows 32 (or whatever my current terminal size was), and then I could run vim and it would stay at 32 rather than reverting to 24. So that was the important setting vim was following.

The basic problem was that screen, over a serial line, doesn't have a protocol for passing the terminal's size information, the way a remote login program like ssh, rsh or telnet does. So how could I get my terminal size set appropriately on login?

Auto-detecting terminal size

There's one program that will do it for you, which I remembered from the olden days of Unix, back before programs like telnet had this nice size-setting built in. It's called resize, and on Debian, it turned out to be part of the xterm package.

That's actually okay on my current Raspberry Pi, since I have X libraries installed in case I ever want to hook up a monitor. But in general, a little embedded Linux box shouldn't need X, so I wasn't very satisfied with this solution. I wanted something with no X dependencies. Could I do the same thing in Python?

How it works

Well, as I mentioned, there are ways of getting the size of the actual terminal window, by printing an escape sequence and parsing the result.

But finding the escape sequence was trickier than I expected. It isn't written about very much. I ended up running script and capturing the output that resize sent, which seemed a little crazy: '\e[7\e[r\e[999;999H\e[6n' (where \e means the escape character). Holy cow! What are all those 999s?

Apparently what's going on is that there isn't any sequence to ask xterm (or other terminal programs) "What's your size?" But there is a sequence to ask, "Where is the cursor on the screen right now?"

So what you do is send a sequence telling it to go to row 999 and column 999; and then another sequence asking "Where are you really?" Then read the answer: it's the window size.

(Note: if we ever get monitors big enough for 1000x1000 terminals, this will fail. I'm not too worried.)

Reading the answer

Okay, great, we've asked the terminal where it is, and it responds. How do we read the answer? That was actually the trickiest part.

First, you have to write to /dev/tty, not just stdout.

Second, you need the output to be available for your program to read, not just echo in the terminal for the user to see. Setting the tty to noncanonical mode does that.

Third, you can't just do a normal blocking read of stdin -- it'll never return. Instead, put stdin into non-blocking mode and use select() to see when there's something available to read.

And of course, you have to make sure you reset the terminal back to normal canonical line-buffered mode when you're done, whether or not your read succeeds.

Once you do all that, you can read the output, which will look something like "\e[32;80R". The two numbers, of course, are the lines and columns values you want; ignore the rest.

stty in python

Oh, yes, and one other thing: once you've read the terminal size, how do you set the stty size appropriately? You can't just run system('stty rows %d' % (rows) seems like it should work, but it doesn't, probably because it's using stdout instead of /dev/tty. But I did find one way to do it, the enigmatic:

fcntl.ioctl(fd, termios.TIOCSWINSZ,
            struct.pack("HHHH", rows, cols, 0, 0))

Here it all is in one script, which you can install on your Raspberry Pi (or other embedded Linux box) and run from .bash_profile:
termsize: set stty size to the size of the current terminal window.

Tags: , , , ,
[ 19:47 May 25, 2013    More hardware | permalink to this entry | comments ]

Wed, 15 May 2013

Finding versions of installed packages in Debian/Ubuntu

Checking versions in Debian-based systems is a bit of a pain.

This happens to me a couple of times a month: for some reason I need to know what version of something I'm currently running -- often a library, like libgtk. aptitude show will tell you all about a package -- but only if you know its exact name. You can't do aptitude show libgtk or even aptitude show '*libgtk*' -- you have to know that the package name is libgtk2.0-0. Why is it libgtk2.0-0? I have no idea, and it makes no sense to me.

So I always have to do something like aptitude search libgtk | egrep '^i' to find out what packages I have installed that matches the name libgtk, find the package I want, then copy and paste that name after typing aptitude show.

But it turns out it's super easy in Python to query Debian packages using the Python apt package. In fact, this is all the code you need:

import sys
import apt

cache = apt.cache.Cache()

pat = sys.argv[1]

for pkgname in cache.keys():
    if pat in pkgname:
        pkg = cache[pkgname]
        instver = pkg.installed
        if instver:
            print pkg.name, instver.version
Then run aptver libgtk and you're all set.

In practice, I wanted nicer formatting, with columns that lined up, so the actual script is a little longer. I also added a -u flag to show uninstalled packages as well as installed ones. Amusingly, the code to format the columns took about twice as many lines as the code that does the actual work. There doesn't seem to be a standard way of formatting columns in Python, though there are lots of different implementations on the web. Now there's one more -- in my aptver on github.

Tags: , , , ,
[ 16:07 May 15, 2013    More linux | permalink to this entry | comments ]

Sat, 13 Apr 2013

Parsing NOAA historical weather data

We've been considering the possibility of moving out of the Bay Area to somewhere less crowded, somewhere in the desert southwest we so love to visit. But that also means moving to somewhere with much harsher weather.

How harsh? It's pretty easy to search for a specific location and get average temperatures. But what if I want to make a table to compare several different locations? I couldn't find any site that made that easy.

No problem, I say. Surely there's a Python library, I say. Well, no, as it turns out. There are Python APIs to get the current weather anywhere; but if you want historical weather data, or weather data averaged over many years, you're out of luck.

NOAA purports to have historical climate data, but the only dataset I found was spotty and hard to use. There's an FTP site containing directories by year; inside are gzipped files with names like 723710-03162-2012.op.gz. The first two numbers are station numbers, and there's a file at the top level called ish-history.txt with a list of the station codes and corresponding numbers. Not obvious!

Once you figure out the station codes, the files themselves are easy to parse, with lines like

STN--- WBAN   YEARMODA    TEMP       DEWP      SLP        STP       VISIB      WDSP     MXSPD   GUST    MAX     MIN   PRCP   SNDP   FRSHTT
724945 23293  20120101    49.5 24    38.8 24  1021.1 24  1019.5 24    9.9 24    1.5 24    4.1  999.9    68.0    37.0   0.00G 999.9  000000
Each line represents one day (20120101 is January 1st, 2012), and the codes are explained in another file called GSOD_DESC.txt. For instance, MAX is the daily high temperature, and SNDP is snow depth.

[NOAA historical temp program] So all I needed was to decode the station names, download the right files and parse them. That took about a day to write (including a lot of time wasted futzing with mysterious incantations for matplotlib).

Little accessibility refresher: I showed it to Dave -- "Neat, look at this, San Jose is the blue pair, Flagstaff is green and Page is red." His reaction: "This makes no sense. They all look the same to me. I have no idea which is which." Oops -- right. Don't use color as your only visual indicator. I knew that, supposedly! So I added markers in different shapes for each site. (I wish somebody would teach that lesson to Google Maps, which uses color as its only indicator on the traffic layer, so it's useless for red-green colorblind people.)

Back to the data -- it turns out NOAA doesn't actually have that much historical data available for download. If you search on most of these locations, you'll find sites that claim to have historical temperatures dating back 50 years or more, sometimes back to the 1800s. But NOAA typically only has files starting at about 2005 or 2006. I don't know where sites are getting this older data, or how reliable it is.

Still, averages since 2006 are still interesting to compare. Here's a run of noaatemps.py KSJC KFLG KSAF KLAM KCEZ KPGA KCNY. It's striking how moderate California weather is compared to any of these inland sites. No surprise there. Another surprise was that Los Alamos, despite its high elevation, has more moderate weather than most of the others -- lower highs, higher lows. I was a bit disappointed at how sparse the site list was -- no site in Moab? Really? So I used Canyonlands Field instead.

Anyway, it's fun for a data junkie to play around with, and it prints data on other weather factors, like precipitation and snowpack, although it doesn't plot them yet. The code is on my GitHub scripts page, under Weather.

Anyone found a better source for historical weather information? I'd love to have something that went back far enough to do some climate research, see what sites are getting warmer, colder, or seeing greater or lesser spreads between their extreme temperatures. The NOAA dataset obviously can't do that, so there must be something else that weather researchers use. Data on other countries would be interesting, too. Is there anything that's available to the public?

Tags: , , ,
[ 22:57 Apr 13, 2013    More programming | permalink to this entry | comments ]

Tue, 19 Mar 2013

Letters not used in Python keywords

One of the closing lightning talks at PyCon this year concerned the answers to a list of Python programming puzzles given at some other point during the conference. I hadn't seen the questions (I'm still not sure where they are), but some of the problems looked fun.

One of them was: "What are the letters not used in Python keywords?" I hadn't known about Python's keyword module, which could come in handy some day:

>>> import keyword
>>> keyword.kwlist
['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']

So, given the list of keywords, what's the best way to find the list of unique letters?

Any time you want a list of unique anything, you want a set. For instance,

>>> set([1, 2, 3, 2, 2, 4, 5, 1, 5])
set([1, 2, 3, 4, 5])
But first you need a list of letters so can make a set out of it.

Split the list of words into a list of letters

My first idea was to use list comprehensions. You can split a single word into letters like this:

>>> [ x for x in 'hello' ]
['h', 'e', 'l', 'l', 'o']

It took a bit of fiddling to get the right syntax to apply that to every word in the list:

>>> [[c for c in w] for w in keyword.kwlist]
[['a', 'n', 'd'], ['a', 's'], ['a', 's', 's', 'e', 'r', 't'], ... ]

Update: Dave Foster points out that [list(w) for w in keyword.kwlist] is another way, simpler and cleaner way than the double list comprehension.

That's a list of lists, so it needs to be flattened into a single list of letters before we can turn it into a set.

Flatten the list of lists

There are lots of ways to flatten a list of lists. Here are four of them:

[item for sublist in [[c for c in w] for w in keyword.kwlist] for item in sublist]

reduce(lambda x,y: x+y, [[c for c in w] for w in keyword.kwlist])

import itertools
list(itertools.chain.from_iterable([[c for c in w] for w in keyword.kwlist]))

sum([[c for c in w] for w in keyword.kwlist], [])

That last one, using sum(), makes use of the fact that Python uses + for list concatenation -- in other words, that [1, 2, 3] + [4, 5, 6] is [1, 2, 3, 4, 5, 6]. But the first method (item for sublist in) is faster: see Making a flat list out of list of lists in Python on StackOverflow. And another StackOverflow thread has a nice script for plotting speed vs. list size of various flatteners.

A simpler way of making the set

But it turns out none of this list comprehension stuff is needed anyway. set('word') splits words into letters already:

>>> set('bubble')
set(['e', 'b', 'u', 'l'])
Ignore the order -- elements of a set often end up displaying in some strange order. The important thing is that it has all the letters and no repeats.

Now we have an easy way of making a set containing the letters in one word. But how do we apply that to a list of words?

Again I initially tried using list comprehensions, then realized there's an easier way. Given a list of strings, it's trivial to join them into a single string using ''.join(). And that gives us our set of letters within keywords:

>>> set(''.join(keyword.kwlist))
set(['a', 'c', 'b', 'e', 'd', 'g', 'f', 'i', 'h', 'k', 'm', 'l', 'o', 'n', 'p', 's', 'r', 'u', 't', 'w', 'y', 'x'])

What letters are not in the set?

Almost done! But the original problem was to find the letters not in keywords. We can do that by subtracting this set from the set of all letters from a to z. How do we get that? The string module will give us a list:

>>> string.lowercase
'abcdefghijklmnopqrstuvwxyz'

You could also use a list comprehension and ord and chr (alas, range won't give you a range of letters directly):

>>> [chr(i) for i in range(ord('a'), ord('z')+1)]
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
It's a bit longer, but doesn't require an import.

Now that you have your a-z set, just subtract the two sets:

>>> set(string.lowercase[:]) - set(''.join(keyword.kwlist))
set(['q', 'j', 'z', 'v'])

So the only letters not used in Python keywords are q, j, z and v.

Just a useless little ditty, really ... but I thought it was a fun exercise, so maybe you will too.

Tags: ,
[ 13:36 Mar 19, 2013    More programming | permalink to this entry | comments ]

Sat, 16 Mar 2013

SimpleCV on Raspberry Pi

I'm at PyCon, and I spent a lot of the afternoon in the Raspberry Pi lab.

Raspberry Pis are big at PyCon this year -- because everybody at the conference got a free RPi! To encourage everyone to play, they have a lab set up, well equipped with monitors, keyboards, power and ethernet cables, plus a collection of breadboards, wires, LEDs, switches and sensors.

I'm primarily interested in the RPi as a robotics controller, one powerful enough to run a camera and do some minimal image processing (which an Arduino can't do). And on Thursday, I attended a PyCon tutorial on the Python image processing library SimpleCV. It's a wrapper for OpenCV that makes it easy to access parts of images, do basic transforms like greyscale, monochrome, blur, flip and rotate, do edge and line detection, and even detect faces and other objects. Sounded like just the ticket, if I could get it to work on a Raspberry Pi.

SimpleCV can be a bit tricky to install on Mac and Windows, apparently. But the README on the SimpleCV git repository gives an easy 2-line install for Ubuntu. It doesn't run on Debian Squeeze (though it installs), because apparently it depends on a recent version of pygame and Squeeze's is too old; but Ubuntu Pangolin handled it just fine.

The question was, would it work on Raspbian Wheezy? Seemed like a perfect project to try out in the PyCon RPi lab. Once my RPi was set up and I'd run an apt-get update, I used used netsurf (the most modern of the lightweight browsers available on the RPi) to browse to the SimpleCV installation instructions. The first line,

sudo apt-get install ipython python-opencv python-scipy python-numpy python-pygame python-setuptools python-pip
was no problem. All those packages are available in the Raspbian repositories.

But the second line,

sudo pip install https://github.com/ingenuitas/SimpleCV/zipball/master
failed miserably. Seems that pip likes to put its large downloaded files in /tmp; and on Raspbian, running off an SD card, /tmp quite reasonably is a tmpfs, running in RAM. But that means it's quite small, and programs that expect to be able to use it to store large files are doomed to failure.

I tried a couple of simple Linux patches, with no success. You can't rename /tmp to replace it with a symlink to a directory on the SD card, because /tmp is always in use. And pip makes a new temp directory name each time it's run, so you can't just symlink the pip location to a place on the SD card.

I thought about rebooting after editing the tmpfs out of /etc/fstab, but it turns out it's not set up there, and it wasn't obvious how to disable the tmpfs. Searching later from home, the size is set in /etc/default/tmpfs. As for disabling the tmpfs and using the SD card instead, it's not clear. There's a block of code in /etc/init.d/mountkernfs.sh that makes that decision; it looks like symlinking /tmp to somewhere else might do it, or else commenting out the code that sets RAMTMP="yes". But I haven't tested that.

Instead of rebooting, I downloaded the file to the SD card:

wget https://github.com/ingenuitas/SimpleCV/master

But it turned out it's not so easy to pip install from a local file. After much fussing around I came up with this, which worked:

pip install http:///home/pi/master --download-cache /home/pi/tmp

That worked, and the resulting SimpleCV install worked nicely! I typed some simple tests into the simplecv shell, playing around with their built-in test image "lenna":

img = Image('lenna')
img.show()
img.binarize().show()
img.toGray().show()
img.edges().show()
img.invert().show()

And, for something a little harder, some face feature detection: let's find her eyes and outline them in yellow.

img.listHaarFeatures()
img.findHaarFeatures('eye.xml').draw(color=Color.YELLOW)
[Lenna, edges] [Lenna, eyes detected]

SimpleCV is lots of fun! And the edge detection was quite fast on the RPi -- this may well be usable by a robot, once I get the motors going.

Tags: , , , ,
[ 21:43 Mar 16, 2013    More linux/install | permalink to this entry | comments ]

Sat, 09 Mar 2013

Whac-a-Moon: Watch Europa appear and disappear this Sunday

This is an edited and updated version of my "Shallow Sky" column this month in the SJAA Ephemeris newsletter.

A few months ago, I got email from a Jupiter observer calling my attention to an interesting phenomenon of Jupiter's moons that I hadn't seen before. The person who mailed me described himself as a novice, and wasn't quite sure what he had seen, but he knew it was odd. After some further discussion we pinned it down.

He was observing Jupiter at 11/11/12 at 00.25 UT (which would have been mid-afternoon here in San Jose). Three of the moons were visible, with only Ganymede missing. Then Ganymede appeared: near Jupiter's limb, but not right on it. As he watched over the next few minutes, Ganymede seemed to be moving backward -- in toward Jupiter rather than away from it. Eventually it disappeared behind the planet.

It turned out that what he was seeing was the end of an eclipse. Jupiter was still a few months away from opposition, so the shadow thrown by the planet streamed off to one side as seen from our inner-planet vantage point on Earth. At 0:26 UT on that evening, long before he started observing, Ganymede, still far away from Jupiter's limb, had entered Jupiter's shadow and disappeared into eclipse. It took over two hours for Ganymede to cross Jupiter's shadow; but at 2:36, when it left the shadow, it hadn't yet disappeared behind the planet. So it became visible again. It wasn't until 2:50 that Ganymede finally disappeared behind Jupiter.

So it was an interesting effect -- bright Ganymede appearing out of nowhere, moving in toward Jupiter then disappearing again fourteen minutes later. It was something I'd never seen, or thought to look for. It's sort of like playing Whac-a-mole -- the moon appears only briefly, so you've got to hit it with your telescope at just the right time if you want to catch it before it disappears again.

A lot of programs don't show this eclipse effect -- including, I'm sad to say, my own Javascript Jupiter's moons web page. (I have since remedied that.) The open source program Stellarium shows the effect; on the web, Sky and Telescope's Jupiter's Moons page shows it, and even prints out a table of times of various moon events, including eclipses.

[Europa eclipse on Mar 10 2013]

These eclipse events aren't all that uncommon -- but only when the sun angle is just right. Searching in late February and early March this year, I found several events for Ganymede and Europa (though, sadly, many of them were during our daytime). By mid-March, the angles have changed so that Europa doesn't leave Jupiter's shadow until after it's disappeared behind the planet's limb; but Ganymede is farther out, so we can see Ganymede appearances all the way through March and for months after.

The most interesting view, it seems to me, is right on the boundary when the moon only appears for a short time before disappearing again. Like the Europa eclipse that's happening this Sunday night, March 10.

Reporting on that one got a little tricky -- because that's the day we switch to Daylight Savings time. I have to confess that I got a little twisted up trying to compare results between programs that use UTC and programs that use local time -- especially when the time zone converter I was using to check my math told me "That time doesn't exist!" Darnit, if we'd all just use UTC all the time, astronomy calculations would be a lot easier! (Not to mention dropping the silly Daylight Savings Time fiasco, but that's another rant.)

Before I go into the details, I want to point out that Jupiter's moons are visible even in binoculars. So even if you don't have a telescope, grab binoculars and set them up in as steady a way as you can -- if you don't have a tripod adaptor, try bracing them on the top of a gate or box.

On Sunday night, March 10, at some time around 7:40 pm PDT, Europa peeks out from behind Jupiter's northeast limb. (All times are given in PDT; add 7 hours for GMT.) The sky will still be bright here in California -- the sun sets at 7:12 that night -- but Jupiter will be 66 degrees up and well away from the sun, so it shouldn't give you too much trouble. Once Europa pops out, keep a close eye on it -- because if Sky & Tel's calculations are right, it will disappear again just four minutes later, at 7:44, into eclipse in Jupiter's shadow. It will remain invisible for almost three hours, finally reappearing out of nowhere, well off Jupiter's limb, at around 10:24 pm.

Here's a link to my Javascript Jupiter just before Europa reappears.

I want to stress that those times are approximate. In fact, I tried simulating the event in several different programs, and got wildly varying times:
Io disappears Europa appears Europa disappears Europa reappears Io appears
XEphem 7:15 7:43 7:59 10:06 10:43
S&T Jupiter's Moons 7:16 7:40 7:44 10:24 10:48
Javascript Jupiter 7:17 7:45 7:52 10:15 10:41
Stellarium 6:21 6:49 7:05 9:32 10:01

You'll note Stellarium seems to have a time zone problem ... maybe because I ran the prediction while we were still in standard time, not daylight savings time.

I'm looking forward to timing the events to see which program is most accurate. I'm betting on XEphem. Once I know the real times, maybe I can adjust my Javascript Jupiter's code to be more accurate. If anyone else times the event, please send me your times, in case something goes wrong here!

Anyway, the spread of times makes it clear that when observing this sort of phenomenon, you should always set up the telescope ten or fifteen minutes early, just in case. And ten extra minutes spent observing Jupiter -- even without moons -- is certainly never time wasted! Just keep an eye out for Europa to appear -- and be ready to whack that moon before it disappears again.

Tags: , ,
[ 11:30 Mar 09, 2013    More science/astro | permalink to this entry | comments ]

Thu, 21 Feb 2013

New project: Metapho image tagger

I'm excited about my new project: MetaPho, an image tagger.

It arose out of a discussion on the LinuxChix Techtalk list: photo collection management software. John Sturdy was looking for an efficient way of viewing and tagging large collections of photos. Like me, he likes fast, lightweight, keyboard-driven programs. And like me, he didn't want a database-driven system that ties you forever to one image cataloging program. I put my image tags in plaintext files, named Keywords, so that I can easily write scripts to search or modify them, or user grep, and I can even make quick changes with a text editor.

I shared some tips on how I use my Pho image viewer for tagging images, and it sounded close to what he was looking for. But as we discussed ideas about image tagging, we realized that there were things he wanted to do that pho doesn't do well, things not offered by any other image tagger we've been able to find. While discussing how we might add new tagging functionality to pho, I increasingly had the feeling that I was trying to fit off-road tires onto a Miata -- or insert your own favorite metaphor for "making something do something it wasn't designed to do."

Pho is a great image viewer, but the more I patched it to handle tagging, the uglier and more complicated the code got, and it also got more complex to use.

[metapho screenshot] And really, everything we needed for tagging could be easily done in a Python-GTK application. (Pho is written in C because it does a lot of complicated focus management to deal with how window managers handle window moving and resizing. A tagger wouldn't need any of that.)

I whipped up a demo image viewer in a few hours and showed it to John. We continued the discussion, I made a GitHub repo, and over the next week or so the code grew into an efficient and already surprisingly usable image tagger.

We have big plans for it, like tags organized into categories so we can have lots of tags without cluttering the interface too much. But really, even as it is, it's better than anything I've used before. I've been scanning in lots of photos from old family albums (like this one of my mother and grandmother, and me at 9 months) and it's been great to be able to add and review tags easily.

If you want to check out MetaPho, or contribute to it (either code or user interface design), it lives in my MetaPho repository on GitHub. And I wrote up a quick man page in markdown format: metapho.1.md.

Feedback and contributors welcome!

Tags: , , , , ,
[ 19:31 Feb 21, 2013    More programming | permalink to this entry | comments ]

Sat, 19 Jan 2013

Converting C to Python with a vi regexp

I'm fiddling with a serial motor controller board, trying to get it working with a Raspberry Pi. (It works nicely with an Arduino, but one thing I'm learning is that everything hardware-related is far easier with Arduino than with RPi.)

The excellent Arduino library helpfully provided by Pololu has a list of all the commands the board understands. Since it's Arduino, they're in C++, and look something like this:

#define QIK_GET_FIRMWARE_VERSION         0x81
#define QIK_GET_ERROR_BYTE               0x82
#define QIK_GET_CONFIGURATION_PARAMETER  0x83
[ ... ]
#define QIK_CONFIG_DEVICE_ID                        0
#define QIK_CONFIG_PWM_PARAMETER                    1
and so on.

On the Arduino side, I'd prefer to use Python, so I need to get them to look more like:

    QIK_GET_FIRMWARE_VERSION = 0x81
    QIK_GET_ERROR_BYTE = 0x82
    QIK_GET_CONFIGURATION_PARAMETER = 0x83
[ ... ]
    QIK_CONFIG_DEVICE_ID = 0
    QIK_CONFIG_PWM_PARAMETER = 1
... and so on ... with an indent at the beginning of each line since I want this to be part of a class.

There are 32 #defines, so of course, I didn't want to make all those changes by hand. So I used vim. It took a little fiddling -- mostly because I'd forgotten that vim doesn't offer + to mean "one or more repetitions", so I had to use * instead. Here's the expression I ended up with:

.,$s/\#define *\([A-Z0-9_]*\) *\(.*\)/ \1 = \2/

In English, you can read this as:

From the current line to the end of the file (,.$/), look for a pattern consisting of only capital letters, digits and underscores ([A-Z0-9_]). Save that as expression #1 (\( \)). Skip over any spaces, then take the rest of the line (.*), and call it expression #2 (\( \)).

Then replace all that with a new line consisting of 4 spaces, expression 1, a spaced-out equals sign, and expression 2 ( \1 = \2).

Who knew that all you needed was a one-line regular expression to translate C into Python?

(Okay, so maybe it's not quite that simple. Too bad a regexp won't handle the logic inside the library as well, and the pin assignments.)

Tags: , , , , ,
[ 21:38 Jan 19, 2013    More linux/editors | permalink to this entry | comments ]

Wed, 31 Oct 2012

Comparing sunset times with PyEphem

We were marveling at how early it's getting dark now -- seems like a big difference even compared to a few weeks ago. Things change fast this time of year.

Since we're bouncing back and forth a lot between southern and northern California, Dave wondered how Los Angeles days differed from San Jose days. Of course, San Jose being nearly 4 degrees farther north, it should have shorter days -- but by the weirdness of orbital mechanics that doesn't necessarily mean that the sun sets earlier in San Jose. His gut feel was that LA was actually getting an earlier sunset.

"I can calculate that," I said, and fired up a Python interpreter to check with PyEphem. Since PyEphem doesn't know San Jose (hmph! San Jose is bigger than San Francisco) I used San Francisco.

Since PyEphem's Observer class only has next_rising() and next_setting(), I had to set a start date of midnight so I could subtract the two dates properly to get the length of the day.

>>> sun = ephem.Sun()
>>> la = ephem.city('Los Angeles')
>>> sf = ephem.city('San Francisco')
>>> 
>>> mid = ephem.Date('2012/10/31 8:00')
>>> 
>>> la.next_rising(sun, start=mid)
2012/10/31 14:11:57
>>> la.next_setting(sun, start=mid)
2012/11/1 01:00:45
>>> la.next_setting(sun, start=mid) - la.next_rising(sun, start=mid)
0.45055988136300584
>>> 
>>> sf.next_rising(sun, start=mid)
2012/10/31 14:34:19
>>> sf.next_setting(sun, start=mid)
2012/11/1 01:11:44
>>> sf.next_setting(sun, start=mid) - sf.next_rising(sun, start=mid)
0.4426457611261867

So Dave's intuition was right: northern California really does have a later sunset than southern California at this time of year, even though the total day length is shorter -- the difference in sunrise time makes up for the later sunset.

How much shorter?

>>> (la.next_setting(sun, start=mid) - la.next_rising(sun, start=mid)) - (sf.next_setting(sun, start=mid) - sf.next_rising(sun, start=mid))
0.007914120236819144
>>> ((la.next_setting(sun, start=mid) - la.next_rising(sun, start=mid)) - (sf.next_setting(sun, start=mid) - sf.next_rising(sun, start=mid))) * 24
0.18993888568365946
>>> ((la.next_setting(sun, start=mid) - la.next_rising(sun, start=mid)) - (sf.next_setting(sun, start=mid) - sf.next_rising(sun, start=mid))) * 24 * 60
11.396333141019568

And we have our answer -- there's about 11 minutes difference in day length between SF and LA.

Tags: , ,
[ 11:46 Oct 31, 2012    More science/astro | permalink to this entry | comments ]

Wed, 17 Oct 2012

Asynchronous sound playing in Python

A little while back I wrote about my Python xchat script to play sound alerts.

But one thing that's been annoying me about it -- it was a problem with the old perl alert script too -- is the repeated sounds. If lots of twitter updates come in on the Bitlbee channel, or if someone pastes numerous lines into a channel, I hear POPPOPPOPPOPPOPPOP or repetitions of whatever the alert sound is for that type of message. It's annoying to me, but even more so to anyone else in the same room.

It would be so much nicer if I could have it play just one repetition of any given alert, even if there are eight lines all coming in at the same time. So I decided to write a Python class to handle that.

My existing code used subprocesses to call the basic ALSA sound player, /usr/bin/aplay -q. Initially I used
if not os.fork() : os.execl(APLAY, APLAY, "-q", alertfile)
but I later switched to the cleaner
subprocess.call([APLAY, '-q', alertfile])
But of course, it would be better to do it all from Python without requiring an external process like aplay. So I looked into that first.

Sadly, it turns out Python audio support is a mess. The built-in libraries are fairly limited in functionality and formats, and the external libraries that handle sound are mostly unmaintained, unless you want to pull in a larger library like pygame. After a little web searching I decided that maybe an aplay subprocess wasn't so bad after all.

Okay, so how should I handle the subprocesses? I decided the best way was to keep track of what sound was currently playing. If another alert fires for the same sound while that sound is already playing, just ignore it. If an alert comes in for a different sound, then wait() for the current sound to finish, then start the new sound.

That's all quite easy with Python's subprocess module. subprocess.Popen() returns a Popen object that tracks a process ID and can check whether that process has finished or not. If self.curpath is the path to the sound currently playing and self.current is the Popen object for whatever aplay process is currently running, then:

    if self.current :
        if self.current.poll() == None :
            # Current process hasn't finished yet. Is this the same sound?
            if path == self.curpath :
                # A repeat of the currently playing sound.
                # Don't play it more than once.
                return
            else :
                # Trying to play a different sound.
                # Wait on the current sound then play the new one.
                self.wait()

    self.curpath = path
    self.current = subprocess.Popen([ "/usr/bin/aplay", '-q', path ] )

Finally, it's a good idea when exiting the program to check whether any aplay process is running, and wait() for it. Otherwise, you might end up with a zombie aplay process.

    def __del__(self) :
        self.wait()

I don't know if xchat actually closes down Python objects gracefully, so I don't know whether the __del__ destructor will actually be called. But at least I tried. It's possible that a context manager might be more reliable.

The full scripts are on github at pyplay.py for the basic SoundPlayer class, and chatsounds.py for the xchat script that includes SoundPlayer.

Tags: , ,
[ 13:07 Oct 17, 2012    More programming | permalink to this entry | comments ]

Wed, 26 Sep 2012

Writing xchat scripts in Python (to play sound alerts)

I use xchat as my IRC client. Mostly I like it, but its sound alerts aren't quite as configurable as I'd like. I have a few channels, like my Bitlbee Twitter feed, where I want a much more subtle alert, or no alert at all. And I want an easy way of turning sounds on and off, in case I get busy with something and need to minimize distractions.

Years ago I grabbed a perl xchat plug-in called "Smet's NickSound" that did something close to what I wanted. I've hacked a few things into it. But every time I try to customize it any further, I'm hit with the pain of write-only Perl. I've written Perl scripts, honest. But I always have a really hard time reading anyone else's Perl code and figuring out what it's doing. When I dove in again recently to try to figure out why I was getting so many alerts when first starting up xchat, I finally decided: learning how to write a Python xchat script couldn't be any harder than reverse engineering a Perl one.

First, of course, I looked for an existing nick sound Python script ... and totally struck out. In fact, mostly I struck out on finding any xchat Python scripts at all. I know there are Python bindings for xchat, because there's documentation for them. But sample plug-ins? Nope. For some reason, nobody's writing xchat plug-ins in Python.

I eventually found two minimal examples: this very simple example and the more elaborate utf8decoder. I was able to put them together and cobble up a working nick sound plug-in. It's easy once you have an example to work from to help you figure out the event hook arguments.

So here's my own little example, which may help the next person trying to learn xchat Python scripting: chatsounds.py on github.

Tags: , , ,
[ 22:13 Sep 26, 2012    More programming | permalink to this entry | comments ]

Wed, 19 Sep 2012

Python: Do two dates span a particular day of the week or month?

When I'm using my RSS reader FeedMe, I normally check every feed every day. But that can be wasteful: some feeds, like World Wide Words, only update once a week. A few feeds update even less often, like serialized books that come out once a month or whenever the author has time to add something new.

So I decided it would be nice to add some "when" logic to FeedMe, so I could add when = Sat in the config section for World Wide Words and have it only update once a week.

That sounded trivial -- a little python parsing logic to tell days from numbers, a few calls to time.localtime() and I was done.

Except of course I wasn't. Because sometimes, like when I'm on vacation, I don't always update every day. If I missed a Saturday, then I'd never see that week's edition of World Wide Words. And that would be terrible!

So what I really needed was a way to ask, "Has a Saturday occurred (including today) since the last time I ran feedme?"

The last time I ran feedme is easy to determine: it's in the last modified date of the cache file. Or, in more Pythonic terms, it's statbuf = os.stat(cachefile).st_mtime. And of course I can get the current time with time.localtime(). But how do I figure out whether a given week or month day falls between those two dates?

I'm sure this particular wheel has been invented many times. There's probably even a nifty Python library somewhere to do it. But how do you google for that? I tried to think of keywords and found nothing. So I went for a nice walk in the redwoods and thought about it for a bit, and came up with a solution.

Turns out for the week day case, you can just use modular arithmetic: if (weekday_2 - target_weekday) % 7 < (weekday_2 - weekday_1) then the day does indeed fall between the two dates.

Things are a little more complicated for the day of the month, though, because you don't know whether you need mod 30 or 31 or 29 or 28, so you either have to make your own table, or import the calendar module just so you can call calendar.monthrange().

I decided it was easier to use logic: if the difference between the two dates is greater than 31, then it definitely includes any month day. Otherwise, check whether they're in the same month or not, and do greater than/less than comparisons on the three dates.

Throw in a bunch of conversion to make it easy to call, and a bunch of unit tests to make sure everything works and my later tweaks don't break anything, and I had a nice function I could call from Feedme.

falls_between.py on github

Tags: ,
[ 22:07 Sep 19, 2012    More programming | permalink to this entry | comments ]

Wed, 05 Sep 2012

Pho 0.9.8 released

I decided to give myself a birthday present and release version 0.9.8 of Pho, my image viewer, at long last.

I've been using it essentially unchanged for many months now, occasionally tweaking things or fixing minor bugs ... but I haven't run into any bugs in quite a while, and think I've fixed all the pending ones. Been meaning to make a release for a long time, but somehow I keep getting sidetracked and forgetting about it.

This should rationalize the version number again ... the official releases have been 0.9.7-preN forever, but there was an unofficial 0.9.7 and even a 0.9.8 that snuck in along with some patches I got from David Gardner. It's been confusing. So now it's officially 0.9.8, and any figure versions will start with 0.9.9, and we might even see a 1.0 one of these days. (I suppose it's time -- Pho is ten years old!)

So here it is: Pho 0.9.8. I think it's working well. If you're already a Pho user, or if you want a lightweight image viewer that's also good at triaging and annotating large batches of images, you might want to take a look.

Tags: , ,
[ 13:36 Sep 05, 2012    More programming | permalink to this entry | comments ]

Sun, 02 Sep 2012

GIMP plug-in to export scaled-down versions of images

In a discussion on Google+arising from my Save/Export clean plug-in, someone said to the world in general

PLEASE provide an option to select the size of the export. Having to scale the XCF then export then throw out the result without saving is really awkward.

I thought, What a good idea! Suppose you're editing a large image, with layers and text and other jazz, saving in GIMP's native XCF format, but you want to export a smaller version for the web. Every time you make a significant change, you have to: Scale (remembering the scale size or percentage you're targeting); Save a Copy (or Export in GIMP 2.8); then Undo the Scale. If you forget the Undo, you're in deep trouble and might end up overwriting the XCF original with a scaled-down version.

If I had a plug-in that would export to another file type (such as JPG) with a scale factor, remembering that scale factor so I didn't have to, it would save me both effort and risk.

And that sounded pretty easy to write, using some of the tricks I'd learned from my Save/Export Clean and wallpaper scripts. So I wrote export-scaled.py

It's still brand new, so if anyone tries it, I'd appreciate knowing if it's useful or if you have any problems with it.

Geeky programming commentary

(Folks not interested in the programming details can stop reading now.)

Linked input fields

[screenshot: linked input fields] One fun project was writing a set of linked text entries for the dialog:
Scale to: Percentage 100 % Width: 640 Height: 480

Change any one of the three, and the other two change automatically. There's no chain link between width and height: It's assumed that if you're exporting a scaled copy, you won't want to change the image's aspect ratio, so any one of the three is enough.

That turned out to be surprisingly hard to do with GTK SpinBoxes: I had to read their values as strings and parse them, because the numeric values kept snapping back to their original values as soon as focus went to another field.

Image parasites

Another fun challenge was how to save the scale ratio, so the second time you call up the plug-in on the same image it uses whatever values you used the first time. If you're going to scale to 50%, you don't want to have to type that in every time. And of course, you want it to remember the exported file path, so you don't have to navigate there every time.

For that, I used GIMP parasites: little arbitrary pieces of data you can attach to any image. I've known about parasites for a long time, but I'd never had occasion to use them in a Python plug-in before. I was pleased to find that they were documented in the official GIMP Python documentation, and they worked just as documented. It was easy to test them, too: in the Python console (Filters->Python->Console...), type something like

img = gimp_image_list()[0]
img.parasite_list()
img.parasite_find(img.parasite_list()[0])
and so forth. Nice!

Not prompting for JPG settings

My plug-in was almost done. But when I ran it and told it to save to filenamecopy.jpg, it prompted me with that annoying JPEG settings dialog. Okay, being prompted once isn't so bad. But then when I exported a second time, it prompted me again, and didn't remember the values from before. So the question was, what controls whether the settings dialog is shown, and how could I prevent it?

Of course, I could prompt the user for JPEG quality, then call jpeg-save-file directly -- but what if you want to export to PNG or GIF or some other format? I needed something more general

Turns out, nobody really remembers how this works, and it's not documented anywhere. Some people thought that passing run_mode=RUN_WITH_LAST_VALS when I called pdb.gimp_file_save() would do the trick, but it didn't help.

So I guessed that there might be a parasite that was storing those settings: if the JPEG save plug-in sees the parasite, it uses those values and doesn't prompt. Using the Python console technique I just mentioned, I tried checking the parasites on a newly created image and on an image read in from an existing JPG file, then saving each one as JPG and checking the parasite list afterward.

Bingo! When you read in a JPG file, it has a parasite called 'jpeg-settings'. (The new image doesn't have this, naturally). But after you write a file to JPG from within GIMP, it has not only 'jpeg-settings' but also a second parasite, 'jpeg-save-options'.

So I made the plug-in check the scaled image after saving it, looking for any parasites with names ending in either -settings or -save-options; any such parasites are copied to the original image. Then, the next time you invoke Export Scaled, it does the same search, and copies those parasites to the scaled image before calling gimp-file-save.

That darned invisible JPG settings dialog

One niggling annoyance remained. The first time you get the JPG settings dialog, it pops up invisibly, under the Export dialog you're using. So if you didn't know to look for it by moving the dialog, you'd think the plug-in had frozen. GIMP 2.6 had a bug where that happened every time I saved, so I assumed there was nothing I can do about it.

GIMP 2.8 has fixed that bug -- yet it still happened when my plug-in called gimp_file_save: the JPG dialog popped up under the currently active dialog, at least under Openbox.

There isn't any way to pass window IDs through gimp_file_save so the JPG dialog pops up as transient to a particular window. But a few days after I wrote the export-scaled, I realized there was still something I could do: hide the dialog when the user clicks Save. Then make sure that I show it again if any errors occur during saving.

Of course, it wasn't quite that simple. Calling chooser.hide() by itself does nothing, because X is asynchronous and things don't happen in any predictable order. But it's possible to force X to sync the display: chooser.get_display().sync().

I'm not sure how robust this is going to be -- but it seems to work well in the testing I've done so far, and it's really nice to get that huge GTK file chooser dialog out of the way as soon as possible.

Tags: , ,
[ 18:34 Sep 02, 2012    More gimp | permalink to this entry | comments ]

Tue, 21 Aug 2012

GIMP: Re-uniting Save and Export

In GIMP 2.8, the developers changed the way you save files. "Save" is now used only for GIMP's native format, XCF (and compressed variants like .xcf.gz and .xcf.bz2). Other formats that may lose information on layers, fonts and other aspects of the edited image must be "Exported" rather than saved.

This has caused much consternation and flameage on the gimp-user mailing list, especially from people who use GIMP primarily for simple edits to JPEG or PNG files.

I don't particularly like the new model myself. Sometimes I use GIMP in the way the developers are encouraging, adding dozens of layers, fonts, layer masks and other effects. Much more often, I use GIMP to crop and rescale a handful of JPG photos I took with my camera on a hike. While I found it easy enough to adapt to using Ctrl-E (Export) instead of Ctrl-S (Save), it was annoying that when I exited the app, I'd always get am "Unsaved images" warning, and it was impossible to tell from the warning dialog which images were safely exported and which might not have been saved or exported at all.

But flaming on the mailing lists, much as some people seem to enjoy it (500 messages on the subject and still counting!) wasn't the answer. The developers have stated very clearly that they're not going to change the model back. So is there another solution?

Yes -- a very simple solution, in fact. Write a plug-in that saves or exports the current image back to its current file name, then marks it as clean so GIMP won't warn about it when you quit.

It turned out to be extremely easy to write, and you can get it here: GIMP: Save/export clean plug-in. If it suits your GIMP workflow, you can even bind it to Ctrl-S ... or any other key you like.

Warning: I deliberately did not add any "Are you sure you want to overwrite?" confirmation dialogs. This plug-in will overwrite your current file, without asking for permission. After all, that's its job. So be aware of that.

How it's written

Here are some details about how it works. Non software geeks can skip the rest of this article.

When I first looked into writing this, I was amazed at how simple it was: really just two lines of Python (plus the usual plug-in registration boilerplate).

    pdb.gimp_file_save(img, drawable, img.filename, img.filename)
    pdb.gimp_image_clean_all(img)

The first line saves the image back to its current filename. (The gimp-file-save PDB call still handles all types, not just XCF.) The second line marks the image as clean.

Both of those are PDB calls, which means that people who don't have GIMP Python could write script-fu to do this part.

So why didn't I use script-fu? Because I quickly found that if I bound the plug-in to Ctrl-S, I'd want to use it for new images -- images that don't have a filename yet. And for that, you need to pop up some sort of "Save as" dialog -- something Python can do easily, and Script-fu can't do at all.

A Save-as dialog with smart directory default

I couldn't use the standard GIMP save-as dialog: as far as I can tell, there's no way to call that dialog from a plug-in. But it turned out the GTK save-as dialog has no default directory to start in: you have to set the starting directory every time. So I needed a reasonable initial directory.

I didn't want to come up with some desktop twaddle like ~/My Pictures or whatever -- is there really anyone that model fits? Certainly not me. I debated maintaining a preference you could set, or saving the last used directory as a preference, but that complicates things and I wasn't sure it's really that helpful for most people anyway.

So I thought about where I usually want to save images in a GIMP session. Usually, I want to save them to the same directory where I've been saving other images in the same session, right?

I can figure that out by looping through all currently open images with for img in gimp.image_list() : and checking os.path.dirname(img.filename) for each one. Keep track of how many times each directory is being used; whichever is used the most times is probably where the user wants to store the next image.

Keeping count in Python

Looping through is easy, but what's the cleanest, most Pythonic way of maintaining the count for each directory and finding the most popular one? Naturally, Python has a class for that, collections.Counter.

Once I've counted everything, I can ask for the most common path. The code looks a bit complicated because most_common(1) returns a one-item list of a tuple of the single most common path and the number of times it's been used -- for instance, [ ('/home/akkana/public_html/images/birds', 5) ]. So the path is the first element of the first element, or most_common(1)[0][0]. Put it together:

    counts = collections.Counter()
    for img in gimp.image_list() :
        if img.filename :
            counts[os.path.dirname(img.filename)] += 1
    try :
        return counts.most_common(1)[0][0]
    except :
        return None

So that's the only tricky part of this plug-in. The rest is straightforward, and you can read the code on GitHub: save-export-clean.py.

Tags: , ,
[ 12:26 Aug 21, 2012    More gimp | permalink to this entry | comments ]

Mon, 25 Jun 2012

Driving motors, CHEAPLY, with an Arduino

Some time ago, I wrote about my explorations into the options for driving motors from an Arduino.

Motor shields worked well -- but they cost around $50 each, more than the Arduino itself. That's fine for a single one, but I'm teaching an Arduino workshop (this Thursday!) for high school girls, and I needed something I could buy more cheaply so I could get more of them.

(Incidentally, if any women in the Bay Area want to help with the workshop this Thursday, June 28 2012, I could definitely use a few more helpers! Please drop me an email.)

What I found on the web and on the Arduino IRC channel was immensely confusing to someone who isn't an electronics expert -- most people recommended things like building custom H-bridge circuits out of zener diodes.

[Simple Arduino h-bridge (half-bridge) circuit] But it's not that complicated after all. I got the help I needed from ITP Physical Computing's DC Motor Control Using an H-Bridge. It turns out you can buy a chip called an SN754410 that implements an H-bridge circuit -- including routing a power source to the motors while keeping the Arduino's power supply isolated -- for under $2. I ordered my SN754410 chips from Jameco and they arrived the next day.

(Technically, the SN754410 is a "quad half-bridge" rather than an "dual h-bridge". In practice I'm not sure of the difference. There's another chip, the L298, which is a full h-bridge and is also cheap to buy -- but it's a bit harder to use because the pins are wonky and it doesn't plug in directly to a breadboard unless you bend the pins around. I decided to stick with the SN754410; but the L298 might be better for high-powered motors.)

Now, the SN754410 isn't as simple to use as a motor shield. It has a lot of wires -- for two motors, you'll need six Arduino output pins, plus a 5v reference and ground, the four wires to the two motors, and the two wires to the motor power supply. Here's the picture of the wiring, made with Fritzing.

[Half-bridge circuit on breadboard] With all those wires, I didn't want to make the girls wire them up themselves -- it's way too easy to make a mistake and connect the wrong pin (as I found when doing my own testing). So I've wired up several of them on mini-breadboards so they'll be more or less ready to use. They look like little white mutant spiders with all the wires going everywhere.

A simple library for half-bridge motor control

The programming for the SN754410 is a bit less simple than motor shields as well. For each motor, you need an enable pin on the Arduino -- the analog signal that controls the motor's speed, so it needs to be one of the Arduino's PWM output pins, 9, 10 or 11 -- plus two logic pins, which can be any of the digital output pins.

To spin the motor in one direction, set the first logic pin high and the second low; to spin in the other direction, reverse the pins, with the first one low and the second one high. That's simple enough to program -- but I didn't look forward to trying to explain it to a group of high school students with basically no programming experience.

To make it simpler for them, I wrote a drop-in library that simplifies the code quite a bit. It defines a Motor object that you can initialize with the pins you'll be using -- the enable pin first, then the two logic pins. Initialize them in setup() like this:

#include 

Motor motors[2] = { Motor(9, 2, 3), Motor(10, 4, 5) };

void setup()
{
    motors[0].init();
    motors[1].init();
}

Then from your loop() function, you can make calls like this:

    motors[0].setSpeed(128);
    motors[1].setSpeed(-85);
Setting a negative speed will tell the library to reverse the logic pins so the motor spins the opposite direction.

I hope this will make motors easier to deal with for the girls who choose to try them. (We'll be giving them a choice of projects, so some of them may prefer to make light shows with LEDs, or music with piezo buzzers.)

You can get the code for the HalfBridge library, and a sample sketch that uses it, at my Arduino github repository

Cheap and easy motor control -- and I have a fleet of toy cars to connect to them. I hope this ends up being a fun workshop!

Tags: , , ,
[ 22:32 Jun 25, 2012    More hardware | permalink to this entry | comments ]

Sat, 09 Jun 2012

Viewing and modifying epub ebook tags

My epub Books folder is starting to look like my physical bookshelf at home -- huge and overflowing with books I hope to read some day. Mostly free books from the wonderful Project Gutenberg and DRM-free books from publishers and authors who support that model.

With the Nook's standard library viewer that's impossible to manage. All you can do is sort all those books alphabetically by title or author and laboriously page through, some five books to a page, hoping the one you want will catch your eye. Worse, sometimes books show up in the author view but don't show up in the title view, or vice versa. I guess Barnes & Noble think nobody keeps more than ten or so books on their shelves.

Fortunately on my rooted Nook I have the option of using better readers, like FBreader and Aldiko, that let me sort by tags. If I want to read something about the Civil War, or Astronomy, or just relax with some Science Fiction, I can browse by keyword.

Well, in theory. In practice, tagging of ebooks is inconsistent and not very useful.

For instance, the Gutenberg tags for Othello are:

while the tags for Vanity Fair are

The Prince and the Pauper's tag list looks like:

while Captains Courageous looks like

I can understand wanting to tag details like this, but few of those tags are helpful when I'm browsing books on my little handheld device. I can't imagine sitting down to read and thinking, "Let's see, what books do I have on Interracial marriage? Or Saltwater fishing? No, on second thought I'd rather read some fiction set in the time of Edward VI, King of England, 1537-1553."

And of course, with over 90 books loaded on my ebook readers, it means I have hundreds of entries in my tags list, with few of them including more than one book.

Clearly what I needed to do was to change the tags on my ebooks.

Viewing and modifying epub tags

That ought to be simple, right? But ebooks are still a very young technology, and there's surprisingly little software devoted to them. Calibre can probably do it if you don't mind maintaining your whole book collection under calibre; but I like to be able to work on files one at a time or in small groups. And I couldn't find a program that would let me do that.

What to do? Well, epub is a fairly simple XML format, right? So modifying it with Python shouldn't that hard.

Managing epub in Python

An epub file is a collection of XML files packaged in a zip archive. So I unzipped one of my epub books and poked around. I found the tags in a file called content.opf, inside a <metadata> tag. They look like this:

<dc:subject>Science fiction</dc:subject>

So I could use Python's zipfile module to access the content.opf file inside the zip archive, then use the xml.dom.minidom parser to get to the tags. Writing a script to display existing tags was very easy.

What about replacing the old, unweildy tag list with new, simple tags?

It's easy enough to add nodes in Python's minidom. So the trick is writing it back to the epub file. The zipfile module doesn't have a way to modify a zip file in place, so I created a new zip archive and copied files from the old archive to the new one, replacing content.opf with a new version.

Python's difficulty with character sets in XML

But I hit a snag in writing the new content.opf. Python's XML classes have a toprettyxml() method to write the contents of a DOM tree. Seemed simple, and that worked for several ebooks ... until I hit one that contained a non-ASCII character. Then Python threw a UnicodeEncodeError: 'ascii' codec can't encode character u'\u2014' in position 606: ordinal not in range(128).

Of course, there are ways (lots of them) to encode that output string -- I could do

ozf.writestr(info, dom.toprettyxml().encode(encoding, 'xmlcharrefreplace'))
, or
writestr(info, dom.toprettyxml(encoding=encoding)
Except ... what should I pass as the encoding? The content.opf file started with its encoding:
<?xml version='1.0' encoding='UTF-8'?>
but Python's minidom offers no way to get that information. In fact, none of Python's XML parsers seem to offer this.

Since you need a charset to avoid the UnicodeEncodeError, the only options are (1) always use a fixed charset, like utf-8, for content.opf, or (2) open content.opf and parse the charset line by hand after Python has already parsed the rest of the file. Yuck! So I chose the first option ... I can always revisit that if the utf-8 in content.opf ever causes problems.

The final script

Charset difficulties aside, though, I'm quite pleased with my epubtags.py script. It's very handy to be able to print tags on any .epub file, and after cleaning up the tags on my ebooks, it's great to be able to browse by category in FBreader. Here's the program: epubtag.py.

Tags: , ,
[ 13:05 Jun 09, 2012    More programming | permalink to this entry | comments ]

Wed, 30 May 2012

Creating packages for a Launchpad PPA

In a previous article I wrote about how to use stdeb to turn a Python script into source and binary Debian/Ubuntu packages.

You can distribute a .deb file that people can download and install; but it's a lot easier for people to install if you set up a repository, so they can get automatic updates from you. If you're targeting Ubuntu, the best way to do that is to set up a Launchpad Personal Package Archive, or PPA.

Create your PPA

First, create your PPA. If you don't have a Launchpad account yet, create one, add a GPG key, and sign the Code of Conduct. Then log in to your account and click on Create a new PPA.

You'll have to pick a name and a display name for your PPA. The default is "ppa", and many people leave personal PPAs as that. You might want to give it a display name of yourname-ppa or something similar if it's for a collection of stuff; or you're only going to use it for software related to one program or package, name it accordingly.

Ubuntu requires nonstandard paths

When you're creating your package with stdeb, if you're ultimately targeting a PPA, you'll only need the souce dsc package, not the binary deb. But as you'll see, you'll need to rebuild it to make Launchpad happy.

If you're intending to go through the Developer.ubuntu.com process, there are specific requirements for version numbering and tarball naming -- see "Packaging" in the App Review Board Guidelines. Your app will also need to install unusual locations -- in particular, any files it installs, including the script itself, need to be in /opt/extras.ubuntu.com/<packagename> instead of a more standard location.

How the user is supposed to run these apps (run a script to add each of /opt/extras.ubuntu.com/* to your path?) is not clear to me; I'm not sure this app review thing has been fully thought out. In any case, you may need to massage your setup.py accordingly, and keep a separate version around for when you're creating the Ubuntu version of your app.

There are also apparently some problems loading translation files for an app in /opt/extras.ubuntu.com which may require some changes to your Python code.

Prepare and sign your package

Okay, now comes the silly part. You know that source .dsc package you just made? Now you have to unpack it and "build" it before you can upload it. That's partly because you have to sign it with your GPG key -- stdeb apparently can't do the signing step.

Normally, you'd sign a package with debsign deb_dist/packagename_version.changes (then type your GPG passphrase when prompted). Unfortunately, that sort of signing doesn't work here. If you used stdeb's bdist_deb to generate both binary and source packages, the .changes file it generates will contain both source and binary and Launchpad will reject it. If you used sdist_dsc to generate only the source package, then you don't have a .changes file to sign and submit to Launchpad. So here's how you can make a signed, source-only .changes file Launchpad will accept.

Since this will extract all your files again, I suggest doing this in a temporary directory to make it easier to clean up afterward:

$ mkdir tmp
$ cd tmp
$ dpkg-source -x ../deb_dist/packagename_version.dsc
$ cd packagename_version

Now is a good time to take a look at the deb_dist/packagename_version/debian/changelog that stdeb created, and make sure it got the right version and OS codename for the Ubuntu release you're targeting -- oneiric, precise, quantal or whatever. stdeb's default is "unstable" (Debian) so you'll probably need to change it. You can cross-check this information in the deb_dist/packagename_version.changes file, which is the file you'll actually be uploading to the PPA.

Finally, build and sign your source package:

$ debuild -S -sa
  [type your GPG passphrase when prompted, twice]
$ dput ppa:yourppa ../packagename_version_source.changes

Upload the package

Finally, it's time to upload the package:

$ dput ppa:your-ppa-name deb_dist/packagename_version.changes

This will give you some output and eventually probably tell you Successfully uploaded packages. It's lying -- it may have failed. Watch your inbox for messages. If Launchpad rejects your changes, you should get an email fairly quickly.

If Launchpad accepts the changes, you'll get an Accepted email. Great! But don't celebrate quite yet. Launchpad still has to build your package before it can be installed. If you try to add your PPA now, you'll get a 404.

Wait for Launchpad to build

You might as well add your repository now so you can install from it once it's ready:

$ sudo add-apt-repository ppa:your-ppa-name

But don't apt-get update yet! if you try that too soon, you'll get a 404, or an Ign meaning that the repository exists but there are no packages in it for your architecture. It might be as long as a few hours before Launchpad builds your package.

To keep track of this, go to your Launchpad PPA page (something like https://launchpad.net/~yourname/+archive/ppa) and look under PPA Statistics for something like "1 package waiting to build". Click on that link, then in the page that comes up, click on the link like i386 build of pkgname version in ubuntu precise RELEASE. That should give you a time estimate.

Wondering why it's being built for i386 when Python should be arch independent? Worry not -- that's just the architecture that's doing the building. Once it's built, your package should install anywhere.

Once the Launchpad build page finally says the package is built, it's finally safe to run the usual apt-get update.

Add your key

But when you apt-get update you may get an error like this:

The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 16126D3A3E5C1192

Obviously you have your own public key, so what's up? You have to import the key from Ubuntu's keyserver, and then export it into apt-key, before apt can use it -- even if it's your own key.

For this, you need the last 8 digits given in the NO PUBKEY message. Take those 8 digits and run these two commands:

gpg --keyserver keyserver.ubuntu.com --recv 3E5C1192
gpg --export --armor 3E5C1192 | sudo apt-key add -

I'm told that apt-add-repository is supposed to add the key automatically, but it didn't for me. Maybe it will if you wait until after your package is built before calling apt-add-repository.

Now if you apt-get update, you should see no errors. Finally, you can apt-get install pkgname. Congratulations! You have a working PPA package.

Tags: , ,
[ 13:34 May 30, 2012    More programming | permalink to this entry | comments ]

Sat, 26 May 2012

Use stdeb to make Debian packages for a Python package

I write a lot of little Python scripts. And I use Ubuntu and Debian. So why aren't any of my scripts packaged for those distros?

Because Debian packaging is absurdly hard, and there's very little documentation on how to do it. In particular, there's no help on how to take something small, like a Python script, and turn it into a package someone else could install on a Debian system. It's pretty crazy, since RPM packaging of Python scripts is so easy.

Recently at the Ubuntu Developers' Summit, Asheesh of OpenHatch pointed me toward a Python package called stdeb that simplifies a lot of the steps and makes Python packaging fairly straightforward.

You'll need a setup.py file to describe your Python script, and you'll probably want a .desktop file and an icon. If you haven't done that before, see my article on Packaging Python for MeeGo for some hints.

Then install python-stdeb. The package has some requirements that aren't listed as dependencies, so you'll need to install:

apt-get install python-stdeb fakeroot python-all
(I have no idea why it needs python-all, which installs only a directory /usr/share/doc/python-all with some policy documentation files, but if you don't install it, stdeb will fail later.)

Now create a config file for stdeb to tell it what Debian/Ubuntu version you're going to be targeting, if it's anything other than Debian unstable (stdeb's default). Unfortunately, there seems to be no way to pass this on the command line rather than in a config file. So if you want to make packages for several distros, you'll have to edit the config file for every distro you want to support. Here's what I'm using for Ubuntu 12.04 Precise Pangolin:

[DEFAULT]
Suite: precise

Now you're ready to run stdeb. I know of two ways to run it. You can generate both source and binary packages, like this:

python setup.py --command-packages=stdeb.command bdist_deb
Or you can generate source packages only, like this:
python setup.py --command-packages=stdeb.command sdist_dsc

Either syntax creates a directory called deb_dist. It contains a lot of files including a source .dsc, several tarballs, a copy of your source directory, and (if you used bdist_deb) a binary .deb package.

If you used the bdist_deb form, don't be put off that it concludes with a message:

dpkg-buildpackage: binary only upload (no source included)
It's fibbing: the source .dsc is there as well as the binary .deb. I presume it prints the warning because it creates them as separate steps, and the binary is the last step.

Now you can use dpkg -i to install your binary deb, or you can use the source dsc for various purposes, like creating a repository or a Launchpad PPA. But those involve a lot more steps -- so I'll cover that in a separate article about creating PPAs.

Update: you can find that article here: Creating packages for a Launchpad PPA.

Tags: , , , ,
[ 11:44 May 26, 2012    More programming | permalink to this entry | comments ]

Sun, 06 May 2012

Playing an MP3 file from an Android app

I've mostly been enormously happy with my upgrade from my old Archos 5 to the Samsung Galaxy Player 5.0. The Galaxy does everything I always wanted the Archos to do, all those things the Archos should have done but couldn't because of its buggy and unsupported Android 1.6.

That is, I've been happy with everything except one thing: my birdsong app no longer worked.

I have a little HTML app based on my "tweet" python script which lets you choose from a list of birdsong MP3 files. (The actual MP3 files are ripped from the excellent 4-CD Stokes Field Guide to Western Bird Songs set.) The HTML app matches bird names as you type in characters. (If you're curious, an earlier test version is at tweet.html.)

On the Archos, I ran that under my WebClient Android app (I had to modify the HTML to add a keyboard, since in Android 1.6 the soft keyboard doesn't work in WebView text fields). I chose a bird, and WebView passed off the MP3 file to the Archos' built-in audio player. Worked great.

On the Samsung Galaxy, no such luck. Apparently Samsung's built-in media player can only play files it has indexed itself. If you try to use it to play an arbitrary file, say, "Song_Sparrow.mp3", it will say: unknown file type. No matter that the file ends in .mp3 ... and no matter that I've called intent.setDataAndType(Uri.parse(url), "audio/mpeg"); ... and no matter that the file is sitting on the SD cad and has in fact been indexed already by the media player. You didn't navigate to it via the media player's UI, so it refuses to play it.

I haven't been able to come up with an answer to how to make Samsung's media player more flexible, and I was just starting a search for alternate Android MP3 player apps, when I ran across Play mp3 in SD Card, using Android's MediaPlayer and Error creating MediaPlayer with Uri or file in assets which gave me the solution. Instead of using an intent and letting WebView call up a music application, you can use an Android MediaPlayer to play your file directly.

Here's what the code looks like, inside setWebViewClient() which is itself inside onCreate():

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.endsWith(".mp3")) {
                    MediaPlayer mediaPlayer = new MediaPlayer();
                    try {
                        mediaPlayer.setDataSource(getApplicationContext(), Uri.parse(url));
                        mediaPlayer.prepare();
                        mediaPlayer.start();
                    }
                    catch (IllegalArgumentException e) { showMessage("Illegal argument exception on " + url); }
                    catch (IllegalStateException e) { showMessage("Illegal State exception on " + url); }
                    catch (IOException e) { showMessage("I/O exception on " + url); }
                }
            }

showMessage() is my little wrapper that pops up an error message dialog. Of course, you can handle other types, not just files ending in .mp3.

And now I can take the Galaxy out on a birdwalk and use it to help me identify bird songs.

Tags: , , ,
[ 14:28 May 06, 2012    More programming | permalink to this entry | comments ]

Fri, 27 Apr 2012

Venus is at its brightest -- why? And how to calculate it

Venus has been a beautiful sight in the evening sky for months, but at the end of April it's reaching a brightness peak, magnitude -4.7.

By then, if you look at it in a telescope or even good binoculars, you'll see it has waned to a crescent. That's a bit non-obvious: when the moon is a crescent, it's a lot fainter than a full moon. So why is Venus brightest in its crescent phase?

It has to do with their orbits. The moon is always about the same distance away, about 385,000 km or 239,000 miles (I've owned cars with more miles than that!), though it varies a little, from 362,600 km at perigee to 405,400 km at apogee.

When we look at the full moon, not only are we seeing the whole Earth-facing surface illuminated, but the central part of that light is reflecting straight up off the moon's surface. When we look at a crescent moon, we're seeing light that's near the moon's sunrise or sunset point -- dimmer and more spread out than the concentrated light of noon -- and in addition we're seeing less of it.

Venus, in contrast, varies its distance from us immensely. We can't see Venus when it's "full", because it's on the other side of the sun from us and lost in the sun's glow. It'll next be there a year from now, in April of 2013. But if we could see it when it's full, Venus would be a distant 1.7 AU from us. An AU is an Astronomical Unit, the average distance of the earth from the sun or about 89 million miles, so Venus when it's full is about 170 million miles away. Its disk is a tiny 9.9 arcseconds (an arcsecond is 1/3600 of a degree) -- about the size of Mars this month.

In contrast, when we look at the crescent Venus around the end of this month, although we're only seeing about 28% of its surface illuminated, and that only with glancing twilight rays, it's much closer to us -- less than half an AU, or about 45 million miles -- and its disk extends a huge 37 arcseconds, bigger than Jupiter this month.

Of course, eventually, as Venus pulls between us and the sun, its crescent gets so slim that even its huge size can't compensate. So its peak brightness happens when those two curves cross, when the disk is somewhere around 27% illuminated, as happens at the end of this month and the beginning of May.

Exactly when? Good question. The RASC Handbook says Venus' "greatest illuminated extent" is on April 30, but PyEphem and XEphem say Venus is actually brighter from May 3-8 ... and when it emerges from the sun's glare and moves into the morning sky in June, it'll be slightly brighter still, peaking at magnitude -4.8 in the first week of July.)

Tracking Venus with PyEphem

When I started my Shallow Sky column this month, I saw the notice of Venus's maximum brightness and greatest illuminated extent in the RASC Handbook. But I wanted more details -- how much did its distance and size really change, when would the brightness peak again as it emerged from the sun's glare, when would it next be "full"?

PyEphem made it easy to calculate all this. Just create an ephem.Venus() object, calculate its values for any date of interest, then print out parameters like phase, mag, earth_distance and size. In just a few minutes of programming, I had a nice table of Venus data.

import ephem

venus = ephem.Venus()

print '%10s   %6s %6s %6s %6s' % ('date', '%', 'mag', 'dist', 'size')
def print_venus(when) :
    venus.compute(when)
    fmt = '%02d-%02d-%02d   %6.2f %6.2f %6.2f %6.2f'
    trip = when.triple()
    print fmt % (trip[0], trip[1], trip[2],
                 venus.phase, venus.mag, venus.earth_distance, venus.size)

# Loop from the beginning of 2012 through the middle of 2013:
d = ephem.date('2012')
end_date = ephem.date('2013/6/1')
while d < end_date :
    print_venus(d)
    # Add a week:
    d = ephem.date(d + ephem.hour * 24)

I've found PyEphem very handy for calculations like this -- and it's great to be able to double-check listings in other publications.

Tags: , ,
[ 14:44 Apr 27, 2012    More science/astro | permalink to this entry | comments ]

Sat, 21 Apr 2012

Android WebView can't goBack from a page with iframes

I've been fighting a bug in Android's WebView class for ages: on some pages, clicking FeedViewer's back arrow (which calls WebView::goBack()) doesn't go back to the previous page. Instead, it jumps to some random position in the current page. If you repeat it, eventually, after five or so tries (depending on the page), eventually goBack() will finally work and you'll be back at the previous page.

It was especially frustrating in that it didn't happen everywhere -- only in pages from certain sites. I saw it all the time in pages from the Los Angeles Times and from Make Magazine, but only rarely on other sites.

But I finally tracked it down: it's because those pages include the HTML <iframe> tag. Apparently, if a WebView is on a page (at least if it's a local page) that contains N iframes, the first N calls to goBack will jump somewhere in the document -- probably the location of the associated iframe, though I haven't verified that -- and only on the N+1st call will the WebView actually go back to the previous page.

The oddest thing is, this doesn't seem to be reported anywhere. Android's bug tracker finds nothing for webview iframe goback, and web searching hasn't found a hint of it, even though I see this in Android versions from 1.6 through 2.3.5. How is it possible that other people haven't noticed this? I wonder if it only happens on local file:// URLs, and not many people use those.

In any case, I'm very happy to have found the answer at last. It was easy enough to modify FeedMe to omit iframes (and who wants iframes in simplified HTML anyway?), and it's great to have a Back button that works on any page.

Tags: ,
[ 20:56 Apr 21, 2012    More programming | permalink to this entry | comments ]

Mon, 09 Apr 2012

Quick Guide to Android ADB

I've been fiddling with several new Android devices, which means I have to teach myself how to use adb all over again.

adb is the Android Debug Bridge, and it's great for debugging. It lets you type commands on your desktop keyboard rather than tapping them into the soft keyboard in Android's terminal emulator, it gives you a fast way to install apps, and most important, it lets you get Java stack traces from crashing programs.

Alas, the documentation is incomplete and sometimes just plain wrong. Since I don't need adb very often, I always forget how to use it between sessions, and it takes some googling to figure out the tricks. Here are the commands I've found most helpful.

Start the server

First you have to start the adb, and that must be done as root. But adb isn't a system program and probably lives in some path like /home/username/path/to/android-sdk-linux_x86/tools. Even if you put it in your own path, it may not be in root's. You can probably run it with the explicit path:

$ sudo /path/to/android-sdk-linux_x86/tools/sudo adb start-server
or you can add it to root's path:
# export PATH=$PATH:/path/to/android/android-sdk-linux_x86/tools
# adb start-server

If you're also running eclipse, that probably won't work the first time, because eclipse may also have started an adb server (that gets in the way when you try to run adb manually). if you don't see "* daemon started successfully *", try killing the server and restarting it:

# adb kill-server
# adb start-server
* daemon not running. starting it now on port 5037 *
* daemon started successfully *

Keep trying until you see that "* daemon started successfully *" message.

Connecting

$ adb usb

Occasionally, this will give "error: closed". Don't panic -- sometimes this actually means "I noticed something connected on USB and automatically connected to it, so no need to connect again." It's mysterious, and no one seems to have an explanation for what's really happening. Anyway, try running some adb commands and you may find you're actually connected.

Shell and install

The most fun is running an interactive shell on your Android device.

$ adb shell
It's just busybox, not a full shell, so you don't have nice things like tab completion. But it's still awfully useful.

You can also install apps. On some devices (like the Nook, where I haven't found a way to allow install from non-market sources), it's the only way to install an apk file.

$ adb install /path/to/appname.apk

If the app is already installed, you'll get an error. Theoretically you can also do adb uninstall first, but when I tried that it just printed "Failure". But you can use -r for "reinstall":

$ adb install -r /path/to/appname.apk

There is no mention of either uninstall or -r in the online adb documentation, though adb -h mentions it.

Update: To uninstall, you need the full name of the package. To get the names of installed packages (another undocumented command), do this: adb shell pm list packages

Debug crashes with logcat

Finally, for debugging crashes, you can start up a logcat and see system messages, including stack traces from crashing apps:

$ adb logcat

Logcat is great for fixing reproducible crashes. Sadly, it's not so good for random/sporadic crashes that happen during the course of using the device.

You're supposed to be able to do adb logcat -s AppName if you're only interested in debugging messages from one app, but that doesn't work for me -- I get no output even when the app runs and crashes.

Tags: ,
[ 11:32 Apr 09, 2012    More tech | permalink to this entry | comments ]

Fri, 16 Mar 2012

Image manipulation in Python

Someone asked me about determining whether an image was "portrait" or "landscape" mode from a script.

I've long had a script for automatically rescaling and rotating images, using ImageMagick under the hood and adjusting automatically for aspect ratio. But the scripts are kind of a mess -- I've been using them for over a decade, and they started life as a csh script back in the pnmscale days, gradually added ImageMagick and jpegtran support and eventually got translated to (not very good) Python.

I've had it in the back of my head that I should rewrite this stuff in cleaner Python using the ImageMagick bindings, rather than calling its commandline tools. So the question today spurred me to look into that. I found that ImageMagick isn't the way to go, but PIL would be a fine solution for most of what I need.

ImageMagick: undocumented and inconstant

Ubuntu has a python-pythonmagick package, which I installed. Unfortunately, it has no documentation, and there seems to be no web documentation either. If you search for it, you find a few other people asking where the documentation is.

Using things like help(PythonMagick) and help(PythonMagick.Image), you can ferret out a few details, like how to get an image's size:

import PythonMagick
filename = 'img001.jpg'
img = PythonMagick.Image(filename)
size = img.size()
print filename, "is", size.width(), "x", size.height()

Great. Now what if you want to rescale it to some other size? Web searching found examples of that, but it doesn't work, as illustrated here:

>>> img.scale('1024x768')
>>> img.size().height()
640

The built-in help was no help:

>>> help(img.scale)
Help on method scale:

scale(...) method of PythonMagick.Image instance
    scale( (Image)arg1, (Geometry)arg2) -> None :
    
        C++ signature :
            void scale(Magick::Image {lvalue},Magick::Geometry)

So what does it want for (Geometry)? Strings don't seem to work, 2-tuples don't work, and there's no Geometry object in PythonMagick. By this time I was tired of guesswork. Can the Python Imaging Library do better?

PIL -- the Python Imaging Library

PIL, happily, does have documentation. So it was easy to figure out how to get an image's size:

from PIL import Image
im = Image.open(filename)
w = im.size[0]
h = im.size[1]
print filename, "is", w, "x", h
It was equally easy to scale it to half its original size, then write it to a file:
newim = im.resize((w/2, h/2))
newim.save("small-" + filename)

Reading EXIF

Wow, that's great! How about EXIF -- can you read that? Yes, PIL has a module for that too:

import PIL.ExifTags

exif = im._getexif()
for tag, value in exif.items():
    decoded = PIL.ExifTags.TAGS.get(tag, tag)
    print decoded, '->', value

There are other ways to read exif -- pyexiv2 seems highly regarded. It has documentation, a tutorial, and apparently it can even write EXIF tags.

If neither PIL nor pyexiv2 meets your needs, here's a Stack Overflow thread on other Python EXIF solutions, and here's another discussion of Python EXIF. But since you probably already have PIL, it's certainly an easy way to get started.

What about the query that started all this: how to find out whether an image is portrait or landscape? Well, the most important thing is the image dimensions themselves -- whether img.size[0] > img.size[1]. But sometimes you want to know what the camera's orientation sensor thought. For that, you can use this code snippet:

for tag, value in exif.items():
    decoded = PIL.ExifTags.TAGS.get(tag, tag)
    if decoded == 'Orientation':
        print decoded, ":", value
Then compare the number you get to this Exif Orientation table. Normal landscape-mode photos will be 1.

Given all this, have I actually rewritten resizeall and rotateall using PIL? Why, no! I'll put it on my to-do list, honest. But since the scripts are actually working fine (just don't look at the code), I'll leave them be for now.

Tags: , , , ,
[ 15:33 Mar 16, 2012    More programming | permalink to this entry | comments ]

Thu, 12 Jan 2012

HTML and Javascript Presentations

When I give talks that need slides, I've been using my Slide Presentations in HTML and JavaScript for many years. I uploaded it in 2007 -- then left it there, without many updates.

But meanwhile, I've been giving lots of presentations, tweaking the code, tweaking the CSS to make it display better. And every now and then I get reminded that a few other people besides me are using this stuff.

For instance, around a year ago, I gave a talk where nearly all the slides were just images. Silly to have to make a separate HTML file to go with each image. Why not just have one file, img.html, that can show different images? So I wrote some code that lets you go to a URL like img.html?pix/whizzyphoto.jpg, and it will display it properly, and the Next and Previous slide links will still work.

Of course, I tweak this software mainly when I have a talk coming up. I've been working lately on my SCALE talk, coming up on January 22: Fun with Linux and Devices (be ready for some fun Arduino demos!) Sometimes when I overload on talk preparation, I procrastinate by hacking the software instead of the content of the actual talk. So I've added some nice changes just in the past few weeks.

For instance, the speaker notes that remind me of where I am in the talk and what's coming next. I didn't have any way to add notes on image slides. But I need them on those slides, too -- so I added that.

Then I decided it was silly not to have some sort of automatic reminder of what the next slide was. Why should I have to put it in the speaker notes by hand? So that went in too.

And now I've done the less fun part -- collecting it all together and documenting the new additions. So if you're using my HTML/JS slide kit -- or if you think you might be interested in something like that as an alternative to Powerpoint or Libre Office Presenter -- check out the presentation I have explaining the package, including the new features.

You can find it here: Slide Presentations in HTML and JavaScript

Tags: , , , , ,
[ 21:08 Jan 12, 2012    More speaking | permalink to this entry | comments ]

Sun, 08 Jan 2012

Parsing HTML in Python

I've been having (mis)adventures learning about Python's various options for parsing HTML.

Up until now, I've avoided doing any HTMl parsing in my RSS reader FeedMe. I use regular expressions to find the places where content starts and ends, and to screen out content like advertising, and to rewrite links. Using regexps on HTML is generally considered to be a no-no, but it didn't seem worth parsing the whole document just for those modest goals.

But I've long wanted to add support for downloading images, so you could view the downloaded pages with their embedded images if you so chose. That means not only identifying img tags and extracting their src attributes, but also rewriting the img tag afterward to point to the locally stored image. It was time to learn how to parse HTML.

Since I'm forever seeing people flamed on the #python IRC channel for using regexps on HTML, I figured real HTML parsing must be straightforward. A quick web search led me to Python's built-in HTMLParser class. It comes with a nice example for how to use it: define a class that inherits from HTMLParser, then define some functions it can call for things like handle_starttag and handle_endtag; then call self.feed(). Something like this:

from HTMLParser import HTMLParser

class MyFancyHTMLParser(HTMLParser):
  def fetch_url(self, url) :
    request = urllib2.Request(url)
    response = urllib2.urlopen(request)
    link = response.geturl()
    html = response.read()
    response.close()
    self.feed(html)   # feed() starts the HTMLParser parsing

  def handle_starttag(self, tag, attrs):
    if tag == 'img' :
      # attrs is a list of tuples, (attribute, value)
      srcindex = self.has_attr('src', attrs)
      if srcindex < 0 :
        return   # img with no src tag? skip it
      src = attrs[srcindex][1]
      # Make relative URLs absolute
      src = self.make_absolute(src)
      attrs[srcindex] = (attrs[srcindex][0], src)

    print '<' + tag
    for attr in attrs :
      print ' ' + attr[0]
      if len(attr) > 1 and type(attr[1]) == 'str' :
        # make sure attr[1] doesn't have any embedded double-quotes
        val = attr[1].replace('"', '\"')
        print '="' + val + '"')
    print '>'

  def handle_endtag(self, tag):
    self.outfile.write('</' + tag.encode(self.encoding) + '>\n')

Easy, right? Of course there are a lot more details, but the basics are simple.

I coded it up and it didn't take long to get it downloading images and changing img tags to point to them. Woohoo! Whee!

The bad news about HTMLParser

Except ... after using it a few days, I was hitting some weird errors. In particular, this one:
HTMLParser.HTMLParseError: bad end tag: ''

It comes from sites that have illegal content. For instance, stories on Slate.com include Javascript lines like this one inside <script></script> tags:
document.write("<script type='text/javascript' src='whatever'></scr" + "ipt>");

This is technically illegal html -- but lots of sites do it, so protesting that it's technically illegal doesn't help if you're trying to read a real-world site.

Some discussions said setting self.CDATA_CONTENT_ELEMENTS = () would help, but it didn't.

HTMLParser's code is in Python, not C. So I took a look at where the errors are generated, thinking maybe I could override them. It was easy enough to redefine parse_endtag() to make it not throw an error (I had to duplicate some internal strings too). But then I hit another error, so I redefined unknown_decl() and _scan_name(). And then I hit another error. I'm sure you see where this was going. Pretty soon I had over 100 lines of duplicated code, and I was still getting errors and needed to redefine even more functions. This clearly wasn't the way to go.

Using lxml.html

I'd been trying to avoid adding dependencies to additional Python packages, but if you want to parse real-world HTML, you have to. There are two main options: Beautiful Soup and lxml.html. Beautiful Soup is popular for large projects, but the consensus seems to be that lxml.html is more error-tolerant and lighter weight.

Indeed, lxml.html is much more forgiving. You can't handle start and end tags as they pass through, like you can with HTMLParser. Instead you parse the HTML into an in-memory tree, like this:

  tree = lxml.html.fromstring(html)

How do you iterate over the tree? lxml.html is a good parser, but it has rather poor documentation, so it took some struggling to figure out what was inside the tree and how to iterate over it.

You can visit every element in the tree with

for e in tree.iter() :
  print e.tag

But that's not terribly useful if you need to know which tags are inside which other tags. Instead, define a function that iterates over the top level elements and calls itself recursively on each child.

The top of the tree itself is an element -- typically the <html></html> -- and each element has .tag and .attrib. If it contains text inside it (like a <p> tag), it also has .text. So to make something that works similarly to HTMLParser:

def crawl_tree(tree) :
  handle_starttag(tree.tag, tree.attrib)
  if tree.text :
    handle_data(tree.text)
  for node in tree :
    crawl_tree(node)
  handle_endtag(tree.tag)

But wait -- we're not quite all there. You need to handle two undocumented cases.

First, comment tags are special: their tag attribute, instead of being a string, is <built-in function Comment> so you have to handle that specially and not assume that tag is text that you can print or test against.

Second, what about cases like <p>Here is some <i>italicised</i> text.</p> ? in this case, you have the p tag, and its text is "Here is some ". Then the p has a child, the i tag, with text of "italicised". But what about the rest of the string, " text."?

That's called a tail -- and it's the tail of the adjacent i tag it follows, not the parent p tag that contains it. Confusing!

So our function becomes:

def crawl_tree(tree) :
  if type(tree.tag) is str :
    handle_starttag(tree.tag, tree.attrib)
    if tree.text :
      handle_data(tree.text)
    for node in tree :
      crawl_tree(node)
    handle_endtag(tree.tag)
  if tree.tail :
    handle_data(tree.tail)

See how it works? If it's a comment (tree.tag isn't a string), we'll skip everything -- except the tail. Even a comment might have a tail:
<p>Here is some <!-- this is a comment --> text we want to show.</p>
so even if we're skipping comment we need its tail.

I'm sure I'll find other gotchas I've missed, so I'm not releasing this version of feedme until it's had a lot more testing. But it looks like lxml.html is a reliable way to parse real-world pages. It even has a lot of convenience functions like link rewriting that you can use without iterating the tree at all. Definitely worth a look!

Tags: , ,
[ 15:04 Jan 08, 2012    More programming | permalink to this entry | comments ]

Thu, 29 Dec 2011

Plotting the Analemma

My SJAA planet-observing column for January is about the Analemma and the Equation of Time.

The analemma is that funny figure-eight you see on world globes in the middle of the Pacific Ocean. Its shape is the shape traced out by the sun in the sky, if you mark its position at precisely the same time of day over the course of an entire year.

The analemma has two components: the vertical component represents the sun's declination, how far north or south it is in our sky. The horizontal component represents the equation of time.

The equation of time describes how the sun moves relatively faster or slower at different times of year. It, too, has two components: it's the sum of two sine waves, one representing how the earth speeds up and slows down as it moves in its elliptical orbit, the other a function the tilt (or "obliquity") of the earth's axis compared to its orbital plane, the ecliptic.

[components of the Equation of time] The Wikipedia page for Equation of time includes a link to a lovely piece of R code by Thomas Steiner showing how the two components relate. It's labeled in German, but since the source is included, I was able to add English labels and use it for my article.

But if you look at photos of real analemmas in the sky, they're always tilted. Shouldn't they be vertical? Why are they tilted, and how does the tilt vary with location? To find out, I wanted a program to calculate the analemma.

Calculating analemmas in PyEphem

The very useful astronomy Python package PyEphem makes it easy to calculate the position of any astronomical object for a specific location. Install it with: easy_install pyephem for Python 2, or easy_install ephem for Python 3.

import ephem
observer = ephem.city('San Francisco')
sun = ephem.Sun()
sun.compute(observer)
print sun.alt, sun.az

The alt and az are the altitude and azimuth of the sun right now. They're printed as strings: 25:23:16.6 203:49:35.6 but they're actually type 'ephem.Angle', so float(sun.alt) will give you a number in radians that you can use for calculations.

Of course, you can specify any location, not just major cities. PyEphem doesn't know San Jose, so here's the approximate location of Houge Park where the San Jose Astronomical Association meets:

observer = ephem.Observer()
observer.name = "San Jose"
observer.lon = '-121:56.8'
observer.lat = '37:15.55'

You can also specify elevation, barometric pressure and other parameters.

So here's a simple analemma, calculating the sun's position at noon on the 15th of each month of 2011:

    for m in range(1, 13) :
        observer.date('2011/%d/15 12:00' % (m))
        sun.compute(observer)

I used a simple PyGTK window to plot sun.az and sun.alt, so once it was initialized, I drew the points like this:

    # Y scale is 45 degrees (PI/2), horizon to halfway to zenith:
    y = int(self.height - float(self.sun.alt) * self.height / math.pi)
    # So make X scale 45 degrees too, centered around due south.
    # Want az = PI to come out at x = width/2.
    x = int(float(self.sun.az) * self.width / math.pi / 2)
    # print self.sun.az, float(self.sun.az), float(self.sun.alt), x, y
    self.drawing_area.window.draw_arc(self.xgc, True, x, y, 4, 4, 0, 23040)

So now you just need to calculate the sun's position at the same time of day but different dates spread throughout the year.

[analemma in San Jose at noon clock time] And my 12-noon analemma came out almost vertical! Maybe the tilt I saw in analemma photos was just a function of taking the photo early in the morning or late in the afternoon? To find out, I calculated the analemma for 7:30am and 4:30pm, and sure enough, those were tilted.

But wait -- notice my noon analemma was almost vertical -- but it wasn't exactly vertical. Why was it skewed at all?

Time is always a problem

As always with astronomy programs, time zones turned out to be the hardest part of the project. I tried to add other locations to my program and immediately ran into a problem.

The ephem.Date class always uses UTC, and has no concept of converting to the observer's timezone. You can convert to the timezone of the person running the program with localtime, but that's not useful when you're trying to plot an analemma at local noon.

At first, I was only calculating analemmas for my own location. So I set time to '20:00', that being the UTC for my local noon. And I got the image at right. It's an analemma, all right, and it's almost vertical. Almost ... but not quite. What was up?

Well, I was calculating for 12 noon clock time -- but clock time isn't the same as mean solar time unless you're right in the middle of your time zone.

You can calculate what your real localtime is (regardless of what politicians say your time zone should be) by using your longitude rather than your official time zone:

    date = '2011/%d/12 12:00' % (m)
    adjtime = ephem.date(ephem.date(date) \
                    - float(self.observer.lon) * 12 / math.pi * ephem.hour)
    observer.date = adjtime

Maybe that needs a little explaining. I take the initial time string, like '2011/12/15 12:00', and convert it to an ephem.date. The number of hours I want to adjust is my longitude (in radians) times 12 divided by pi -- that's because if you go pi (180) degrees to the other side of the earth, you'll be 12 hours off. Finally, I have to multiply that by ephem.hour because ... um, because that's the way to add hours in PyEphem and they don't really document the internals of ephem.Date.

[analemma in San Jose at noon clock time] Set the observer date to this adjusted time before calculating your analemma, and you get the much more vertical figure you see here. This also explains why the morning and evening analemmas weren't symmetrical in the previous run.

This code is location independent, so now I can run my analemma program on a city name, or specify longitude and latitude.

PyEphem turned out to be a great tool for exploring analemmas. But to really understand analemma shapes, I had more exploring to do. I'll write about that, and post my complete analemma program, in the next article.

Tags: , , , , ,
[ 20:54 Dec 29, 2011    More science/astro | permalink to this entry | comments ]

Thu, 22 Dec 2011

Calculating the Solstice and shortest day

Today is the winter solstice -- the official beginning of winter.

The solstice is determined by the Earth's tilt on its axis, not anything to do with the shape of its orbit: the solstice is the point when the poles come closest to pointing toward or away from the sun. To us, standing on Earth, that means the winter solstice is the day when the sun's highest point in the sky is lowest.

You can calculate the exact time of the equinox using the handy Python package PyEphem. Install it with: easy_install pyephem for Python 2, or easy_install ephem for Python 3. Then ask it for the date of the next or previous equinox. You have to give it a starting date, so I'll pick a date in late summer that's nowhere near the solstice:

>>> ephem.next_solstice('2011/8/1')
2011/12/22 05:29:52
That agrees with my RASC Observer's Handbook: Dec 22, 5:30 UTC. (Whew!)

PyEphem gives all times in UTC, so, since I'm in California, I subtract 8 hours to find out that the solstice was actually last night at 9:30. If I'm lazy, I can get PyEphem to do the subtraction for me:

ephem.date(ephem.next_solstice('2011/8/1') - 8./24)
2011/12/21 21:29:52
I used 8./24 because PyEphem's dates are in decimal days, so in order to subtract 8 hours I have to convert that into a fraction of a 24-hour day. The decimal point after the 8 is to get Python to do the division in floating point, otherwise it'll do an integer division and subtract int(8/24) = 0.

The shortest day

The winter solstice also pretty much marks the shortest day of the year. But was the shortest day yesterday, or today? To check that, set up an "observer" at a specific place on Earth, since sunrise and sunset times vary depending on where you are. PyEphem doesn't know about San Jose, so I'll use San Francisco:

>>> import ephem
>>> observer = ephem.city("San Francisco")
>>> sun = ephem.Sun()
>>> for i in range(20,25) :
...   d = '2011/12/%i 20:00' % i
...   print d, (observer.next_setting(sun, d) - observer.previous_rising(sun, d)) * 24
2011/12/20 20:00 9.56007901422
2011/12/21 20:00 9.55920379754
2011/12/22 20:00 9.55932991847
2011/12/23 20:00 9.56045709446
2011/12/24 20:00 9.56258416496
I'm multiplying by 24 to get hours rather than decimal days.

So the shortest day, at least here in the bay area, was actually yesterday, 2011/12/21. Not too surprising, since the solstice wasn't that long after sunset yesterday.

If you look at the actual sunrise and sunset times, you'll find that the latest sunrise and earliest sunset don't correspond to the solstice or the shortest day. But that's all tied up with the equation of time and the analemma ... and I'll cover that in a separate article.

Tags: , , , ,
[ 11:28 Dec 22, 2011    More science/astro | permalink to this entry | comments ]

Wed, 16 Nov 2011

New trails, and new PyTopo 1.1 release

A new trail opened up above Alum Rock park! Actually a whole new open space preserve, called Sierra Vista -- with an extensive set of trails that go all sorts of interesting places.

Dave and I visit Alum Rock frequently -- we were married there -- so having so much new trail mileage is exciting. We tried to explore it on foot, but quickly realized the mileage was more suited to mountain bikes. Even with bikes, we'll be exploring this area for a while (mostly due to not having biked in far too long, so it'll take us a while to work up to that much riding ... a combination of health problems and family issues have conspired to keep us off the bikes).

Of course, part of the fun of discovering a new trail system is poring over maps trying to figure out where the trails will take us, then taking GPS track logs to study later to see where we actually went.

And as usual when uploading GPS track logs and viewing them in pytopo, I found some things that weren't working quite the way I wanted, so the session ended up being less about studying maps and more about hacking Python.

In the end, I fixed quite a few little bugs, improved some features, and got saved sites with saved zoom levels working far better.

Now, PyTopo 1.0 happened quite a while ago -- but there were two of us hacking madly on it at the time, and pinning down the exact time when it should be called 1.0 wasn't easy. In fact, we never actually did it. I know that sounds silly -- of all releases to not get around to, finally reaching 1.0? Nevertheless, that's what happened.

I thought about cheating and calling this one 1.0, but we've had 1.0 beta RPMs floating around for so long (and for a much earlier release) that that didn't seem right.

So I've called the new release PyTopo 1.1. It seems to be working pretty solidly. It's certainly been very helpful to me in exploring the new trails. It's great for cross-checking with Google Earth: the OpenCycleMap database has much better trail data than Google does, and pytopo has easy track log loading and will work offline, while Google has the 3-D projection aerial imagery that shows where trails and roads were historically (which may or may not correspond to where they decide to put the new trails). It's great to have both.

Anyway, here's the new PyTopo.

Tags: , ,
[ 20:59 Nov 16, 2011    More mapping | permalink to this entry | comments ]

Sat, 05 Nov 2011

The Stanford online Machine Learning class

In case you haven't been following it, Stanford's computer science department began a grand experiment in online learning early this month: free, upper division college courses, given online and open to the whole world. There are three classes offered: Artificial Intelligence, Machine Learning and Introduction to Databases.

They've sparked an incredible response: exact numbers don't seem to be available, but rumor is that AI had about 130,000 enrolees, while ML had about 70,000. (Nobody seems to have published numbers for DB.) Update, a day later: @seemsArtless tweets that ML currently has 87,000 registered users.

Why so much interest? Surely there are lots of places to get free information (like wikipedia) and even course lectures (like MIT). And there are plenty of places to take classes for relatively low cost, like local junior colleges or ed2go.

What's different about the Stanford classes is that they cover advanced material, in far more depth than you'd find at a junior college or typical online site. They offer graded homework so you can see how you're doing, and there are other students taking the class at the same time, so if you get stuck, there are all sorts of discussion groups you can turn to. It's one thing to read a textbook or watch a video by yourself; I find a class much more helpful, and judging by the response to the Stanford classes, I'm not alone in that.

I agonized over whether to take AI or Machine Learning. They both sounded so interesting! Since I couldn't decide, I initially signed up for both, figuring I'd drop one if the load was too great. By the end of the second week, I'd settled on Machine Learning. I was starting to dread the AI class flash quizzes -- which didn't always work right, but made it hard to proceed until you'd answered the question right even if you couldn't see the question -- and to feel frustrated about the lectures, which clearly were meant as a jumping off point for students to go do their own outside reading.

On the other hand, I was really enjoying the Machine Learning lectures, and looking forward to each new one. And the real kicker: Machine Learning includes programming assignments, so students can implement the algorithms Professor Ng talks about in the lectures.

What's great about Machine Learning

Andrew Ng's video lectures are wonderfully clear, well paced and full of interesting content.

He uses a lot of graphs to help students visualize what's going on geometrically, rather than just relying on the equations. (Better yet, in the programming exercises he shows us how to create those graphs for ourselves.)

And he's great about flagging certain portions as possibly review (you can skip this lecture if you already know linear algebra) or advanced (this is some extra background for people who know calculus, but you can skip it and still do fine in the course).

The technology is simpler than that used in the AI course. If you have a slow net connection or travel a lot, you can download the lectures as mp4 files and watch them offline. You can download lecture slides as a PDF or PPT. Review questions (graded) are handled with simple HTML forms. All very simple, well-tested technology, and it works great. I've had no problems accessing the servers, submitting homework or anything else -- very impressive!

But the heart of the course is the programming exercises. ML is taught in GNU octave, a framework and language for numerical computing and matrix operations. Students aren't absolutely required to use octave, but it's highly recommended: Professor Ng says he's found that students learn much faster that way. Sounds good to me, and octave looks like a useful skill, well worth acquiring. I'm having fun learning it.

The programming exercises come with a lot of scaffold code plus a few files with "Your code goes here". The actual amount of coding isn't large. But I'm finding that it does the job: it forces me to make sure I understand the matrix operations discussed in the lectures. And at the end, you come out with something that's actually useful! From the first few weeks, I have linear and logistic regression code that I could use to analyze and visualize all sorts of datasets. Now, at the end of week 4, we're halfway through writing a neural network to recognize handwritten numerals from image data. How cool is that?

Suggestions for improvement

The class is a huge success. Who would have thought that you could teach something this advanced on such a huge scale, so effectively?

I have only a couple of small suggestions -- ways the class could be even better next time.

Hope for future expansion

I mentioned my suggestions because I fervently hope there is a "next time". These classes are a great service, and I hope the huge response isn't putting too much burden on the instructors.

"Common wisdom" among providers of online classes seems to be that there's no demand outside of enrolled university students for hard courses, courses with prerequisites, and especially courses that involve (shudder) math. Just look at the offerings from any online courseware or adult ed program -- they're long on art appreciation and "Introduction to MS Word", short on physics and econometrics. Even the for-pay online degree mills concentrate on humanities and business, not technical subjects.

Stanford's experiment has proven that "common wisdom" is wrong -- that tens of thousands of students will jump at the chance to take highly technical, mathematical courses. I'd love to see the model expanded to other subjects, such as statistics, economics, physics, geology and climate science.

And, yes, there is money to be made here. If this many people will take a free class, wouldn't quite a few of them be willing to pay? Most couldn't afford $1000 like UC Extension classes -- but how about $100, comparable to other online education classes? Would people pay more if you offered college credit?

Online education providers, take note! There's a large, underserved market for scientific and technical classes out here in the long tail.

Tags: ,
[ 17:31 Nov 05, 2011    More education | permalink to this entry | comments ]

Sun, 16 Oct 2011

Monitor an Arduino's serial output from Python

Debugging Arduino sensors can sometimes be tricky. While working on my Arduino sonar project, I found myself wanting to know what values the Arduino was reading from its analog port.

It's easy enough to print from the Arduino to its USB-serial line. First add some code like this in setup():

    Serial.begin(9600);
Then in loop(), if you just read the value "val":
    Serial.println(val);

Serial output from Python

That's all straightforward -- but then you need something that reads it on the PC side.

When you're using the Arduino Java development environment, you can set it up to display serial output in a few lines at the bottom of the window. But it's not terrifically easy to read there, and I don't want to be tied to the Java IDE -- I'm much happier doing my Arduino development from the command line. But then how do you read serial output when you're debugging? In general, you can use the screen program to talk to serial ports -- it's the tool of choice to log in to plug computers. For the Arduino, you can do something like this: screen /dev/ttyUSB0 9600

But I found that a bit fiddly for various reasons. And I discovered that it's easy to write something like this in Python, using the serial module.

You can start with something as simple as this:

import serial

ser = serial.Serial("/dev/ttyUSB0", 9600)
while True:
    print ser.readline()

Serial input as well as output

That worked great for debugging purposes. But I had another project (which I will write up separately) where I needed to be able to send commands to the Arduino as well as reading output it printed. How do you do both at once?

With the select module, you can monitor several file descriptors at once. If the user has typed something, send it over the serial line to the Arduino; if the Arduino has printed something, read it and display it for the user to see.

That loop looks like this:

while True :
    # Check whether the user has typed anything (timeout of .2 sec):
    inp, outp, err = select.select([sys.stdin, self.ser], [], [], .2)

    # If the user has typed anything, send it to the Arduino:
    if sys.stdin in inp :
        line = sys.stdin.readline()
        self.ser.write(line)

    # If the Arduino has printed anything, display it:
    if self.ser in inp :
line = self.ser.readline().strip()
print "Arduino:", line

Add in a loop to find the right serial port (the Arduino doesn't always show up on /dev/ttyUSB0) and a little error and exception handling, and I had a useful script that met all my Arduino communication needs: ardmonitor.

Tags: , , ,
[ 20:27 Oct 16, 2011    More hardware | permalink to this entry | comments ]

Tue, 27 Sep 2011

Banishing errant tooltips

Every now and then I have to run a program that doesn't manage its tooltips well. I mouse over some button to find out what it does, a tooltip pops up -- but then the tooltip won't go away. Even if I change desktops, the tooltip follows me and stays up on all desktops. Worse, it's set to stay on top of all other windows, so it blocks anything underneath it.

The places where I see this happen most often are XEphem (probably as an artifact of the broken Motif libraries we're stuck with on Linux); Adobe's acroread (Acrobat Reader), though perhaps that's gotten better since I last used it; and Wine.

I don't use Wine much, but lately I've had to use it for a medical imaging program that doesn't seem to have a Linux equivalent (viewing PETscan data). Every button has a tooltip, and once a tooltip pops up, it never goes aawy. Eventually I might have five of six of these little floating windows getting in the way of whatever I'm doing on other desktops, until I quit the wine program.

So how does one get rid of errant tooltips littering your screen? Could I write an Xlib program that could nuke them?

Finding window type

First we need to know what's special about tooltip windows, so the program can identify them. First I ran my wine program and produced some sticky tooltips.

Once they were up, I ran xwininfo and clicked on a tooltip. It gave me a bunch of information about the windows size and location, color depth, etc. ... but the useful part is this:

  Override Redirect State: yes

In X, override-redirect windows are windows that are immune to being controlled by the window manager. That's why they don't go away when you change desktops, or move when you move the parent window.

So what if I just find all override-redirect windows and unmap (hide) them? Or would that kill too many innocent victims?

Python-Xlib

I thought I'd have to write my little app in C, since it's doing low-level Xlib calls. But no -- there's a nice set of Python bindings, python-xlib. The documentation isn't great, but it was still pretty easy to whip something up.

The first thing I needed was a window list: I wanted to make sure I could find all the override-redirect windows. Here's how to do that:

from Xlib import display

dpy = display.Display()
screen = dpy.screen()
root = screen.root
tree = root.query_tree()

for w in tree.children :
    print w

w is a Window (documented here). I see in the documentation that I can get_attributes(). I'd also like to know which window is which -- calling get_wm_name() seems like a reasonable way to do that. Maybe if I print them, those will tell me how to find the override-redirect windows:

for w in tree.children :
    print w.get_wm_name(), w.get_attributes()

Window type, redux

Examining the list, I could see that override_redirect was one of the attributes. But there were quite a lot of override-redirect windows. It turns out many apps, such as Firefox, use them for things like menus. Most of the time they're not visible. But you can look at w.get_attributes().map_state to see that.

So that greatly reduced the number of windows I needed to examine:

for w in tree.children :
    att = w.get_attributes()
    if att.map_state and att.override_redirect :
        print w.get_wm_name(), att

I learned that tooltips from well-behaved programs like Firefox tended to set wm_name to the contents of the tooltip. Wine doesn't -- the wine tooltips had an empty string for wm_name. If I wanted to kill just the wine tooltips, that might be useful to know.

But I also noticed something more important: the tooltip windows were also "transient for" their parent windows. Transient for means a temporary window popped up on behalf of a parent window; it's kept on top of its parent window, and goes away when the parent does.

Now I had a reasonable set of attributes for the windows I wanted to unmap. I tried it:

for w in tree.children :
    att = w.get_attributes()
    if att.map_state and att.override_redirect and w.get_wm_transient_for():
        w.unmap()

It worked! At least in my first test: I ran the wine program, made a tooltip pop up, then ran my killtips program ... and the tooltip disappeared.

Multiple tooltips: flushing the display

But then I tried it with several tooltips showing (yes, wine will pop up new tooltips without hiding the old ones first) and the result wasn't so good. My program only hid the first tooltip. If I ran it again, it would hide the second, and again for the third. How odd!

I wondered if there might be a timing problem. Adding a time.sleep(1) after each w.unmap() fixed it, but sleeping surely wasn't the right solution.

But X is asynchronous: things don't necessarily happen right away. To force (well, at least encourage) X to deal with any queued events it might have stacked up, you can call dpy.flush().

I tried adding that after each w.unmap(), and it worked. But it turned out I only need one

dpy.flush()
at the end of the program, just exiting. Apparently if I don't do that, only the first unmap ever gets executed by the X server, and the rest are discarded. Sounds like flush() is a good idea as the last line of any python-xlib program.

killtips will hide tooltips from well-behaved programs too. If you have any tooltips showing in Firefox or any GTK programs, or any menus visible, killtips will unmap them. If I wanted to make sure the program only attacked the ones generated by wine, I could add an extra test on whether w.get_wm_name() == "".

But in practice, it doesn't seem to be a problem. Well-behaved programs handle having their tooltips unmapped just fine: the next time you call up a menu or a tooltip, the program will re-map it.

Not so in wine: once you dismiss one of those wine tooltips, it's gone forever, at least until you quit and restart the program. But that doesn't bother me much: once I've seen the tooltip for a button and found out what that button does, I'm probably not going to need to see it again for a while.

So I'm happy with killtips, and I think it will solve the problem. Here's the full script: killtips.

Tags: , , , ,
[ 11:36 Sep 27, 2011    More programming | permalink to this entry | comments ]

Fri, 09 Sep 2011

Count characters or words in the X selection from Python

This post is, above all, a lesson in doing a web search first. Even when what you're looking for is so obscure you're sure no one else has wanted it. But the script I got out of it might turn out to be useful.

It started with using Bitlbee for Twitter. I love bitlbee -- it turns a Twitter stream into just another IRC channel tab in the xchat I'm normally running anyway.

The only thing I didn't love about bitlbee is that, unlike the twitter app I'd previously used, I didn't have any way to keep track of when I neared the 140-character limit. There were various ways around that, mostly involving pasting the text into other apps before submitting it. But they were all too many steps.

It occurred to me that one way around this was to select-all, then run something that would show me the number of characters in the X selection. That sounded like an easy app to write.

Getting the X selection from Python

I was somewhat surprised to find that Python has no way of querying the X selection. It can do just about everything else -- even simulate X events. But there are several command-line applications that can print the selection, so it's easy enough to run xsel or xclip from Python and read the output.

I ended up writing a little app that brings up a dialog showing the current count, then hangs around until you dismiss it, querying the selection once a second and updating the count. It's called countsel.

Of course, if you don't want to write a Python script you can use commandline tools directly. Here are a couple of examples, using xclip instead of xsel: xterm -title 'lines words chars' -geometry 25x2 -e bash -c 'xclip -o | wc; read -n 1' pops up a terminal showing the "wc" counts of the selection once, and xterm -title 'lines words chars' -geometry 25x1 -e watch -t 'xclip -o | wc' loops over those counts printing them once a second.

Binding commands to a key is different for every window manager. In Openbox, I added this to rc.xml to call up my program whenever I type W-t (short for Twitter):

    <keybind key="W-t">
      <action name="Execute">
        <execute>/home/akkana/bin/countsel</execute>
      </action>
    </keybind>

Now, any time I needed to check my character count, I could triple-click or type Shift-Home, then hit W-t to call up the dialog and get a count. Then I could leave the dialog up, and whenever I wanted a new count, just Shift-Home or triple-click again, and the dialog updates automatically. Not perfect, but not bad.

Xchat plug-in for a much more elegant solution

Only after getting countsel working did it occur to me to wonder if anyone else had the same Bitlbee+xchat+twitter problem. And a web search found exactly what I needed: xchat-inputcount.pl, a wonderful xchat script that adds a character-counter next to the input box as you're typing. It's a teensy bit buggy, but still, it's far better than my solution. I had no idea you could add user-interface elements to xchat like that!

But that's okay. Countsel didn't take long to write. And I've added word counting to countsel, so I can use it for word counts on anything I'm writing.

Tags: , ,
[ 12:32 Sep 09, 2011    More programming | permalink to this entry | comments ]

Wed, 31 Aug 2011

Read Excel XLS spreadsheets with Python

Someone mailed out information to a club I'm in as an .XLS file. Another Excel spreadsheet. Sigh.

I do know one way to read them. Fire up OpenOffice, listen to my CPU fan spin as I wait forever for the app to start up, open the xls file, then click in one cell after another as I deal with the fact that spreadsheet programs only show you a tiny part of the text in each cell. I'm not against spreadsheets per se -- they're great for calculating tables of interconnected numbers -- but they're a terrible way to read tabular data.

Over the years, lots of open-source programs like word2x and catdoc have sprung up to read the text in MS Word .doc files. Surely by now there must be something like that for XLS files?

Well, I didn't find any ready-made programs, but I found something better: Python's xlrd module, as well as a nice clear example at ScienceOSS of how to Read Excel files from Python.

Following that example, in six lines I had a simple program to print the spreadsheet's contents:

import xlrd

for filename in sys.argv[1:] :
    wb = xlrd.open_workbook(filename)
    for sheetname in wb.sheet_names() :
        sh = wb.sheet_by_name(sheetname)
        for rownum in range(sh.nrows) :
            print sh.row_values(rownum)

Of course, having gotten that far, I wanted better formatting so I could compare the values in the spreadsheet. Didn't take long to write, and the whole thing still came out under 40 lines: xlsrd. And I was able to read that XLS file that was mailed to the club, easily and without hassle.

I'm forever amazed at all the wonderful, easy-to-use modules there are for Python.

Tags: ,
[ 10:58 Aug 31, 2011    More programming | permalink to this entry | comments ]

Sun, 28 Aug 2011

Teaching programming with "program a person"

A few weeks ago, at the annual GetSET engineering summer camp for high school girls, I taught my usual one-day workshop on beginning programming in Javascript.

The big question every year is always how to make the class more interactive. The girls who come to GetSET are great -- smart and motivated -- but after six hours of lectures and working through exercises, anyone, of any age, is going to glaze over. Especially when it's their first introduction to programming and they only have a day to learn it. People learn better when they're asking questions, thinking and solving problems, not just listening or following instructions.

For years I've heard vague references to "programming a person" as an exercise for teaching the basic idea of programming. The idea is to get the students to come up with step-by-step instructions for someone to do something -- say, walk across the room and pick up a water bottle -- so they realize how specific you have to be. It also solves another problem: giving everyone a break from sitting still and focusing on a computer screen.

But how do you really do it? What kind of problems work best in practice? How much time should you allow? If you have a volunteer carrying out the instructions, how do you keep them from skipping steps? Surprisingly, I couldn't find anything written up to help an inexperienced would-be teacher of programming.

What I needed was a chance to try out some ideas, or watch someone with more of a clue on this sort of teaching. This year, I found opportunities for both.

First try: Toastmasters

One of the reasons I love Toastmasters, especially with a small and friendly club like Coherent Communicators, is that it offers a safe place to try new presentation techniques and get good feedback about what does and doesn't work. So I made my first try at a Toastmasters meeting a few weeks before the GetSET workshop.

I allowed 15-20 minutes for the exercise. I explained to the audience that I wanted them to get me to turn left, walk over to the easel at the side of the room, touch it, turn around, walk back to the lectern, pick up the gavel and pound it on the lectern. I would solicit a command from them, write it on the whiteboard, then carry out the command and ask for the next command.

The day's audience was a fairly even mix of techies and non. I had wondered whether the audience would be widely mixed in how specific their instructions were, but they were fairly uniform -- mostly along the lines of "Turn 90 degrees left." "Take 5 steps." "Take 2 more steps". Of course, there were a few joking suggestions from the techies, like "send an electrical impulse from your brain to your left quadriceps", that you wouldn't expect with a high school group, but mostly everyone was on the same page.

When I got near the easel, we hit "Raise your right arm". (Oops, not close enough yet.) "Um ... lean forward about a foot?" A good illustration of being specific ... just the sort of thing I was hoping for.

They got me back to the lectern, got me to pick up the gavel (I was letting them skip a few steps by this point) ... and improvised a little, getting me to knock my head rather than the lectern. That was fun, and got some laughs ... it worked well.

I had hoped to do a second run where I guided them into understanding a while loop ("while (not yet to the easel), take another step"). But seeing a yellow light from the timer, I opted for a quick explanation of how a loop would work rather than guiding the audience into it. I found out later that the timer had hit the wrong button and only given me 8 minutes rather than my requested 15-20 ... so 20 minutes actually would have been plenty of time to cover loops as well as basic instructions. Disappointing ... but I was surprised we'd gotten so much done in so little time.

Lessons learned:

Try 2: "Program a blind robot"

For the real workshop, I had help in the form of Esther Heller, an experienced girl scout leader as well as many year GetSET veteran. Esther had done exercises like this before and was willing to take the lead; I was looking forward to learning from her. We had discussed two different variants, and decided to try both of them at different times during the day.

For the first variant, we waited until mid-morning when the class was bogging down a bit and looked like they needed a break. Esther called for two volunteers: one programmer and one robot. The girl playing the robot was blindfolded with a bandanna and escorted to the door of the room, while Esther whispered the task to the other girl. The task was something like walking over to a water bottle, picking it up, walking over to another girl and handing it to her -- though the rest of us didn't know that until it was completed.

The instructions suggested by the girls were quite similar to the ones I'd heard in Toastmasters. There was lots of "Take 5 steps" ... "take two more steps", guessing at how many steps it would take to get from one place to another. No one came up with anything like a loop or conditional. I'd wondered if anyone would try remote control -- "walk" then wait until the right moment to yell "STOP!" -- but no one did.

The blindfolding worked really well. I'd worried that with a volunteer chosen to be the robot, she might skip steps she hadn't been given. But if the "robot" is blindfolded and doesn't know the task, she can't skip steps; she can only do what she's programmed to. The only problem was that a blindfolded person told to walk straight ahead does not necessarily hold to a straight line, much to the consternation of the girl playing the programmer.

There was a lot of "turn right" ... "no, not that much, turn back left again" ... "now turn JUST A LITTLE to the right" that helped stress the need for specificity -- exactly what we were after. I had wondered beforehand whether anyone would ever suggest anything like "turn right by 30 degrees", but no one, either in Toastmasters or GetSET, ever did.

The exercise was successful and everybody seemed to have fun, so it broke up the morning well. We didn't get to loops or conditionals, though. I didn't record how long we spent, but it was probably in the neighborhood of 20 minutes.

Lessons:

Try 3, in groups: "The muffin is ready"

At the end of the day, we tried Esther's favorite variant. You're watching TV, and you want to go to the kitchen, get an English muffin, toast it, put butter/jam/peanut butter/whatever on it, take it back to your seat and eat it. What are the steps?

Esther divided the girls into groups of 4-5 and passed out post-its on which to write the steps. There was some inertia getting started ... it was late in the day and everybody was tired. (That's not unique to this exercise -- it's always a challenge to come up with something that will hold the girls' interest for the last hour. It's a long day for everyone.)

Eventually they got rolling and got into it -- I saw some very long stacks of post-its from various groups. With ten minutes left to go in the session, Esther picked two volunteers from one group: one to read the instructions, one to execute them. She pointed out places where they skipped steps -- "Hey, wait, how can she get the muffins out of the cupboard without opening the cupboard first?" After a minute or two, Esther called on a new pair from a different group to continue where the first pair had left off.

As she worked through all the groups, you could see each group becoming more cognizant of steps they had skipped, and improvising them on the spot. Despite the end-of-day crankiness, you could see they were learning from the exercise.

Lessons:

So which is better? The muffin exercise was definitely more time consuming than the previous "robot" exercise, due to overhead of splitting into groups and bringing up volunteers from each group. On the other hand, I could see there was benefit in having them work in small groups, and in the touch of competition in comparing their group's answers with the ones from other groups.

It was hard to compare the two exercises directly to see which one worked better, because of end-of-day crankiness. But they both worked well -- I'm going to keep using some variant of this in future workshops, ideally with loops and conditionals added. Thanks, Esther, for your expertise ... and to the students and the rest of the volunteers for making it a successful class!

Tags: , ,
[ 16:34 Aug 28, 2011    More education | permalink to this entry | comments ]

Thu, 25 Aug 2011

Deleting email from a mail server with Python

How do you delete email from a mail server without downloading or reading it all?

Why? Maybe you got a huge load of spam and you need to delete it. Maybe you have your laptop set up to keep a copy of your mail on the server so you can get it on your desktop later ... but after a while you realize it's not worth downloading all that mail again. In my case, I use an ISP that keeps copies of all mail forwarded from one alias to another, so I periodically need to clean out the copies.

There are quite a few reasons you might want to delete mail without reading it ... so I was surprised to find that there didn't seem to be any easy way to do so.

But POP3 is a fairly simple protocol. How hard could it be to write a Python script to do what I needed?

Not hard at all, in fact. The poplib package does most of the work for you, encapsulating both the networking and the POP3 protocol. It even does SSL, so you don't have to send your password in the clear.

Once you've authenticated, you can list() messages, which gives you a status and a list of message numbers and sizes, separated by a space. Just loop through them and delete each one.

Here's a skeleton program to delete messages:

server = "mail.example.com"
port = 995
user = "myname"
passwd = "seekrit"

pop = poplib.POP3_SSL(server, port)
pop.user(user)
pop.pass_(passwd)

poplist = pop.list()
if poplist[0].startswith('+OK') :
    msglist = poplist[1]
    for msgspec in msglist :
        # msgspec is something like "3 3941", 
        # msg number and size in octets
        msgnum = int(msgspec.split(' ')[0])
        print "Deleting msg %d\r" % msgnum,
        pop.dele(msgnum)
    else :
        print "No messages for", user
else :
    print "Couldn't list messages: status", poplist[0]
pop.quit()

Of course, you might want to add more error checking, loop through a list of users, etc. Here's the full script: deletemail.

Tags: , ,
[ 17:41 Aug 25, 2011    More programming | permalink to this entry | comments ]

Fri, 19 Aug 2011

Beginning Python: Sorting lists of objects

The Beginning Python class has pretty much died down -- although there are still a couple of interested students posting really great homework solutions, I think most people have fallen behind, and it's time to wrap up the course.

So today, I didn't post a formal lesson. But I did have something to share about how I used Python's object-oriented capabilities to solve a problem I had copying new podcast files onto my MP3 player. I used Python's built-in list sort() function, along with the easy way it lets me define operators like < and > for any object I define.

You can read all about it in my post to the Courses list describing how I sorted my list of podcast objects. Or just go straight to the final program, pods.

Tags: , ,
[ 19:48 Aug 19, 2011    More education | permalink to this entry | comments ]

Fri, 12 Aug 2011

Beginning Python, Lesson 9: More extras

Lesson 9 in my online Python course is up: Lesson 9: Extras (requested topics), including string operations, web development and GUI toolkits.

The web development and GUI toolkits are topics which were requested by students, while the string ops are things that just seemed too useful not to include.

Tags: , ,
[ 17:45 Aug 12, 2011    More education | permalink to this entry | comments ]

Fri, 05 Aug 2011

Beginning Python, Lesson 8: Extras

Lesson 8 in my online Python course is up: Lesson 8: Extras, including exception handling, optional arguments, and running system commands. A motley collection of fun and useful topics that didn't quite fit anywhere in the earlier formal lessons, but you'll find a lot of use for them in writing real-world Python scripts. In the homework, I have some examples of some of my scripts using these techniques; I'm sure the students will have lots of interesting problems of their own.

Tags: , ,
[ 14:56 Aug 05, 2011    More education | permalink to this entry | comments ]

Sat, 30 Jul 2011

Beginning Python, Lesson 7: Object-oriented programming

Lesson 7 in my online Python course is up: Lesson 7: Object-oriented programming.

This is the last formal lesson in the Beginning Python class. But I will be posting a few more "tips and tricks" lessons, little things that didn't fit in other lessons plus suggestions for useful Python packages students may want to check out as they continue their Python hacking.

Tags: , ,
[ 10:28 Jul 30, 2011    More education | permalink to this entry | comments ]

Fri, 22 Jul 2011

Beginning Python, Lesson 6: Functions and Dictionaries

Lesson 6 in my online Python course is up: Lesson 6: Functions and Dictionaries.

We're getting near the end of the course -- partly because I think students may be saturated, though I may post one more lesson. I'll post on the list and see what the students think about it.

This afternoon, though, is pretty much booked up trying to get my mother's new Nook Touch e-book reader working with Linux. Would be easy ... except that she wants to be able to check out books from her local public library, which of course uses proprietary software from Adobe and other companies to do DRM. It remains to be seen if this will be possible ... of course, I'll post the results once we know.

Tags: , ,
[ 17:49 Jul 22, 2011    More education | permalink to this entry | comments ]

Fri, 15 Jul 2011

Beginning Python, Lesson 5

Lesson 5 in my online Python course is up: Infinite loops, modulo, and random numbers.

It's a motley mix of topics, mostly because I wanted to have a fun homework project that actually did something interesting. I hope everyone enjoys it!

Tags: , ,
[ 16:44 Jul 15, 2011    More education | permalink to this entry | comments ]

Fri, 08 Jul 2011

Beginning Python, Lesson 4

Lesson 4 in my online Python course is up: Modules and command-line arguments.

This lesson is a little longer than previous lessons, but that's partly because of a couple of digressions at the beginning. Hope I didn't overdo it! The homework includes an optional debugging problem for folks who want to dive a little deeper into this stuff.

Tags: , ,
[ 20:20 Jul 08, 2011    More education | permalink to this entry | comments ]

Sun, 03 Jul 2011

Beginning Python, Lesson 3: Strings and Lists

Lesson 3 in my online Python course is up: Fun with Strings and Lists.

There may be some backlog on the mailing list -- my first attempt to post the lesson didn't show up at all, but my second try made it. Mail seems to be flowing now, but if you try to post something and it doesn't show up, let me know or tell us on irc.linuxchix.org, so we know if there's a continuing problem that needs to be fixed, not just a one-time glitch.

Meanwhile, I'm having some trouble getting new blog entries posted. Due to some network glitches, I had to migrate shallowsky.com to a different ISP, and it turns out the PyBlosxom 1.4 I'd been using doesn't work with more recent versions of Python; but none of my PyBlosxom plug-ins work in 1.5. Aren't software upgrades a joy? So I'm getting lots of practice debugging other people's Python code trying to get the plug-ins updated, and there probably won't be many blog entries until I've figured that out.

Once that's all straightened out, I should have a cool new PyTopo feature to report on, as well as some Arduino hacks I've had on the back burner for a while.

Tags: , ,
[ 11:57 Jul 03, 2011    More education | permalink to this entry | comments ]

Fri, 24 Jun 2011

Beginning Python, Lesson 2 posted

I've just posted Lesson 2 in my online Python course, covering loops, if statements, and beer! You can read it in the list archives: Lesson 2: Loops, if, and beer, or, better, subscribe to the list so you can join the discussion.

I hope everybody has fun writing loops!

Tags: , ,
[ 16:10 Jun 24, 2011    More education | permalink to this entry | comments ]

Thu, 16 Jun 2011

Beginning Programming in Python course starting

I'm about to start a new LinuxChix course: Beginning Programming in Python.

It will be held on the Linuxchix Courses mailing list: to follow the course, subscribe to the list. Lessons will be posted weekly, on Fridays, with the first lesson starting tomorrow, Friday, June 17.

This is intended a short course, probably only 4-5 weeks to start with, aimed mostly at people who are new to programming. Though of course anyone is welcome, even if you've programmed before. And experienced programmers are welcome to hang out, lurk and help answer questions. I might extended the course if people are still interested and having fun.

The course is free (just subscribe to the mailing list) and open to both women and men. Standard LinuxChix rules apply: Be polite, be helpful. And do the homework. :-)

Tags: , ,
[ 09:51 Jun 16, 2011    More education | permalink to this entry | comments ]

Mon, 30 May 2011

Command-line Arduino development

I've been doing more Arduino development lately. But I don't use the Arduino Java development environment -- programming is so much easier when you have a real editor, like emacs or vim, and key bindings to speed everything up.

I've found very little documentation on how to do command-line Arduino development, and most of the Makefiles out there are old and no longer work. So I've written up a tutorial. It ended up too long for a blog post, so I've made it a separate article:

Command-line Arduino development.

Tags: , , ,
[ 14:45 May 30, 2011    More programming | permalink to this entry | comments ]

Fri, 20 May 2011

Packaging Python for MeeGo (or other RPM-based distros)

Writing Python scripts for MeeGo is easy. But how do you package a Python script in an RPM other MeeGo users can install?

It turned out to be far easier than I expected. Python and Ubuntu had all the tools I needed.

First you'll need a .desktop file describing your app, if you don't already have one. This gives window managers the information they need to show your icon and application name so the user can run it. Here's the one I wrote for PyTopo: pytopo.desktop.

Of course, you'll also want a desktop icon. Most other applications on MeeGo seemed to use 48x48 pixel PNG images, so that's what I made, though it seems to be quite flexible -- an SVG is ideal.

With your script, desktop file and an icon, you're ready to create a package.

Create a setup.py file describing your package, as in the distutils simple example or the more detailed distutils setup script page. For a sample standalone script with a desktop file and icon, you can take a look at my PyTopo setup.py.

Starting from the Python setup script, Python's distutils can generate RPM or even Windows packages -- assuming you have the appropriate tools installed on your machine.

I'm on an Ubuntu (Debian-based) machine, and all the docs imply you have to be on an RPM-based distro to make an RPM. Happily, that's not true: Ubuntu has RPM tools you can install.

$ sudo apt-get install rpm

Then let Python do its thing:

$ python setup.py bdist_rpm

Python generates the spec file and everything else needed and builds a multiarch RPM that's ready to install on MeeGo. You can install it by copying it to the MeeGo device with scp dist/PyTopo-1.0-1.noarch.rpm meego@address.of.device:/tmp/. Then, as root on the device, install it with rpm -i /tmp/PyTopo-1.0-1.noarch.rpm. You're done!

To see a working example, you can browse my latest PyTopo source (only what's in SVN; it needs a few more tweaks before it's ready for a formal release). Or try the RPM I made for MeeGo: PyTopo-1.0-1.noarch.rpm. I'd love to hear whether this works on other RPM-based distros.

What about Debian packages?

Curiously, making a Debian package on Debian/Ubuntu is much less straightforward even if you're starting on a Debian/Ubuntu machine. Distutils can't do it on its own. There's a Debian Python package recipe, but it begins with a caution that you shouldn't use it for a package you want to submit. For that, you probably have to wade through the Complete Ubuntu Packaging Guide. Clearly, that will need a separate article.

Tags: , , , ,
[ 18:44 May 20, 2011    More programming | permalink to this entry | comments ]

Fri, 13 May 2011

Children of the Code -- Derived Python projects

I got some fun email today -- two different people letting me know about new projects derived from my Python code.

One is M-Poker, originally based on a PyQt tutorial I wrote for Linux Planet. Ville Jyrkkä has taken that sketch and turned it into a real poker program. And it uses PySide now -- the new replacement for PyQt, and one I need to start using for MeeGo development. So I'll be taking a look at M-Poker myself and maybe learning things from it. There are some screenshots on the blog A Hacker's Life in Finland.

The other project is xkemu, a Python module for faking keypresses, grown out of pykey, a Python version of my Crikey keypress generation program. xkemu-server.py looks like a neat project -- you can run it and send it commands to generate key presses, rather than just running a script each time.

(Sniff) My children are going out into the world and joining other projects. I feel so proud. :-)

Tags: ,
[ 21:04 May 13, 2011    More programming | permalink to this entry | comments ]

Mon, 09 May 2011

Firefox 4: Fixing middlemouse.content load, and hacking jars

Mostly the transition to Firefox4 has pretty smooth. But there's been one big hassle: middlemouse content load URL doesn't work as well as it used to.

Middlemouse content load is a great Firefox feature on Linux and other Unix platforms. You see a URL somewhere that doesn't have clickable URLs -- say, a plaintext mail message, or something somebody typed in IRC. You highlight it with the mouse -- no need to Copy explicitly -- X does that automatically whenever you highlight text). Then move to the Firefox window and click the middle mouse button somewhere in the content window -- anywhere as long as it's not over a link or text input -- and Firefox goes straight to the URL you pasted.

A few silly Linux distros, like Ubuntu, disable this feature by default. You can turn it back on by going to about:config, searching for middlemouse, and setting middlemouse.contentLoadURL to true.

Except, in Firefox 4, much of the time nothing happens. In Firefox 4, contentLoadURL only works if the URL you pasted is a complete URL, like http://example.com. This is completely silly, because most of the time, if you had a complete URL, it would have been clickable already in whatever window you originally found it in. When you need contentLoadURL is when someone types "Go to example.com if you want to see this". It's also great for when you get those horrible Facebook or Stumbleupon or Google URLs like http://www.facebook.com/l/bfd4f/example.com/somelink and you want just the real link (example.com/somelink), without the added cruft.

Hacking the jar

Hooray! It turns out the offending code is in browser.js, so it's hackable without needing to recompile all of Firefox. You just need to unpack omni.jar, patch browser.js, then zip up a new omni.jar.

In other words, something like this (on Ubuntu Natty, starting in an empty directory):

$ cp /usr/lib/firefox-4.0/omni.jar ~/omni-jar.sav
$ unzip /usr/lib/firefox-4.0/omni.jar
  [ edit or patch chrome/browser/content/browser/browser.js ]
$ rm -f /tmp/new-omni.jar; zip /tmp/new-omni.jar `find .`
$ sudo cp /tmp/new-omni.jar /usr/lib/firefox-4.0/omni.jar

Getting Firefox to notice code changes

Except, as I was testing this, I discovered: I could make changes and most of the time Firefox wouldn't see them. I would put in something obvious like alert("Hello, world");, verify that the alert was really in omni.jar, run Firefox, click the middle mouse button and -- no alert. Where was Firefox getting the code it was actually running, if not from omni.jar?

I'll spare you the agonizing details of the hunt and just say that eventually I discovered that if I ran Firefox from a different profile on the same machine, I got a different result. It turns out that if you remove either of two files, extensions.sqlite and XUL.mfasl, Firefox4 will re-read the new code in omni.jar.

Removing XUL.mfasl seems to be a little safer: extensions.sqlite contains some details of which extensions are enabled. Of course, back up both files first before experimenting with removing them.

Why these files are keeping a cache of code that's already in omni.jar is anybody's guess.

The Patch: fix contentLoadURL

Oh, and the change? Mikachu came up with a cleaner fix than mine, so this is his. It accepts partial URLs like example.com and also bookmarklet keywords:

--- browser.js.sav      2011-05-07 16:40:03.672540242 -0700
+++ omni/chrome/browser/content/browser/browser.js      2011-05-08 16:29:28.943313984 -0700
@@ -10597,12 +10597,10 @@
   clipboard.replace(/\s*\n\s*/g, "");
 
   let url = getShortcutOrURI(clipboard);
-  try {
-    makeURI(url);
-  } catch (ex) {
-    // Not a valid URI.
-    return;
-  }
+  var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
+                           .getService(Components.interfaces.nsIURIFixup);
+  url = URIFixup.createFixupURI(url, 1).spec;
+  // 1 is FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
 
   try {
     addToUrlbarHistory(url);

Tags: , , ,
[ 20:28 May 09, 2011    More tech/web | permalink to this entry | comments ]

Sat, 07 May 2011

Overview of MeeGo Development Tools

Over the past week I've been playing with the MeeGo ExoPC tablet, experimenting with the various options for building programs.

One advantage MeeGo has over Android or iOS is that there are quite a lot of language and toolkit options. If you have an existing program, especially if it runs on Linux, you can probably find a way to port it to MeeGo fairly without much extra coding.

But since MeeGo is still quite new, not all of the options work quite as well as you might hope -- yet. I'm sure they'll get better, but here's what the development climate looks like now.

C++, Qt and Meego SDK

C++ and Qt are the primary supported environment on MeeGo, via the MeeGo SDK, run in an IDE called QtCreator.

The documentation turns out to be somewhat incomplete; I'll write up a howto as a separate article. But once you get it installed (much easier than installing, say, Eclipse for Android), it runs nicely. You can use normal Qt, not a specialized environment, so existing Qt programs should be quick to port, and the IDE takes care of packaging, copying the app to your remote device and starting it running. Very painless.

Testing apps locally isn't so easy. They're set up to use QEMU, which only works if your development machine has hardware virtualization. Supposedly there's a way to make this work in virtualbox (which doesn't require hardware virtualization), but the docs don't address how.

Still, testing on the target device is easy. Unfortunately, not many of my existing programs are C++ and Qt. I mostly write Python, C, and Javascript/HTML web apps.

Update: I should have mentioned that QtCreator also lets you program in QML, an XML-like language that lets you design Qt user interface and even write application code.

Web Apps under C++ and QWebView

So what about those web apps? Since the C++ SDK was so well supported, I figured, why not use a QWebView so I can run my HTML and Javascript code?

Unfortunately QWebView turns out to be tricky to use, has very few sample apps available, and so far I've been unable to get it to work under MeeGo at all.

Nokia Web RunTime

And anyway, for pure web apps, there's a kit explicitly for that: WRT, the Nokia Web RunTime. But it's pretty heavyweight: the official download and documentation is all based around Eclipse (for HTML/Javascript apps? Seriously?) and a required set of Nokia libraries that I had trouble installing.

But WRT apps are packaged according to the W3C Widget Packaging and Configuration specification -- so it's probably possible to roll your own without the big Nokia WRT package. More research required.

Python, Qt and PySide

I have a lot of Python apps, so I was very happy to see that Python comes already installed on MeeGo. (Perl is also supported; Ruby theoretically is but it's not installed by default.)

Since Qt is the officially blessed user interface, Python-Qt seems like the way to go. Except -- Python in MeeGo has no Qt bindings installed. The package that provides them is called PySide, but depending on where you look you'll either be steered toward a lengthy source compile on the MeeGo device, or a zypper install from someone's personal repository.

None of that is difficult for developers, but you can't expect users to enable experimental repositories or compile from source. Is PySide going to be a standard part of MeeGo by the time it ships to users? Nobody's saying. It makes me leery of putting much energy into Python-Qt development until I hear more.

Python-GTK

A little frustrated with the options so far, I was idly poking around an interactive Python session and typed

>>> import gtk

And it worked! Even though Qt is the supported UI toolkit and nobody talks about GTK, it's installed on MeeGo -- complete with Python bindings. It gave two deprecation warnings, but what's a little deprecation among friends?

A simple test program ran just fine. So I whipped up a starter page for PyTopo, made a .desktop file and icon for it, copied the three files over with scp -- and everything worked great.

There's no standard way yet to make a MeeGo RPM from a Python program, so that will require a little hand-fiddling, but only a little. And sadly, python-webkit-gtk isn't included, so this isn't a good solution for porting web apps.

I'll be keeping an eye on PySide and continuing to experiment with PySide, WRT and C++. But in the meantime, it's great that it's so easy to port all my existing Python-GTK programs to MeeGo.

Tags: ,
[ 11:33 May 07, 2011    More programming | permalink to this entry | comments ]

Wed, 13 Apr 2011

GIMP: Build-it

Are you a GIMP user or Summer of Code student who's been wanting to get involved, but having trouble building, or a bit intimidated by the build process?

I'll be running a session on IRC to help anyone build GIMP on Linux, as part of the OpenHatch "Build it" project.

The session will take place on #gimp on irc.gimp.org (also known as GimpNet), on Fri, Apr 15, 0300 UTC -- that's Thursday night in the Americas. To convert to your time zone, run this command on your local machine:

$ date -d 'Fri Apr 15 03:00 UTC'
Thu Apr 14 20:00:00 PDT 2011
Or try this link: world time server.

This is a time that's usually fairly quiet on #gimp -- European users don't fret, since it's pretty easy to get help there during more Europe-friendly time zones. I'll hang around for at least two hours; that should be plenty of time to build GIMP and all its prerequisites.

For folks new to IRC, note that irc.gimp.org is its own server -- this is not the #gimp channel on Freenode. You can learn more about IRC on the LinuxChix IRC for Beginners page, or, if you have trouble getting an IRC client configured, try this link for mibbit web chat.

Note: The #gimp IRC channel was recently under attack by trolls, and it's possible that it may not be usable at the time of the session. In that case, I will update this blog page with the name of an alternate channel to use, and any other necessarily details.

Preparation

If you want to get your system set up ahead of time, I've put the instructions needed to build on Ubuntu Lucid and other older Linux distros here: Gimp Building Tips (for Linux). I might be able to offer a little help with building on Macs, but no guarantees.

Mac and Windows users, or people running a very old Linux distro (more than a year old) might want to consider an alternate approach: install Virtualbox or VMware and install Ubuntu "Natty Narwhal" (currently still in beta) in a virtual machine.

Of course, this isn't the only time you can get help with building GIMP. There are folks around on #gimp most of the time who are happy to help with problems. But if you've been meaning to get started and want a good excuse, or you've been holding off on asking for help ... come hang out with us and try it!

Tags: , ,
[ 12:50 Apr 13, 2011    More gimp | permalink to this entry | comments ]

Mon, 11 Apr 2011

Plug Computer + Arduino = Proximity Camera

In the last article, I wrote about how to get a very simple webcam demo running on a plug computer, also known as a Sheevaplug.

Okay, a webcam is sorta cool, but it's still a pretty easy thing to do from any laptop. I wanted to demonstrate some lower-level hardware control.

As I mentioned in the previous article, trying to run hardware directly from a plug computer is an exercise in frustration. So what do you do when you want to drive low-level hardware? Use an Arduino, of course!

Add the Arduino

[light sensor and Arduino] Happily, the sheeva.with-linux kernels include the FTDI driver you need to talk to an Arduino. So you can plug the Arduino to the plug computer, then let the Arduino read the sensor and write its value to the serial port, which you can read on the plug.

First I tried a simple light sensor from Adafruit, using the circuit from the LadyAda photocell tutorial.

I wrote a very simple Arduino sketch to read the analog output: lightsensor.pde. I'm allergic to Java IDEs, so I compiled the sketch from the commandline using this lightsensor Arduino Makefile. Edit the Makefile to point to wherever you've installed the Arduino software.

Now, on the plug, I needed a Python script to read the numbers coming in on the serial line. I ran apt-get install python-serial, then wrote this script: readsensor.py.

The script loops, reading the sensor and writing the output to an HTML file called arduino.html. Visit that in a browser from your desktop or laptop, and watch it reload and change the number as you wave your hand or a flashlight over the photocell.

Ultrasonic rangefinder for proximity detection

Pretty cool ... if you're extremely geeky and have no life. Otherwise, it's maybe a bit limited. But can we use this Arduino technique to do something useful in combination with the webcam exercise? How about an ultrasonic sonar rangefinder?

The rangefinder comes with a little PC board, and you have to solder wires to it. I wanted to be able to plug and unplug -- the rangefinger also has digital outputs and I may want to experiment with those some day. So I soldered an 8-pin header to the board. (The rangefinder board only has 7 holes, so I had to cut off the 8th pin on the header.)

I ran power and ground wires to 5v and Gnd on the Arduino, and a wire from the rangefinder's analog out to the Arduino's Analog In 2. A little heatshrink keeps the three wires together.

Then I rubber-banded the rangefinder to the front of the webcam, and I was ready to test.

[rangefinder and Arduino] [camera with rangefinder]

Use a sketch almost identical to the one for the light sensor: rangefinder.pde, and its rangefinder Arduino Makefile. I used pin 2 so I could leave the light sensor plugged in on Pin 1.

Now I ran that same readsensor.py script, paying attention to the numbers being printed out. I found that they generally read around 35-40 when I was sitting right in front of it (camera mounted on my laptop screen), and more like 150-250 when I got out of the way and pointed it across the room.

So I wrote a script, proximity.py, that basically does this:

  if data < 45 :
    if verbose :
      print "Snapping photo!"
    os.system("fswebcam --device /dev/video0 -S 1 output.jpg")
It also rewrites the HTML file to display the value it read from the rangefinder, though that part isn't so important.

Put it all together, and the proximity-sensitive camera snaps a photo any time something is right in front of it; otherwise, it keeps displaying the last photo and doesn't snap a new one. Sample uses: find out who's using your computer when you're away at lunch, or run a security camera at home, or set up a camera to snap shots of the exotic wildlife that's visiting your feeder or research station.

You could substitute an infra-red motion sensor and use it as a motion-operated security camera or bird feeder camera. I ordered one, but got bogged down trying to reverse-engineer the sensor (I should have just ordered one from Adafruit or Sparkfun).

I'm happy to say this all worked pretty well as a demo. But mostly, it's fun to know that I can plug in virtually any sensor and collect any sort of data I want. Adding the Arduino makes the plug computer much more fun and useful.

Tags: , , ,
[ 21:23 Apr 11, 2011    More tech | permalink to this entry | comments ]

Sun, 10 Apr 2011

A simple plug computer webcam

I was asked to give a talk on plug computers ("sheevaplugs") at a local LUG. I thought at first I didn't have much to say about them, but after writing down an outline I realized that wouldn't be a problem.

But plugs aren't that interesting unless you have something fun to demonstrate. Sure, plugs can run a web server or a file server, but that doesn't make for a very fun demo -- "woo, look, I'm loading a web page!" What's more fun? Hardware.

The first step to running any hardware off a plug computer is to get an upgraded kernel. The kernels that come with these things can't drive any useful external gizmos.

I've often lamented how the folks who build plug computers seem oblivious to the fact that a large part of their potential customer base wants to drive hardware -- temperature and light sensors, weather stations, garage door openers, servos, whatever. By not including drivers for GPIO, 1-wire, video and so forth, they're shutting out anyone who doesn't feel up to building a kernel.

And make no mistake: building a kernel for a sheevaplug is quite a bit harder than building one for your laptop or workstation. Some of the hardware isn't supported by fully open source drivers, and most Linux distros don't offer a cross-compiler that can do the job. I covered some of the issues in my LinuxPlanet article on Cross-compiling Custom Kernels for Plug Computers.

Fortunately, the sheeva.with-linux kernels include a webcam driver. That seemed like a good start for a demo.

A simple webcam demo

My demo plug is running Debian Squeeze, which has a wealth of webcam software available. Although there are lots of packages to stream live video to a web server, they all have a lot of requirements, so I settled for a simple snapshot program, fswebcam.

The command I needed to snap a photo is:

fswebcam --device /dev/video0 -S 1 output.jpeg
The -S 1 skips a frame to account for the fact that my cheap and crappy webcam (a Gearhead Sound FX) tends to return wildly striped green and purple images otherwise.

So I run that in a loop, something like:

while /bin/true; do
  fswebcam --device /dev/video0 -S 1 output.jpeg
  sleep 2
done

Now that I have a continuously updating image, I need to run some sort of web server on the plug. Plugs are perfectly capable of running apache or lighttpd or whatever server you favor. But for this simple demo, I used a tiny Python server script: simpleserver.py.

Then all I have to do is a simple web page that includes <img src="output.jpg"> and point my computer at http://192.168.1.102:8080 to see the image. Either refresh the page to see the image update, or add something like

<meta http-equiv="Refresh" content='2'>
to make it refresh

The next parts of the demo added an Arduino to the mix. But this is already getting long and I'm out of time ... so the second part of this demo will follow in a day or two.

Tags: , ,
[ 22:18 Apr 10, 2011    More tech | permalink to this entry | comments ]

Fri, 18 Mar 2011

Finding Twitter references to you

Twitter is a bit frustrating when you try to have conversations there. You say something, then an hour later, someone replies to you (by making a tweet that includes your Twitter @handle). If you're away from your computer, or don't happen to be watching it with an eagle eye right then -- that's it, you'll never see it again. Some Twitter programs alert you to @ references even if they're old, but many programs don't.

Wouldn't it be nice if you could be notified regularly if anyone replied to your tweets, or mentioned you?

Happily, you can. The Twitter API is fairly simple; I wrote a Python function a while back to do searches in my Twitter app "twit", based on a code snippet I originally cribbed from Gwibber. But if you take out all the user interface code from twit and use just the simple JSON code, you get a nice short app. The full script is here: twitref, but the essence of it is this:

import sys, simplejson, urllib, urllib2

def get_search_data(query):
    s = simplejson.loads(urllib2.urlopen(
            urllib2.Request("http://search.twitter.com/search.json",
                            urllib.urlencode({"q": query}))).read())
    return s

def json_search(query):
    for data in get_search_data(query)["results"]:
        yield data

if __name__ == "__main__" :
    for searchterm in sys.argv[1:] :
        print "**** Tweets containing", searchterm
        statuses = json_search(searchterm)
        for st in statuses :
            print st['created_at']
            print "<%s> %s" % (st['from_user'], st['text'])
            print ""

You can run twitref @yourname from the commandline now and then. You can even call it as a cron job and mail yourself the output, if you want to make sure you see replies. Of course, you can use it to search for other patterns too, like twitref #vss or twitref #scale9x.

You'll need the simplejson Python library, which most distros offer as a package; on Ubuntu, install python-simplejson.

It's unclear how long any of this will continue to be supported, since Twitter recently announced that they disapprove of third-party apps using their API. Oh, well ... if Twitter stops allowing outside apps, I'm not sure how interested I'll be in continuing to use it.

On the other hand, their original announcement on Google Groups seems to have been removed -- I was going to link to it here and discovered it was no longer there. So maybe Twitter is listening to the outcry and re-thinking their position.

Tags: , , ,
[ 10:53 Mar 18, 2011    More programming | permalink to this entry | comments ]

Thu, 10 Mar 2011

On Linux Planet: Plotting mail logs with CairoPlot

[Pie chart showing origins of spam]

My latest LinuxPlanet article is on plotting pretty graphs from Python with CairoPlot.

Of course, to demonstrate a graphing package I needed some data. So I decided to plot some stats parsed from my Postfix mail log file. We bounce a lot of mail (mostly spam but some false positives from mis-configured email servers) that comes in with bogus HELO addresses. So I thought I'd take a graphical look at the geographical sources of those messages.

The majority were from IPs that weren't identifiable at all -- no reverse DNS info. But after that, the vast majority turned out to be, surprisingly, from .il (Israel) and .br (Brazil).

Surprised me! What fun to get useful and interesting data when I thought I was just looking for samples for an article.

Tags: , , ,
[ 15:08 Mar 10, 2011    More programming | permalink to this entry | comments ]

Tue, 22 Feb 2011

Python for (Cartalk) Puzzlers

Last week's Car Talk had a fun puzzler called "Three Pieces of Paper":

Three different numbers are chosen at random, and one is written on each of three slips of paper. The slips are then placed face down on the table. The objective is to choose the slip upon which is written the largest number.

Here are the rules: You can turn over any slip of paper and look at the amount written on it. If for any reason you think this is the largest, you're done; you keep it. Otherwise you discard it and turn over a second slip. Again, if you think this is the one with the biggest number, you keep that one and the game is over. If you don't, you discard that one too.

What are the odds of winning? The obvious answer is one in three, but you can do better than that. After thinking about it a little I figured out the strategy pretty quickly (I won't spoil it here; follow the link above to see the answer). But the question was: how often does the correct strategy give you the answer?

It made for a good "things to think about when trying to fall asleep" insomnia game. And I mostly convinced myself that the answer was 50%. But probability problems are tricky beasts (witness the Monty Hall Problem, which even professional mathematicians got wrong) and I wasn't confident about it. Even after hearing Click and Clack describe the answer on this week's show, asserting that the answer was 50%, I still wanted to prove it to myself.

Why not write a simple program? That way I could run lots of trials and see if the strategy wins 50% of the time.

So here's my silly Python program:

#! /usr/bin/env python

# Cartalk puzzler Feb 2011

import random, time

random.seed()

tot = 0
wins = 0

while True:
    # pick 3 numbers:
    n1 = random.randint(0, 100)
    n2 = random.randint(0, 100)
    n3 = random.randint(0, 100)

    # Always look at but discard the first number.
    # If the second number is greater than the first, stick with it;
    # otherwise choose the third number.
    if n2 > n1 :
        final = n2
    else :
        final = n3

    biggest = max(n1, n2, n3)
    win = (final == biggest)
    tot += 1
    if win :
        wins += 1
    print "%4d %4d %4d %10d %10s %6d/%-6d = %10d%%" % (n1, n2, n3, final,
                                                       str(win),
                                                       wins, tot,
                                                       int(wins*100/tot))
    if tot % 1000 == 0:
        print "(%d ...)" % tot
        time.sleep(1)

It chooses numbers between 0 and 100, for no particular reason; I could randomize that, but it wouldn't matter to the result. I made it print out all the outcomes, but pause for a second after every thousand trials ... otherwise the text scrolls too fast to read.

And indeed, the answer converges very rapidly to 50%. Hurray!

After I wrote the script, I checked Car Talk's website. They have a good breakdown of all the possible outcomes and how they map to a probability. Of course, I could have checked that first, before writing the program. But I was thinking about this in the car while driving home, with no access to the web ... and besides, isn't it always more fun to prove something to yourself than to take someone else's word for it?

Tags: , , ,
[ 21:17 Feb 22, 2011    More programming | permalink to this entry | comments ]

Fri, 18 Feb 2011

New GIMP Arrow Designer

[arrow] While writing a blog post on GIMP's confusing Auto button (to be posted soon), I needed some arrows, and discovered a bug in my Arrow Designer script when making arrows that are mostly vertical.

So I fixed it. You can get the new Arrow Designer 0.5 on my GIMP Arrow Designer page.

It's purely a coincidence that I discovered this a week before SCALE, where I'll be speaking on Writing GIMP Scripts and Plug-Ins. Arrow Designer is one of my showpieces for making interactive plug-ins with GIMP-Python, so I'm glad I noticed the bug when I did.

Tags: , ,
[ 21:28 Feb 18, 2011    More gimp | permalink to this entry | comments ]

Mon, 31 Jan 2011

Feedme 0.7

[FeedMe, Seymour!] I've been enjoying my Android tablet e-reader for a couple of months now ... and it's made me realize some of the shortcomings in FeedMe. So of course I've been making changes along the way -- quite a few of them, from handling multiple output file types (html, plucker, ePub or FictionBook) to smarter handling of start, end and skip patterns to a different format of the output directory.

It's been fairly solid for a few weeks now, so it's time to release ... FeedMe 0.7.

Tags: , , ,
[ 22:32 Jan 31, 2011    More programming | permalink to this entry | comments ]

Tue, 25 Jan 2011

Getting rid of extra whitespace from Eclipse

Eclipse has been driving me batty with all the extra spaces it adds everywhere -- blank lines all have indents on them, and lots of code lines have extra spaces randomly tacked on to the end. I sure wouldn't want to share files like that with coworkers or post them as open source.

I found lots of suggestions on the web for eliminating extra whitespace, and several places to configure this within Eclipse, but most of them don't do anything. Here's the one that actually worked:

Window->Preferences
Jave->Editor->Save Actions
Enable Perform the selected actions on save.
Enable Additional actions.
Click Configure.
In the Code Organizing tab., enable Remove trailing whitespace for All lines.
Review all the other options there, since it will all happen automatically whenever you save -- make sure there isn't anything there you don't want.
Dismiss the Configure window.
Review the other options under Save Actions, since these will also happen automatically now.
Don't forget to click Apply in the Save Actions preference page.

Whew! There are other places to set this, in various Code style and Cleanup options, but all all the others require taking some action periodically, like Source->Clean up...

By the way, while you're changing whitespace preferences, you may also want the Insert spaces for tabs preference under General->Editors->Text Editors.

An easy way to check whether you've succeeded in exorcising the spaces -- eclipse doesn't show them all, even when you tell it to -- is to :set hlsearch in vim, then search for a space. (Here are some other ways to show spaces in vim.) In emacs, you can M-x set-variable show-trailing-whitespace to true, but that doesn't show spaces on blank lines; for that you might want whitespace.el or similar packages.

Tags: , ,
[ 15:42 Jan 25, 2011    More programming | permalink to this entry | comments ]

Tue, 18 Jan 2011

X Terminal Colors (and dark and light backgrounds)

[Displaying colors in an xterm] At work, I'm testing some web programming on a server where we use a shared account -- everybody logs in as the same user. That wouldn't be a problem, except nearly all Linuxes are set up to use colors in programs like ls and vim that are only readable against a dark background. I prefer a light background (not white) for my terminal windows.

How, then, can I set things up so that both dark- and light-backgrounded people can use the account? I could set up a script that would set up a different set of aliases and configuration files, like when I changed my vim colors. Better, I could fix all of them at once by changing my terminal's idea of colors -- so when the remote machine thinks it's feeding me a light color, I see a dark one.

I use xterm, which has an easy way of setting colors: it has a list of 16 colors defined in X resources. So I can change them in ~/.Xdefaults.

That's all very well. But first I needed a way of seeing the existing colors, so I knew what needed changing, and of testing my changes.

Script to show all terminal colors

I thought I remembered once seeing a program to display terminal colors, but now that I needed one, I couldn't find it. Surely it should be trivial to write. Just find the escape sequences and write a script to substitute 0 through 15, right?

Except finding the escape sequences turned out to be harder than I expected. Sure, I found them -- lots of them, pages that conflicted with each other, most giving sequences that didn't do anything visible in my xterm.

Eventually I used script to capture output from a vim session to see what it used. It used <ESC>[38;5;Nm to set color N, and <ESC>[m to reset to the default color. This more or less agreed Wikipedia's ANSI escape code page, which says <ESC>[38;5; does "Set xterm-256 text coloor" with a note "Dubious - discuss". The discussion says this isn't very standard. That page also mentions the simpler sequence <ESC>[0;Nm to set the first 8 colors.

Okay, so why not write a script that shows both? Like this:

#! /usr/bin/env python

# Display the colors available in a terminal.

print "16-color mode:"
for color in range(0, 16) :
    for i in range(0, 3) :
        print "\033[0;%sm%02s\033[m" % (str(color + 30), str(color)),
    print

# Programs like ls and vim use the first 16 colors of the 256-color palette.
print "256-color mode:"
for color in range(0, 256) :
    for i in range(0, 3) :
        print "\033[38;5;%sm%03s\033[m" % (str(color), str(color)),
    print

Voilà! That shows the 8 colors I needed to see what vim and ls were doing, plus a lovely rainbow of other possible colors in case I ever want to do any serious ASCII graphics in my terminal.

Changing the X resources

The next step was to change the X resources. I started by looking for where the current resources were set, and found them in /etc/X11/app-defaults/XTerm-color:

$ grep color /etc/X11/app-defaults/XTerm-color
irrelevant stuff snipped
*VT100*color0: black
*VT100*color1: red3
*VT100*color2: green3
*VT100*color3: yellow3
*VT100*color4: blue2
*VT100*color5: magenta3
*VT100*color6: cyan3
*VT100*color7: gray90
*VT100*color8: gray50
*VT100*color9: red
*VT100*color10: green
*VT100*color11: yellow
*VT100*color12: rgb:5c/5c/ff
*VT100*color13: magenta
*VT100*color14: cyan
*VT100*color15: white
! Disclaimer: there are no standard colors used in terminal emulation.
! The choice for color4 and color12 is a tradeoff between contrast, depending
! on whether they are used for text or backgrounds.  Note that either color4 or
! color12 would be used for text, while only color4 would be used for a
! Originally color4/color12 were set to the names blue3/blue
!*VT100*color4: blue3
!*VT100*color12: blue
!*VT100*color4: DodgerBlue1
!*VT100*color12: SteelBlue1

So all I needed to do was take the ones that don't show up well -- yellow, green and so forth -- and change them to colors that work better, choosing from the color names in /etc/X11/rgb.txt or my own RGB values. So I added lines like this to my ~/.Xdefaults:

!! color2 was green3
*VT100*color2: green4
!! color8 was gray50
*VT100*color8: gray30
!! color10 was green
*VT100*color10: rgb:00/aa/00
!! color11 was yellow
*VT100*color11: dark orange
!! color14 was cyan
*VT100*color14: dark cyan
... and so on.

Now I can share accounts, and I no longer have to curse at those default ls and vim settings!

Update: Tip from Mikachu: ctlseqs.txt is an excellent reference on terminal control sequences.


Tags: , , , , ,
[ 10:56 Jan 18, 2011    More linux | permalink to this entry | comments ]

Tue, 04 Jan 2011

Fontasia v 0.5

[Fontasia, a font viewer and categorizer] I had a nice relaxing holiday season. A little too relaxing -- I didn't get much hacking done, and spent more time fighting with things that didn't work than making progress fixing things.

But I did spend quite a bit of time with my laptop, currently running Arch Linux, trying to get the fonts to work as well as they do in Ubuntu. I don't have a definite solution yet to my Arch font issues, but all the fiddling with fonts did lead me to realize that I needed an easier way to preview specific fonts in bold.

So I added Bold and Italic buttons to fontasia, and called it Fontasia 0.5. I'm finding it quite handy for previewing all my fixed-width fonts while trying to find one emacs can display.

Tags: , ,
[ 23:00 Jan 04, 2011    More programming | permalink to this entry | comments ]

Tue, 21 Dec 2010

Developing for Android

I wrote yesterday about my quest for an app for reading news feeds and other timely information from the web. And how existing ebook readers didn't meet that need. That meant I would have to write something.

Android development is done in Java, using Eclipse as an IDE. Let me just state up front that (a) I dislike Java (and have forgotten most of what I once knew about it) and (b) I hate IDEs -- they make you use their crippled editor instead of your own, they control what you can put where on the screen, and they're always popping up windows that get in your way.

Okay, not an auspicious beginning. But let's try to be open-minded, follow the instructions and see what happens.

I installed Eclipse from eclipse.org after being advised that the version on Ubuntu is out of date. Then I installed all the various Android plug-ins and SDKs and set them up (there is no single page that lists all the steps, so I did a lot of googling). It took maybe an hour or so to get it all installed to the point where it could produce its "Hello world".

And then ... wow! Hello world worked right off the bat, and the emulator worked for basic testing. Hmm, okay ... how about if we use HTML as a format ... is there a standard HTML display component? Sure enough -- I added a WebView to my app and right away I had a working HTML reader. Okay, how about a row of buttons and a status bar on top? Sure, no problem.

The standard Android online docs aren't great -- they're a wonderful example of how to write seemingly comprehensive documentation that somehow manages to tell you nothing of what you actually need to know. But that's not as bad as it sounds, because there are lots of forums and tutorials to fill in the gaps. Stack Overflow is particularly good for Android tips.

And yes, I did some swearing at Eclipse and spent too much time googling how to disable features, like the "Content Assist" that periodically freezes the whole UI for a minute or so in the middle of your typing a line of code, while it thinks about some unhelpful and irrelevant hints to offer you in a popup. Turn it off in the prefs under Java/Editor. (Eclipse's actual useful hints, like the ones you get when you hover over something that's red because of an error, will still work. I found them very helpful.)

More specifically: Java/Editor/Content Assist/Hovers, and turn off Combined Hover and maybe anything else that happens without a modifier key. You probably also want to turn off Enable Auto Activation under Java/Editor/Content Assist. And possibly others -- I kept turning things off until the popups and delays went away, and I haven't found anything explaining how all these parameters relate.

Okay, so there were snags, and it's frustrating how there are almost no open source apps for this open source OS. (Yes, my app will be.) But here's the thing: in about 4 days, starting from nothing, I had a little RSS reader that did everything I needed. I've been adding features since then. Android doesn't have a reasonable battery status monitor? Fine, my reader can show battery percentage in the status bar. Android doesn't dim the screen enough? Fine, I can dim it further inside the application (an idea borrowed from Aldiko).

After less than a week of work I have an RSS reader that's better than my Palms running Plucker ever were. And that says a lot about the ease of the Android programming environment. I'm impressed!

Update: The source, an apk, and a brief discussion of how I use my feed reader are now here: FeedViewer.

Tags: ,
[ 16:26 Dec 21, 2010    More programming | permalink to this entry | comments ]

Tue, 07 Dec 2010

Android/Eclipse Spellchecker is a bit confused

I've been doing some Android development, using the standard Eclipse development tools. A few days ago, I pasted some code that included a comment about different Android versions, and got a surprise:

[The word 'Android' is not correctly spelled. Change to 'Undried'?]

What do you think -- should I change all the "Android" references to "Undried"?

Tags: , , ,
[ 11:09 Dec 07, 2010    More humor | permalink to this entry | comments ]

Wed, 01 Dec 2010

Escaping HTML characters in Emacs (and how to do replaces in elisp)

Last week I found myself writing another article that includes code snippets in HTML.

So what, you ask? The problem is, when you're writing articles in HTML, every time you include a code snippet inside a <pre> tag you invariably forget that special characters like < > & have special meanings in HTML, and must be escaped. Every < has to change to &lt;, and so forth, after you paste the code.

In vi/vim, replacing characters is straightforward. But I usually write longer articles in emacs, for various unimportant reasons, and although emacs has global replace, it only works from wherever you are now (called "point" in emacs lingo) to the end of the file. So if you're trying to fix something you pasted in the middle of the article, you can't do it with normal emacs replace.

Surely this is a wheel that has already been re-invented a thousand times, I thought! But googling and asking emacs experts turned up nothing. Looks like I'd have to write it.

And that turned out to be more difficult than I expected, for the same reason: emacs replace-string works the same way from a program as it does interactively, and replaces from point to the end of the file, and there's no way to restrict it to a more limited range.

Several helpful people on #emacs chimed in with ideas, but most of them didn't pan out. But ggole knew a way to do it that was both clean and reliable (thanks!).

Here's the elisp function I ended up with. It uses save-excursion to put the cursor back where it started before you ran the function, narrow-to-region to make replace-string work only on the region, and save-restriction get rid of that narrow-to-region after we're done. Nice!

(defun unhtml (start end)
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region start end)
      (goto-char (point-min))
      (replace-string "&" "&amp;")
      (goto-char (point-min))
      (replace-string "<" "&lt;")
      (goto-char (point-min))
      (replace-string ">" "&gt;")
      )))

And yes, I used it just now on that elisp snippet.

Tags: , ,
[ 20:08 Dec 01, 2010    More linux/editors | permalink to this entry | comments ]

Wed, 03 Nov 2010

Garmin GPX timestamp bizarreness

My last entry mentioned some work I'd done to one of my mapping programs, Ellie, to gather statistics from the track logs I get from my Garmin GPS.

In the course of working on Ellie, I discovered something phenomenally silly about the GPX files from my Garmin Vista CX, as uploaded with gpsbabel.

Track log points, quite reasonably, have time stamps in "Zulu time" (essentially the same as GMT, give or take some fraction of a second). They look like this:

<trkpt lat="35.289519913" lon="-115.227057561">
  <ele>1441.634277</ele>
  <time>2010-10-14T17:51:35Z</time>
</trkpt>

But the waypoints you set for specific points of interest, even if they're in the same GPX file, have timestamps that have no time zone at all. They look like this:

<wpt lat="35.334813371" lon="-115.178730609">
  <ele>1489.917480</ele>
  <name>001</name>
  <cmt>14-OCT-10 11:18:51AM</cmt>
  <desc>14-OCT-10 11:18:51AM</desc>
  <sym>Flag, Blue</sym>
</wpt>

Notice the waypoint's time isn't actually in a time field -- it's duplicated in two fields, cmt (comment) and desc (description). So it's not really intended to be a time stamp -- but it sure would be handy if you could use it as one.

You might be able to correlate waypoints with track points by comparing coordinates ... unless you spent more than an hour hanging around a particular location, or came back several hours later (perhaps starting and ending your hike at the same place). In that case ... you'd better know what the local time zone was, including daylight savings time.

What a silly omission, considering that the GPS obviously already knows the Zulu time and could just as easily use that!

Tags: , ,
[ 22:09 Nov 03, 2010    More mapping | permalink to this entry | comments ]

Sat, 30 Oct 2010

New versions of mapping programs: Pytopo and Ellie

[pytopo logo] On our recent Mojave trip, as usual I spent some of the evenings reviewing maps and track logs from some of the neat places we explored.

There isn't really any existing open source program for offline mapping, something that works even when you don't have a network. So long ago, I wrote Pytopo, a little program that can take map tiles from a Windows program called Topo! (or tiles you generate yourself somehow) and let you navigate around in that map.

But in the last few years, a wonderful new source of map tiles has become available: OpenStreetMap. On my last desert trip, I whipped up some code to show OSM tiles, but a lot of the code was hacky and empirical because I couldn't find any documentation for details like the tile naming scheme.

Well, that's changed. Upon returning to civilization I discovered there's now a wonderful page explaining the Slippy map tilenames very clearly, with sample code and everything. And that was the missing piece -- from there, all the things I'd been missing in pytopo came together, and now it's a useful self-contained mapping script that can download its own tiles, and cache them so that when you lose net access, your maps don't disappear along with everything else.

Pytopo can show GPS track logs and waypoints, so you can see where you went as well as where you might want to go, and whether that road off to the right actually would have connected with where you thought you were heading.

It's all updated in svn and on the Pytopo page.

Ellie

[Ellie icon]

Most of the pytopo work came after returning from the desert, when I was able to google and find that OSM tile naming page. But while still out there and with no access to the web, I wanted to review the track logs from some of our hikes and see how much climbing we'd done. I have a simple package for plotting elevation from track logs, called Ellie. But when I ran it, I discovered that I'd never gotten around to installing the pylab Python plotting package (say that three times fast!) on this laptop.

No hope of installing the package without a net ... so instead, I tweaked Ellie so that so that without pylab you can still print out statistics like total climb. While I was at it I added total distance, time spent moving and time spent stopped. Not a big deal, but it gave me the numbers I wanted. It's available as ellie 0.3.

Tags: , ,
[ 19:24 Oct 30, 2010    More mapping | permalink to this entry | comments ]

Fri, 15 Oct 2010

Snakes on a Couch! Using Python with CouchDB

Part II of my CouchDB tutorial is out at Linux Planet. In it, I use Python and CouchDB to write a simple application that keeps track of which restaurants you've been to recently, and to suggest new places to eat where you haven't been.

Snakes on a Couch, Part 2: Where do you want to eat?

Tags: , , , ,
[ 21:00 Oct 15, 2010    More writing | permalink to this entry | comments ]

Sun, 26 Sep 2010

GIMP Wallpaper script improvements

Dave was using some old vacation photos to test filesystem performance, and that made me realize that I had beautiful photos from the same trip that I hadn't yet turned into desktop backgrounds.

Sometimes I think that my GIMP Wallpaper script is the most useful of the GIMP plug-ins I've written. It's such a simple thing ... but I bet I use it more than any of my other plug-ins, and since I normally make backgrounds for at least two resolutions (my 1680x1050 desktop and my 1366x768 laptop), it certainly saves me a lot of time and hassle.

But an hour into my background-making, I started to have nagging doubts. I wasn't renaming these images, just keeping the original filenames from the camera, like pict0828.jpg. What if if some of these were overwriting images of the same name? The one thing my script doesn't do is check for that, and gimp_file_save doesn't pop up any warnings. I've always meant to add a check for it.

Of course, once the doubts started, I had to stop generating backgrounds and start generating code. And I'm happy with the result: wallpaper-0.4.py warns and won't let you save over an old background image, but keeps all the logic in one dialog rather than popping up extra warnings.

[wallpaper.py overwrite warning dialog]

Now I can generate backgrounds without worrying that I'm stomping on earlier ones.

Tags: , ,
[ 22:25 Sep 26, 2010    More gimp | permalink to this entry | comments ]

Thu, 23 Sep 2010

Snakes on a Couch! Using Python with CouchDB

I've been learning CouchDB, the hot NoSQL database, as part of my new job. It's interesting -- a very different mindset compared to classic databases like MySQL.

There's a fairly good Python package for it, python-couchdb ... but the documentation is somewhat incomplete and there's very little else written about it, and virtually no sample code to steal.

That makes it a perfect topic for a Linux Planet tutorial! So here it is, Part 1:

Snakes on a Couch! Using Python with CouchDB.

I have a rather fun application for the database I introduce in the article, but you'll have to wait until Part 2, two weeks from now, to see the details.

Tags: , , , ,
[ 11:55 Sep 23, 2010    More writing | permalink to this entry | comments ]

Fri, 03 Sep 2010

Fontasia v 0.3

A couple of weeks ago I posted about fontasia, my new font-chooser app. [Fontasia: font viewer/categorizer It's gone through a couple of revisions since then, and Mikael Magnusson contributed several excellent improvements, like being able to render each font in the font list.

I'd been holding off on posting 0.3, hoping to have time to do something about the font buttons -- they really need to be smaller, so there's space for more categories. But between a new job and several other commitments, I haven't had time to implement that. And the fancy font list is so cool it really ought to be shared.

So here it is: fontasia 0.3.

Tags: , ,
[ 10:31 Sep 03, 2010    More programming | permalink to this entry | comments ]

Tue, 17 Aug 2010

Fontasia: View and categorize your fonts

[Fontasia: font viewer/categorizer We were talking about fonts again on IRC, and how there really isn't any decent font viewer on Linux that lets you group fonts into categories.

Any time you need to choose a font -- perhaps you know you need one that's fixed-width, script, cartoony, western-themed -- you have to go through your entire font list, clicking one by one on hundreds of fonts and saving the relevant ones somehow so you can compare them later. If you have a lot of fonts installed, it can take an hour or more to choose the right font for a project.

There's a program called fontypython that does some font categorization, but it's hard to use: it doesn't operate on your installed fonts, only on fonts you copy into a special directory. I never quite understood that; I want to categorize the fonts I can actually use on my system.

I've been wanting to write a font categorizer for a long time, but I always trip up on finding documentation on getting Python to render fonts. But this time, when I googled, I found jan bodnar's ZetCode Pango tutorial, which gave me all I needed and I was off and running.

Fontasia is initially a font viewer. It shows all your fonts in a list on the left, with a preview on the right. But it also lets you add categories: just type the category name in the box and click Add category and a button for that category will appear, with the current font added to it. A font can be in multiple categories.

Once you've categorized your fonts, a menu at the top of the window lets you show just the fonts in a particular category. So if you're working on a project that needs a Western-style font, show that category and you'll see only relevant fonts.

You can also show only the fonts you've categorized -- that way you can exclude fonts you never use -- I don't speak Tamil or Urdu so I don't really need to see those fonts when I'm choosing a font. Or you can show only the uncategorized fonts: this is useful when you add some new fonts to your system and need to go through them and categorize them.

I'm excited about fontasia. It's only a few days old and already used it several times for real-world font selection problems.

If you want to try it, it's here: Fontasia: View and categorize fonts.

Tags: , ,
[ 12:20 Aug 17, 2010    More programming | permalink to this entry | comments ]

Wed, 21 Jul 2010

Writing scripts for your Canon camera with CHDK

On Linux Planet yesterday: an article on how to write scripts for chdk, the Canon Hack Development Kit -- Part 3 in my series on CHDK.

Time-Lapse Photography with your Inexpensive Canon Camera (CHDK p. 3)

I found that CHDK scripting wasn't quite as good as I'd hoped -- some of the functions, especially the aperture and shutter setting, were quite flaky on my A540 so it really didn't work to write a bracketing script. But it's fantastic for simple tasks like time-lapse photography, or taking a series of shots like the Grass Roots Mapping folk do.

If you're at OSCON and you like scripting and photos, check out my session on Thursday afternoon at 4:30: Writing GIMP Plug-ins and Scripts, in which I'll walk through several GIMP scripts in Python and Script-Fu and show some little-known tricks you can do with Python plug-ins.

Tags: , , , , , ,
[ 10:31 Jul 21, 2010    More photo | permalink to this entry | comments ]

Sat, 10 Jul 2010

Interactive arrow design in GIMP

How many times have you wanted an easy way of making arrows in GIMP?

I need arrows all the time, for screenshots and diagrams. And there really isn't any easy way to do that in GIMP. There's a script-fu for making arrows in the Plug-in registry, but it's fiddly and always takes quite a few iterations to get it right. More often, I use a collection of arrow brushes I downloaded from somewhere -- I can't remember exactly where I got my collection, but there are lots of options if you google gimp arrow brushes -- then use the free rotate tool to rotate the arrow in the right direction.

[GIMP Arrow Designer] The topic of arrows came up again on #gimp yesterday, and Alexia Death mentioned her script-fu in GIMP Fx Foundary that "abuses the selection" to make shapes, like stars and polygons. She suggested that it would be easy to make arrows the same way, using the current selection as a guide to where the arrow should go.

And that got me thinking about Joao Bueno's neat Python plug-in demo that watches the size of the selection and updates a dialog every time the selection changes. Why not write an interactive Python script that monitors the selection and lets you change the arrow by changing the size of the selection, while fine-tuning the shape and size of the arrowhead interactively via a dialog?

Of course I had to write it. And it works great! I wish I'd written this five years ago.

This will also make a great demo for my OSCON 2010 talk on Writing GIMP Scripts and Plug-ins, Thursday July 22. I wish I'd had it for Libre Graphics Meeting last month.

It's here: GIMP Arrow Designer.

Tags: , , ,
[ 11:25 Jul 10, 2010    More gimp | permalink to this entry | comments ]

Fri, 16 Apr 2010

Tee in Python

I needed a way to send the output of a Python program to two places simultaneously: print it on-screen, and save it to a file.

Normally I'd use the Linux command tee for that: prog | tee prog.out saves a copy of the output to the file prog.out as well as printing it. That worked fine until I added something that needed to prompt the user for an answer. That doesn't work when you're piping through tee: the output gets buffered and doesn't show up when you need it to, even if you try to flush() it explicitly.

I investigated shell-based solutions: the output I need is on sterr, while Python's raw_input() user prompt uses stdout, so if I could get the shell to send stderr through tee without stdout, that would have worked. My preferred shell, tcsh, can't do this at all, but bash supposedly can. But the best examples I could find on the web, like the arcane prog 2>&1 >&3 3>&- | tee prog.out 3>&- didn't work.

I considered using /dev/tty or opening a pty, but those calls only work on Linux and Unix and the program is otherwise cross-platform.

What I really wanted was a class that acts like a standard Python file object, but when you write to it it writes to two places: the log file and stderr.

I found an example of someone trying to write a Python tee class, but it didn't work: it worked for write() but not for print >>

I am greatly indebted to KirkMcDonald of #python for finding the problem. In the Python source implementing >>, PyFile_WriteObject (line 2447) checks the object's type, and if it's subclassed from the built-in file object, it writes directly to the object's fd instead of calling write().

The solution is to use composition rather than inheritance. Don't make your file-like class inherit from file, but instead include a file object inside it. Like this:

import sys

class tee :
    def __init__(self, _fd1, _fd2) :
        self.fd1 = _fd1
        self.fd2 = _fd2

    def __del__(self) :
        if self.fd1 != sys.stdout and self.fd1 != sys.stderr :
            self.fd1.close()
        if self.fd2 != sys.stdout and self.fd2 != sys.stderr :
            self.fd2.close()

    def write(self, text) :
        self.fd1.write(text)
        self.fd2.write(text)

    def flush(self) :
        self.fd1.flush()
        self.fd2.flush()

stderrsav = sys.stderr
outputlog = open(logfilename, "w")
sys.stderr = tee(stderrsav, outputlog)

And it works! print >>sys.stderr, "Hello, world" now goes to the file as well as stderr, and raw_input still works to prompt the user for input.

In general, I'm told, it's not safe to inherit from Python's built-in objects like file, because they tend to make assumptions instead of making virtual calls to your overloaded methods. What happened here will happen for other objects too. So use composition instead when extending Python's built-in types.

Tags: ,
[ 09:48 Apr 16, 2010    More programming | permalink to this entry | comments ]

Tue, 02 Feb 2010

Configuring git colors

I spent a morning wrestling with git after writing a minor GIMP fix that I wanted to check in. Deceptively simple ideas, like "Check the git log to see the expected format of check-in messages", turned out to be easier said than done.

Part of the problem was git's default colors: colors calculated to be invisible to anyone using a terminal with dark text on a light background. And that sent me down the perilous path of git configuration.

git-config does have a manual page. But it lacks detail: you can't get from there to knowing what to change so that the first line of commits in git log doesn't show up yellow.

But that's okay, thought I: all I need to do is list the default settings, then change anything that's a light color like yellow to a darker color. Easy, right?

Well, no. It turns out there's no way to get the default settings -- because they aren't part of git's config; they're hardwired into the C code.

But you can find most of them with a seach for GIT_COLOR in the source. The most useful lines are these the ones in diff.c, builtin-branch.c and wt-status.c.

gitconfig

The next step is to translate those C lines to git preferences, something you can put in a .gitconfig. Here's a list of all the colors mentioned in the man page, and their default values -- I used "normal" for grep and interactive where I wasn't sure of the defaults.

[color "diff"]
	plain = normal
	meta = bold
	frag = cyan
	old = red
	new = green
	commit = yellow
	whitespace = normal red
[color "branch"]
	current = green
	local = normal
	remote = red
	plain = normal
[color "status"]
	header = normal
	added = red
	updated = green
	changed = red
	untracked = red
	nobranch = red
[color "grep"]
	match = normal
[color "interactive"]
	prompt = normal
	header = normal
	help = normal
	error = normal

The syntax and colors are fairly clearly explained in the manual: allowable colors are normal, black, red, green, yellow, blue, magenta, cyan and white. After the foreground color, you can optionally list a background color. You can also list an attribute, chosen from bold, dim, ul, blink and reverse -- only one at a time, no combining of attributes.

So if you really wanted to, you could say something like

[color "status"]
	header = normal blink
	added = magenta yellow
	updated = green reverse
	changed = red bold
	untracked = blue white
	nobranch = red white bold

Minimal changes for light backgrounds

What's the minimum you need to get everything readable? On the light grey background I use, I needed to change the yellow, cyan and green entries:

[color "diff"]
	frag = cyan
	new = green
	commit = yellow
[color "branch"]
	current = green
[color "status"]
	updated = green

Disclaimer: I haven't tested all these settings -- because I haven't yet figured out where all of them apply. That's another area where the manual is a bit short on detail ...

Tags: , ,
[ 23:26 Feb 02, 2010    More programming | permalink to this entry | comments ]

Thu, 28 Jan 2010

On Linux Planet: a simple Poker game in Python-Qt

[Poker game in py-qt] I've written in the past about Python GUI programming using the GTK and Tk toolkits, and several KDE fans felt that I was slighting the much nicer looking Qt.

So my latest article on Linux Planet, Make Pretty GUI Apps Fast with Python-Qt, shows how to develop a little poker game using the python-qt toolkit.

I didn't want to dwell on it in the article (and didn't have space anyway), but pyqt turned out to be a bit of a pain. There's no official documentation -- or at least nothing that's obviously official -- and a lot of the examples on google are out of date because of API changes. None of the tutorial examples explain much, and they never demonstrate the practical features I'd want to do in a real app. It was surprisingly hard to come up with an application idea that worked well, looked good and was still easy to explain.

And don't get me started on this whole "Slots and signals are revolutionarily different even though they look just like the callbacks every other toolkit has used for the last three decades" meme. I'm sure there is a subtle technical difference -- but if there's a difference that matters to the average UI programmer, their documentation sure doesn't make it clear.

All that aside, PyQt (and Qt in general) does produce very pretty apps and is worth trying for that reason.

[spade] [diamond] [club] [heart]

The suit images in the article are adapted from some suits I found on Wikimedia Commons (the "Naipe" set). I wanted them to look more 3-dimensional, so I applied my blobipy GIMP script as well as scaling and resizing them. I really liked those shiny-looking Tango heart and spade emblems (also on the Wikimedia Commons page) but I couldn't find a diamond or club to match.

The poker program I wrote has menus and a second round of dealing, where you can mark off the cards you want to keep. I couldn't fit all that in a 700-word article, but the complete program is available here: qpoker.py or you can get it in a tarball along with the suit images at qpoker.tar.gz.

Tags: , , ,
[ 10:53 Jan 28, 2010    More programming | permalink to this entry | comments ]

Sun, 17 Jan 2010

Displaying images from Javascript file inputs

(despite Firefox's attempts to prevent that)

My Linux Planet article last week was on printing pretty calendars. But I hit one bug in Photo Calendar. It had a HTML file chooser for picking an image ... and when I chose an image and clicked Select to use it. it got the pathname wrong every time.

I poked into the code (Photo Calendar's code turned out to be exceptionally clean and well documented) and found that it was expecting to get the pathname from the file input element's value attribute. But input.File.value was just returning the filename, foo.jpg, instead of the full pathname, /home/user/Images/yosemite/foo.jpg. So when the app tried to make it into a file:/// URL, it ended up pointing to the wrong place.

It turned out the cause was a security change in Firefox 3. The issue: it's considered a security hole to expose full pathnames on your computer to Javascript code coming from someone else's server. The Javascript could give bad guys access to information about the directory structures on your disk. That's a perfectly reasonable concern, and it makes sense to consider it as a security hole.

The problem is that this happens even when you're running a local app on your local disk. Programs written in any other language and toolkit -- a Python program using pygtk, say, or a C++ Qt program -- have access to the directories on your disk, but you can't use Javascript inside Firefox to do the same thing. The only ways to make an exception seems to be an elaborate procedure requiring the user to change settings in about:config. Not too helpful.

Perhaps this is even reasonable, given how common cross-site scripting bugs have been in browsers lately -- maybe running a local script really is a security risk if you have other tabs active. But it leaves us with the problem of what to do about apps that need to do things like choose a local image file, then display it.

And it turns out there is: a data URL. Take the entire contents of the file (ouch) and create a URL out of those contents, then set the src attribute of the image to that.

Of course, that makes for a long, horrifying, unreadable URL -- but the user never has to see that part. I suspect it's also horribly memory intensive -- the image has to be loaded into memory anyway, to display it, but is Firefox also translating all of that to a URL-legal syntax? Obviously, any real app using this technique had better keep an eye on memory consumption. But meanwhile, it fixes Photo Calendar's file button.

Here's what the code looks like:

  img = document.getElementById("pic");
  fileinput = document.input.File;
  if (img && fileinput)
    img.src = fileinput.files[0].getAsDataURL();

Here's a working minimal demo of using getAsDataURL() with a file input.

Tags: , ,
[ 14:57 Jan 17, 2010    More programming | permalink to this entry | comments ]

Fri, 08 Jan 2010

Python-GTK regression: How to catch mouse button release

We just had the second earthquake in two days, and I was chatting with someone about past earthquakes and wanted to measure the distance to some local landmarks. So I fired up PyTopo as the easiest way to do that. Click on one point, click on a second point and it prints distance and bearing from the first point to the second.

Except it didn't. In fact, clicks weren't working at all. And although I have hacked a bit on parts of pytopo (the most recent project was trying to get scaling working properly in tiles imported from OpenStreetMap), the click handling isn't something I've touched in quite a while.

It turned out that there's a regression in PyGTK: mouse button release events now need you to set an event mask for button presses as well as button releases. You need both, for some reason. So you now need code that looks like this:

drawing_area.connect("button-release-event", button_event)
drawing_area.set_events(gtk.gdk.EXPOSURE_MASK |
                        # next line wasn't needed before:
                        gtk.gdk.BUTTON_PRESS_MASK |
                        gtk.gdk.BUTTON_RELEASE_MASK )

An easy fix ... once you find it.

I filed bug 606453 to see whether the regression was intentional.

I've checked in the fix to the PyTopo svn repository on Google Code. It's so nice having a public source code repository like that! I'm planning to move Pho to Google Code soon.

Tags: , , ,
[ 14:20 Jan 08, 2010    More programming | permalink to this entry | comments ]

Sat, 28 Nov 2009

Debug logging in Javascript under Firefox

While debugging Javascript, I've occasionally come across references to a useful function called console.log. Supposedly you can log errors with a line like:
  console.log(""key press, char code " + e.charCode);

Since the only native way of logging debug statements in Javascript is with a pop-up alert() box, having a less obtrusive way to print is something any JS programmer could use.

The catch? It didn't do anything -- except print console is not defined.

Today a friend was crowing about how wonderful Javascript debugging was in Google's Chrome browser -- because it has functions like console.log.

[Firebug console menu] After some searching and poking around, we determined that Firefox also has console.log -- it's just well hidden and a bit hard to get going.

First, you need the Firebug extension. If you're developing Javascript, you probably already have it. If not, you need it.

Run Firebug and click to the Console tab. Now click on the tiny arrow that shows up at the right edge of that tab, as shown. Turns out there's a whole menu of options under there -- one of which is Enabled.

But wait, that's not all. In my case, the console was already Enabled according to the menu. To get the console working, I had to

  1. Disable the console
  2. Re-enable it
  3. Shift-reload the page being debugged

My friend also said that if she didn't enable the console in Firebug, then her script died when she called console.log. That didn't happen for me -- all that happened was that I got error messages in the error console (the one accessed from Firefox's Tools menu, different from the Firebug console). But it's a good idea to check for its existence if you're going to use debugging statements in your code. Like this:

  if (typeof console != "undefined") {
    console.log( "key press, char code " + e.charCode
                + ", key code " + e.keyCode
                + ", " + e.ctrlKey + ", " + e.altKey
		+ ", " + e.metaKey );
  }

Here are some more things you can do with Firebug's console.

Tags: , , ,
[ 16:41 Nov 28, 2009    More tech/web | permalink to this entry | comments ]

Wed, 25 Nov 2009

Character Sets and Encodings in Linux, part 2

Continuing the discussion of those funny characters you sometimes see in email or on web pages, today's Linux Planet article discusses how to convert and handle encoding errors, using Python or the command-line tool recode:

Mastering Characters Sets in Linux (Weird Characters, part 2).

Tags: , , , , , , ,
[ 15:06 Nov 25, 2009    More writing | permalink to this entry | comments ]

Wed, 11 Nov 2009

Building a Py-Webkit-GTK presentation tool

I almost always write my presentation slides using HTML. Usually I use Firefox to present them; it's the browser I normally run, so I know it's installd and the slides all work there. But there are several disadvantages to using Firefox:

Last year, when I was researching lightweight browsers, one of the ones that impressed me most was something I didn't expect: the demo app that comes with pywebkitgtk (package python-webkit on Ubuntu). In just a few lines of Python, you can create your own browser with any UI you like, with a fully functional content area. Their current demo even has tabs.

So why not use pywebkitgtk to create a simple fullscreen webkit-based presentation tool?

It was even simpler than I expected. Here's the code:

#!/usr/bin/env python
# python-gtk-webkit presentation program.
# Copyright (C) 2009 by Akkana Peck.
# Share and enjoy under the GPL v2 or later.

import sys
import gobject
import gtk
import webkit

class WebBrowser(gtk.Window):
    def __init__(self, url):
        gtk.Window.__init__(self)
        self.fullscreen()

        self._browser= webkit.WebView()
        self.add(self._browser)
        self.connect('destroy', gtk.main_quit)

        self._browser.open(url)
        self.show_all()

if __name__ == "__main__":
    if len(sys.argv) <= 1 :
        print "Usage:", sys.argv[0], "url"
        sys.exit(0)

    gobject.threads_init()
    webbrowser = WebBrowser(sys.argv[1])
    gtk.main()

That's all! No navigation needed, since the slides include javascript navigation to skip to the next slide, previous, beginning and end. It does need some way to quit (for now I kill it with ctrl-C) but that should be easy to add.

Webkit and image buffering

It works great. The only problem is that webkit's image loading turns out to be fairly poor compared to Firefox's. In a presentation where most slides are full-page images, webkit clears the browser screen to white, then loads the image, creating a noticable flash each time. Having the images in cache, by stepping through the slide show then starting from the beginning again, doesn't help much (these are local images on disk anyway, not loaded from the net). Firefox loads the same images with no flash and no perceptible delay.

I'm not sure if there's a solution. I asked some webkit developers and the only suggestion I got was to rewrite the javascript in the slides to do image preloading. I'd rather not do that -- it would complicate the slide code quite a bit solely for a problem that exists only in one library.

There might be some clever way to hack double-buffering in the app code. Perhaps something like catching the 'load-started' signal, switching to another gtk widget that's a static copy of the current page (if there's a way to do that), then switching back on 'load-finished'.

But that will be a separate article if I figure it out. Ideas welcome!

Update, years later: I've used this for quite a few real presentations now. Of course, I keep tweaking it: see my scripts page for the latest version.

Tags: , , , ,
[ 17:12 Nov 11, 2009    More programming | permalink to this entry | comments ]

Sun, 08 Nov 2009

Friendlier error messages for shell newbies

Helping people get started with Linux shells, I've noticed they tend to make two common mistakes vastly more than any others:
  1. Typing a file path without a slash, like etc/fstab
  2. typing just a filename, without a command in front of it

The first boils down to a misunderstanding of how the Linux file system hierarchy works. (For a refresher, you might want to check out my Linux Planet article Navigating the Linux Filesystem.)

The second problem is due to forgetting the rules of shell grammar. Every shell sentence needs a verb, just like every sentence in English. In the shell, the command is the verb: what do you want to do? The arguments, if any, are the verb's direct object: What do you want to do it to?

(For grammar geeks, there's no noun phrase for a subject because shell commands are imperative. And yes, I ended a sentence with a preposition, so go ahead and feel superior if you believe that's incorrect.)

The thing is, both mistakes are easy to make, especially when you're new to the shell, perhaps coming from a "double-click on the file and let the computer decide what you should do with it" model. The shell model is a lot more flexible and (in my opinion) better -- you, not the computer, gets to decide what you should do with each file -- but it does take some getting used to.

But as a newbie, all you know is that you type a command and get some message like "Permission denied." Why was permission denied? How are you to figure out what the real problem was? And why can't the shell help you with that?

And a few days ago I realized ... it can! Bash, zsh and similar shells have a fairly flexible error handling mechanism. Ubuntu users have seen one part of this, where if you type a command you don't have installed, Ubuntu gives you a fancy error message suggesting what you might have meant and/or what package you might be missing:

$ catt /etc/fstab
No command 'catt' found, did you mean:
 Command 'cat' from package 'coreutils' (main)
 Command 'cant' from package 'swap-cwm' (universe)
catt: command not found

What if I tapped into that same mechanism and wrote a more general handler that could offer helpful suggestions when it looked like the user forgot the command or the leading slash?

It turns out that Ubuntu's error handler uses a ridiculously specific function called command_not_found_handle that can't be used for other errors. Some helpful folks I chatted with on #bash felt, as I did, that such a specific mechanism was silly. But they pointed me to a more general error trapping mechanism that turned out to work fine for my purposes.

It took some fussing and fighting with bash syntax, but I have a basic proof-of-concept. Of course it could be expanded to cover a lot more types of error cases -- and more types of files the user might want to open.

Here are some sample errors it catches:

$ schedule.html
bash: ./schedule.html: Permission denied

schedule.html is an HTML file. Did you want to run: firefox schedule.html

$ screenshot.jpg
bash: ./screenshot.jpg: Permission denied

screenshot.jpg is an image file. Did you want to run:
    pho screenshot.jpg
    gimp screenshot.jpg

$ .bashrc
bash: ./.bashrc: Permission denied

.bashrc is a text file. Did you want to run:
    less .bashrc
    vim .bashrc

$ ls etc/fstab
/bin/ls: cannot access etc/fstab: No such file or directory

Did you forget the leading slash?
etc/fstab doesn't exist, but /etc/fstab does.

You can find the code here: Friendly shell errors and of course I'm happy to take suggestions or contributions for how to make it friendlier to new shell users.

Tags: , , , ,
[ 15:07 Nov 08, 2009    More linux | permalink to this entry | comments ]

Tue, 20 Oct 2009

Gathering RSS files for a Palm PDA: FeedMe

For years I've been reading daily news feeds on a series of PalmOS PDAs, using a program called Sitescooper that finds new pages on my list of sites, downloads them, then runs Plucker to translate them into Plucker's open Palm-compatible ebook format.

Sitescooper has an elaborate series of rules for trying to get around the complicated formatting in modern HTML web pages. It has an elaborate cache system to figure out what it's seen before. When sites change their design (which most news sites seem to do roughly monthly), it means going in and figuring out the new format and writing a new Sitescooper site file. And it doesn't understand RSS, so you can't use the simplified RSS that most sites offer. Finally, it's no longer maintained; in fact, I was the last maintainer, after the original author lost interest.

Several weeks ago, bma tweeted about a Python RSS reader he'd hacked up using the feedparser package. His reader targeted email, not Palm, but finding out about feedparser was enough to get me started. So I wrote FeedMe (Carla Schroder came up with the all-important name).

I've been using it for a couple of weeks now and I'm very happy with the results. It's still quite rough, of course, but it's already producing better files than Sitescooper did, and it seems more maintainable. Time will tell.

Of course it needs to be made more flexible, adjusted so that it can produce formats besides Plucker, and so on. I'll get to it.

And the only site I miss now, because it doesn't offer an RSS feed, is Linux Planet. Maybe I'll find a solution for that eventually.

Tags: , , , ,
[ 21:08 Oct 20, 2009    More programming | permalink to this entry | comments ]

Tue, 15 Sep 2009

GTK dialogs in GIMP (and updated wallpaper script)

[Grosvenor Arch] I've been getting tired of my various desktop backgrounds, and realized that I had a lot of trip photos, from fabulous places like Grosvenor Arch (at right), that I'd never added to my background collection.

There's nothing like lots of repetitions of the same task to bring out the shortcomings of a script, and the wallpaper script I threw together earlier this year was no exception. I found myself frequently irritated by not having enough information about what the script was doing or being able to change the filename. Then I could have backgrounds named grosvenor.jpg rather than img2691.jpg.

Alas, I can't use the normal GIMP Save-as dialog, since GIMP doesn't make that dialog available to plug-ins. (That's a deliberate choice, though I've never been clear on the reason behind it.) If I wanted to give that control to the user, I'd have to make my own dialogs.

It's no problem to make a GTK dialog from Python. Just create a gtk.Dialog, add a gtk.Entry to it, call dialog.run(), then check the return value and get the entry's text to see if it changed. No problem, right?

Ha! If you think that, you don't work with computers. The dialog popped up fine, it read the text entry fine ... but it wouldn't go away afterward. So after the user clicked OK, the plug-in tried to save and GIMP popped up the JPEG save dialog (the one that has a quality slider and other controls, but no indication of filename) under my text entry dialog, which remained there.

All attempts at calling dialog.hide() and dialog.destroy() and similar mathods were of no avail. A helpful person on #pygtk worked with me but ended up as baffled as I was. What was up?

The code seemed so simple -- something like this:

    response = dialog.run()
    if response == gtk.RESPONSE_OK :
        pathname = pathentry.get_text()
        dialog.hide()
        dialog.destroy()
        pdb.gimp_file_save(newimg, newimg.active_layer, pathname, pathname,
                           run_mode=0)

In the end, GIMP guru Sven pointed me to the answer. The problem was that my dialog wasn't part of the GTK main loop. In retrospect, this makes sense: the plug-in is an entirely different process, so I shouldn't be surprised that it would have its own main loop. So when I hide() and destroy(), those events don't happen right away because there's no loop in the plug-in process that would see them.

The plug-in passes control back to GIMP to do the gimp_file_save(). GIMP's main loop doesn't have access to the hide and destroy signals I just sent. So the gimp_file_save runs, popping up its own dialog (under mine, because the JPEG save dialog is transient to the original image window while my python dialog isn't). That finishes, returns control to the plug-in, the plug-in exits and at that point GTK cleans up and finally destroys the dialog.

The solution is to loop over GTK events in the plug-in before calling gimp_file_save, like this:

    response = dialog.run()
    if response == gtk.RESPONSE_OK :
        pathname = pathentry.get_text()
        dialog.hide()
        dialog.destroy()
        while gtk.events_pending() :
            gtk.main_iteration()
        pdb.gimp_file_save(newimg, newimg.active_layer, pathname, pathname,
                           run_mode=0)

That loop gives the Python process a chance to clean up the dialog before passing control to GIMP and its main loop. GTK in the subprocess is happy, the user is happy, and I'm happy because now I have a much more efficient way of making lots of desktop backgrounds for lots of different machines.

The updated script, along with a lot more information on how to use it and how to set up tool presets for it.

Tags: , ,
[ 23:21 Sep 15, 2009    More gimp | permalink to this entry | comments ]

Sun, 06 Sep 2009

Using apt-file to track down build errors

Someone was asking for help building XEphem on the XEphem mailing list. It was a simple case of a missing include file, where the only trick is to find out what package you need to install to get that file. (This is complicated on Ubuntu, which the poster was using, by the way they fragment the X developement headers into a maze of a xillion tiny packages.)

The solution -- apt-file -- is so simple and easy to use, and yet a lot of people don't know about it. So here's how it works.

The poster reported getting these compiler errors:

ar rc libz.a adler32.o compress.o crc32.o uncompr.o deflate.o trees.o zutil.o inflate.o inftrees.o inffast.o
ranlib libz.a
make[1]: Leaving directory `/home/gregs/xephem-3.7.4/libz'
gcc -I../../libastro -I../../libip -I../../liblilxml -I../../libjpegd -I../../libpng -I../../libz -g -O2 -Wall -I../../libXm/linux86 -I/usr/X11R6/include   -c -o aavso.o aavso.c
In file included from aavso.c:12:
../../libXm/linux86/Xm/Xm.h:56:27: error: X11/Intrinsic.h: No such file or directory
../../libXm/linux86/Xm/Xm.h:57:23: error: X11/Shell.h: No such file or directory
../../libXm/linux86/Xm/Xm.h:58:23: error: X11/Xatom.h: No such file or directory
../../libXm/linux86/Xm/Xm.h:59:34: error: X11/extensions/Print.h: No such file or directory
In file included from ../../libXm/linux86/Xm/Xm.h:60,
                 from aavso.c:12:
../../libXm/linux86/Xm/XmStrDefs.h:1373: error: expected `=', `,', `;', `asm' or `__attribute__' before `char'
In file included from ../../libXm/linux86/Xm/Xm.h:60,
                 from aavso.c:12:
../../libXm/linux86/Xm/XmStrDefs.h:5439:28: error: X11/StringDefs.h: No such file or directory
In file included from ../../libXm/linux86/Xm/Xm.h:61,
                 from aavso.c:12:
../../libXm/linux86/Xm/VirtKeys.h:108: error: expected `)' before `*' token
In file included from ../../libXm/linux86/Xm/Display.h:49,
                 from ../../libXm/linux86/Xm/DragC.h:48,
                 from ../../libXm/linux86/Xm/Transfer.h:44,
                 from ../../libXm/linux86/Xm/Xm.h:62,
                 from aavso.c:12:
../../libXm/linux86/Xm/DropSMgr.h:88: error: expected specifier-qualifier-list before `XEvent'
../../libXm/linux86/Xm/DropSMgr.h:100: error: expected specifier-qualifier-list before `XEvent'
How do you go about figuring this out?

When interpreting compiler errors, usually what matters is the *first* error. So try to find that. In the transcript above, the first line saying "error:" is this one:

../../libXm/linux86/Xm/Xm.h:56:27: error: X11/Intrinsic.h: No such file or directory

So the first problem is that the compiler is trying to find a file called Intrinsic.h that isn't installed.

On Debian-based systems, there's a great program you can use to find files available for install: apt-file. It's not installed by default, so install it, then update it, like this (the update will take a long time):

$ sudo apt-get install apt-file
$ sudo apt-file update
Once it's updated, you can now find out what package would install a file like this:
$  apt-file search Intrinsic.h
libxt-dev: /usr/include/X11/Intrinsic.h
tendra: /usr/lib/TenDRA/lib/include/x5/t.api/X11/Intrinsic.h

In this case two two packages could install a file by that name. You can usually figure out from looking which one is the "real" one (usually the one with the shorter name, or the one where the package name sounds related to what you're trying to do). If you're stil not sure, try something like apt-cache show libxt-dev tendra to find out more about the packages involved.

In this case, it's pretty clear that tendra is a red herring, and the problem is likely that the libxt-dev package is missing. So apt-get install libxt-dev and try the build again.

Repeat the process until you have everything you need for the build.

Remember apt-file if you're not already using it. It's tremendously useful in tracking down build dependencies.

Tags: , , , ,
[ 11:25 Sep 06, 2009    More linux | permalink to this entry | comments ]

Mon, 31 Aug 2009

Reading Palm Datebook files on Linux

Over the years, I've kept a few sets of records in the Datebook app on my PalmOS PDA -- health records and such. I've been experimenting with a few python plotting packages (pycha, CairoPlot and a few others) and I wanted to try plotting one of my Datebook databases.

Not so fast. It seems that it's been a year or more since I last crunched any of this data -- and in the time since then, pilot-link has bumped its version numbers and is now shipping libpisock.so.9 instead of .8.

So what? Well, the problem is that Linux hasn't offered any way to read Palm Datebook files for years. The pilot-link package offered on most distros used to include a program called pilot-datebook, but it was deleted from the source several years ago. Apparently it was hard to maintain.

Back when it first disappeared, I built the previous version of the source, stuck the pilot-datebook binary in ~/bin/linux and have been using it ever since. Which worked fine -- until libpisock.so.8 was no longer there. (Linking .9 to .8 didn't work either.) This is all the more ironic because I don't need pilot-datebook to talk to the PDA with libpisock -- all I want to do is parse the format of a file I've already uploaded.

Off to hunt for an old version of the source. I started at pilot-link.org, but gave up after a while -- they don't seem to have source there except for the latest couple of versions, nor do they have any documentation. Ironically, in their FAQ the very first question is "How can I read the databook entries from a Palm backup?" but the FAQ page is broken and the "answer" is actually another unrelated FAQ question.

Anyway, no help there. I tried googling for old tarballs but there doesn't seem to be anything like archive.org for source code. All I found was the original pilot-datebook page, with a tarball that you insert into a copy of pilot-link 0.9.5 then modify the Makefile. Might work but that's really old.

So I fell back on old distributions. I guessed that Ubuntu Dapper was old enough that it might still have pilot-datebook. So I went to the Dapper pilot-link source and downloaded the source tarball (curiously, they don't offer src debs -- you have to download the tarball and patches separately).

Of course, it doesn't build on Ubuntu Jaunty. It had various entertaining errors ranging from wanting a mysterious tcl.m4 file not present in the code ... to not being able to find <iostream.h< because all the C++ stdlib files have recently been renamed to remove the .h ... to a change in the open() system call where I needed to add permissions argument for O_CREAT.

But I did get it working! So now I have a pilot-datebook program that builds and runs on Ubuntu Jaunty, and parses my DatebookDB.pdb file.

Since I bet I'm not the only one in the world who occasionally wants to read a Palm Datebook file, I've put my working version of the source here: pilot-link_0.11.8.jaunty.tar.gz.

After the usual configure and make, if all you want is pilot-datebook, cd src/pilot-datebook then copy both pilot-datebook and the directory .libs to wherever you want to install them.

And yeah, it would be better to write a standalone program that just parsed the format. But it's hard to justify that for what's essentially a dead platform. The real solution is to quit using a Palm for this, import the data into some common format and keep it on my Linux workstation from now on.

Tags: , , ,
[ 12:39 Aug 31, 2009    More linux | permalink to this entry | comments ]

Thu, 27 Aug 2009

Linux Bloat 102

Part 2 of my Linux bloat article looks at information you can get from the kernel via some useful files in /proc, at three scripts that display that info, and also at how to use exmap, an app and kernel module that shows you a lot more about what resources your apps are using. How Do You Really Measure Linux Bloat?

Tags: , , , ,
[ 20:52 Aug 27, 2009    More writing | permalink to this entry | comments ]

Wed, 19 Aug 2009

Crikey 0.8.3: bug fixes and several new syntaxes

Sometimes I love open source. A user contacted me about my program Crikey!, which lets you generate key events to do things like assign a key that will type in a string you don't want to type in by hand. He had some thorny problems where crikey was failing, and a few requests, like sending Alt and other modifier keys.

We corresponded a bit, figured out exactly how things should work and some test cases, went through a couple iterations of changes where I got lots of detailed and thoughtful feedback and more test cases, and now Crikey can do an assortment of new useful stuff.

New features: crikey now handles number codes like \27, modifier keys like \A for alt, does a better job with symbols like \(Return\), and handles a couple of new special characters like \e for escape. It also works better at sending window manager commands, like "\A\t" to change the active window.

I've added some better documentation on all the syntaxes it understands, both on the web page and in the -h and -l (longhelp) command-line arguments, and made a release: crikey 0.8.3.

Plus: a list of great regression tests that I can use when testing future updates (in the file TESTING in the tarball).

Tags: , , ,
[ 17:38 Aug 19, 2009    More programming | permalink to this entry | comments ]

Thu, 13 Aug 2009

Linux Bloat 101

Continuing my Linux Planet series on Linux performance monitoring, the latest article looks at bloat and how you can measure it: Finding and Trimming Linux Bloat.

This one just covers the basics. The followup article, in two weeks, will dive into more detail on how to analyze what resources programs are really using.

Tags: , , , ,
[ 11:27 Aug 13, 2009    More writing | permalink to this entry | comments ]

Fri, 07 Aug 2009

On Teaching Programming (GetSET 2009)

I survived another GetSET Javascript-in-a-day workshop for GetSET 2009.

It went okay, but not as well as I'd hoped. This year's class was more distractable than classes of past years -- and, judging by their career goals, less interested in computers or engineering, unfortunate in a program run by the Society of Women Engineers.

In the morning, we had a hard time getting them to focus long enough to learn basics like what a variable was. After a caucus at lunchtime, we decided to skip the next exercise (looping over an array of colors) and spend some time drilling on the basics, and keep at it 'til they got it. It took a while but we eventually got through. We needed more examples in the morning, more interaction, some visceral way of explaining programming basics so they really get it.

They do better working as a group on a concrete problem, like the final whiteboard exercise, "How do we figure out whether the click was on the flower?". That always ends up being a highlight of the class, even though it involves (gasp) doing math. This year was no exception, but it did take a while to get through. Using variables lost them completely ("is the mouse's X coordinate bigger than or less than the flower's X?") but when we used actual numnbers and ran through several examples, things eventually clicked. "The flower starts at (2, 5) and is 200 pixels wide. If the mouse click is at (34, 45), who thinks it's inside the flower? Raise your hands. Who thinks it's not? Now what if I click at (300, 24)?" A couple of them got it right away, but it took a long time to bring the whole class along.

I'm not still sure how to use that method for more basic concepts like "what is a variable?". Perhaps some sort of role-playing? Watching William Phelps guide the girls through planet motions in our astronomy workshop Wednesday, each girl playing the role of a solar system object, inspired me. I'd used role-playing like that with little kids, but William says it works even with adults to get concepts across, and after seeing him with the high schoolers I believe it. But how to adapt that to programming concepts? A recent Slate article on teaching programming had some interesting ideas I want to try.

Printed handouts for GetSET may be a waste of time. Nobody was even bothering to look at them, despite the fact that they had complete instructions for everything we were doing. Do schools not give students printed assignments or homework any more? Last year, they used the printed exercises but not the quick reference guides; this year they wouldn't even read the exercises. On the other hand, it might be worth it for the handful in each class who really love programming. I always hope some of them take the handouts home and try some of the extras on their own.

Finally, the class would be so much easier if we could teach it on a less pointy-clicky OS! Or at least on machines where IE isn't the default browser. The first 3-4 exercises go painfully slowly, guiding a roomful of girls through many GUI navigation steps:

Then the helpers have to go around the room ensuring that the girls have the correct file loaded in both Wordpad and Firefox. This took way too long with only four people to check the whole class, especially since we had to do it for every exercise. Invariably some girls will doubleclick instead of right-clicking or dragging, and will end up in whatever HTML editor Microsoft is pushing this year, or with an IE window instead of Firefox (and then the Error Console won't be there when she looks for it later).

Suggestions like "Keep that window open, you'll need it throughout the class" or "Try making that window smaller, so you can see both windows at once" don't help. The girls are too used to the standard Windows model of one screen-filling window at a time. Keeping two apps visible at once is too foreign. A few them are good at using the taskbar to switch among apps, but for the rest, loading new files is awkward and error prone.

In postmortems two years ago we talked about having them work on one file throughout the whole workshop. That would solve the problem, but I'm still working on how to do it without a lot of "Now comment out the code you just wrote, so you won't get the prompt every time, then scroll down to the next block of code and uncomment it."

I couldn't help thinking how on Linux, we could just tell them to type leafpad whatever.html; firefox whatever.html and be done. Or even give them an alias that would do it. Hmm ... I wonder if I could make a Windows .bat file that would open the same file in Wordpad and Firefox both? Must try that.

Tags: ,
[ 20:12 Aug 07, 2009    More education | permalink to this entry | comments ]

Mon, 03 Aug 2009

Twit: Now with pattern searches

During OSCON a couple of weeks ago, I kept wishing I could do Twitter searches for a pattern like #oscon in a cleaner way than keeping a tab open in Firefox where I periodically hit Refresh.

Python-twitter doesn't support searches, alas, though it is part of the Twitter API. There's an experimental branch of python-twitter with searching, but I couldn't get it to work. But it turns out Gwibber is also written in Python, and I was able to lift some JSON code from Gwibber to implement a search. (Gwibber itself, alas, doesn't work for me: it bombs out looking for the Gnome keyring. Too bad, looks like it might be a decent client.)

I hacked up a "search for OSCON" program and used it a little during the week of the conference, then got home and absorbed in catching up and preparing for next week's GetSET summer camp, where I'm running an astronomy workshop and a Javascript workshop for high school girls. That's been keeping me frazzled, but I found a little time last night to clean up the search code and release Twit 0.3 with search and a few other new command-line arguments.

No big deal, but it was nice to take a hacking break from all this workshop coordinating. I'm definitely happier program than I am organizing events, that's for sure.

Tags: , ,
[ 18:23 Aug 03, 2009    More programming | permalink to this entry | comments ]

Sun, 12 Jul 2009

Newbie Greasemonkey script writing

I was reading a terrific article on the New York Times about Watching Whales Watching Us. At least, I was trying to read it -- but the NYT website forces font faces and sizes that, on my system, end up giving me a tiny font that's too small to read. Of course I can increase font size with Ctrl-+ -- but it gets old having to do that every time I load a NYT page.

The first step was to get Greasemonkey working on Firefox 3.5. "Update scripts" doesn't find a new script, and if you go to Greasemonkey's home page, the last entry is from many months ago and announces Firefox 3.1 support. But curiously, if you go to the Greasemonkey page on the regular Mozilla add-ons site, it does support 3.5.

I've had Greasemonkey for quite some time, but every time I try to get started writing a script I have trouble getting started. There are dozens of Greasemonkey tutorials on the web, but most of them are oriented toward installing scripts and don't address "What do you type into the fields of the Greasemonkey New User Script dialog?"

Fortunately, I did find one that explained it: The beginner's guide to Greasemonkey scripting. I gave my script a name (NYT font) and a namespace (my own domain), added http://*nytimes.com/* for Includes, and nothing for Excludes.

Click OK, and Greasemonkey offers a "choose editor" dialog. I chose emacs, which mostly worked though the emacs window unaccountably came up with a split window that I had to dismiss with C-x 1.

Now what to type in the editor? Firebug came to the rescue here.

I went back to the NYT page with the too-small fonts and clicked on Firebug. The body style showed that they're setting

font-family: Georgia, serif
font-size: 84.5%

84.5%? Where does that come from? What happens if I change that to 100%? Fortunately, I can test that right there in the Firebug window. 100% made the fonts fairly huge, but 90% was about right.

I went back to greasemonkey's editor window and added:

document.body.style.fontSize = "90%";

Saved the file, and that was all I needed! Once I hit Reload on the NYT page I got a much more readable font size.

Tags: , , , ,
[ 12:30 Jul 12, 2009    More tech/web | permalink to this entry | comments ]

Thu, 09 Jul 2009

Twittering -- and writing Twitter clients

I finally dragged myself into 2009 and tried Twitter.

I'd been skeptical, but it's actually fairly interesting and not that much of a time sink. While it's true that some people tweet about every detail of their lives -- "I'm waiting for a bus" / "Oh, hooray, the bus is finally here" / "I got a good seat in the second row of the bus" / "The bus just passed Second St. and two kids got on" / "Here's a blurry photo from my phone of the Broadway Av. sign as we pass it" -- it's easy enough to identify those people and un-follow them.

And there are tons of people tweeting about interesting stuff. It's like a news ticker, but customizable -- news on the latest protests in Iran, the latest progress on freeing the Mars Spirit Rover, the latest interesting publication on dinosaur fossils, and what's going on at that interesting conference halfway around the world.

The trick is to figure out how you want the information delivered. I didn't want to have to leave a tab open in Firefox all the time. There was an xchat plug-in that sounded perfect -- I have an xchat window up most of the time I'm online -- but it turned out it works by picking one of the servers you're connected to, making a private channel and posting things there. That seemed abusive to the server -- what if everyone on Freenode did that?

So I wanted a separate client. Something lightweight and simple. Unfortunately, all the Twitter clients available for Linux either require that I install a lot of infrastructure first (either Adobe Air or Mono), or they just plain didn't work (a Twitter client where you can't click on links? Come on!)

But then I tried out the Python-Twitter bindings, and they were so easy to use I decided to write them up for my next Linux Planet article, which came out today: Write Your Own Linux Twitter Client In Less Time Than It Takes To Find One!.

The article shows how to use the bindings to write a bare-bones client. But of course, I've been hacking on the client all along, so the one I'm actually using has a lot more features like *ahem* letting you click on links. And letting you block threads, though I haven't actually tested that since I haven't seen any threads I wanted to block since my first day.

You can download the current version of Twit, and anyone who's interested can follow me on Twitter. I don't promise to be interesting -- that's up to you to decide -- but I do promise not to tweet about every block of my bus ride.

Tags: , , ,
[ 16:09 Jul 09, 2009    More writing | permalink to this entry | comments ]

Sun, 28 Jun 2009

A Beginner's Guide to Free Software Programming Languages

Linux Planet asked me for an intro article for prospective programmers, explaining the pros and cons of various programming languages. Here it is: A Beginner's Guide to Free Software Programming Languages

Tags: ,
[ 13:00 Jun 28, 2009    More writing | permalink to this entry | comments ]

Sat, 20 Jun 2009

Pytopo 0.8 released

On my last Mojave trip, I spent a lot of the evenings hacking on PyTopo.

I was going to try to stick to OpenStreetMap and other existing mapping applications like TangoGPS, a neat little smartphone app for downloading OpenStreetMap tiles that also runs on the desktop -- but really, there still isn't any mapping app that works well enough for exploring maps when you have no net connection.

In particular, uploading my GPS track logs after a day of mapping, I discovered that Tango really wasn't a good way of exploring them, and I already know Merkaartor, nice as it is for entering new OSM data, isn't very good at working offline. There I was, with PyTopo and a boring hotel room; I couldn't stop myself from tweaking a bit.

Adding tracklogs was gratifyingly easy. But other aspects of the code bother me, and when I started looking at what I might need to do to display those Tango/OSM tiles ... well, I've known for a while that some day I'd need to refactor PyTopo's code, and now was the time.

Surprisingly, I completed most of the refactoring on the trip. But even after the refactoring, displaying those OSM tiles turned out to be a lot harder than I'd hoped, because I couldn't find any reliable way of mapping a tile name to the coordinates of that tile. I haven't found any documentation on that anywhere, and Tango and several other programs all do it differently and get slightly different coordinates. That one problem was to occupy my spare time for weeks after I got home, and I still don't have it solved.

But meanwhile, the rest of the refactoring was done, nice features like track logs were working, and I've had to move on to other projects. I am going to finish the OSM tile MapCollection class, but why hold up a release with a lot of useful changes just for that?

So here's PyTopo 0.8, and the couple of known problems with the new features will have to wait for 0.9.

Tags: , , , ,
[ 20:49 Jun 20, 2009    More programming | permalink to this entry | comments ]

Fri, 19 Jun 2009

Python: show all methods in a given object or module

A silly little thing, but something that Python books mostly don't mention and I can never find via Google:

How do you find all the methods in a given class, object or module?

Ideally the documentation would tell you. Wouldn't that be nice? But in the real world, you can't count on that, and examining all of an object's available methods can often give you a good guess at how to do whatever you're trying to do.

Python objects keep their symbol table in a dictionary called __dict__ (that's two underscores on either end of the word). So just look at object.__dict__. If you just want the names of the functions, use object.__dict__.keys().

Thanks to JanC for suggesting dir(object) and help(object), which can be more helpful -- not all objects have a __dict__.

Tags: , ,
[ 12:44 Jun 19, 2009    More programming | permalink to this entry | comments ]

Sun, 14 Jun 2009

Programming With PyGTK, part 3: Key events and object oriented Python

Part 3 of "Graphical Python Programming With PyGTK" uses object-oriented Python to clean up the code from Part 2, and also adds handling of key events to get rid of that silly Quit button. PythonGTK Programming part 3: Screensaver, Objects, and User Input

Tags: , ,
[ 12:18 Jun 14, 2009    More writing | permalink to this entry | comments ]

Mon, 01 Jun 2009

A GPX file manager

Someone on the OSM newbies list asked how he could strip waypoints out of a GPX track file. Seems he has track logs of an interesting and mostly-unmapped place that he wants to add to openstreetmap, but there are some waypoints that shouldn't be included, and he wanted a good way of separating them out before uploading.

Most of the replies involved "just edit the XML." Sure, GPX files are pretty simple and readable XML -- but a user shouldn't ever have to do that! Gpsman and gpsbabel were also mentioned, but they're not terribly easy to use either.

That reminded me that I had another XML-parsing task I'd been wanting to write in Python: a way to split track files from my Garmin GPS.

Sometimes, after a day of mapping, I end up with several track segments in the same track log file. Maybe I mapped several different trails; maybe I didn't get a chance to upload one day's mapping before going out the next day. Invariably some of the segments are of zero length (I don't know why the Garmin does that, but it always does). Applications like merkaartor don't like this one bit, so I usually end up editing the XML file and splitting it into segments by hand. I'm comfortable with XML -- but it's still silly.

I already have some basic XML parsing as part of PyTopo and Ellie, so I know the parsing very easy to do. So, spurred on by the posting on OSM-newbies, I wrote a little GPX parser/splitter called gpxmgr. gpxmgr -l file.gpx can show you how many track logs are in the file; gpxmgr -w file.gpx can write new files for each non-zero track log. Add -p if you want to be prompted for each filename (otherwise it'll use the name of the track log, which might be something like "ACTIVE\ LOG\ #2").

How, you may wonder, does that help the original poster's need to separate out waypoints from track files? It doesn't. See, my GPS won't save tracklogs and waypoints in the same file, even if you want them that way; you have to use two separate gpsbabel commands to upload a track file and a waypoint file. So I don't actually know what a tracklog-plus-waypoint file looks like. If anyone wants to use gpxmgr to manage waypoints as well as tracks, send me a sample GPX file that combines them both.

Tags: , ,
[ 20:43 Jun 01, 2009    More mapping | permalink to this entry | comments ]

Sun, 31 May 2009

JS Jup: now, with variable animation speed

I wrote last week about the sorts of programmer compulsions that lead to silly apps like my animated Javascript Jupiter. I got it working well enough and stopped, knowing there were more features that would be easy to add but trying to ignore them.

My mom, immediately upon seeing it, unerringly zeroed in on the biggest missing feature I'd been trying to ignore. "Can you make it go faster or slower?"

I put it off for a while, but of course I had to do it -- so now there are Faster and Slower buttons. It still goes by hour jumps, so the fastest you can go is an hour per millisecond. Fun to watch. Or you can slow it down to 1 hour per 3600000 milliseconds if you want to see it animate in real time. :-)

Tags: , ,
[ 11:42 May 31, 2009    More programming | permalink to this entry | comments ]

Thu, 28 May 2009

Programming With PyGTK, part 2: pretty screensaver-type graphics

Part 2 of Graphical Python Programming With PyGTK gets into how to do some cool Qix screensaver-style graphics, in: Graphical Python Programming With PyGTK, part 2: Write Your Own Screensaver.

There's also a digg link.

Tags: , ,
[ 18:09 May 28, 2009    More writing | permalink to this entry | comments ]

Sat, 23 May 2009

Javascript Jupiter

It's a sickness, I tell you.

It's not like I needed another Jupiter's moons application. I've already written more or less the same app for four platforms.

I don't use the Java web version, Juplet, very much any more, because I often have Java disabled or missing. And I don't use my Zaurus any more so Juplet for Zaurus isn't very relevant. But I can always call up my Xlib or PalmOS Jupiter's moons app if I need to check on those Galilean moons. They work fine. Another version would be really pointless. A waste of time.

So it should have been no big deal when, during the course of explaining to someone the difference between Java and Javascript, it suddenly occurred to me that it would be awfully easy to re-implement that Java Juplet web page using Javascript, HTML and CSS. I mean, a rational person would just say "oh, yeah, I suppose that's true" and go on with life.

But what I'm trying to say is that programming isn't a career path, or a hobby, or a field of academic study. It's a disease. It's a compulsion, where, sometimes, just realizing that something could be done renders you unable to think about anything else until you just ... try ... just a few minutes ... see how well it works ... oh, wow, that really looks a lot better than the Java version, wouldn't it look even nicer if you just added in this one other little tweak ... but wait, now it's so close to working, I bet it wouldn't be all that hard to take the Java class and turn it into ...

... and before you know it, it's tomorrow and you have something that's almost a working app, and it's just really a shame to get that far and not finish it at least to the point where you can share it.

But then, Javascript and web pages are so easy to work on that it really isn't that much extra work to add in some features that the old version didn't have, like an animate button ...

... and your Saturday morning is gone forever, and there's not much you can do about that, but at least you have a nice animated Jupiter's moons (and shadows) page when the sickness passes and you can finally think about other things.

Tags: , ,
[ 21:10 May 23, 2009    More programming | permalink to this entry | comments ]

Thu, 14 May 2009

Graphical Python Programming With PyGTK

This week's Linux Planet article is another one on Python and graphical toolkits, but this time it's a little more advanced: Graphical Python Programming With PyGTK.

This one started out as a fun and whizzy screensaver sort of program that draws lots of pretty colors -- but I couldn't quite fit it all into one article, so that will have to wait for the sequel two weeks from now.

Tags: , ,
[ 19:53 May 14, 2009    More writing | permalink to this entry | comments ]

Thu, 26 Mar 2009

GUI Programming in Python For Beginners

Latest on Linux Planet: another introductory programming article, this time on Python's tkinter library: GUI Programming in Python For Beginners. (As usual, there's a Digg link and also a Reddit one.)

Tags: , ,
[ 16:36 Mar 26, 2009    More writing | permalink to this entry | comments ]

Tue, 03 Mar 2009

Ellie: Plot GPS elevation profiles

Ever since I got the GPS I've been wanting something that plots the elevation data it stores. There are lots of apps that will show me the track I followed in latitude and longitude, but I couldn't find anything that would plot elevations.

But GPX (the XML-based format commonly used to upload track logs) is very straightforward -- you can look at the file and read the elevations right out of it. I knew it wouldn't be hard to write a script to plot them in Python; it just needed a few quiet hours. Sounded like just the ticket for a rainy day stuck at home with a sore throat.

Sure enough, it was fairly easy. I used xml.dom.minidom to parse the file (I'd already had some experience with it in gimplabels for converting gLabels templates), and pylab from matplotlib for doing the plotting. Easy and nice looking.

I even threw in the nice "conditional main" code from Matt Harrison's SCALE7x Python talk, so it should be callable from other Python code.

Here's the page and a screenshot: Ellie: plot elevation from a GPS track.

Tags: , ,
[ 17:57 Mar 03, 2009    More programming | permalink to this entry | comments ]

Sat, 28 Feb 2009

langgrep: search only in scripts of a specified language

I was making a minor tweak to my garmin script that uses gpsbabel to read in tracklogs and waypoints from my GPS unit, and I needed to look up the syntax of how to do some little thing in sh script. (One of the hazards of switching languages a lot: you forget syntax details and have to look things up a lot, or at least I do.)

I have quite a collection of scripts in various languages in my ~/bin (plus, of course, all the scripts normally installed in /usr/bin on any Linux machine) so I knew I'd have lots of examples. But there are scripts of all languages sharing space in those directories; it's hard to find just sh examples. For about the two-hundredth time, I wished, "Wouldn't it be nice to have a command that can search for patterns only in files that are really sh scripts?"

And then, the inevitable followup ... "You know, that would be really easy to write."

So I did -- a little python hack called langgrep that takes a language, grep arguments and a file list, looks for a shebang line and only greps the files that have a shebang matching the specified language.

Of course, while writing langgrep I needed langgrep, to look up details of python syntax for things like string.find (I can never remember whether it's string.find(s, pat) or s.find(pat); the python libraries are usually nicely object-oriented but strings are an exception and it's the former, string.find). I experimented with various shell options -- this is Unix, so of course there are plenty of ways of doing this in the shell, without writing a script. For instance:

grep find `egrep -l '#\\!.*python' *`
grep find `file * | grep python | sed 's/:.*//'`
i in foo; file $i|grep python && grep find $i; done    # in sh/bash
These are all pretty straightforward, but when I try to make them into tcsh aliases things get a lot trickier. tcsh lets you make aliases that take arguments, so you can use !:1 to mean the first argument, !2-$ to mean all the arguments starting with the second one. That's all very well, but when you put them into a shell alias in a file like .cshrc that has to be parsed, characters like ! and $ can mean other things as well, so you have to escape them with \. So the second of those three lines above turns into something like
alias greplang "grep \!:2-$ `file * | grep \!:1 | sed 's/:.*//'`"
except that doesn't work either, so it probably needs more escaping somewhere. Anyway, I decided after a little alias hacking that figuring out the right collection of backslash escapes would probably take just as long as writing a python script to do the job, and writing the python script sounded more fun.

So here it is: my langgrep script. (Awful name, I know; better ideas welcome!) Use it like this (if python is the language you're looking for, find is the search pattern, and you want -w to find only "find" as a whole word):

langgrep python -w find ~/bin/*

Tags: , ,
[ 10:57 Feb 28, 2009    More programming | permalink to this entry | comments ]

Thu, 05 Feb 2009

Use GIMP Tool Presets for setting multiple crop aspect ratios

Making desktop backgrounds in GIMP is a bit tedious if you have several machines with screens of different sizes. The workflow goes something like this:

First, choose Crop tool and turn on Fixed: Aspect Ratio. Then loop over images:

  1. Load an image
  2. Go to Tool Options
  3. Type in the aspect ratio: 4:3, 8:5, 5:4, 1366:768 etc.
  4. Go to the image and crop.
  5. Image->Scale (I have this on Shift-S, can't remember whether that was a default binding or not).
  6. Ctrl-K to delete the current width (Ctrl-U also works, but beeps; I'm not sure why)
  7. Type in the desired width (1024 or 1680 or 1366 or whatever) (I always hit Tab here, though it's probably not necessary)
  8. Click Scale or type Alt-S (unfortunately, Return doesn't work in this dialog).
  9. Save As to the appropriate name and path for the current resolution
  10. Undo (the Scale), Undo (the Crop)
  11. Load a new image (continue loop)

But you can use Save Options (Tool Presets) to avoid step 3, typing in the aspect ratio. Here's how:

Repeat, for each aspect ratio you might want to use.

Now clicking on Restore Options gives you a menu of all your commonly used aspect ratios -- much faster than typing them in every time. Too bad there's no way to use this shortcut for the Scale step, or to do Crop and Scale in one operation.

Nice shortcut! But having done that, I realized I could shorten it even more: I could make a selection (not a crop) with one of my preset aspect ratios, then run a script that would figure out from the aspect ratio which absolute size I wanted, crop and scale, and either save it to the right place, or make a new image so I could save without needing to Redo or Save a Copy. That was an easy script to write, so here it is: wallpaper.py.

Tags: , , ,
[ 23:45 Feb 05, 2009    More gimp | permalink to this entry | comments ]

Wed, 14 Jan 2009

Crikey 0.8

Mostly this week has been consumed with preparations for LCA ... but programming is a sickness. When you get email from someone suggesting something relatively simple and obviously useful, well ... it's simply impossible not to pull out that emacs window and start typing.

And so it was when I got a request for a backspace character in crikey. Of course backspace and delete seem like perfectly reasonable and useful characters to want; don't know why I didn't think of putting them in before. So I did.

But while I was in there, suddenly it occurred to me that it really wouldn't be much harder to let users specify any key by symbol. (Did I mention being a programmer is a sickness?) And then I realized that specifying control characters with a caret, like ^H, would also be quite useful. (Did I mention that ...)

So anyway, now there's a Crikey 0.8 and it's time to get back to packing and endless fiddling with my talk slides. Except, wait, I need to update my netscheme script to work right with the new laptop, and ...

Did I mention that programming is a sickness?

Tags: ,
[ 21:56 Jan 14, 2009    More programming | permalink to this entry | comments ]

Tue, 13 Jan 2009

Debian/Ubuntu repositories for Pho

I've been wanting for a long time to make Debian and Ubuntu repositories so people can install pho with apt-get, but every time I try to look it up I get bogged down.

But I got mail from a pho user who really wanted that, and even suggested a howto. That howto didn't quite do it, but it got me moving to look for a better one, which I eventually found in the Debian Repository Howto.

It wasn't complete either, alas, so it took some trial-and-error before it actually worked. Here's what finally worked:

I created two web-accessible directories, called hardy and etch. I copied all the files created by dpgk-buildpkg on each distro -- .deb, .dsc, .tar.gz, and .changes (I don't think this last file is used by anything) -- into each directory (renaming them to add -etch and -hardy as appropriate). Then:

% cd hardy/
% dpkg-scanpackages . /dev/null | gzip > Packages.gz
% dpkg-scansources . /dev/null | gzip > Sources.gz
% cd ../etch/
% dpkg-scanpackages . /dev/null | gzip > Packages.gz
% dpkg-scansources . /dev/null | gzip > Sources.gz
It gives an error,
** Packages in archive but missing from override file: **
but seems to work anyway.

Now you can use one of the following /etc/apt/sources.list lines:
deb http://shallowsky.com/apt/hardy ./
deb http://shallowsky.com/apt/etch ./

After an apt-get update, it saw pho, but it warned me

WARNING: The following packages cannot be authenticated!
  pho
Install these packages without verification [y/N]?
There's some discussion in the SecureAPT page on the Debian wiki, but it's a bit involved and I'm not clear if it helps me if I'm not already part of the official Debian keychain.

This page on Release check of non Debian sources was a little more helpful, and told me how to create the Release and Release.gpg file -- but then I just get a different error,

 The following signatures couldn't be verified because the public key is not available: NO_PUBKEY
And worse, it's an error now, not just a warning, preventing any apt-get update.

Going back to the SecureApt page, under Setting up a secure apt repository they give the two steps the other page gave for creating Release and Release.gpg, with a third step: "Publish the key fingerprint, that way your users will know what key they need to import in order to authenticate the files in the archive."

So apparently if users don't take steps to import the key manually, they can't update at all. Whereas if I leave out the Release and Release.gpg files, all they have to do is type y when they see the warning. Sounds like it's better to leave off the key. I wish, though, that there was a middle ground, where I could offer the key for those who wanted it without making it harder for those who don't care.

Tags: , , , ,
[ 21:14 Jan 13, 2009    More linux | permalink to this entry | comments ]

Sun, 16 Nov 2008

Cleaning up the edges of Moonroot's transparent images

[moonroot] I wrote moonroot more to figure out how to do it than to run it myself. But on the new monitor I have so much screen real estate that I've started using it -- but the quality of the images was such an embarrassment that I couldn't stand it. So I took a few minutes and cleaned up the images and made a moonroot 0.6 release.

Turned out there was a trick I'd missed when I originally made the images, years ago. XPM apparently only allows 1-bit transparency. When I was editing the RGB image and removing the outside edge of the circle, some of the pixels ended up semi-transparent, and when I saved the file as .xpm, they ended up looking very different (much darker) from what I had edited.

Here are two ways to solve that in GIMP:

  1. Use the "Hard edge" option on the eraser tool (and a hard-edged brush, of course, not a fuzzy one).
  2. Convert the image to indexed, in which case GIMP will only allow one bit's worth of transparency. (That doesn't help for full-color images, but for a greyscale image like the moon, there's no loss of color since even RGB images can only have 8 bits per channel.)

Either way, the way to edit a transparent image where you're trying to make the edges look clean is to add a solid-color background layer (I usually use white, but of course it depends on how you're going to use the image) underneath the layer you're trying to edit. (In the layers dialog, click the New button, chose White for the new layer, click the down-arrow button to move it below the original layer, then click on the original layer so your editing will all happen there.)

Once you're editing a circle with sharp edges, you'll probably need to adjust the colors for some of the edge pixels too. Unfortunately the Smudge tool doesn't seem to work on indexed images, so you'll probably spend a lot of time alternating between the Color Picker and the Pencil tool, picking pixel colors then dabbing them onto other pixels. Key bindings are the best way to do that: o activates the Color Picker, N the Pencil, P the Paintbrush. Even if you don't normally use those shortcuts it's worth learning them for the duration of this sort of operation.

Or use the Clone tool, where the only keyboard shortcut you have to remember is Ctrl to pick a new source pixel. (I didn't think of that until I was already finished, but it works fine.)

Tags: , ,
[ 15:48 Nov 16, 2008    More gimp | permalink to this entry | comments ]

Sun, 09 Nov 2008

Pho 0.9.6 finally released!

[pho image viewer] Pho 0.9.6-pre3 has been working great for me for about a month, and I've been trying to find the time to do a release. I finally managed it this weekend, after making a final tweak to change the default PHO_REMOTE command from gimp-remote to gimp since gimp-remote is obsolete and is no longer built by default.

The big changes from 0.9.5 are Keywords mode, slideshow mode, the new PHO_REMOTE environment variable, swapping -f and -F, and a bunch of performance work and minor bug fixing.

I built deb packages for Ubuntu (Hardy, but they should work on Intrepid too) and Debian (Etch), as well as the usual source tarball, and they're available at the usual place:

http://shallowsky.com/software/pho.

Tags: , ,
[ 18:11 Nov 09, 2008    More programming | permalink to this entry | comments ]

Mon, 03 Nov 2008

A word count bookmarklet

This posting ended up being published as a Linux Planet Quick Tip. You can read about my nifty word counting bookmarklet there: Quick Firefox Tip: Word Count Bookmarklet.

Tags: , , , ,
[ 23:41 Nov 03, 2008    More tech/web | permalink to this entry | comments ]

Mon, 20 Oct 2008

Requesting no window decorations (and moonroot 0.4)

Someone on #openbox this morning wanted help in bringing up a window without decorations -- no titlebar or window borders.

Afterward, Mikael commented that the app should really be coded not to have borders in the first place.

Me: You can do that?

Turns out it's not a standard ICCCM request, but one that mwm introduced, MWM_HINTS_DECORATIONS. Mikael pointed me to the urxvt source as an example of an app that uses it.

My own need was more modest: my little moonroot Xlib program that draws the moon at approximately its current phase. Since the code is a lot simpler than urxvt, perhaps the new version, moonroot 0.4, will be useful as an example for someone (it's also an example of how to use the X Shape extension for making non-rectangular windows).

Tags: , , ,
[ 12:06 Oct 20, 2008    More programming | permalink to this entry | comments ]

Thu, 02 Oct 2008

New Pho 0.9.6-pre3

I've released Pho 0.9.6-pre3. The only change is to fix a sporadic bug where pho would sometimes jump back to the first image after deleting the last one, rather than backing up to the next-to-last image. I was never able to reproduce the bug reliably, but I cleaned up the image list next/prev code quite a bit and haven't seen the bug since then. I'd appreciate having a few testers exercising this code as much as possible.

Otherwise pho is looking pretty solid for a 0.9.6 release.

Tags: , ,
[ 10:57 Oct 02, 2008    More programming | permalink to this entry | comments ]

Sat, 16 Aug 2008

Fast Pixel Ops in GIMP-Python

Last night Joao and I were on IRC helping someone who was learning to write gimp plug-ins. We got to talking about pixel operations and how to do them in Python. I offered my arclayer.py as an example of using pixel regions in gimp, but added that C is a lot faster for pixel operations. I wondered if reading directly from the tiles (then writing to a pixel region) might be faster.

But Joao knew a still faster way. As I understand it, one major reason Python is slow at pixel region operations compared to a C plug-in is that Python only writes to the region one pixel at a time, while C can write batches of pixels by row, column, etc. But it turns out you can grab a whole pixel region into a Python array, manipulate it as an array then write the whole array back to the region. He thought this would probably be quite a bit faster than writing to the pixel region for every pixel.

He showed me how to change the arclayer.py code to use arrays, and I tried it on a few test layers. Was it faster? I made a test I knew would take a long time in arclayer, a line of text about 1500 pixels wide. Tested it in the old arclayer; it took just over a minute to calculate the arc. Then I tried Joao's array version: timing with my wristwatch stopwatch, I call it about 1.7 seconds. Wow! That might be faster than the C version.

The updated, fast version (0.3) of arclayer.py is on my arclayer page.

If you just want the trick to using arrays, here it is:

from array import array

[ ... setting up ... ]
        # initialize the regions and get their contents into arrays:
        srcRgn = layer.get_pixel_rgn(0, 0, srcWidth, srcHeight,
                                     False, False)
        src_pixels = array("B", srcRgn[0:srcWidth, 0:srcHeight])

        dstRgn = destDrawable.get_pixel_rgn(0, 0, newWidth, newHeight,
                                            True, True)
        p_size = len(srcRgn[0,0])               
        dest_pixels = array("B", "\x00" * (newWidth * newHeight * p_size))

[ ... then inside the loop over x and y ... ]
                        src_pos = (x + srcWidth * y) * p_size
                        dest_pos = (newx + newWidth * newy) * p_size
                        
                        newval = src_pixels[src_pos: src_pos + p_size]
                        dest_pixels[dest_pos : dest_pos + p_size] = newval

[ ... when the loop is all finished ... ]
        # Copy the whole array back to the pixel region:
        dstRgn[0:newWidth, 0:newHeight] = dest_pixels.tostring() 

Good stuff!

Tags: , , ,
[ 22:02 Aug 16, 2008    More gimp | permalink to this entry | comments ]

Sat, 09 Aug 2008

GetSET: Teaching Javascript to high school girls

Every summer I volunteer as an instructor for a one-day Javascript programming class at the GetSET summer technology camp for high school girls. GetSET is a great program run by the Society of Women Engineers. it's intended for minority girls from relatively poor neighborhoods, and the camp is free to the girls (thanks to some great corporate sponsors). They're selected through a competitive interview process so they're all amazingly smart and motivated, and it's always rewarding being involved.

Teaching programming in one day to people with no programming background at all is challenging, of course. You can't get into any of the details you'd like to cover, like style, or debugging techniques. By the time you get through if-else, for and while loops, some basic display methods, the usual debugging issues like reading error messages, and typographical issues like "Yes, uppercase and lowercase really are different" and "No, sorry, that's a colon, you need a semicolon", it's a pretty full day and the students are saturated.

I got drafted as lead presenter several years ago, by default by virtue of being the only one of the workshop leaders who actually programs in Javascript. For several years I'd been asking for a chance to rewrite the course to try to make it more fun and visual (originally it used a lot of form validation exercises), and starting with last year's class I finally got the chance. I built up a series of graphics and game exercises (using some of Sara Falamaki's Hangman code, which seemed perfect since she wrote it when she was about the same age as the girls in the class) and it went pretty well. Of course, we had no idea how fast the girls would go or how much material we could get through, so I tried to keep it flexible and we adjusted as needed.

Last year went pretty well, and in the time since then we've exchanged a lot of email about how we could improve it. We re-ordered some of the exercises, shifted our emphasis in a few places, factored some of the re-used code (like windowWidth()) into a library file so the exercise files weren't so long, and moved more of the visual examples earlier.

I also eliminated a lot of the slides. One of the biggest surprises last year was the "board work". I had one exercise where the user clicks in the page, and the student has to write the code to figure out whether the click was over the image or not. I had been nervous about that exercise -- I considered it the hardest of the exercises. You have to take the X and Y coordinates of the mouse click, the X and Y coordinates of the image (the upper left corner of the <div> or <img> tag), and the size of the image (assumed to be 200x200), and turn that all into a couple of lines of working Javascript code. Not hard once you understand the concepts, but hard to explain, right?

I hadn't made a slide for that, so we went to the whiteboard to draw out the image, the location of the mouse click, the location of the image's upper left corner, and figure out the math ... and the students, who had mostly been sitting passively through the heavily slide-intensive earlier stuff, came alive. They understood the diagram, they were able to fill in the blanks and keep track of mouse click X versus image X, and they didn't even have much trouble turning that into code they typed into their exercise. Fantastic!

Remembering that, I tried to use a lot fewer slides this year. I felt like I still needed to have slides to explain the basic concepts that they actually needed to use for the exercises -- but if there was anything I thought they could figure out from context, or anything that was just background, I cut it. I tried for as few slides as possible between exercises, and more places where we could elicit answers from the students. I think we still have too many slides and not enough "board work" -- but we're definitely making progress, and this year went a lot better and kept them much better engaged. We're considering next year doing the first several exercises on the board first, then letting them type it in to their own copies to verify that it works.

We did find we needed to leave code examples visible: after showing slides saying something like "Ex 7: Write a loop that writes a line of text in each color", I had to back up to the previous slide where I'd showed what the code actually looked like. I had planned on their using my "Javascript Quick Reference" handout for reference and not needing that information on the slides; but in fact, I think they were confused about the quickref and most never even opened it. Either that information needs to be in the handout, or it needs to be displayed on the screen as they work, or I have to direct them to the quickref page explicitly ("Now turn to page 3 in ...") or put that information in the exercises.

The graphical flower exercises were a big hit this year (I showed them early and promised we'd get to them, and when we did, just before lunch, several girls cheered) and, like last year, some of the girls who finished them earlier decided on their own that they wanted to change them to use other images, which was also a big hit. Several other girls decided they wanted more than 10 flowers displayed, and others hit on the idea of changing the timeout to be a lot shorter, which made for some very fun displays. Surprisingly, hardly anyone got into infinite loops and had to kill the browser (always a potential problem with javascript, especially when using popups like alert() or prompt()).

I still have some issues I haven't solved, like what to do about semicolons and braces. Javascript is fairly agnostic about them. Should I tell the girls that they're required? (I did that this year, but it's confusing because then when you get to "if" statements you have to explain why that's different.) Not mention them at all? (I'm leaning toward that for next year.)

And it's always a problem figuring out what the fastest girls should do while waiting for the rest to finish. This year, in addition to trying to make each exercise shorter, we tried having the girls work on them in groups of two or three, so they could help each other. It didn't quite work out that way -- they all worked on their own copies of the exercises but they did seem to collaborate more, and I think that's the best balance. We also encourage the ones who finish first to help the girls around them, which mostly they do on their own anyway.

And we really do need to find a better editor we can use on the Windows lab machines instead of Wordpad. Wordpad's font is too small on the projection machine, and on the lab machines it's impossible for most of us to tell the difference between parentheses, brackets and braces, which leads to lots of time-wasting subtle bugs. Surely there's something available for Windows that's easy to use, freely distributable, makes it easy to change the font, and has parenthesis and brace matching (syntax highlighting would be nice too). Well, we have a year to look for one now.

All in all, we had a good day and most of the girls gave the class high marks. Even the ones who concluded "I learned I shouldn't be a programmer because it takes too much attention to detail" said they liked the class. And we're fine with that -- not everybody wants to be a programmer, and the point isn't to force them into any specific track. We're happy if we can give them an idea of what computer programming is really like ... then they'll decide for themselves what they want to be.

Tags: , , ,
[ 12:54 Aug 09, 2008    More education | permalink to this entry | comments ]

Sun, 25 May 2008

Crikey in Python, and generating key events with XTest

A user on the One Laptop Per Child (OLPC, also known as the XO) platform wrote to ask me how to use crikey on that platform.

There are two stages to getting crikey running on a new platform:

  1. Build it, and
  2. Figure out how to make a key run a specific program.

The crikey page contains instructions I've collected for binding keys in various window managers, since that's usually the hard part. On normal Linux machines the first step is normally no problem. But apparently the OLPC comes with gcc but without make or the X header files. (Not too surprising: it's not a machine aimed at developers and I assume most people developing for the machine cross-compile from a more capable Linux box.)

We're still working on that (if my correspondant gets it working, I'll post the instructions), but while I was googling for information about the OLPC's X environment I stumbled upon a library I didn't know existed: python-xlib. It turns out it's possible to do most or all of what crikey does from Python. The OLPC is Python based; if I could write crikey in Python, it might solve the problem. So I whipped up a little key event generating script as a test.

Unfortunately, it didn't solve the OLPC problem (they don't include python-xlib on the machine either) but it was a fun exercises, and might be useful as an example of how to generate key events in python-xlib. It supports both event generating methods: the X Test extension and XSendEvent. Here's the script: /pykey-0.1.

But while I was debugging the X Test code, I had to solve a bug that I didn't remember ever solving in the C version of crikey. Sure enough, it needed the same fix I'd had to do in the python version. Two fixes, actually. First, when you send a fake key event through XTest, there's no way to specify a shift mask. So if you need a shifted character like A, you have to send KeyPress Shift, KeyPress a. But if that's all you send, XTest on some systems does exactly what the real key would do if held down and never released: it autorepeats. (But only for a little while, not forever. Go figure.)

So the real answer is to send KeyPress Shift, KeyPress a, KeyRelease a, KeyRelease Shift. Then everything works nicely. I've updated crikey accordingly and released version 0.7 (though since XTest isn't used by default, most users won't see any change from 0.6). In the XSendEvent case, crikey still doesn't send the KeyRelease event -- because some systems actually see it as another KeyPress. (Hey, what fun would computers be if they were consistent and always predictable, huh?)

Both C and Python versions are linked off the crikey page.

Tags: , , ,
[ 15:50 May 25, 2008    More programming | permalink to this entry | comments ]

Tue, 18 Mar 2008

Setting app name and class in Xlib

I was looking at Dave's little phase-of-the-moon Mac application, and got the urge to play with moonroot, the little xlib ditty I wrote several years ago to put a moon (showing the right phase) on the desktop.

I fired it up, and got the nice moon-shaped window ... but with a titlebar. I didn't want that! Figuring out how to get rid of the titlebar in openbox was easy, just

<application name="moonroot">
    <decor>no</decor>
    <desktop>all</desktop>
</application>
... but it didn't work! A poke with xwininfo showed the likely cause: instead of "moonroot", the window was listed as "Unnamed window". Whoops!

A little poking around revealed three different ways to set "name" for a window: XStoreName, XSetClassHint (which sets both class name and app name), and XSetWMName. Available online documentation on these functions was not very helpful in explaining the differences; fortunately someone hanging out on the openbox channel knew the difference (thanks, Crazy_Hopper). Thus:

I didn't see much in the way of example code for what an app ought to do with these, so I'll post mine here:

    char* appname;
    XClassHint* classHint;
[ ... ]
    if (argv && argc > 1)
        appname = basename(argv[0]);
    else
        appname = "moonroot";

    /* set the titlebar name */
    XStoreName(dpy, win, appname);

    /* set the name and class hints for the window manager to use */
    classHint = XAllocClassHint();
    if (classHint) {
        classHint->res_name = appname;
        classHint->res_class = "MoonRoot";
    }
    XSetClassHint(dpy, win, classHint);
    XFree(classHint);

And if anyone is interested in my silly moon program, it's at moonroot-0.3.tar.gz. moonroot gives you a large moon, moonroot -s gives a smaller one. I'm not terribly happy with its accuracy and wasted too much time today fiddling with it and verifying that it's doing the right time conversions. All I can figure is that the approximation in Meeus' Astronomical Algorithms is way too approximate (it's sometimes off by more than a day) and I should just rewrite all my moon programs to calculate moon phase the hard (and slow) way.

Tags: , ,
[ 21:15 Mar 18, 2008    More programming | permalink to this entry | comments ]

Fri, 29 Feb 2008

Script to add tags

Python is so cool. I love how I'll be working on a script and suddenly think "Oh, it should also do X, but I bet that'll be a lot more work", and then it occurs to me that I can do exactly that by adding about 2 more lines of python. And I add them and it works the first time.

Anyway, it turned out to be very easy to go through all existing blog articles and add tags for the current category hierarchy, being careful to preserve each file's last-modified date since that's what pyblosxom uses for the date of the entry. add-tags.py

Tags: ,
[ 19:37 Feb 29, 2008    More blogging | permalink to this entry | comments ]

Wed, 27 Feb 2008

Got tags working with pybloxsom

Entries on this blog are arranged by category. But all too often I have something that really belongs equally well in two categories. Since pyblosxom's categories follow the hierarchy on disk, there's no way to have an entry in two categories. Enter tags.

Tags are a way of assigning any number of keywords to each blog entry. Search engines apparently pay attention to tags, and most tagged blogs also let you search by tag.

I wanted my tags to follow whatever canonical tag format the big blogging sites use, so search engines would index them. Unfortunately, this isn't well documented anywhere. Wikipedia has a tags entry that mentions a couple of common formats; the HTML format given in that entry (<a rel="tag" ...>) turns out to be the format used on most popular sites like livejournal and blogspot, so that's what I wanted to use. Later, someone pointed me to a much better tag explanation on technorati, which is useful whether or not you decide to register with technorati.

Next: how to implement searching? The simplest pyblosxom tags plug-in is called simply tags.py. All the others are much more complex and do tons of things I'm not interested in. But tags.py doesn't support static mode, and points to a modified tags.py that's supposedly modified to work with static blogs.

Alas, when I tried that version, it didn't work (and an inquiry on the pybloxsom list got a response from someone who agreed it didn't work). So I hacked around and eventually got it working. Here's a diff for what I changed or just the tags-static.py plug-in.

Additional steps I needed that weren't mentioned in tags.py:

I also wrote a little python index.cgi for my blog's /tags directory, so you can see the list of tags used so far. Strangely, tags.py didn't create any such index, and it was easier to make a cgi than to figure out how to do it from a blosxom plug-in.

And as long as I'm posting pyblosxom diffs, here's the little filename diff for 1.4.3 that I apply to pyblosxom whenever I update it, to let me use the .blx extension rather than .txt for my blog source files. (That way I can configure my editor to treat blog files as html, which they are -- they aren't plaintext.)

Anyway, it all seems to be working now, and in theory I can tag all future articles. I'll probably go back and gradually add tags to older articles, but that's a bigger project and there's no rush.

Tags: ,
[ 16:04 Feb 27, 2008    More blogging | permalink to this entry | comments ]

Fri, 12 Oct 2007

PyTopo and PyGTK pixbuf memory leakage

On a recent Mojave desert trip, we tried to follow a minor dirt road that wasn't mapped correctly on any of the maps we had, and eventually had to retrace our steps. Back at the hotel, I fired up my trusty PyTopo on the East Mojave map set and tried to trace the road. But I found that as I scrolled along the road, things got slower and slower until it just wasn't usable any more.

PyTopo was taking up all of my poor laptop's memory. Why? Python is garbage collected -- you're not supposed to have to manage memory explicitly, like freeing pixbufs. I poked around in all the sample code and man pages I had available but couldn't find any pygtk examples that seemed to be doing any explicit freeing.

When we got back to civilization (read: internet access) I did some searching and found the key. It's even in the PyGTK Image FAQ, and there's also some discussion in a mailing list thread from 2003.

Turns out that although Python is supposed to handle its own garbage collection, the Python interpreter doesn't grok the size of a pixbuf object; in particular, it doesn't see the image bits as part of the object's size. So dereferencing lots of pixbuf objects doesn't trigger any "enough memory has been freed that it's time to run the garbage collector" actions.

The solution is easy enough: call gc.collect() explicitly after drawing a map (or any other time a bunch of pixbufs have been dereferenced).

So there's a new version of PyTopo, 0.6 that should run a lot better on small memory machines, plus a new collection format (yet another format from the packaged Topo! map sets) courtesy of Tom Trebisky.

Oh ... in case you're wondering, the ancient USGS maps from Topo! didn't show the road correctly either.

Tags: , , ,
[ 22:21 Oct 12, 2007    More programming | permalink to this entry | comments ]

Tue, 04 Sep 2007

Egg Timer in Python and TkInter

I left the water on too long in the garden again. I keep doing that: I'll set up something where I need to check back in five minutes or fifteen minutes, then I get involved in what I'm doing and 45 minutes later, the cornbread is burnt or the garden is flooded.

When I was growing up, my mom had a little mechanical egg timer. You twist the dial to 5 minutes or whatever, and it goes tick-tick-tick and then DING! I could probably find one of those to buy (they're probably all digital now and include clocks and USB plugs and bluetooth ports) but since the problem is always that I'm getting distracted by something on the computer, why not run an app there?

Of course, you can do this with shell commands. The simple solution is:

(sleep 300; zenity --info --text="Turn off the water!") &

But the zenity dialogs are small -- what if I don't notice it? -- and besides, I have to multiply by 60 to turn a minute delay into sleep seconds. I'm lazy -- I want the computer to do that for me!
Update: Ed Davies points out that "sleep 5m" also works.

A slightly more elaborate solution is at. Say something like: at now + 15 minutes and when it prompts for commands, type something like:

export DISPLAY=:0.0
zenity --info --text="Your cornbread is ready"
to pop up a window with a message. But that's too much typing and has the same problem of the small easily-ignored dialogs. I'd really rather have a great big red window that I can't possibly miss.

Surely, I thought, someone has already written a nice egg-timer application! I tried aptitude search timer and found several apps such as gtimer, which is much more complicated than I wanted (you can define named events and choose from a list of ... never mind, I stopped reading there). I tried googling, but didn't have much luck there either (lots of Windows and web apps, no Linux apps or cross-platform scripts).

Clearly just writing the damn thing was going to be easier than finding one. (Why is it that every time I want to do something simple on a computer, I have to write it? I feel so sorry for people who don't program.)

I wanted to do it in python, but what to use for the window that pops up? I've used python-gtk in the past, but I've been meaning to check out TkInter (the gui toolkit that's kinda-sorta part of Python) and this seemed like a nice opportunity since the goal was so simple.

The resulting script: eggtimer. Call it like this:

eggtimer 5 Turn off the water
and in five minutes, it will pop up a huge red window the size of the screen with your message in big letters. (Click it or hit a key to dismiss it.)

First Impressions of TkInter

It was good to have an excuse to try TkInter and compare it with python-gtk. TkInter has been recommended as something normally installed with Python, so the user doesn't have to install anything extra. This is apparently true on Windows (and maybe on Mac), but on Ubuntu it goes the other way: I already had pygtk, because GIMP uses it, but to use TkInter I had to install python-tk.

For developing I found TkInter irritating. Most of the irritation concerned the poor documentation: there are several tutorials demonstrating very basic uses, but not much detailed documentation for answering questions like "What class is the root Tk() window and what methods does it have?" (The best I found -- which never showed up in google, but was referenced from O'Reilly's Programming Python -- was here.) In contrast, python-gtk is very well documented.

Things I couldn't do (or, at least, couldn't figure out how to do, and googling found only postings from other people wanting to do the same thing):

I expect I'll be sticking with pygtk for future projects. It's just too hard figuring things out with no documentation. But it was fun having an excuse to try something new.

Tags: , ,
[ 14:35 Sep 04, 2007    More programming | permalink to this entry | comments ]

Mon, 11 Jun 2007

Find the nearest matching color name

Someone showed up on #gimp today with a color specified as an HTML hex color specifier, and wanted to know how to find the nearest color name.

Easy, right? There have got to be a bazillion pages that do that, plus at least a couple of Linux apps.

But I googled for a while and couldn't find a single one. There are lots of pages that list all the RGB colors, or convert decimal red, green and blue into HTML #nnn hex codes, or offer aesthetic advice about pleasing combinations of colors for themes (including this lovely page on butterfly-inspired color themes, courtesy of Rik) but nothing I could find that gave color names. Apparently there used to be a Linux app that did that, a piece of Gnome 1 called GColorSel, but it's gone now.

I got to thinking (always dangerous!) ... /etc/X11/rgb.txt has a list of color names with their RGB color equivalents. It would be really easy to write something that just read down the list finding the ones closest to the specified color.

Uh-oh ... of course, once that thought occurred to me, I was doomed. Programmer's disease. I had to write it. So I did, and here it is: Find the Nearest Matching Color Name. It checks against both rgb.txt and the much smaller list of 17 CSS color names.

Tags:
[ 23:18 Jun 11, 2007    More programming | permalink to this entry | comments ]

Tue, 29 May 2007

A Culture of Regressions (or, Why I no longer work on Mozilla)

A couple of friends periodically pester me to write about why I stopped contributing to Mozilla after so many years with the project. I've held back, feeling like it's airing dirty laundry in public.

But a discussion on mozilla.dev.planning over the last week, started by Nelson Bolyard, aired it for me: it was their culture of regressions.

I love Mozilla technology. I'm glad it exists, and I still use it for my everyday browsing. But trying to contribute to Mozilla just got too frustrating. I spent more time chasing down and trying to fix other people's breakages than I did working on anything I wanted to work on.

That might be okay, barely, when you're getting paid for it. But when you're volunteering your own time, who wants to spend it fixing code checked in by some other programmer who just can't be bothered to clean up his own mess?

It's the difference between spending a day cleaning your own house ... and spending every day cleaning other people's houses.

Nelson said it eloquently in this exchange:

(Robert Kaiser writes)
As we are open source, everyone can access and test that code, and find and file the regressions, so that they get fixed over time.

(Boris Zbarsky writes)
That last conclusion doesn't necessarily follow. To get them fixed you need someone fixing them.

(Nelson Bolyard writes)
We're very unlikely to get volunteers to spent large amounts of effort, rewriting formerly working code to get it to work again, after it was broken by someone else's checkin. This demotivates developers and drives them away. They think "why should I keep working on this when others can break my code and I must pay for their mistakes?" and "I worked hard to get that working, and now person X has broken it. Let HIM fix it."

This was exactly how I felt, and it's the reason I quit working on Mozilla.

A little later in the thread, Boris Zbarsky reports that the trunk has been so broken with regressions that it's been unusable for him for weeks or months. (When you have someone as committed and sharp as Boris unable to use your software, you know there's something wrong with your project's culture.) He writes: "For example, on my machine (Linux) about one in three SVG testcases in Bugzilla causes trunk Gecko to hang X ..."

Justin Dolske replies, "Oh, Linux," and asks if it's related to turning on Cairo. Boris replies affirmatively. Just another example where a change was checked in that caused serious regressions keeping at least one important contributor from using the browser on a regular basis; yet it's still there and hasn't been backed out. Of course, it's "only Linux".

David Baron appears to take Nelson's concerns seriously, and suggests criteria for closing the tree and making everyone stop work to track down regressions. As he correctly comments, closing the tree is very serious and inefficient, and should be avoided in all but the most serious cases.

But Nelson repeats the real question:

(Nelson Bolyard writes)
Under what circumstances does a Sheriff back out a patch due to functional regressions? From what you wrote above, I gather it's "never". :(

Alas, the thread peters out after that; there's no reply to Nelson's question.

The problem with Mozilla isn't that there are regressions. Mistakes happen. The problem is that regressions never get fixed, because the project's culture encourages regressions. The prevailing attitude is that it's okay to check in changes that break other people's features, as long as your new feature is cool enough or the right people want it. If you break something, well, hey, someone will figure out a fix eventually. Or not. Either way, it's not your problem.

Working on new features is fun, and so is getting the credit for being the one to check them in. Fixing bugs, writing API documentation, extensive testing -- these things aren't fun, they're hard work, and there isn't much glory in them either (you don't get much appreciation or credit for it). So why do them if you don't have to? Let someone else worry about it, as long as the project lets you get away with it!

A project with a culture of responsibility would say that the person who broke something should fix it, and that broken stuff should stay out of the tree. If programmers don't do that themselves just because it's the right thing to do, the project could enforce it: just insist that regression-causing changes that can't be fixed right away be backed out. Fix the regressions out of the tree where they aren't causing problems for other people. Get help from people to test it and to integrate it with those other modules you forgot about the first time around.

Yes, even if it's a change that's needed -- even if it's something a lot of people want. If it's a good change, there will always be time to check it in later.

When it's really working.

Tags:
[ 11:07 May 29, 2007    More programming | permalink to this entry | comments ]

Sun, 27 May 2007

A Kitfox Extension

For a bit over a year I've been running a patched version of Firefox, which I call Kitfox, as my main browser. I patch it because there are a few really important features that the old Mozilla suite had which Firefox removed; for a long time this kept me from using Firefox (and I'm not the only one who feels that way), but when the Mozilla Foundation stopped supporting the suite and made Firefox the only supported option, I knew my only choice was to make Firefox do what I needed. The patches were pretty simple, but they meant that I've been building my own Firefox all this time.

Since all my changes were in JavaScript code, not C++, I knew this was probably all achievable with a Firefox extension. But never around to it; building the Mozilla source isn't that big a deal to me. I did it as part of my job for quite a few years, and my desktop machine is fast enough that it doesn't take that long to update and rebuild, then copy the result to my laptop.

But when I installed the latest Debian, "Etch", on the laptop, things got more complicated. It turns out Etch is about a year behind in its libraries. Programs built on any other system won't run on Etch. So I'd either have to build Mozilla on my laptop (a daunting prospect, with builds probably in the 4-hour range) or keep another system around for the purpose of building software for Etch. Not worth it. It was time to learn to build an extension.

There are an amazing number of good tutorials on the web for writing Firefox extensions (I won't even bother to link to any; just google firefox extension and make your own choices). They're all organized as step by step examples with sample code. That's great (my favorite type of tutorial) but it left my real question unanswered: what can you do in an extension? The tutorial examples all do simple things like add a new menu or toolbar button. None of them override existing Javascript, as I needed to do.

Canonical URL to the rescue. It's an extension that overrides one of the very behaviors I wanted to override: that of adding "www." to the beginning and ".com" or ".org" to the end of whatever's in the URLbar when you ctrl-click. (The Mozilla suite behaved much more usefully: ctrl-click opened the URL in a new tab, just like ctrl-clicking on a link. You never need to add www. and .com or .org explicitly because the URL loading code will do that for you if the initial name doesn't resolve by itself.) Canonical URL showed me that all you need to do is make an overlay containing your new version of the JavaScript method you want to override. Easy!

So now I have a tiny Kitfox extension that I can use on the laptop or anywhere else. Whee!

Since extensions are kind of a pain to unpack, I also made a source tarball which includes a simple Makefile: kitfox-0.1.tar.gz.

Tags: , , , ,
[ 11:59 May 27, 2007    More tech/web | permalink to this entry | comments ]

Thu, 22 Mar 2007

Pho 0.9.5 Released

Pho 0.9.5-pre5 has been working nicely since I released it two weeks ago. And meanwhile, I've already started working on the next version. So I've released it as 0.9.5 with no changes (except for version string and some updates to the documentation and debian config files).

I made a .deb on Ubuntu Edgy, but haven't actually tested it yet (anyone who sees problems, please let me know) and I'll try to make a straight Debian package on Sarge sometime soon.

So what's this stuff I've been working on for the next version? Image categorization. I shoot so many photos, and categorizing them by keyword can be a lot of work. Although Pho's "Notes 0 through 9" are helpful for a small number of notes, it's tough keeping track of which note corresponds to which keyword when I'm categorizing a directory full of photos from a trip. The next Pho release (which will have a much shorter release cycle than 0.9.5 did, honest!) will have an optional Keywords dialog where you can type in keywords and associate them with photos. I know there are apps such as f-spot, gthumb and Picasa, but they all seem much more heavyweight than what I need, and Pho only needs a tiny bit of work to get there.

While I'm working on dialogs, I'm also cleaning up modality: Pho dialogs will now stay visible so they can't get lost behind the image, and the question dialog ("Really delete?" or "Do you want to quit?" will be modal.

But that's all coming in the next version. For now, 0.9.5 is the stable version: get it from the Pho page.

Tags:
[ 16:56 Mar 22, 2007    More programming | permalink to this entry | comments ]

Tue, 06 Mar 2007

Pho 0.9.5-pre5

Pho's been static for a long time -- it's been working well enough that I keep forgetting that there were a couple of bugs that need fixing for a 0.9.5 release.

I had some time tonight, so I dug in and fixed the bugs I remembered: some issues with zooming in and out, a bug with aspect ratio when switching out of fullscreen mode, and the fact that Note 0 didn't work.

While I was at it, I added an environment variable, PHO_ARGS, where you can preset your default values. I find that I always want -p (presentation mode), so now I can specify that with PHO_ARGS=p, and use pho -P when I want window borders. I also updated the man page.

I'll test this for a little while and if nobody finds any serious bugs, maybe I can finally release 0.9.5.

Get Pho here.

Tags:
[ 00:06 Mar 06, 2007    More programming | permalink to this entry | comments ]

Wed, 28 Feb 2007

Random Wallpaper

I was talking about desktop backgrounds -- wallpaper -- with some friends the other day, and it occurred to me that it might be fun to have my system choose a random backdrop for me each morning.

Finding backgrounds is no problem: I have plenty of images stored in ~/Backgrounds -- mostly photos I've taken over the years, with a smattering of downloads from sites like the APOD. So all I needed was a way to select one file at random from the directory.

This is Unix, so there's definitely a commandline way to do it, right? Well, surprisingly, I couldn't find an easy way that didn't involve any scripting. Some shells have a random number generator built in ($RANDOM in bash) but you still have to do some math on the result.

Of course, I could have googled, since I'm sure other people have written random-wallpaper scripts ... but what's the fun in that? If it has to be a script, I might as well write my own.

Rather than write a random wallpaper script, I wanted something that could be more generally useful: pick one random line from standard input and print it. Then I could pass it the output of ls -1 $HOME/Backgrounds, and at the same time I'd have a script that I could also use for other purposes, such as choosing a random quotation, or choosing a "flash card" question when studying for an exam.

The obvious approach is to read all of standard input into an array, count the lines, then pick a random number between one and $num_lines and print that array element. It took no time to whip that up in Python and it worked fine. But it's not very efficient -- what if you're choosing a line from a 10Mb file?

Then Sara Falamaki (thanks, Sara!) pointed me to a page with a neat Perl algorithm. It's Perl so it's not easy to read, but the algorithm is cute. You read through the input line by line, keeping track of the line number. For each line, the chance that this line should be the one printed at the end is the reciprocal of the line number: in other words, there's one chance out of $line_number that this line is the one to print.

So if there's only one line, of course you print that line; when you get to the second line, there's one chance out of two that you should switch; on the third, one chance out of three, and so on.

A neat idea, and it doesn't require storing the whole file in memory. In retrospect, I should have thought of it myself: this is basically the same algorithm I used for averaging images in GIMP for my silly Chix Stack Mars project, and I later described the method in the image stacking section of my GIMP book. To average images by stacking them, you give the bottom layer 100% opacity, the second layer 50% opacity, the third 33% opacity, and so on up the stack. Each layer makes an equal contribution to the final result, so what you see is the average of all layers.

The randomline script, which you can inspect here, worked fine, so I hooked it up to accomplish the original problem: setting a randomly chosen desktop background each day. Since I use a lightweight window manager (fvwm) rather than gnome or kde, and I start X manually rather than using gdm, I put this in my .xinitrc:

(xsetbg -fullscreen -border black `find $HOME/Backgrounds -name "*.*" | randomline`) &

Update: I've switched to using hsetroot, which is a little more robust than xsetbg. My new command is:

hsetroot -center `find -L $HOME/Backgrounds -name "*.*" | randomline`

So, an overlong article about a relatively trivial but nontheless nifty algorithm. And now I have a new desktop background each day. Today it's something prosaic: mud cracks from Death Valley. Who knows what I'll see tomorrow?

Update, years later: I've written a script for the whole job, randombg, because on my laptop I want to choose from a different set of backgrounds depending on whether I'm plugged in to an external monitor or using the lower resolution laptop display.
But meanwhile, I've just been pointed to the shuf command, which does pretty much what my randomline script did. So you don't actually need any scripts, just

hsetroot -fill `find ~/Images/Backgrounds/1680x1050/ -name '*.jpg' | shuf -n 1`

Tags: , ,
[ 14:02 Feb 28, 2007    More programming | permalink to this entry | comments ]

Fri, 25 Aug 2006

PyTopo 0.5

Belated release announcement: 0.5b2 of my little map viewer PyTopo has been working well, so I released 0.5 last week with only a few minor changes from the beta. I'm sure I'll immediately find six major bugs -- but hey, that's what point releases are for. I only did betas this time because of the changed configuration file format.

I also made a start on a documentation page for the .pytopo file (though it doesn't really have much that wasn't already written in comments inside the script).

Tags: , , , ,
[ 22:10 Aug 25, 2006    More programming | permalink to this entry | comments ]

Tue, 01 Aug 2006

Javascript Warnings Everywhere

I'm working on some little Javascript demos (for a workshop at this summer's Get SET girls' technology camp) so I've had the Javascript Console up for most of my browsing over the last few days. I also have Mozilla's strict Javascript checking on (user_pref("javascript.options.strict", true); in prefs.js or user.js) since I don't want to show the girls code that generates warnings. (Strict mode also reports errors in CSS.)

It's been eye opening how many sites give warnings. You know that nice clean ultra-simple Google search page? One CSS error and one JS warning. But that's peanuts to the pages of errors I see on most sites, and they're not all missing "var" declarations. I have to hit the "Clear" button frequently if I want to be able to see the errors on the pages I'm working on.

And my own sites? Yes, I admit it, I've seen some errors in my own pages too. Though it makes me feel better that there aren't very many of them (mostly CSS problems, not JS). I'm going to keep the JS Console visible more often so I'll see these errors and correct them.

Tags:
[ 00:06 Aug 01, 2006    More programming | permalink to this entry | comments ]

Sat, 03 Jun 2006

Cleaner, More Flexible Python Map Viewing

A few months ago, someone contacted me who was trying to use my PyTopo map display script for a different set of map data, the Topo! National Parks series. We exchanged some email about the format the maps used.

I'd been wanting to make PyTopo more general anyway, and already had some hacky code in my local version to let it use a local geologic map that I'd chopped into segments. So, faced with an Actual User (always a good incentive!), I took the opportunity to clean up the code, use some of Python's support for classes, and introduce several classes of map data.

I called it 0.5 beta 1 since it wasn't well tested. But in the last few days, I had occasion to do some map exploring, cleaned up a few remaining bugs, and implemented a feature which I hadn't gotten around to implementing in the new framework (saving maps to a file).

I think it's ready to use now. I'm going to do some more testing: after visiting the USGS Open House today and watching Jim Lienkaemper's narrated Virtual Tour of the Hayward Fault, I'm all fired up about trying again to find more online geologic map data. But meanwhile, PyTopo is feature complete and has the known bugs fixed. The latest version is on the PyTopo page.

Tags: , , , ,
[ 18:25 Jun 03, 2006    More programming | permalink to this entry | comments ]

Mon, 29 May 2006

Aid for Java Victims

Over dinner, I glanced at the cover of the latest Dr. Dobb's (a new article on Ruby on Rails), then switched to BBC World News. The first Beeb headline was Aid flow begins for Java victims.

I guess I was a little distracted from dinner preparations ... my first thought was "Are they going to give them all copies of Ruby and Rails?"

Then, of course, I remembered the earthquake. Oh, right, those Java victims!

(Not to make light of the situation there, which sounds grim. And just as I was writing this, I got email from the USGS Earthquake Notification Service reporting another aftershock in Indonesia, this one magnitude 5.6. I hope it doesn't make matters worse.)

Tags:
[ 22:05 May 29, 2006    More programming | permalink to this entry | comments ]

Fri, 17 Mar 2006

MySQL Losing its Socket!

This morning I was all ready to continue working on an ongoing web project when I discovered that mysql wasn't running.

That's funny, it was running fine yesterday! I tried /etc/init.d/mysql start, but it failed. The only error message was, "Please take a look at the syslog."

So I hied myself over to /var/log, to discover that mysql.log and mysql.err were both there, but empty.

Some poking around /etc/mysql/my.cnf revealed that logging is commented out by default, because: "# Be aware that this log type is a performance killer."

I uncommented logging and tried again, but /var/log/mysql.err remained blank, and all that was in mysql.log was three lines related basically giving its runtime arguments and the name of the socket file.

Back to the drawing board. I was well aware that I had changed the mysql settings yesterday. See, mysqld on Ubuntu likes to create its socket as /var/run/mysqld/mysqld.sock, but other apps, like Ruby, all expect to find it in /tmp/mysql.sock. It's easy enough to change Ruby's expectations. But then I found out that although the cmdline client mysql also expects the socket in /var/run/mysqld, it depends on something called mysqladmin that wants the socket in /tmp. (I may have those two reversed. No matter: the point is that you can't use the client to talk to the database because it and the program it depends on disagree about the name of the socket. This is probably a Dapper bug.)

Okay, so I had to pick one. I decided that /tmp/mysql.sock was easier to remember and more standard with non-Debian setups. I knew where to change it in the server (/etc/mysql/my.cnf is there and well commented) but the mysql client doesn't use that, and it took some googling and help from clueful friends to find out that what it wanted was a new file called /etc/my.cnf (how's that for a nice clear configuration file name?) containing one line:

socket          = /tmp/mysql.sock

That done, mysql started and ran and everything worked. Woo!

Except that it didn't the following morning after a reboot, and didn't give any error messages as to why.

Off I went on a merry chase across init files: /etc/init.d/mysql calls /etc/mysql/debian-start (which made me realize that debian has added yet another config file, debian.cnf, which has yet another copy of the line specifying the socket filename) which calls /usr/share/mysql/debian-start.inc.sh as well as calling various other programs. But those were all red herrings: the trick to debugging the problem was to run mysqld directly (not via /etc/init.d/mysql start: it actually does print error messages, but they were being hidden by using the init.d script.

The real problem turned out to be that I had changed the location of the socket file, but not the pid file, in /etc/mysql/my.cnf, which was also located by default in /var/run/mysqld. Apparently that directory is created dynamically at each boot, and it isn't created unless it's needed for the socket file (whether the pid file needs it doesn't matter). So since I'd moved the socket file to /tmp, /var/run/mysqld wasn't created, mysqld couldn't create its pid file and it bailed. Solution: edit my.cnf to use /tmp for the pid file.

Tags:
[ 13:29 Mar 17, 2006    More programming | permalink to this entry | comments ]

Mon, 13 Mar 2006

Ruby on Rails on Ubuntu

Back when I laboriously installed Ruby and Rails on Ubuntu "Hoary Hedgehog" (which involved basically ignoring all the Ubuntu packages and building everything, including Ruby itself, from source), I was cheered by the notes in Ubuntu's forums and bugzilla indicating that as of the next release ("Breezy Badger") all the right versions would be there, and all this source compilation would no longer be necessary.

I didn't get around to trying it until today. Breezy and its successor "Dapper Drake" do indeed have a rails package as well as a Ruby package, and I happily installed them. All looked great -- until I actually tried to use them on a real-world application. It turns out that the Ruby and Rails packages don't include gems, Ruby's package manager (similar to the CPAN system familiar to Perl programmers). And gems is required for doing anything useful in Rails.

Drat! After several false starts, I eventually found the instructions on this page. Except that installs way more than seems necessary for what I need to do, and if you copy/paste lines from that page you may end up with a bunch of packages you don't want, like an out of date version of mysql.

So here are simplified instructions for using Ruby on Rails on Ubuntu Breezy or Dapper.

As yourself:

wget http://rubyforge.org/frs/download.php/5207/rubygems-0.8.11.tgz
tar zxvf rubygems-0.8.11.tgz
As root:
cd rubygems-0.8.11
ruby setup.rb
gem install rubygems-update
gem install rails

Say yes to all dependency questions during the gem install of rails. Add your web server and database of choice (you probably already have them installed, anyway) and you should be good to go.

You may note that the page I referenced tells you to install two versions of rails: the Ubuntu package plus the one from gems. At least on Dapper, you don't need both. Installing rails pulls in the packages:
irb irb1.8 libpgsql-ruby1.8 libreadline-ruby1.8 libredcloth-ruby1.8 libruby1.8 rake rdoc rdoc1.8 ruby ruby1.8
I haven't experimented with which of these packages are and are not needed. If you run into problems, some set of packages from this list may solve them.

Update: it seems that none of these are required. Many of them are dummy packages anyway, which contain no files related to the actual package name. For instance, the rake package contains, not rake, but a single bash completion file related to rake. So you should be fine ignoring all of them, installing just Ruby and nothing else.

(I filed bug 34840 requesting that Ubuntu ship a usable version of Rails, since it didn't seem to be filed already.)

Tags:
[ 22:56 Mar 13, 2006    More programming | permalink to this entry | comments ]

Fri, 30 Sep 2005

Set C Styles Automatically in Emacs

I just figured out a nifty emacs trick: how to set C styles automatically based on the location of the file I'm editing. That way, emacs will autoindent with the proper style whether I'm editing my own code, GIMP code, Linux kernel code, etc. assuming that I keep the source for these projects in predictable places.

First, define a derived mode for each style you might want to use. Each minor mode inherits from c-mode, but adds a c-set-style call. (Obviously, this assumes that the styles you want to use are already defined.)

(define-derived-mode gnu-c-mode c-mode "GNU C mode"
  (c-set-style "gnu"))
(define-derived-mode linux-c-mode c-mode "GNU C mode"
  (c-set-style "linux"))

Then add entries to your auto-mode-alist for each directory you want to treat specially:

(setq auto-mode-alist
[ . . . ]
      (cons '("gimp.*/" . gnu-c-mode)
      (cons '("linux-.*/" . linux-c-mode)
            auto-mode-alist) ) )

Your normal default style will still work for any files not matching the patterns specified in auto-mode-alist.

It might be possible to skip the step of defining derived modes by switching on the file's pathname in the C mode hook, but I've never figured out how to get the pathname of the file being loaded in any reliable way.

Tags:
[ 13:42 Sep 30, 2005    More programming | permalink to this entry | comments ]

Tue, 26 Jul 2005

New Pho

Pho 0.9.5-pre4 seems to be working pretty well and fixes a couple of bugs in pre3, so I posted a tarball. I really need to quit this pre- stuff and just release 0.9.5. Soon, really!

Tags:
[ 12:51 Jul 26, 2005    More programming | permalink to this entry | comments ]

Tue, 21 Jun 2005

A Fast Volume Control App

I updated my Debian sid system yesterday, and discovered today that gnome-volume-control has changed their UI yet again. Now the window comes up with two tabs, Playback and Capture; the default tab, Playback, has only one slider in it, PCM, and all the important sliders, like Volume, are under Capture. (I'm told this is some interaction with how ALSA sees my sound chip.)

That's just silly. I've never liked the app anyway -- it takes forever to come up, so I end up missing too much of any clip that starts out quiet. All I need is a simple, fast window with a single slider controlling master volume. But nothing like that seems to exist, except panel applets that are tied to the panels of particular window managers.

So I wrote one, in PyGTK. vol is a simple script which shows a slider, and calls aumix under the hood to get and set the volume. It's horizontal by default; vol -h gives a vertical slider.

Aside: it's somewhat amazing that Python has no direct way to read an integer out of a string containing more than just that integer: for example, to read 70 out of "70,". I had to write a function to handle that. It's such a terrific no-nonsense language most of the time, yet so bad at a few things. (And when I asked about a general solution in the python channel at [large IRC network], I got a bunch of replies like "use int(str[0:2])" and "use int(str[0:-1])". Shock and bafflement ensued when I pointed out that 5, 100, and -27 are all integers too and wouldn't be handled by those approaches.)

Tags: , , ,
[ 15:54 Jun 21, 2005    More programming | permalink to this entry | comments ]

Wed, 13 Apr 2005

PyTopo 0.3

I needed to print some maps for one of my geology class field trips, so I added a "save current map" key to PyTopo (which saves to .gif, and then I print it with gimp-print). It calls montage from Image Magick.

Get yer PyTopo 0.3 here.

Tags: , , ,
[ 17:56 Apr 13, 2005    More programming | permalink to this entry | comments ]

Sat, 09 Apr 2005

Python Expose vs. Focus

A few days ago, I mentioned my woes regarding Python sending spurious expose events every time the drawing area gains or loses focus.

Since then, I've spoken with several gtk people, and investigated several workarounds, which I'm writing up here for the benefit of anyone else trying to solve this problem.

First, "it's a feature". What's happening is that the default focus in and out handlers for the drawing area (or perhaps its parent class) assume that any widget which gains keyboard focus needs to redraw its entire window (presumably because it's locate-highlighting and therefore changing color everywhere?) to indicate the focus change. Rather than let the widget decide that on its own, the focus handler forces the issue via this expose event. This may be a bad decision, and it doesn't agree with the gtk or pygtk documentation for what an expose event means, but it's been that way for long enough that I'm told it's unlikely to be changed now (people may be depending on the current behavior).

Especially if there are workarounds -- and there are.

I wrote that this happened only in pygtk and not C gtk, but I was wrong. The spurious expose events are only passed if the CAN_FOCUS flag is set. My C gtk test snippet did not need CAN_FOCUS, because the program from which it was taken, pho, already implements the simplest workaround: put the key-press handler on the window, rather than the drawing area. Window apparently does not have the focus/expose misbehavior.

I worry about this approach, though, because if there are any other UI elements in the window which need to respond to key events, they will never get the chance. I'd rather keep the events on the drawing area.

And that becomes possible by overriding the drawing area's default focus in/out handlers. Simply write a no-op handler which returns TRUE, and set it as the handler for both focus-in and focus-out. This is the solution I've taken (and I may change pho to do the same thing, though it's unlikely ever to be a problem in pho).

In C, there's a third workaround: query the default focus handlers, and disconnect() them. That is a little more efficient (you aren't calling your nop routines all the time) but it doesn't seem to be possible from pygtk: pygtk offers disconnect(), but there's no way to locate the default handlers in order to disconnect them.

But there's a fourth workaround which might work even in pygtk: derive a class from drawing area, and set the focus in and out handlers to null. I haven't actually tried this yet, but it may be the best approach for an app big enough that it needs its own UI classes.

One other thing: it was suggested that I should try using AccelGroups for my key bindings, instead of a key-press handler, and then I could even make the bindings user-configurable. Sounded great! AccelGroups turn out to be very easy to use, and a nice feature. But they also turn out to have undocumented limitations on what can and can't be an accelerator. In particular, the arrow keys can't be accelerators; which makes AccelGroup accelerators less than useful for a widget or app that needs to handle user-initiated scrolling or movement. Too bad!

Tags: , , ,
[ 21:52 Apr 09, 2005    More programming | permalink to this entry | comments ]

Wed, 06 Apr 2005

PyTopo is usable; pygtk is inefficient

While on vacation, I couldn't resist tweaking pytopo so that I could use it to explore some of the areas we were visiting.

It seems fairly usable now. You can scroll around, zoom in and out to change between the two different map series, and get the coordinates of a particular location by clicking. I celebrated by making a page for it, with a silly tux-peering-over-map icon.

One annoyance: it repaints every time it gets a focus in or out, which means, for people like me who use mouse focus, that it repaints twice for each time the mouse moves over the window. This isn't visible, but it would drag the CPU down a bit on a slow machine (which matters since mapping programs are particularly useful on laptops and handhelds).

It turns out this is a pygtk problem: any pygtk drawing area window gets spurious Expose events every time the focus changes (whether or not you've asked to track focus events), and it reports that the whole window needs to be repainted, and doesn't seem to be distinguishable in any way from a real Expose event. The regular gtk libraries (called from C) don't do this, nor do Xlib C programs; only pygtk.

I filed bug 172842 on pygtk; perhaps someone will come up with a workaround, though the couple of pygtk developers I found on #pygtk couldn't think of one (and said I shouldn't worry about it since most people don't use pointer focus ... sigh).

Tags: , , , ,
[ 17:26 Apr 06, 2005    More programming | permalink to this entry | comments ]

Sun, 27 Mar 2005

Python GTK Topographic Map Program

I couldn't stop myself -- I wrote up a little topo map viewer in PyGTK, so I can move around with arrow keys or by clicking near the edges. It makes it a lot easier to navigate the map directory if I don't know the exact starting coordinates.

It's called PyTopo, and it's in the same place as my earlier two topo scripts.

I think CoordsToFilename has some bugs; the data CD also has some holes, and some directories don't seem to exist in the expected place. I haven't figured that out yet.

Tags: , , , ,
[ 18:53 Mar 27, 2005    More programming | permalink to this entry | comments ]

Topographic Maps for Linux

I've long wished for something like those topographic map packages I keep seeing in stores. The USGS (US Geological Survey) sells digitized versions of their maps, but there's a hefty setup fee for setting up an order, so it's only reasonable when buying large collections all at once.

There are various Linux mapping applications which do things like download squillions of small map sections from online mapping sites, but they're all highly GPS oriented and I haven't had much luck getting them to work without one. I don't (yet?) have a GPS; but even if I had one, I usually want to make maps for places I've been or might go, not for where I am right now. (I don't generally carry a laptop along on hikes!)

The Topo! map/software packages sold in camping/hiking stores (sometimes under the aegis of National Geographic are very reasonably priced. But of course, the software is written for Windows (and maybe also Mac), not much help to Linux users, and the box gives no indication of the format of the data. Googling is no help; it seems no Linux user has ever tried buying one of these packages to see what's inside. The employees at my local outdoor equipment store (Mel Cotton's) were very nice without knowing the answer, and offered the sensible suggestion of calling the phone number on the box, which turns out to be a small local company, "Wildflower Productions", located in San Francisco.

Calling Wildflower, alas, results in an all too familiar runaround: a touchtone menu tree where no path results in the possibility of contact with a human. Sometimes I wonder why companies bother to list a phone number at all, when they obviously have no intention of letting anyone call in.

Concluding that the only way to find out was to buy one, I did so. A worthwhile experiment, as it turned out! The maps inside are simple GIF files, digitized from the USGS 7.5-minute series and, wonder of wonders, also from the discontinued but still useful 15-minute series. Each directory contains GIF files covering the area of one 7.5 minute map, in small .75-minute square pieces, including pieces of the 15-minute map covering the same area.

A few minutes of hacking with python and Image Magick resulted in a script to stitch together all images in one directory to make one full USGS 7.5 minute map; after a few hours of hacking, I can stitch a map of arbitrary size given start and end longitude and latitude. My initial scripts, such as they are.

Of course, I don't yet have nicities like a key, or an interactive scrolling window, or interpretation of the USGS digital elevation data. I expect I have more work to do. But for now, just being able to generate and print maps for a specific area is a huge boon, especially with all the mapping we're doing in Field Geology class. GIMP's "measure" tool will come in handy for measuring distances and angles!

Tags: , , ,
[ 12:13 Mar 27, 2005    More programming | permalink to this entry | comments ]

Wed, 16 Mar 2005

Kitfox: Firefox for People Who Liked the Mozilla Browser

Debate rages on the mozilla-seamonkey list since the Mozilla Foundation announced that there would be no 1.8 release of the Mozilla browser (also called "the suite", or by its code name, "seamonkey"). Suite users are frustrated at lack of notice: anyone who was paying attention knew that seamonkey was going to be dropped eventually, but everyone expected at least a 1.8 final release. Mozilla.org is frustrated because they wish suite users would quit whining and switch to Firefox. Various people are slinging flames and insults, while a few try to mediate with logic and sense. There's a volunteer effort ramping up to continue support for the suite, but no plans for what to do about fixing all the regressions. Go read the list if you want all the gory details.

Anyway, the writing on the wall (and on the newsgroup) is clear: if you want a browser with continuing support from mozilla.org, Firefox is your only choice. Unfortunately for Linux users, firefox is designed by and for Windows users, copying Internet Explorer's user interface and dropping support for a number of nice features which the old mozilla browser offered.

I've decided that the best way to get a usable browser is to take firefox and put back the mozilla features that I miss. Mostly these are easy user interface tweaks. I pulled a tree last week and had most of the items that were blocking me addressed in a few hours. Building wasn't entirely straightforward: the build page doesn't make all the options clear, like the fact that xft and freetype are both enabled by default, so one of them has to be explicitly disabled. Updating the tree turns out to be a bit problematic: firefox' build dependencies turn out to be dicy, so sometimes changing a single .xul file causes the entire tree to rebuild, while other times an update builds a few files and the resulting build fails to run, and requires a clobber and a rebuild. Still, those problems are relatively minor.

So far, I have fixes for these bugs:

Next up: try to figure out why firefox takes so much longer than mozilla to start up. Fortunately, once it's up, it seems just as fast at browsing, but startup takes forever, and firefox doesn't even offer a splash option to tell me that something is happening.

Here is my patch, in case anyone else is bothered by these issues.

Perhaps this could be built as an extension. Some day I'll look into that. Certainly the current set of patches could be implemented as a script which exploded, edited, and re-packed the .jar archives in a firefox binary build, since the patch touches no C++ code as yet.

I'm calling my firefox-derived browser "Kitfox".

Tags:
[ 12:03 Mar 16, 2005    More programming | permalink to this entry | comments ]

Tue, 09 Nov 2004

New Crikey!

Crikey 0.5 is out, with some changes contributed by Efraim Feinstein -- it can read from stdin now, and has a debug flag. Reading from stdin means you can generate multi-line text now.

It's so cool when people send patches to my programs! Especially when they're nice clean code implementing useful features. Thanks, Efraim!

Tags:
[ 22:39 Nov 09, 2004    More programming | permalink to this entry | comments ]

Sun, 10 Oct 2004

Pho 0.9.5-pre3: evil Metacity window sizing

It turns out that the problem with pho windows not resizing in metacity is this: when metacity sees a window that's slightly larger (in either dimension) than the screen size, it unilaterally makes that window maximized, and thereafter refuses any request from the app to resize the window smaller.

Mandatory maximize might actually be useful in some circumstances (anyone who's ever tried to run on an 800x600 laptop has doubtless seen dialogs which don't fit on the screen) but the subsequent refusal to resize makes little sense, and causes bustage in programs which work fine under other window managers.

A workaround is for pho to unmaximize before any window resize. This would be a bummer with an app where the user might click the maximize button manually; with pho, that's unlikely (I hope) because anyone who wants to run maximized is better off running in fullscreen/presentation mode (which now finally sets its background to black, hooray).

Get'cher Pho 0.9.5-pre3 here.

Tags:
[ 19:21 Oct 10, 2004    More programming | permalink to this entry | comments ]

Fri, 01 Oct 2004

New showpix script now uses PHP

I'd been meaning for ages to write a PHP version of my showpix.cgi Perl script, to show images without needing a separate .html file generated for each image. I finally did it this morning, and it was much easier than I expected, and seems to run a lot faster than the perl CGI (not surprising, since PHP is cached in our web server and perl isn't; so this should be more scaleable and less load on the server).

The hardest part was writing the Python script to generate a new showpix.php for a directory of images, and that only because of all the escaping of quotes that needed to be done when telling python to print a line that tells php to print a line to serve up over http ...

Anyway, I've converted the Flume Trail images to use the new PHP stuff, and I've updated the page for the Imagebatch scripts to include PHP ability.

Tags:
[ 15:40 Oct 01, 2004    More programming | permalink to this entry | comments ]

Thu, 30 Sep 2004

Pho 0.9.5-pre2

I finally gathered together some of the pending fixes for Pho, so it keeps better track of where it is in the linked list when deleting or jumping around. No progress on the focus handling, though. I sure would like to solve that before 0.9.5 final.

Tags:
[ 22:42 Sep 30, 2004    More programming | permalink to this entry | comments ]

Tue, 28 Sep 2004

ADA doesn't apply to the web?!

The 11th Circuit Court of Appeals ruled last Friday that the Americans with Disabilities Act does not apply to the web. In other words, web designers are not required to make their pages accessible to readers with disabilities.

That seems very odd. It's required of software -- several lawsuits have been filed (and settled) related to inaccessible software. Accessibility is required universally for web sites in the EU, and I thought it was required of US government sites as well. It's a shame the court felt that it wasn't important for web sites in general.

Accessibility helps everybody, not just people with disabilities (even setting aside the fact that most of us will eventually experience some impairment, if we live long enough). Accessible web sites are usually easier to read and navigate, and translate more easily to offline readers (such as Sitescooper) and PDA readers (such as Opera and Plucker).

The decision was "largely on procedural grounds" and the court suggested that the decision could be revisited in a future case. I hope that case comes soon, before US web designers conclude there's no point in designing for accessibility.

W3C Web Content Accessibility Guidelines 2.0.

Tags:
[ 13:05 Sep 28, 2004    More programming | permalink to this entry | comments ]

Sun, 25 Jul 2004

XShapeCombineMask generates an Expose loop

I tried moonroot on blackbird today, under icewm for the first time. It went into an infinite expose/redraw loop. It turns out that XShapeCombineMask (the call that sets the shaped window's shape mask) generates an extra Expose, which of course happens asynchronously so disabling expose handling in the draw routine doesn't help.

What does help is maintaining a static variable to ensure that it only shapes the window the first time, and not on subsequent draws.

I also tweaked sonypid.c a bit -- 2.4.25 is generating two jogdial-release events whenever the machine resumes from bios suspend. But there's no jogdial-press event corresponding, so I fixed sonypid to ignore jogdial release unless there's already been a jogdial press (again, maintaining a static variable; I already had one so that it doesn't trigger a release after an UP+PRESSED or DOWN+PRESSED event, so I just had to tweak that code a little). That should eliminate that annoying paste that was happening every time I resumed from suspend.

Wish sonypi would quit changing, though I shouldn't complain since it's also good to see that it's still being worked on.

Tags:
[ 23:55 Jul 25, 2004    More programming | permalink to this entry | comments ]

Wed, 14 Jul 2004

Pho Makefile now builds under either gtk1 or gtk2

I fixed the pho Makefile to build under either gtk1 or gtk2, which required some grody bash code. I wish I knew of a better way to do that. Aren't massive API changes fun? Whee!

Patti told me about a park I didn't know about: the Ulistac Natural Area. I drove by it today after Toastmasters. It's a very small linear park along a levee, with one wooded area and a longer and skinnier open area. I didn't go in -- the combination of migraine and hot sun sapped my interest in a walk.

Tags:
[ 20:00 Jul 14, 2004    More programming | permalink to this entry | comments ]

Sun, 11 Jul 2004

Rootmoon

I finally whipped up an app I've been thinking about for a while: a little moon drawn with a shaped window, so you can put it on your root like that OS X moondock applet that Dave uses. Partly it was an excuse to play with shaped windows, which I hadn't used before, and partly it's that I hate seeing stuff that OS X can do that Linux can't.

I put it up (currently temp-named "moonroot" on my software page.

I also caught some nice shots of a hummer at the feeder (through the screen) and added them to my Hummingbird Photo page. A fairly productive day, really, including a nice hike.

Tags:
[ 20:00 Jul 11, 2004    More programming | permalink to this entry | comments ]

Thu, 08 Jul 2004

Pho and window managers

I worked some more on pho yesterday, trying to fix some of the problems Dave has been seeing in other window managers, particularly Metacity. I got rid of most of the problems, but metacity still asks him to place and size the initial window. I have no idea why; I'm specifying initial window size, and no other program asks him that. I also haven't solved the problem of the window getting focus after it resizes out from under the mouse cursor (in lots of window managers, such as openbox). Typing that, I just got a brainstorm: maybe it's a race condition, that the focus loss doesn't happen until after expose, but the "ask for focus" happens before the resize has propogated through the X server. Must try!

Tags:
[ 15:00 Jul 08, 2004    More programming | permalink to this entry | comments ]

Wed, 07 Jul 2004

Rewrote Pho

I finally got around to rewriting pho! The code is much cleaner now -- images are stored as structs in a linked list, no more motley collections of weird global arrays. Code is factored better, and it builds for gtk2. But the main motivation for rewriting it was to have it make a new window for each new or resized image, replacing the old window, to solve a bunch of window manager bugs I keep hitting:

I handed the new pho to Dave for testing, and he hates the "new window each time" model; it takes too long in the window managers he runs. He says he wasn't that bothered by the repainting/resizing problems.

Fortunately, the rewrite factored the code so that it should be easy to provide both options (that was the plan anyway), isolated in the NewWindow routine. So I'll put back the "resize and reposition existing window" code, as a switchable option, and maybe try to grab focus to solve the pointer focus issues that have been plaguing me. I don't know what to do about the window manager resize/repaint issues; more research is required.

Tags:
[ 23:00 Jul 07, 2004    More programming | permalink to this entry | comments ]

Syndicated on:
LinuxChix Live
Ubuntu Women
Women in Free Software
Graphics Planet
DevChix
Ubuntu California
Planet Openbox
Devchix
Planet LCA2009

Friends' Blogs:
Morris "Mojo" Jones
Jane Houston Jones
Dan Heller
Long Live the Village Green
Ups & Downs
DailyBBG

Other Blogs of Interest:
DevChix
Scott Adams
Dave Barry
BoingBoing

Powered by PyBlosxom.