If all else fails, try Preview

November 25th, 2010

I’m totally amazed at all the things Preview does in OSX Snow Leopard. I already use it for knocking out backgrounds, using the “Instant Alpha” tool in the “Select” dropdown. But what happened today is more interesting.

To my everlasting regret I got me a Canon Lide 60 scanner a couple of years ago. Canon’s hardware is pretty nice, but their driver support stinks, especially for OSX. This scanner costs me on average much more work than it should to keep going. Same for my Pixma 5200 Canon printer, by the way. Awful.

Anyway, I needed to scan a page from a mag to show on a slide. Hooked up the scanner, tried Canon Toolbox, and sure enough “Failed to open driver”. Internet next, user groups, downloads, complicated shit about uninstalling, reinstalling, rebooting the Mac Pro ten times. No joy. After a few hours (!) of this, I got an inspiration: hey, since I saw “Twain” mentioned, maybe Acrobat Pro 9 (CS4) could import it, instead of using Canon Toolbox? Sure enough, Acrobat found the scanner, looked it over, and promptly crashed.

And then I got my second inspiration: check out OSX Preview. And yes, that one worked. Not only that, but it automatically calibrated the scanner, proceeded to analyze the page, divided it into sections, scanned it, and served it up already partitioned into useful chunks. See the screenshots below. All the time I was just sitting there watching, doing nothing. The only thing I had to do was select the image and hit cmd-R twice to turn it the right way up.

Jeez, that innocent looking little Preview app is becoming mighty useful for any number of things.

I probably should mention that the driver I installed came in a file called “lide60osx11131en.dmg” to be found, somehow, on Canon’s support site. It installs both the drivers and the toolbox, but the toolbox doesn’t work.

Don’t do parts.parts

November 17th, 2010

I just found another weirdness in Apple’s Objective C. If you have the same name for several components of an object path, the runtime starts stuttering and behaving very badly. Intermittently, you can’t scroll, or only extremely slowly, everything turns into molasses. No errors, mind, just mindbogglingly slow.

This stuttering and slowness seems to affect the simulator, mainly, not so much if you run code on your device (in my case the iPad). Also, if you run under instruments, things work better (Murphy strikes again), except I had it stutter under “Allocations” but not under other instruments.

I had these repeat names in two places in my code and resolving those two seems to have fixed the stuttering entirely. To illustrate what I mean, see this code snippet:

for (IDRBase *idr in self.parts.parts) {
   if ([idr isSuperCell]) break;
   CGRect newFrame = myCGRectDown(hugeFrame, verticalOffset);
   UIView *view = [idr getAsView:newFrame];
   if (view) {
      [returnView addSubview:view];
      verticalOffset += view.frame.size.height;
   }
}

(Yes, it’s the same snippet as in the previous example with nil or not nil. Coincidence. My code does indeed consist of more than just this.)

In the very first line you see a property referred to as “self.parts.parts”. That is what screws things up. Even though the two names “parts” refer to entirely different things, and even though the code actually delivers the values it should, this repetition of identical literal names confuses the crap out of the runtime, it seems. The solution is to not name these two things the same, of course. In this case, I changed the innermost “parts” to “aParts”, resulting in “self.parts.aParts”, and hey presto, no stuttering no more.

Fighting the good fight

October 22nd, 2010

Comment spam on a site like this is out of this world. Completely nuts. I just looked over the stats and this is what they look like:

Click the image for a better look at it. No, you’re not misreading it, 99.27% of all comments I get are spam. Un-frickin-believable. Only between one and five a month get through and have to be manually removed.

Without Akismet, there’s no way I’d allow comments at all. Killing on average 30 spam comments a day to get one sensible comment a week is not what I’d call a fruitful use of time.

Even the machine agrees

October 8th, 2010

