| AI & nets |
[16 Jan 2009|02:09pm] |
The bpnn.py module is rather nice and does the job but it's rather slow. I am modifying the code to use numpy which should make it faster. So far, it's been an easy process except for some of the backpropogation method's code which is a bit confusing. I will have it done and it will make a difference, but not just yet. :-/
I wondered whether it's worth writing in a lower level language? Something like Ada would provide a large speed bonus. I even considered something like C# more as a programming exercise but haven't got around to a nice design yet. C++ or C would probably be the fastest but I don't feel too good using them. Perhaps I should grow a pair? A C module would probably do the trick and compilers are plenty but I prefer an interface with Python and would like something more object-oriented. Having said that, each network is quite fixed so a struct would probably do better. In fact, that's probably a good way to go particularly if I make a dll / shared lib which can interface with Python code readily.
The biggest problem I have now is speed. The system I'm using requires the use of several networks all working together. The numpy version should work well but C might be a better choice in the long run because each analysis run takes quite a long time (about 1 hour) which slows down development and learning.
|
|
| ASUS EEE |
[02 Oct 2008|03:17pm] |
Been having lots of fun with this lately. I managed to install some games (including Duke Nukem 3D - how sad!) which makes it fun but it's far from a toy. It will be useful also for developing and testing my program's interface - if my program can work on an 800 x 480 screen, then it should be able to work in most places.
Talking of which, I've been re-thinking the choice of GUI toolkit. My original choice was wx, but I'm tending towards others now.
wx - I know it well and it is quite capable. Against is the lack of a decent HTML renderer (ie, with ECMAScript and CSS) QT - has a good HTML renderer but costs for commercial licenses Tk - venerable and very well-tested but may be limited in terms of look 'n' feel, and again doesn't have an adequate HTML renderer that I can think of. It does however have the TkCanvas widget which is tremendously useful. Also means a much smaller download as it is already in Python.
The program has been written so that the GUI front end is separate from the business side of the code. This means that I can swap different bits in and out as I see fit which is good. However, I want to make the right choice as early as possible so that time isn't wasted.
|
|
| Memory fun |
[25 Sep 2008|07:43pm] |
Crucial really came through with the memory - it has already been delivered so my laptop now has a whopping 512MB and my ASUS a paltry 2GB. Hmm, I've stated this wrong somehow... ;-)
I have a couple of USB sticks coming (8GB each). I was thinking of using one initially to install Ubuntu-eee on the ASUS but after that I can give one to Jell to store her photos and keep one myself for computer stuff. It will be useful to transfer things between another computer and the ASUS. There is also a very cheap USB SD card reader on its way which could be useful - especially as Jell takes so many pictures and puts them onto a computer. Having a small thumbdrive would be useful. She has one already but it's older and too small to store much. An 8GB one should keep a lot of pictures and videos.
The Toshiba laptop now feels a little bit quicker though of course running XP and applications (even Photoshop CS3!) slows it right down to a crawl. When I got it, it only had 128MB which was fairly minimal. I installed a single extra 256MB stick and this made an enormous improvement. Changing the original 128MB for the new 256MB will make a difference again but not nearly as dramatic.
Aside from that I've been very busy though I woke up late today after wrestling all night with a difficult problem. I still haven't got it satisfactorily sorted yet and I need to get it done quickly. It's down to how IE renders HTML across different browsers - of course, it can't be straightforward :-/ so all kinds of hacks have to be introduced which I'm not happy with but it has to be done.
In other news, someone contacted me with a business opportunity - a genuine one it sounds like. I'll do some investigating but do not really think that there is much use for it right now.
I'll be home in 2 weeks time. It has been wonderful to come over and visit family but I must admit that I'm looking forward to being back with my wife and baby. Jell sent me lots of pictures and 2 videos earlier today and baby is just wonderful! Heh, we parents are always fascinated, aren't we?
What I thought of doing (and this is just an idea) was to install different operating systems on the USB - I would put Windows, BSD, Solaris, Linux or whatever. Then, if I need to use an OS, I can boot from the USB drive! It's not necessary for the laptop as that will have lots of HD space anyway, but it would be good for the ASUS which can boot from a USB device.
|
|
| Google Analytics is borked |
[15 Jul 2008|06:26pm] |
Okay, it's not official, but I think it's best to consider Google Analytics as a toy rather than a serious commercial tool.
That's a big claim from a single bloke to make - what on earth justifies me arguing against Google's PhDs and money?
Well I have a PhD too and know how to use a computer properly. But checking the user:number 1 website just now with the Analytics tool, I was surprised. A few days ago, a recent (ie, three days ago) visitor from the US had disappeared. I only had one so it was obvious (only one? hey, the site has just been launched!). It was there though.
So I checked my logs and found that I've had lots of visitors from the US. I check the domain when recording them so I can tell if they're spiders. These visitors are not but rather from consumer and business-orientated ISPs. In other words, ordinary people. Unless someone is trying to establish a search engine using their home connection.
And there were several more visits, similar IPs with the same geographic location, all from the US. Still not showing up. I even had a visitor from Redmond in Washington State which hasn't showed up and neither has the person from Germany. None of these are showing up at all in analytics even though they have visited my site.
The only explanation I can think of is that they visit with Javascript disabled, most likely for security reasons (which is an excellent reason). I have it disabled myself using FireFox's NoScript extension which is brill.
But if a lot of my visitors are not using Javascript, Google's Analytics tool won't be showing them up. So no record there.
But surely, there are only a few of them? Well almost half of them so far for my site. Admittedly, I am getting the more curious of web surfers but they will still figure. Either way, the Analytics tool is not fully capable of providing a complete and reliable analysis of a website's visitors.
Yeah, but it's information and it's easy to install. Well, it is easy to install, but the information is incorrect. A business targeting developers (who are more likely to block Javascript I would guess) could be missing out on an important part of their market.
So it's time to go back to the server logs and learn how to use them properly. It's hard work, but the information is better and it will help any company that does to be more competitive than those that do not.
|
|
| Indexing etc |
[14 Jul 2008|12:41am] |
The consultancy website user:number 1 has now been indexed by Google, Yahoo and MSN which is good. I've been developing the tutorial scripts and they're going okay except for the analysing data one which I think I will scrap and begin again with. It certainly seems feasible to make them though and they could be valuable resources.
Jell is due to give birth this Friday which is the big news. We're all getting quite nervous now about this, though in a happy way. A lot of people are waiting to see the little girl when she's born. She is going to be brought into a world where people will love her and want her and that's more than some have. She's doing fine so far. It's going to be a long week for all of us and especially Jell. I'm so proud of her.
|
|
| Usability development |
[11 Jul 2008|11:08am] |
The usability consultancy is slowly coming along. I've announced the website at a few search engines (Google are slow though - I guess they can afford to be: no worries, it will show up in time) and I have started marketing albeit quite quietly until I can say that the website is ready for promotion. I need to update the staff pages mostly.
I've also started a blog at blogger. I've kept it separate from this one because it focuses entirely on usability whereas this one covers lots of stuff. I've written a couple of articles already and feel that I can write away quite happily there about the topic.
The company is even planning to make some movies about how to do usability testing. These will be short but extremely dense clips of testing methods and when to use and not use them. Oh, and how to analyse data from those methods too. This should be useful to people wishing to do usability work and who don't have the resources available to hire someone else. It should also work as a promotional tool too. There are a few clips out there already but the production isn't so good. This kind of clip needs to be very densely packed with information so people can learn something.
I've also been fixing my old Fed 2 camera and making it all ship-shape and ready for work. Curiously, I found that in the UK (ie, cold weather), it was working fine. Here, (ie, in hot weather), the curtain was slow. I suspected that curtain tension was not strong enough so I've tightened it up since and it seems to be okay.
The RangeFinderFilipinas website also has second hand cameras for sale! I just missed out on a Zorki 6 which was just as well as we badly need the money for baby. Still, if the business works out as well as I hope, then it should be a good place to go to buy film and maybe cameras and lenses too!
|
|
| user:number 1 launch |
[09 Jul 2008|08:43am] |
|
This is a semi-official announcement of user:number 1, an international usability consultancy that I have founded. The company is not yet formed but will be soon (a UK company) and we will be dealing in offshore user testing here in the Philippines.
|
|
| Am seriously impressed... |
[09 Jun 2008|11:24am] |
I am getting seriously even more impressed with wxPython. I read about pivot tables today - I never used them in my work, preferring to use general stats programs rather than a spreadsheet to calculate my work - and they are way good, much like the ideas I had myself.
But can we implement them in SOFA? We certainly can! wxPython can embed widgets already into the wxHTML widget easily by importing the wxptag module and then calling for the widget within the html code itself. This means that it should be a doddle to implement pivot tables. These look nice than the idea I had for using context menus, plus I will look at what pivot tables can already do and I may try to see if I can't add a few features (or make them far more "usable").
This wxptag thing looks seriously cool indeed, and it's just made my job much easier. The only drawback is that I will have to go back and change the descriptive statistics code. For statistics that need extra values (like the confidence level or the alpha), I need to get the value before I can print the table (though it might be fun to show the table all completed except for those values that need the extra information and instead have a single widget where the user can enter the details - and once they do so, the values appear as if by magic!
But this needs a strong re-working of the various table modules (in particular the descriptivestatistics module by which this program will live or die). Either way, we are stealing a march on the competition already with the level of interaction we can provide.
in terms of good looks, perhaps each table should have a little "edit this table..." link which brings up the widgets for editing. During editing, it won't look attractive at all, but the final result can look good.
Plus the code will be easier to transport to remote HTML (the proper HTML) which might be able to use AJAX type requests to get the proper information from the server.
but yes, I am seriously impressed with wxPython again!
|
|
| Mods & SPSS files |
[07 Jun 2008|02:56am] |
I merged the SPSS file import into the main program and it works nicely indeed! I had to change the DO to alter its response when a complete variable object was sent, but that was trivial coding.
The code comes up rather nicely in the GUI! I was quite surprised to see it looking so nice there. And when the data came up after an import, I was very chuffed indeed.
I've also decided to scale back or dump the use of the event handler. In most cases, I didn't need to use it. The SPSS import features on the GUI for example always and only needed the SPSS import module so there was no need to route their communications via the CEH. Instead, I just passed them directly which is okay. I will do the same for the other front end inputs.
There still might be a role for the CEH, but a smaller one than is current. This should speed the program up considerably too.
I also need to make a module for holding all system-wide variables (akin to globals, but better and more safely designed). This could also hold references to all the global objects and modules there (such as the dataobject which is needed in lots of places).
|
|
| Adventures in binary file formats |
[04 Jun 2008|09:41am] |
I've been busy writing a module to read SPSS files. It's not too straight forward because the format is entirely binary, and the results are to be presented as an object.
However things are going well! It seems to be able to read compressed data well enough for one of the test files I have but not for the other which is a shame. I'm sure that the bug is quite a simple one to solve and the module should be working very soon. This software might be useful outside of SPSS and our program too. I might release it separately somehow. Personally I think it improves upon existing versions because it offers a nice object oriented API which is easier to call from another program.
Back to work to find out what the problem with importing the compressed data is!
|
|
| Factorial ANOVA with large data sets |
[29 May 2008|04:27am] |
I've been working more on the factorial ANOVA code for large data sets (these aren't quantified other than by saying they are too large to fit in memory). The important thing is obviously to load only those data that are needed. In the case of large returns, it is important to load a part of the data at a time rather than trying to cram everything into memory.
SQLAlchemy can easily help with this. This is done using the LIMIT and OFFSET keywords during a select operation.
After importing:
from sqlalchemy import Table, Column, MetaData
then sort everything out ready for the SELECT operation:
tm = Table(obj.NewTableName, self.meta, autoload=True) # gets table metadata s = select([tm], limit=10, offset=20)
This returns records 21-30 (offset of 20 and a limit of 10 records to be returned).
I have a feeling that SQLAlchemy is clever in that it only retrieves those records that are needed; and so could automatically deal with large data sets. However, I would prefer to have the fine grained control of using this type of method. A function to retrieve would be something like:
offst = 0 lim = 1000
while 1: s = select([tm], limit=lim, offset=offst) # test for end of record - if none then break out # do processing here offst = offst + lim
Which is a nice and simple way to do things. In fact, I think that I could program this code more easily than the routine I did that held everything in memory - purely because the database does the matching of the indices so I just need to write a suitable SELECT statement instead of writing my own matching code (which is easily possible - I've done it - but the DB code is more terse).
In other news, I was thinking of writing an article about implementing a Python interpreter. This might be useful to people wishing to put in interpreter into their own programs. I know of a magazine (I think it's still running) that might be willing to publish it too. I'll need to search.
|
|
| Code analysis |
[28 May 2008|02:34pm] |
I've been having lots of fun going over my code with PyChecker. I've tried PyLint, and while it's useful, it seems very pedantic. I must learn how to set the defaults.
The code I've written generally seems okay. The only module that needed lots of changes was one that I refactored recently (and didn't do so well enough), but I've caught a lot of the errors now. The best bit is submitting a module to analysis and seeing only 1 or 2 errors come up, preferably something like importing a module that isn't used. That's easily put right.
Of course, PyChecker cannot guarantee that code is totally reliable (which is one problem with a dynamically typed language), but it does help to reduce the risks.
Most communication within the program is via objects. If it isn't a straightforward object a -> object b transaction (eg, something like the event handler), then I can check the type of object as it comes in. My code does this already which helps to reduce errors because if the right type of object is coming in, then an error is less likely. For example, if a new source is made available for import and data are imported, the signal gets sent to any objects watching which might be a database object, CSV object, Excel object etc. This could cause problems as a spreadsheet tries to import data that only come from a database - so checking the type of call (ie, it asks itself, "did this come from a spreadsheet?") helps to reduce problems. There is a slight overhead in this though and I want to think about the best way around it.
|
|
| Lots of fun |
[21 May 2008|09:47pm] |
So looking into how the program will handle factorial anova, it is important to remember that it doesn't work like a normal program. The competition just outputs the results and makes it available if assigned to a variable. That's okay, but won't work well for the type of persistent user interaction I am aiming for.
What I mean is this: I want the user to be able to analyse data and interact with the results. All well and good and easily possible. But I also want them to be able to save the output and load it again at a later date: and then be able to continue the interaction!. This is important for me because it removes some of the "difficult" barriers that computer systems currently have - they should work around the user, not vice versa.
So how can this be done? When old data are reloaded, how can everything necessary be recalled into the program to enable interaction? Say the user produces a factorial anova and they save the results. They come back weeks later, and realise that they want to perform simple effects to test the interactions. I could have them repeat the analysis from scratch, which is what the competition does.
Or, I could have them simply reload the data, and have the interaction capabilities available right from the start without them needing to repeat anything, or even load up the original data (which might have changed, been deleted, or whatever - mistakes happen).
When a factorial anova is performed, it will produce a results object that will contain all the results and also other pertinent details (such as cell means/variances etc). All this information should (in theory - I need to check) be sufficient to allow users to perform all the further analysis they need.
The best way that I can think of is using the embedded HTML code to contain a pickled representation of the results objects. The Pickle module can pickle an object to a string representation and this can be buried (along with the time stamp to identify it) within a HTML comment. With a little bit of parsing, the pickled object will then be available for reproduction and therefore further interaction. All the user does is reload the data, the system checks that it is its own HTML, and it can then unpickle all the objects along with their time-stamps ready for interaction.
And instead of actually loading them upon loading, it could just load them upon request so that memory overload is kept low. Though slightly slower (though not noticeably for our typical users), this would help during the automation of projects that require vast amounts of analysis. Say several thousand ANOVA are produced, keeping the results tables in storage (though still in the HTML - work that one out Al!) should reduce overhead. Perhaps we can set an option for users - a setting for vast amounts of data will cause the program to take steps to preserve as much memory as possible; whereas for most of us (myself included) will not need this so it's not so important.
I've checked out the pickling module and its string representation of the object is quite neat and ready to fit into a HTML comment.
Whether we go for loading the objects in at load or only from the page one at a time, either way, we will offer our users persistence and a much better user experience.
|
|
| Problem... |
[21 May 2008|05:21pm] |
I've just noted a minor problem with numpy. For this factorial ANOVA code, I have to compare a set of index combinations against the actual index of the variables to check which group a measurement should be in. All well and good except for one thing:
When the index contains a zero, it automatically defaults to False, even if the match is perfect.
So comparing [3,2,4] with [3,2,4] will produce True but [3,0,4] and [3,0,4] will produce false even though the comparison holds the same weight. This is because 0 is a value for False, and any presence of False implies that the rest is False. In this case, it doesn't hold because the zero is not meant to be a representation of False. It is just meant to be a plain comparison of two integer vectors; and as such, the [3,0,4] above equates to equal and should be True not False. Sadly, this is one occasion when the language is standing in the way of work.
I did find a way around it though - I tested for an equal (which gives a matching boolean array in return); and storing this into a variable (t) allowed a test for all (t.all()) with True being a match and False being a mismatch.
It's just a little confusing to find things not operating as they should. I had to examine the sample output and only after a few tests did I see that the indices with 0 in produced nothing. The new method works nicely though.
The only drawback I have is that the routine doesn't work for univariate anova. That's a shame because one routine for the lot would be nice. However, there are so many special cases with univariate anova that it's probably best for me to rely upon the simple routine that I've already written.
Now that the fanova routine is producing the groups, I can begin the actual analysis. The analysis itself (the real statistical content) is fine and should be quite easy. The real problem is setting things up so that the analysis will proceed through different designs without having to cludge the code.
|
|
| More finishing and polishing |
[18 May 2008|11:20am] |
I've been making the finishing touches to the import data GUI - things like making sure that the used sources list works properly (it does now), and that everything does what it should when it should and nothing else. It's actually a bit of good fun to put the polish onto this work.
In other news, we're out of a name because our current choices are too close to competition. We have been brainstorming a little but with not much success outside of one idea (which I won't discuss here due to squatters - just in case! :-P ) but we need our legal advice before we can proceed with this.
I've been spending lots of time thinking about how to do the graphics stuff. I still think that the GraphicsContext is the way to do for this purely because of the quality of output (the anti-aliasing will make a large difference to graph quality when viewed on screen). It's not the fastest way to do things, but speed won't actually be too important for this matter. We're unlikely to be drawing millions of shapes, though possibly thousands. Either way, it's not too much of a problem. It might be better to put a faster but "sparer" version for mass production in somewhere.
I've also been considering factorial ANOVA too. It is quite important that we offer this (our market research shows a need for it), and curiously, the biggest problem is not the statistics code, but the interface and architecture. We also need a sane way to do post-hoc tests, simple effects, interaction effects and so on - something that is easy for users to produce. I have my ideas already... ;-)
|
|
| Security |
[16 May 2008|10:58am] |
71 responses to the survey now! That is far more than I ever expected and great news for us.
I also need to consider security issues. In particular, I mean sanity-checks on input whether from users or data sources. If we can check that the incoming data are sane, we can reduce possible errors.
The problem is that I'm not experienced in this area so I'm not sure what to look for or what to handle. I need to do some reading on this to see what other people consider is important.
|
|
| Starting graphics |
[16 May 2008|10:38am] |
The next major step is to start the graphics. Although I have entirely neglected this aspect, it is quite an important part of analysis and far more than just eye-candy.
I tried using graphics before on wx and wasn't too successful, so I have to be careful to have a good design.
The obvious thing is to set up wx objects that store positions, but instead, I think I will set all objects up in a more complex arrangement.
First of all, the architecture. Graphs of our sort, are either simple objects or composites. The overall is a composite, as is the actual graph itself (the space where the bars, lines etc are). The graph is otherwise composed of fairly simple separate and non-overlapping bits. This is what I have:
Frame - the overall thing, this is a composite Title - text Subtitle - text Axes titles - text Axes indicators - text Axes major tickmarks - graphics Axes minor tickmarks - graphics Chart - composite Legend - composite
And that's it. Not all elements will be needed. A layout algorithm of some sort will be needed to make things fit as they should. Some things will need minimum sizes like the Chart which cannot be made so small as to be invisible. In that case, there will be limits on the other objects sizes. The composites are made of other things, so the legend has:
Legend title - text Legend itemtype - graphic Legend itemtext - text
Users will be able to edit things. Generally, things can be present or absent (except the "Chart" itself). Text should be fully editable and the font (size, colour, font etc) can be changed.
But how to store all this information? The obvious thing is not to store the absolute positions, but rather to store an abstraction - and I would suggest a proportion (0.0 = one edge, 1.0 = the other edge). Then, when needed to be drawn onto screen or bitmap, the size of the chart area can be used to calculate the absolute coordinates. This can be used also to draw things in sub-areas too like the Chart. Instead of using wx objects and calling a method every time, it might be best to store each object as a reference in a list. This list is linked in turn to a numpy array which holds the proportional coordinates. In the case of re-sizing a window then, the absolute coordinates can be very quickly calculated for a complex graph. In some ways, it's like the dataObject holding a list of references to the variable objects. This allows very quick access indeed, and the coords of every object can be calculated in a few simple steps.
I still need to think about this more and work out the code design. I also need to consider how to display best. Our plan is to store the graphic purely in memory. When it is needed, the absolute coords are calculated and then drawn with reference to the list of graphics objects. This should work for drawing to bitmap for display in the HTML output display or if the user wants a bitmap image; or a vector graphic for publication quality display; or to a wx frame for display ready to edit.
Reading up about display, it seems like the wxGraphicsContext seems to work quite well and is fast. I think we need to use this for the display of editing and holding the memory image. PIL should be able to convert to bitmaps; SVG output can be done by hand fairly easily from the memory; other vector formats, I'm not sure about though ReportLab should handle the PDF output easily.
The only regret I have about using Python is that you cannot limit the type of coordinate used. They will be specified as floats which is okay, but in languages like Ada, you can limit them so that no value could be over 1.0 or under 0.0. Good programming should avoid this problem, but it's nice to have the safety net.
|
|
| Analysis loops |
[15 May 2008|12:21pm] |
I have encountered one problem: the interactive code is getting up and working now so I can view the source of a variable and have it displayed on the results which is actually quite a nice little feature.
However, part of the interaction allows users to redo analysis and the architecture doesn't allow this easily. The problem is in the descriptivestatistics module and its interaction with the dataObject module. The dataObject is fine and just needs a method to re-establish an analysis, and then I need to adjust the Describe function (in descriptivestatistics) so that it can pipe results. Although, perhaps it just needs a new event instead?
In fact, a new event type being sent would be a much better solution. I just need to communicate to the DO that the analysis is an adjustment and this determines whether a NewTable or AmendTable event is sent out. The former would append a new table to the results whereas the latter would insert it as required.
I chose to deal with the interactive "dynamic" html by making a few methods that locate the table and use its position to replace / insert before / insert after the string that needs to go in. It all worked so easily and quickly that I'm beginning to get scared - I might actually be a competent programmer after all! :-/
|
|
| Replacing HTML code |
[15 May 2008|01:19am] |
One of the main parts of having dynamic results is that the HTML code may be interacted with by the user, if they wish. This means adding and removing variables, tests, and other forms of goodness. Messing around extensively with AJAX is one possibility, but the humble wxHTML doesn't do JavaScript (ECMA Script). So instead, I have to take the HTML code, find the relevant bit and snip it out, and in its place, insert the new code. That is done. I was a bit concerned that this would be a hard task (text processing is not my strongest point), but it came off really well. A minor correction was needed because I formatted a HTML comment tag wrongly, and another because I didn't set the name tag to jump to properly; and it came up trumps!
Python is so good at things like this and it's a blessing to program with when the results happen so easily. It was quite fast too. Now I need to implement the feedback loop so that results can be recalculated and fed to the replace code and voila, the user will have interactive tables.
|
|
| Importing data and html tags |
[14 May 2008|02:42pm] |
The import data dialog is now coming along. Once I had resolved the problems, the CSV module went in nicely enough though a few rough edges need to be sorted out and a way to infer the variable types accurately needs to be done too. The final import should be finished as well which means that Ecstatistics can import variables easily enough. The only problem is that I wrote the CSV module before I had finalised the API for the frontend objects so it's a little bit "unsorted". It imports all the data in one go which is probably a mistake especially for large files. It would be better just to import the first 41 lines for browsing and then import the data entirely when the user wants to import it.
I also sorted out the HTML name tag problem I had. The html output wasn't jumping to the end of the document so the first analyses results were showing. I found out that the name tag cannot have decimal points in there. I used the time of analysis as a tag, but Python shows system time as a float. I have removed the extra information and now it jumps nicely.
There might be an opportunity to hawk my software around somewhere soon. I'm not sure just yet and the software isn't really ready yet for industrial use. I need to secure it a lot more (because in my testing, I have been the one to use it and I know how it goes - other people will use it in ways that I could not anticipate). Things like ensuring all the inputs to and from databases are secure and won't cause overflow problems; that the interface is sane and makes sense; and probably because some type of graphing is absolutely necessary to a program like this.
But more work has been done and more steps have been trod on the long path to finishdom (haha! is there such a place?!)
|
|
| navigation |
| [ |
viewing |
| |
most recent entries |
] |
| [ |
go |
| |
earlier |
] |
|
|
|
|