Sign in to follow this  
Followers 0
pjkim

SubiCount: an improved tally counter

15 posts in this topic

My day job is a rheumatologist and immunology researcher. An important but stupid-boring part of research involves an accurate counting of cells. Whether you work in  immunology, cancer biology, sperm counting, etc, an accurate cell count is essential for meaningful and reproducible results. There are electronic Coulter counters but they are expensive and unless you are constantly counting cells, it doesn't make sense because of the constant adjusting and cleaning involved.

 

When I was first shown how to count cells, I thought they were taking the new guy on a snipe hunt! You take a suspension of cells and put 10 microliters (ul) to a specially calibrated slide called a hemocytometer. It has a calibrated grid of lines and a precision ground spacing between the slide and coverslip so that the volume of fluid being examined is precisely known. You typically count the number of cells in a 1 x 1 x 0.1 mm (0.1 ul) volume. So for example if you count 100 cells, you have 100/0.1ul = 1 million cells/ml.

 

See http://en.wikipedia.org/wiki/Hemocytometer for pictures and an explanation. If you want accurate and reproducible results, you need to do a good job. Once you learn few rules, it is easy but at the same time mind numbing and tedious.

 

A friend of mine did research on subtizing (https://en.wikipedia.org/wiki/Subitizing). Whats that you say? If I flash up fingers for a fraction of a second, you don't count the individual fingers-- you instantly recognize the number. Most people can subitize up to ~5 or 6 items and as you can imagine, it is much faster than counting. I wanted to see if I could subitize to speed up counting. Enter the SubiCount.

 

The hardware is rather simple so I routed the board manually, i.e. without a schematic. Two transistors, two resistors and a bypass cap. The first version of the firmware was not particularly power efficient-- it was constantly in LPM3 with a timer driven interrupt routine. About 100 times per second, it would run a button debounce routine, take care of sending pulses to the tally counter, and fall back into LPM3. Pulses to the tally counter need to be sent at a controlled rate otherwise pulses get lost. The MSP430 keeps track of how many need to be streamed to the tally counter.

 

Problem was, it would do this 24/7 regardless of whether it was being used or not. Despite this, the two button cell batteries lasted three and a half months. For my project entry, I wanted to really leverage the low power aspect of the MSP430. I haven't had a chance to see what the new battery life is but I reckon it will likely be several years because it spends the bulk of its time in LPM4.

 

Here is a video of SubiCount in action:

https://www.youtube.com/watch?v=_fc95bzSDj4

 

My implementation was to stay in LPM4. From LPM4, pressing any of the buttons will trigger a PORT1 interrupt which puts the 430 into LPM3 and turns off the PORT1 interrupt. In LPM3, a timer driven interrupt routine is called 100 times per second to debounce buttons and send pulses to the tally counter. When all the pulses are sent and no button presses are active or pending, the PORT1 interrupt is turned back on and goes into LPM4.

 

Here is the code for main. It does nothing other than turning on global interrupts and going into LPM3.

int main(void)
{
    Grace_init();                   // Activate Grace-generated configuration
    
    // >>>>> Fill-in user code here <<<<<
    _BIS_SR(LPM3_bits + GIE);
    // never should get here
}

The meat is in two interrupt routines: one for PORT1 and another for TimerA0. I used GRACE so the code is in the automatically generated InterruptVectors_init.c file. Comments have been edited out for brevity:

#include <msp430.h>
/* SubiCount by Peter Kim 2013. Creative Commons License: Attribution-NonCommercial-ShareAlike 4.0 International */


/* USER CODE START (section: InterruptVectors_init_c_prologue) */
/* User defined includes, defines, global variables and functions */

#define MAXDEBOUNCE 2	//threshold for debounce
#define MAXRESET 300	// threshold for three button reset

int processKey(unsigned int pinValue, unsigned int *pkeyInteg, unsigned int *plastValue);

int i;

unsigned int oneKeyInteg = 0;
unsigned int twoKeyInteg = 0;
unsigned int threeKeyInteg = 0;
unsigned int resetInteg = 0;

unsigned int oneKeyLastVal = 0;
unsigned int twoKeyLastVal = 0;
unsigned int threeKeyLastVal = 0;

volatile unsigned int pulses;

int processKey(unsigned int pinValue, unsigned int *pkeyInteg, unsigned int *plastValue) {
/* Debounce routine adapted from http://www.kennethkuhn.com/electronics/debounce.c
*/
  int posEdge = 0;

  if (pinValue)  //pinValue inverted
  {
    if (*pkeyInteg > 0)
      (*pkeyInteg)--;
  }
  else if (*pkeyInteg < MAXDEBOUNCE)
    (*pkeyInteg)++;

  if (*pkeyInteg == 0)
    *plastValue = 0;
  else if (*pkeyInteg >= MAXDEBOUNCE && *plastValue == 0)
  {
		posEdge = 1;
		*plastValue = 1;
		*pkeyInteg = MAXDEBOUNCE;  /* defensive code if integrator got corrupted */
  }

  return(posEdge);
}

void InterruptVectors_graceInit(void)
{
}

#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR_HOOK(void)
{
    /* USER CODE START (section: PORT1_ISR_HOOK) */
	P1IFG &= ~(BIT3+BIT2+BIT1);	//clear interrupt flag
	P1IE &= ~(BIT3+BIT2+BIT1);		//clear P1.3 interrupt enable


	__bic_SR_register_on_exit(OSCOFF);	//put into LPM3
    /* USER CODE END (section: PORT1_ISR_HOOK) */
}

/*
 *  ======== Timer_A2 Interrupt Service Routine ======== 
 */
#pragma vector=TIMERA0_VECTOR
__interrupt void TIMERA0_ISR_HOOK(void)
{
    /* USER CODE START (section: TIMERA0_ISR_HOOK) */
	static int count =0; // to keep track of times here
	int P1;

	P1 = P1IN;

	if ( processKey(P1 & BIT1, &oneKeyInteg, &oneKeyLastVal) )
		pulses++;
	if ( processKey(P1 & BIT2, &twoKeyInteg, &twoKeyLastVal) ) {
		pulses += 2;
	}
	if ( processKey(P1 & BIT3, &threeKeyInteg, &threeKeyLastVal) ) {
		pulses += 3;
	}
	if ( (P1 & (BIT1|BIT2|BIT3)) == 0) {
		resetInteg++;
		if (resetInteg > MAXRESET) {
			resetInteg = 0;
			P1OUT |= BIT6; //turn on bit6, reset
			_delay_cycles(40000);
			P1OUT = BIT1|BIT2|BIT3; //need pull ups on
			pulses = 0;		//reset pulses count after reset
			return;
		}
	} else if (resetInteg)
		resetInteg--;

	if (count++ >7)
	{
		count = 0;
		if (P1OUT & BIT0)   //count pin high?
		{
			P1OUT &= ~BIT0; //then turn off
			pulses--;		//and decrement pulses
		} else if (pulses) {
			P1OUT |= BIT0;	//turn on count pin
		}
	}

	if (pulses == 0)	// done with pulses?
	{
		if ( (P1IN & BIT1+BIT2+BIT3) == BIT1+BIT2+BIT3 )	//are all buttons not pressed?
		{
			if (oneKeyLastVal==0 && twoKeyLastVal==0 && threeKeyLastVal==0)
			{
				P1IFG &= ~(BIT3+BIT2+BIT1);	//clear P1.3 int flag
				P1IE |= BIT3+BIT2+BIT1;		//enable P1.3 interrupt
				__bis_SR_register_on_exit(LPM4_bits + GIE);	//go into LPM4
			}
		}
	}
    /* USER CODE END (section: TIMERA0_ISR_HOOK) */
}
 

Here is the hand routed board:

post-3699-0-18707200-1388508070_thumb.png

 

And a hand drawn schematic:

post-3699-0-66610600-1388508966_thumb.jpg

bluehash, chicken, Rickta59 and 2 others like this

Share this post


Link to post
Share on other sites

I surely like projects that do something usefull and don't just blink. nice job.

P1IFG &= ~(BIT3+BIT2+BIT1);    //clear interrupt flag

in your code are you sure you want to clear all three irq flags irregardless of what actually triggered the interrupt request? it's a sure way of missing concurrent button presses to say the least.

volatile uint8_t trigger1 = 0;
[..]
    if (P1IFG & BIT1) {
        trigger1++;
        P1IFG &= ~BIT1;
        __bic_SR_register_on_exit(OSCOFF);
    } else if ..

would be more sensible I guess.

Share this post


Link to post
Share on other sites

@@pjkim Nice to see something that improves your work.. how do you get the counts out?

The video is finally done. The counts are displayed on tally counter with an LCD screen. Here is a picture of the device:

post-3699-0-03242800-1388566414_thumb.jpg

 

I surely like projects that do something usefull and don't just blink. nice job.

P1IFG &= ~(BIT3+BIT2+BIT1);    //clear interrupt flag

in your code are you sure you want to clear all three irq flags irregardless of what actually triggered the interrupt request? it's a sure way of missing concurrent button presses to say the least.

...

would be more sensible I guess.

I thought long and hard about how to implement low power modes for this project-- partly because I wanted to do a good job but mostly because of inexperience in working with LPMs. In the PORT1 interrupt routine, I don't care about which buttons are pressed because the PORT1 interrupt is only used to go into LPM3 so that the timer driven interrupt routine can check which buttons are pressed and debounce them. This routine checks which buttons are pressed, checks for concurrency, etc. In fact, the PORT1 interrupt routine turns off the PORT 1 interrupt enable (which is then reenabled before going into LPM4). The video shows that the buttons work fully independently except for when all three buttons are pressed to reset the counter. Sorry about the delay in getting the video up.

https://www.youtube.com/watch?v=_fc95bzSDj4

Share this post


Link to post
Share on other sites

I have to get hold of some of that polymorph plastic. do you happen to know if it holds it's shape at about 60degrees C?

 

and I see that people have tried using image processing techniques aka automated pattern recognition to count cells. why do you think that is not used on a wide scale?

Share this post


Link to post
Share on other sites

I have to get hold of some of that polymorph plastic. do you happen to know if it holds it's shape at about 60degrees C?

 

and I see that people have tried using image processing techniques aka automated pattern recognition to count cells. why do you think that is not used on a wide scale?

According to the Polymorph page at Sparkfun, the melting temp is 60 deg C. https://www.sparkfun.com/products/10951

 

As for using image processing, it takes a lot of fiddling to work correctly. Another thing I didn't mention is that you use trypan blue to mark the dead cells (the live cells actively pump out the dye and stay clear). You often also have debris mixed in. The image processing has to be able to detect correct size/shape, blue vs clear, and distinguish between cells and debris. If you use a program like ImageJ, it requires a fair amount of twiddling to get it right. And will likely require more twiddling from sample to sample.

 

There are benchtop all in one models that include a microscope, CPU to do the image processing, and a display for the image and results. We demoed one and there were several shortcomings. First, I could count faster using a conventional tally counter. Second, it had a lot of difficulty when there was any debris in the sample-- isolating cells from skin produces a fair amount of debris. Third, they required that samples be loaded on a special single use plastic slide that cost something approaching a $1 per sample. For what we routinely do, the benchtop counter would have been slower, less accurate, and a whole lot more expensive.

petertux likes this

Share this post


Link to post
Share on other sites

Cool project and a great way to make a boring task more interesting.

 

I can't help but be curious about the image processing stuff though! ImageJ probably is not the right tool but I've used some algorithms with vision guided robots that can do some pretty sophisticated pattern identification.

 

Maybe you could upload some pictures and see what the community can think of for processing techniques. This sounds like a great problem to investigate!

Share this post


Link to post
Share on other sites

Cool project and a great way to make a boring task more interesting.

 

I can't help but be curious about the image processing stuff though! ImageJ probably is not the right tool but I've used some algorithms with vision guided robots that can do some pretty sophisticated pattern identification.

 

Maybe you could upload some pictures and see what the community can think of for processing techniques. This sounds like a great problem to investigate!

 

For clean cell cultures, i.e. not much debris, the image processing involved is not too difficult but still requires image capture and analysis. Perhaps there could be improvements in the workflow to make it a bit faster.

 

The real problem is when you have samples isolated from tissues such as lymph nodes, spleen skin, gut, etc. The protein digestion required to release the cells also cause debris to co-purify with the cells. I don't have any pictures readily available but here is what I found using GIS. Most of the images are cartoon images drawn to scale. I think the reason why is the focus issue-- more on this later.

 

Here is a link to counting brewing yeast that has decent pictures. Yeast cells are about 4 times the diameter of lymphocytes so the focus issue is less of a problem. These are cultured cells so very little debris but there are some examples of debris near the bottom. Sometimes the debris can outnumber the live cells when isolated from some tissues. http://braukaiser.com/wiki/index.php?title=Microscope_use_in_brewing

 

Here is a link to the Countess automated cell counter: http://www.lifetechnologies.com/content/dam/LifeTech/migration/files/cell-analysis/pdfs.par.42295.file.dat/countess-brochure.pdf

 

You can see the special slides used. The reason special slides are used is that they have a special coating that causes all the cells to stick to one side. This is because if the cells are not all in the exact same plane of focus, it is impossible to tell if the cells are dead or alive-- there is an example of this in the brochure. When doing the cell counts manually, you are constantly adjusting focus up and down in the 100 um (micrometer) thickness between slide and coverslip to get optimal focus.

 

A solution might involve a combination of computer vision/image processing and computer controlled focusing so that single use slides are not required. Problem is that this is not something that labs are going to sink a lot of money into. Although cell counting is important and tedious, the hemocytometer works very well and the per sample cost is a few cents and can be done in under a minute.

Share this post


Link to post
Share on other sites

Very cool, the images so far look straight forward but sounds like the focus could be an issue.

 

I the the automated focus and multi slice image processing is a great idea.

 

I wish that I had more time to play with something like this but unfortunately I don't.

 

One thing that could be done is collect the images by manually increment info the focus and then use those images to prove the algorithm before jumping to getting the hardware setup.

Share this post


Link to post
Share on other sites

@@pjkim Interesting idea.  

 

Edit: Now that voting done, here are my questions.
Reading the writeup left me with a few questions.  (These may have been answered in the video, or maybe I missed where covered in the text).
 
Main question - did it work?  
How accurate are counts done using this different keying method, rather than using a simple tally counter?
How does it affect speed? (faster, slower, same, ...)
Are there other benefits/problems (effects on repetitive motion, etc.)
i.e., In what way(s) is this an improvement in practice (and what are the costs)?
 
Also small side question - what does the device actually do?  It appears to have be some sort of converter from 3 switches (chorded keyboard) to one output.  
(Sure I could read the code, but a quick description of what it is supposed to do would help.)
 
Thanks

Share this post


Link to post
Share on other sites
Main question - did it work?  

 

 

Yes, yes it does. It works well and with the msp430 staying predominantly in LPM4, a set of button cells will last for years.

 

 

 

How accurate are counts done using this different keying method, rather than using a simple tally counter?

The counts are not an approximation-- they are dead on accurate. A button press gives you exactly how many counts you would expect. Concurrent and/or overlapping button presses are also handled correctly.

 

 

How does it affect speed? (faster, slower, same, ...)

Slightly faster but a lot less tedious. My speed is slowly improving with use so will likely be considerably faster in the future-- sort of like touch typing vs hunt/peck.

 

 

Are there other benefits/problems (effects on repetitive motion, etc.)

i.e., In what way(s) is this an improvement in practice (and what are the costs)?

About 3-4 fold fewer keypresses. Takes some getting used to but once I did, the old method felt archaic. No real downsides that I can think of other than I had to make the thing.

 

 

Also small side question - what does the device actually do?  It appears to have be some sort of converter from 3 switches (chorded keyboard) to one output.  

A traditional tally counter requires you to press a button for each count. Want to count to 6, press the button 6 times. Our eyes/brains instinctively group and count up to about 5-6 items. When  I hold up four fingers, you don't count 1,2,3,4. You instantly recognize four fingers-- this is subtilizing. Same with a group of four cells. You instantly recognize there are four cells but you have to press the button four times on a traditional tally counter. This lets you enter 1 to 6 counts at a time-- 1 to 3 with a single button press, 4 to 6 using a combination of buttons.

 

That you ask these questions makes me think I didn't do a very good job explaining/presenting my project. Oh well. More blinkenlights next time.

Share this post


Link to post
Share on other sites

Sorry, I did not intend the questions as criticism of the write-up.  I thought it was an interesting idea, had a nice background of what the

problem is, proposed solution, implementation, etc. but left me wanting to hear the rest of the story (i.e. validation in real world terms).

 

Some of my questions had multiple interpretations, which I hadn't thought of.  Since the write-up is in the context of doing scientific research, I asked questions from that point of view, but I should have made it clearer that that was the angle I had in mind.

 

When I asked about did it work, I did not mean did the device function correctly, (I assumed that it did). I meant did the project achieve the overall goal

(i.e., did it produce counts of comparable or better quality with improved speed, ergonomics, etc.)

 

 

 

 

The counts are not an approximation-- they are dead on accurate. A button press gives you exactly how many counts you would expect. Concurrent and/or overlapping button presses are also handled correctly.

 

 

 

When I asked about accuracy of the counts, I did not mean whether the hardware tallied counts correctly, what I meant was how accurate are the counts produced by using subetizing and this device versus counts produced using the standard technique.

 

The counts are an approximation in that they approximate the true number of cells.  (Whether counting using the traditional method, or using the new method, one can still

expect there to be errors in counting.  Miss a cell here, double count one there, is that a cell or a piece of debris, etc.)  In both cases one is making a measurement of a physical quantity.  As always there will be some error in that measurement - if I repeat the measurement multiple times using the same method, I would expect to get a distribution of different results (expect them to cluster close to the true value, averaging multiple measurements may reduce random error, but won't correct for systematic error, etc.- standard stuff for taking any measurement.)  I don't know how common different counts are in this particular type of measurement, but would be surprised if they never occur.  My experimental background is more from physics, rather than biology, but same ideas apply whatever you are measuring.

 

If one changes the procedure for collecting a measurement, then it is essential to validate the new procedure to see how results compare to those from the old procedure

(can one use them interchangeably, is some systematic correction necessary, ...)

e.g. you take some example slides, count the density using the standard method, count the density using the new method, compare the results.  

Do enough repetitions to establish statistical significance, set up your procedure to minimize biasing the results 

(e.g. intersperse your experiment slides in a big batch of others so the person(s) doing the counting don't know which ones are your test batch,

in some of them count using standard method first, on other slides count using new method first (if having same person do all the counting), have different people do counts to avoid, etc.)

Hope that clarifies what I meant by accuracy - if n people count a set of slides using the new method, and n people count them using the standard method,

will they come up with the same results for each slide?  (Using appropriate statistics to measure degree of same-ness.)

 

Hope that makes clearer what I was trying to ask.

Share this post


Link to post
Share on other sites

A traditional tally counter requires you to press a button for each count. Want to count to 6, press the button 6 times. Our eyes/brains instinctively group and count up to about 5-6 items. When  I hold up four fingers, you don't count 1,2,3,4. You instantly recognize four fingers-- this is subtilizing. Same with a group of four cells. You instantly recognize there are four cells but you have to press the button four times on a traditional tally counter. This lets you enter 1 to 6 counts at a time-- 1 to 3 with a single button press, 4 to 6 using a combination of buttons.

 

 

This got me curious - how did you settle on the chording scheme?

I would have assumed would use a binary representation (so 3 would be switches 1 and 2 held down together).

(If I have to keep tally of a large number of things on my fingers, I count them in binary.  It gets a little cumbersome when get to larger numbers, and readout takes a little bit of time, but

fairly easy to do for the first few digits).

But I have not used or researched chording keyboards.

 

Thanks

Share this post


Link to post
Share on other sites

Sorry I misunderstood you question(s).

 

Not familiar with "chording" scheme, but chose the values for the individual buttons to be as intuitive as possible for non-geek types. You do lose some dynamic range (1 to 6 vs 1 to 7) and introduce some redudancy to numbers (can do 3 with single key or 1 and 2 together) but I thought the tradoff was worth it to keep it as non-geeky as possible. And also the fact that most people don't subitize more than 5 or 6.

 

As for systematically checking whether I get the same answer compared to a traditional tally counter, hadn't really crossed my mind-- counting should be counting. I will try new vs old but I would be surprised/shocked to see a significant difference (more than 1 or 2% which usually less than the difference in counting the same cell suspension twice).

Share this post


Link to post
Share on other sites

Not familiar with "chording" scheme, but chose the values for the individual buttons to be as intuitive as possible for non-geek types. You do lose some dynamic range (1 to 6 vs 1 to 7) and introduce some redudancy to numbers (can do 3 with single key or 1 and 2 together) but I thought the tradoff was worth it to keep it as non-geeky as possible. And also the fact that most people don't subitize more than 5 or 6.

Sorry for the jargon. I look at this as an specialized chording keyboard.  So by chording scheme I just meant the mapping from key chords to output values.

 

Now I think I understand the key structure, you have buttons that represent the values 1, 2 and 3, and if you press multiple buttons at once it adds the button values. (i.e. hold down button 1 and 3 and it enters 1 + 3 = 4)

 

As for systematically checking whether I get the same answer compared to a traditional tally counter, hadn't really crossed my mind-- counting should be counting. I will try new vs old but I would be surprised/shocked to see a significant difference (more than 1 or 2% which usually less than the difference in counting the same cell suspension twice).

Thanks.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0