Picture this. I’m a little nervous, not much, but a little, since I’m to present my first running code on an iPad of my own entirely different idea of what a medical record should actually look like. I’m presenting this to a group of industry folks, doctors, and professors of different kinds, a power group. So we park the car at Karolinska Hospital in Stockholm and I swipe my credit card in the parking ticket dispenser and this is what comes out:

Now, seriously, if even the parking ticket dispenser calls you “leet”, how can you lose? It went beyond great, by the way. But I can’t figure out how the machine knew that in advance.

Is it nil or isn’t it?

October 4th, 2010

A bit of Objective-C weirdness I don’t quite get. The weirdness occurs if “view” in the code below is nil, that is if [idr getAsView:newFrame] returns nil. You’d expect the verticalOffset not to be incremented, but it is. Even though view is nil, view.frame.size.height still evaluates to “21″ (in this case) and verticalOffset gets incremented. (The first returned view was 21 high, the second returned view was nil in the debug run I’m referring to.)

- (UIView *)getAsView:(CGRect)theFrame {
	CGFloat verticalOffset = 0.0;
	CGRect hugeFrame = myCGRectHuge(theFrame);
	IDVBase *returnView = [[IDVBase alloc] initWithFrame:hugeFrame];
	hugeFrame = myCGRectResetToZero(hugeFrame);

	for (IDRBase *idr in self.parts.parts) {
		CGRect newFrame = myCGRectDown(hugeFrame, verticalOffset);
		UIView *view = [idr getAsView:newFrame];
		[returnView addSubview:view];
		verticalOffset += view.frame.size.height;
	}
	[returnView sizeToFit];
	return [returnView autorelease];
}

To avoid the unintended increment, I have to add the conditional if (view) {…} (which is a pretty good idea anyway, since it’s rather pointless, or maybe even bad, to add a nil as a subView):

- (UIView *)getAsView:(CGRect)theFrame {
	CGFloat verticalOffset = 0.0;
	CGRect hugeFrame = myCGRectHuge(theFrame);
	IDVBase *returnView = [[IDVBase alloc] initWithFrame:hugeFrame];
	hugeFrame = myCGRectResetToZero(hugeFrame);

	for (IDRBase *idr in self.parts.parts) {
		CGRect newFrame = myCGRectDown(hugeFrame, verticalOffset);
		UIView *view = [idr getAsView:newFrame];
		if (view) {
			[returnView addSubview:view];
			verticalOffset += view.frame.size.height;
		}
		[returnView addSubview:view];
		verticalOffset += view.frame.size.height;
	}
	[returnView sizeToFit];
	return [returnView autorelease];
}

I don’t know what the moral of this story is. I assume that if I should delve into the language reference, this will turn out to be undefined behaviour. Trying to figure out what could have been defined behaviour, I can’t come up with a reasonable answer, so I guess it’s just one of those things you should avoid doing. Maybe a compiler warning would have been nice, though.

An ode to Juniper

October 1st, 2010

I have a Juniper SSG-5 and the school I’m doing the network setup for also got one identical unit on my recommendation. I wanted to set up a fixed VPN between the two but failed miserably, so I logged a support request with Juniper on my machine, which is still in warranty but without any kind of support contract. Oh, boy, do these guys have great service.

After just a day I got an engineer connecting to my system with desktop sharing software and we together went through a number of different configurations. It wasn’t really trivial, since the first config took us nearly three hours. Then I had another question of how to implement more finegrained control over the firewall policies in one direction, but not the other, which had us online another two hours using desktop sharing. The final result was perfect and I’ve learned so much more about the details of autokey VPN tunnels.

I’m totally blown away by the level and quality of support I got for this issue from Juniper. Maybe this particular engineer was exceptionally good and persistent, but I have the impression that it is more of a rule with Juniper. When I bought the SSG-5 I thought it was a little expensive, but after this experience, I’ve totally changed my mind. The support level and quality makes it worth the price hands down.

No, I don’t have shares in Juniper, but after this experience I think I may get some.

Breaking news!

September 5th, 2010

Hania, my woman and wife, was one of the winners of the Benzelius price from the Royal Society of Science in Uppsala, Sweden, for her Ph.D. work in digital geometry and combinatorics on words (she had to help me spell this). She got this on the 300 year anniversary of the society, which was set up by such luminaries as Anders Celcius and Carl Von Linné. The Royal Society of Science in Uppsala is actually the oldest academic society in Sweden.

I’m so proud I’m certain to break some vital organ if I don’t calm down.

Method forwarding in Objective-C

August 13th, 2010

I scraped together the following from a number of sources and got it to work fine and without compiler warnings. Interesting links are, among others:

http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

http://macdevelopertips.com/objective-c/objective-c-categories.html

But, I get ahead of myself. The idea here is to have one object forward method invocations to another. Typically, you have a class responding to method A, then you decide to make an instance of that class a member of an outer class and any messages for the methods sent to the outer class should be forwarded to the inner class if that’s where they are to be handled. And, of course, you don’t want to sit and write all these methods again in the outer class just so the inner class gets called. An example:

I have a class I call IotaDOM2TableProxy which has a method “rowsInSection:”. The class IssueWorksheet holds an instance of IotaDOM2TableProxy as a property. I now want any “rowsInSection” messages sent to the outer IssueWorksheet to be forwarded to the inner IotaDOM2TableProxy object without having to declare any “rowsInSection” method for the IssueWorksheet class. This is how you do it.

Firstly: the target, the IotaDOM2TableProxy class, needs no modification at all. It never knows what hit it, or rather, where the invocations come from. So we’ll not say anything more about it than that it contains the declaration of the “rowsInSection:” method (among a load of other stuff):

@interface IotaDOM2TableProxy : NSObject {
...
}
- (NSUInteger)rowsInSection:(NSUInteger)section;
@end

The IssueWorksheet class holds an instance of the IotaDOM2TableProxy, and does not declare any method called “rowsInSection”:

@interface IssueWorksheet : NSObject {
    IotaDOM2TableProxy *dom2tableProxy;
}
@property (nonatomic, retain) IotaDOM2TableProxy *dom2tableProxy;
@end

To make IssueWorksheet forward any calls it doesn’t know about to IotaDOM2TableProxy, we have to override three methods in IssueWorksheet:

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [super respondsToSelector:aSelector] ||
           [self.dom2tableProxy respondsToSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig;
    sig = [super methodSignatureForSelector:aSelector];
    if (sig == nil)
        sig = [self.dom2tableProxy methodSignatureForSelector:aSelector];
    return sig;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    SEL aSelector = [invocation selector];
    if ([self.dom2tableProxy respondsToSelector:aSelector])
        [invocation invokeWithTarget:self.dom2tableProxy];
    else
        [self doesNotRecognizeSelector:aSelector];
}

And this works beautifully for one, two, or any number of methods to forward. There’s just one problem: the compiler complains that IssueWorksheet “may not respond to method…” and we should always eliminate warnings. The easiest way is to declare the forwarded methods in a category, which only mildly defeats the purpose of the exercise. But it works and is safe. You do that by adding the category after the interface declaration of IssueWorksheet, so the header file will now look as follows in its entirety:

//
//  IssueWorksheet.h
//  iotaPad1
//
//  Created by Martin on 2010-06-08.
//

#import 
#import "IotaDOM2TableProxy.h"

@interface IssueWorksheet : NSObject {
    IotaDOM2TableProxy *dom2tableProxy;
}

@property (nonatomic, retain) IotaDOM2TableProxy *dom2tableProxy;

- (id)initFromFile:(NSString *)name ofType:(NSString *)type;

@end

@interface IssueWorksheet (ForwardedMethods)
- (NSUInteger)rowsInSection:(NSUInteger)section;
@end

In the above, I call the category “ForwardedMethods”, but you can call it anything you like. The name doesn’t matter and isn’t referenced anywhere else, but the category must have a unique name of some sort.

Please note: there is no implementation in the .m file for “rowsInSection”. The category definition suffices to shut the compiler up.

EHR systems are liars

July 29th, 2010

I’m just copying a post here I just did to a closed forum for CISSPs.

A couple of days ago, I had to create a death certificate in Cosmic, the EHR system produced by Cambio Healthcare Systems and used in many provinces of Sweden and increasingly abroad.

So, I opened up the records for the patient, created a new death certificate form and filled it in. Printed it out, since it needs to go the paper route to the IRS (in Sweden, they handle the population registry). Then, just to make sure my data matched the EHR entry I made a few days before, I opened up the form again and discovered four different entry fields had changed after I saved. Two adress fields were blanked, my “place of employment” was changed to “Summer house” (part of another field I had filled in) and finally, my telephone number I had added was blanked out. I corrected the fields and resaved, same thing happened again. Did it three times, same thing. I never signed the document, of course, instead having a secretary scan in my paper form, which was correct, and have that put in the EHR. The erroneous form remains there, but unsigned.

I pointed out this severe bug to the IT department, and the reply I just got went into some depth explaining to me what the different fields were supposed to contain, but they didn’t touch at all on the hairraising fact of changing the documents behind my back. That’s apparantly entirely ok for them.

In this scenario, I never signed, but if I had done that, nothing would have played out differently. The scary thing is that the normal workflow is to fill in a form, any form, print it out (optionally), then sign it, which flags it as signed and saves it in one operation. You never see what actually gets saved with your “signature” on it. We’ve had a number of bugs before, where dates were changed in sick leave forms, a number of crucial fields erased and so on, so this is just the last in a long series of such bugs.

This system, the largest on the Scandinavian market, uses Acrobat Reader (yes, you read that right, *Reader*) to fill in forms. So they prepare the form data in the background, launch the Reader, lock it down modally since they can’t handle the interactions right, then let you edit and save. The “save” and “signature”, even “delete” buttons are implemented *inside* the document form since they run modally. Just to give you an idea of the “leading edge technology” we’re talking about here.

The forms as such are designed by the end-user organisation, so the problem is in two parts: Cambio enables a sloppy workflow and does not respect the immutability of signed data in their application. The end-user organisation does not test new forms for problems.

So, my issues with all this are:

1. This product has passed CE approval. So where is the systems test? These problems are trivial to find before rollout. Not to mention that I, and others, have pointed these form problems out in public since at least two years. What’s the point of the CE, anyway?

2. If Cosmic is able to change the content of forms behind my back, why isn’t this recorded in a log? There is no way I can show after the fact that the form contains stuff I never wrote, even if I would be able to remember what I wrote and this has caused much consternation before with the sick leave forms. Why isn’t audit trailing of this a requirement from the user organisation or from the CE protocol?

3. Why does the system not warn me or show me the changed information during or after signature? It bloody well warns me for everything else I don’t need warnings for. A typical Windows app, if you get my drift.

4. Why doesn’t the “signature” mean anything? It’s simply a flag set in the system with no functional binding to the information. They’re in the process of rolling out smart cards now; I have one. You stick them into a slot on the keyboard to sign in, at least that’s the idea (doesn’t work, they don’t have the trusted root installed…). But that’s for Windows login. The “signature” in the EHR remains a dumb flag AFAIK.

Meanwhile, the law and regulations governing medical practice make a huge deal out of these signatures. We *have* to sign stuff in a timely fashion and can be sanctioned if we don’t. And if we do sign, we’re held to what we sign, legally, morally, ethically. Our careers can be held hostage by a stupid flag in a stupid database record, designed by an irresponsible designer, and implemented by an agile and equally uninformed coder.

My question is this: is this shitty state of affairs, this total ignorance of what the law and regulations say, this total lack of interest in quality and consistency in application design and implementation, something common to EHR systems everywhere? Is this laissez-faire attitude something you actively try to combat as security professionals if you work in the medical field, and if not, why not?

Or, provocatively, I’ve repeatedly heard on this list (it’s a while since last time) that doctors don’t respect security in EHR systems, but now my question is this: does anyone else? It seems not.

And finally, WTF is the point of the CE approval…? I’ve seen all the cynical answers, now I want a real answer somehow.

A failure of leadership

June 21st, 2010

My previous post got a few reactions from the IT people, all of them sounding as virgins having their panties pulled down. To say they didn’t like it is the understatement of the day. Which leads me to conclude I wasn’t clear enough. It also points to something being seriously wrong with their idea of their role, so let’s clarify that, too. I understand what brought us to this, namely “democracy in the workplace”, but if this is the price we have to pay for it, it’s too expensive by far. Nothing is worth this degree of dereliction of duty.

Healthcare in Sweden, as everywhere else, has one well-defined and unassailable goal and that is to make and keep the population as healty as possible, or some other variation of the Hippocratic oath. That’s what it’s for, nothing more, nothing less. The providers of this service are doctors, nurses, and other paramedicals. To support them in their work, we have IT staff, administrative staff, housekeeping, etc. There is no ambiguity in the roles or lines of authority here. Doctors at the top, nursing and paramedicals under them, except in certain areas of care where nursing and paramedicals work independently under their own authority. Nowhere is IT to be seen in this diagram, since IT staff have no authority of any kind in healthcare. They should have absolutely no say in how healthcare is provided or even with what means it is provided. Theirs is to do what we tell them to do as well as they can. But it seems they’ve lost track of this along the way.

In Sweden, the IT staff in many places has taken it upon themselves to decide what equipment and software doctors and nurses should use. It’s no wonder it has turned into a total disaster. These people have no idea what this stuff is supposed to be used for, they don’t have the training for it, and naturally, I pointed this out with my usual tact and finesse, resulting in the virginal yelps of affront. Amidst the whining, the best offer I got was that they’re willing to sit down on neutral territory for open discussions about what can be done. You must be kidding me!

Now get this, IT support people: you are support people. That means, you’re not to question how healthcare is to be done. You are not to question what we need in the form of IT to do our job. You have one task, and one task only, and that is to provide the medical staff with the best IT support you possibly can. If you’re not willing or able to do that, you shouldn’t be in this business.

Now get this, medical managers: you should never have let IT people misunderstand their role this badly. It’s up to you to clearly state the goals of the organizations and see to it that everyone in the organization understands and supports that goal and keep their noses pointed in the right direction. You’ve failed in that and now you have to fix it!

As things are now, the healthcare IT people behave as if they’re Santa Claus in disguise. If you get a working machine from them, they expect a big smile and a big thanks. And if you’re naughty, you’ll have to wait another year for your gift.

We doctors are also to blame. In our efforts to be nice to people, we have let them believe it’s ok to have to beg them for machines, and by implication, that they can reward us if we’re nice to them. These machines aren’t toys, they are the means we must have to perform our primary task, and that is, as you conveniently seem to have forgotten, taking care of patients. This situation could only arise due to a failure of leadership, and lack of a firm directives given to IT support departments. We’ve let them stray from their task, because we didn’t pay attention when we should have.

IT people, listen up now: your behaviour and your attitude, as I and most of my collegues have encountered it, is inexcusable. Don’t come to us telling us what we can or cannot do. Come to us only to ask what you can do to help improve healthcare, nothing else. And I’d strongly advise all healthcare staff to adopt the same attitude. We have a serious and dangerous attitude problem here, and it’s time IT support got a grip on reality and started supporting us instead of playing “Animal Farm” and keep sabotaging healthcare.

I’m sure there are well-meaning and capable people in healthcare IT support in our provinces, and I love you all. But please, make yourself heard and noticed, will you?