PROSAC for OpenCV (2)

I made some changes to my code from last time. I won’t repost the whole thing, just snippets.

I removed typedef uint int and changed all uint to int, since the OpenCV coding standard seems not to be finicky about using unsigned ints.

In preparation for integrating into OpenCV (3.0), while retaining the ability to test standalone, I defined a macro for random integers:

#ifdef STANDALONE
// for testing outside opencv
#include <stdlib.h> // rand()
#include <math.h> // ceil()
#define rand_uint_less_than(limit) rand()%limit
#else
// for use inside opencv
#define rand_uint_less_than(limit) cvRandInt(&rng) % limit
#endif

In order to easily switch back and forth between PROSAC and RANSAC for comparative testing, I moved the contents of the constructor to an initPROSAC() method, and created a separate initRANSAC() method. All the initRANSAC() method does is set poolmax=N-1; which causes the preexisting randomSample() method to behave in a RANSAC way (i.e. sample m from the entire pool). The new constructor is empty. Perhaps a better pattern would be to have a SacHelper base class with the randomSample() method, and derive RansacHelper and ProsacHelper each with their specific constructors. Maybe I’ll do that at some point.

My test driver was updated to exercise both capabilities, like this:

ProsacHelper pro_or_ran;
#define EM 4
uint sample[EM];
for (int which=0; which<2; ++which) {
  switch(which) {
   case 0: pro_or_ran.initRANSAC(EM,100); break;
   case 1: pro_or_ran.initPROSAC(EM,100); break; }
 
  for (int it=0; it<100; ++it) {
 
    for (int mi=0; mi<EM; ++mi)
      sample[mi] = pro_or_ran.randomSample();
      cout << it << ":";
      for (int mi=0; mi<EM; ++mi)
        cout << " " << sample[mi];
      cout << endl;
    }
  }
}

And output looks something like this:

 0: 83 86 77 15
 1: 93 35 86 92
 2: 49 21 62 27
 3: 90 59 63 26
 4: 40 26 72 36
 5: 11 68 67 29
 ...
 0: 0 1 2 3
 1: 3 1 0 4
 2: 0 4 2 5
 3: 0 2 3 6
 4: 2 0 5 6
 5: 5 4 6 7
 6: 2 6 3 7
 7: 5 4 6 8
 8: 5 0 4 8
 9: 7 4 5 8
 10: 0 4 3 9
 ...

I have cloned the git repository of OpenCV (3.0) and the opencv_extra with testdata, “cmake . ; make” was sufficient to build perfectly first time, and the unit test suites opencv_test_features2d and opencv_test_calib3d are passing. Next post I hope to show first results of slapping this class into the calib3d source code, into RANSACPointSetRegistrator::getSubset() in function modules/calib3d/src/ptsetreg.cpp. This class is used to conduct RANSAC for cv::findHomography() and cv::findFundamentalMatrix(). Hopefully I can slap my ProsacHelper in and not break the test suite.

PROSAC for OpenCV

I recently read about the simple but brilliantly clever improvement to RANSAC, called PROSAC. Maybe it’s not really all that brilliant, because as far as I can tell, it is not picked up and built upon in the authors’ subsequent RANSAC-related papers. But I think it’s cool, and easy enough that I want to play around with adding it to the OpenCV baseline, maybe even eventually get it solid enough to request it to be added officially.

I don’t want to try to explain or reiterate the entire paper, the general idea is, if your prospective matches are usefully ordered (say, small descriptor distances up front, or larger 1st/2nd-best-dist-ratio up front), then it makes no sense for RANSAC to give every element the same chance to be in a sample that defines a model. Why not give priority to the (probably) better elements at the front of the list? So authors Chum&Matas derive a meaningful schedule for drawing samples with max element m, then m+1, then m+2, etc. So instead of simply RANSAC=RANdom SAmple Consensus, we have PROgressive SAmple Consensus. Chum&Matas also provide more useful stopping criteria than RANSAC alone, which I haven’t gotten into yet.

So without further ado, here’s my first stab at code for refactoring a RANSAC implementation over to PROSAC (no thanks to wordpress for not maintaining indentation of my paste!):

#include <stdlib.h> // rand()
#include <math.h> // ceil()

#ifdef DEBUG
#include <iostream>
using std::cout;
using std::endl;
#endif

typedef unsigned int uint;

#ifdef DEBUG
#define DPRINT(x) cout << "\t" << x << endl;
#else
#define DPRINT(x)
#endif

class ProsacHelper {
 public:
 ProsacHelper(uint m_, uint N_, uint TN_=200000) 
 : m(m_) // sample size
 , N(N_) // full size
 , TN(TN_) // max iterations
 , poolmax(m-1) // initial poolmax is m-1
 , poolit(0) // initial iteration is 0
 , si(0) // be ready to serve first element of first sample
 {
   // if (m<2 || m>100) throw...
   DPRINT("m = " << m);
   DPRINT("N = " << N);
   DPRINT("TN = " << TN);
   DPRINT("poolmax = " << poolmax);

   // initialize current T(n=m) by direct computation
   T = TN;
   for (uint i=0; i<=m-1; ++i) {
     T *= m-i;
     T /= N-i;
   }
   DPRINT("T = " << T);

   // spend only 1 it in first pool (sample=0...m-1)
   poolits=1;
   for (uint i=0; i<m; ++i) sample[i] = i;
   DPRINT("3:0 0 1 2 3");
 }

 uint randomSample() {
   uint ret = sample[si]; // this is what will be returned
   si++; // advance within current sample

   if (si==m) { 
     // we have exhausted current sample of m values for iteration. Time
     // to advance to the next iteration, and perhaps the next poolmax, and
     // draw the sample for the next iteration
     poolit++;

     if (poolit >= poolits && // time to expand the poolmax
         poolmax < N) { // ...unless we can't
       poolmax++;
       DPRINT("poolmax = " << poolmax);

       // eq (3) n=poolmax, n+1=poolmax+1
       DPRINT("curT = " << T);
       double nxtT = T * (poolmax+1) / (poolmax+1-m);
       DPRINT("nxtT = " << nxtT);
       double dpoolits = nxtT - T;
       poolits = ceil(dpoolits);
       DPRINT("its with max= "<<poolmax<<": "<<
       dpoolits<<" --> "<<poolits);
       poolit=0;
       T = nxtT;
     }

     // now that we know what the poolmax is, 
     // create the next sample

     uint n_unique_elts = (poolmax >= N ? m : m-1);

     // loop to populate elements 0..m-2 from 0..n-2
     for (uint i=0; i<n_unique_elts; ++i) {
       bool unique=false;
       // draw random r until different from all previous
       do {
         sample[i]=rand()%poolmax; // 0..poolmax-1
         unique=true;
         for (uint j=0; j<i; ++j) {
           if (sample[i] == sample[j]) {
             unique=false;
             break;
           }
         }
       } while (!unique);
     }

     if (poolmax < N) {
     // last element must be end of pool
     sample[m-1] = poolmax;
   }
   si=0; // reset sample counter for next call
   DPRINT(poolmax<<":"<<poolit<<": " << sample[0] << " " << sample[1]
          << " " << sample[2] << " " << sample[3]);
  } 

  return ret;
 }


 protected:
 uint m; // sample size: homographies=4, fundamental matrices=7 or 8
 uint N; // full number of items
 uint poolmax; // current pool max (pool = 0..poolmax)
 uint poolit;  // iteration within current pool size
 uint TN;      // max iterations
 double T;     // T(n)=avg # regular RANSAC seeds out of TN with max<=n-1
 uint poolits; // # iterations to spend in current pool; i=0..poolits-1
 uint sample[100]; // cache holding the current sample of m indices
 uint si;          // current position within the current sample
};

So the whole point is that you construct a ProsacHelper with parameters relevant to your case (m=4 for fitting a homography, or 7 or 8 for fitting a fundamental matrix; and N=number of elements; you can probably leave TN default), and then call randomSample() repeatedly. It remembers its state within each sample of m (with member si), and (as long as you don’t call it to use the random indices for other purposes) it will give you m-samples according to the schedule laid out by PROSAC.

In the OpenCV calib3d module, source file modelest.cpp, method CvModelEstimator2::getSubset(), the line

idx[i] = idx_i = cvRandInt(&rng)%count;

would be replaced by

idx[i] = idx_i = prosac_helper.randomSample();

–that is, assuming a ProsacHelper object could be set up beforehand and persisted across calls to getSubset(). I hope to show how this works out in a next post.

I did a few things a little differently from the paper. Member ‘poolsize’ is n, the size of the current pool of samples (0..n-1, or more specifically, n-1 and m-1 from 0..n-2). Rather than maintain t=global iteration number and T’n=iteration to bump up n=poolsize, I maintain only T'(n) – T'(n-1) = ceil(T(n) – T(n-1)) = ‘poolits’, and ‘poolit’ is my counter from 0..poolits-1.

I did spend time in there to guarantee that each m-sample does not repeat itself. I figured that this time is probably overshadowed by the time required to make sure that random samples are not degenerate (i.e. 3/4 collinear when fitting a homography).

Test code like this:

ProsacHelper prosac(4, 100);
uint sample[4];

for (int it=0; it<100; ++it) {
 
   for (int mi=0; mi<4; ++mi) sample[mi] = prosac.randomSample();
   cout << it << ": " << sample[0] << " " << sample[1]
               << " " << sample[2] << " " << sample[3] << endl;
 }

creates output like this:

0: 0 1 2 3
1: 3 2 1 4
2: 0 3 1 5
3: 0 3 1 6
4: 2 1 5 6
5: 0 3 1 7
6: 2 3 0 7
7: 7 5 6 8
8: 2 6 3 8
9: 3 7 1 8
...

You can see that, since T(n) increases by less than one the first few times, poolmax=3,4,5 each only get one iteration, but then as T(n) grows more quickly, poolmax=6,7 get 2 iterations each, poolmax=8 gets 3 iterations, and it grows from there.

Eulogy for Dave

My friend Dave died this week, a victim of an unexpected stroke — tragically young (early 50s?)

Although I hadn’t seen Dave much in the last two years since I moved, there was a period of maybe 5 years that we met regularly for coffee every Monday morning, with a few other guys, before work. During that time, we all suffered with him through his divorce, and rejoiced with him when he found, wooed, and married Susan. And we got to hear a lot about progress in the lives of his two daughters, of whom he was so proud.

Dave was a manly man; he knew about manly things like cars and preventative maintenance. He also had sophisticated tastes in wine, food, and jazz. On the other hand, he also had a corny sense of humor, epitomized by his love for Ernest movies (“you know what ah mean, Vern?” Ernest Goes to Camp, Ernest Goes to Jail, …).

Dave loved my kids. He taught two of them through 1st-2nd grade sunday school, and he was always telling me or Tina some story about something they said or did in class that amazed him or made him laugh. I know he told other parents about things their kids did in class, but I like to think he loved my boys the most.

I don’t feel any compunction to say only nice things about Dave; he surely had his warts like the rest of us, but I didn’t see much evidence of them. When he was going through his divorce, he told me what he thought his faults were in the marriage: being distant and curt when laid up with back injuries, getting impatient and angry. (He often encouraged me not to take my wife for granted.) He might have had a bit of a temper, but I only ever saw him calmly deal with frustrating issues that would have enraged me.

But I don’t need to have seen his faults to know that Dave was a sinner; the bible tells me so, and he confessed it himself. Dave might have been less of a sinner than most of us, but he was still enough of a sinner to earn and deserve the eternal wrath of God in hell. Thankfully, God gave him the grace to be able to claim Christ’s sacrifice, bearing that punishment on his behalf. That’s the only reason Dave’s untimely death is not a total tragedy; he will be resurrected on the last day. Meanwhile, a lot of people will be missing him.

Memorial services at New Life PCA, la Mesa, Sat Apr 11, 2pm.

Easy Bread

I am rewriting my old post about bread, to make it simpler and better organized. I’ll leave the old one up there for reference though.

Here’s the simplest possible version of the bread recipe from Artisan Bread in Five Minutes a Day (book, video, article, blog) (bold indicates minimally required equipment, I’ll discuss alternatives later):

  • Using a dry 1 cup measuring cup, measure out 6.5 cups of all purpose or bread or baker’s flour into a temporary/staging vessel. Scoop heaping cups and scrape them level with the back of a knife. For the last 1/2 cup you can just eyeball it.
  • In a 5 quart bucket, add (in order):
    • 3 brimming cups of hot tap water
    • 1.5 Tbsp kosher salt
    • 1.5 Tbsp yeast
    • 6.5c Flour
  • Mix just until all flour is wet (make sure you get into the corners of the bucket)
  • Cover loosely and rest unrefrigerated for at least 2 hours, or up to overnight
  • Refrigerate for at least 5 hours, or up to 2 weeks
  • Prepare a cornmealed or floured surface
  • Cut/pull out 1/4 of the bucket of dough, drop into flour and liberally coat
  • Shape into a round loaf by rotating in your hand and stretching the top around to the bottom (easier to show than tell; you can see it at about 3:30 in the video). (If it gets sticky while shaping, drop into flour again.)
  • Repeat for other 3/4 of dough to shape 4 rounds, place on cornmeal to minimize sticking
  • Rest shaped loaves uncovered for 40 min
  • 20 min into rest, put baking stone into oven, also an oven-proof container with a few cups of water, and preheat to 450.
  • After 40 min rest is done, slash an X in the tops with a bread knife, and slide or place loaves onto hot baking stone
  • Bake for 30 min
  • Rest on a rack for at least 10 min
  • Eat within 1 day, or slice, bag, and freeze.

Here are specifics of my implementation of the method, which I’ve worked out after making this bread for a few years now.

Equipment:

  • When you’re just getting started, instead of a lidded bucket you can use a big mixing bowl covered with a dinner plate, but it’s messy, and if you start making bread regularly, a bucket is a lot easier.
  • For my money, the best mixing buckets are 5-quart ice cream buckets. Don’t buy just any bucket of ice cream though, because 4 quarts is not big enough (for this amount of bread dough, nor is it enough ice cream!). If you read the reviews, you can see that a lot of people buy these 6qt buckets to make this kind of bread.
  • Amazon sells a really nice 1.5 Tbsp measuring spoon, probably just for this book’s fan base. Otherwise, 2 Tbsp coffee scoops are easy to find anywhere. Carefully measure 1.5 Tbsp of salt into the scoop and mark the level with a sharpie for future reference.
  • You can use a sturdy wooden spoon, but the best tool for mixing is a Danish Dough Whisk. Seriously, it works SOOO much better (and it’s also great for pancake batter). Just a few flicks to spread the salt and yeast through the water, then dump in all the flour at once, and stir it up.
  • For “cover loosely”, I drill 5 quarter-inch holes in the plastic bucket lid. It is important to keep these vent holes clear, or the yeast exhaust gets trapped and makes the bread taste beery and bitter. Sometimes a fresh batch will rise so much it plugs the holes, in which case you give the bucket a slam onto the counter to knock air out, and then clear the holes.
  • The recommended surface for cornmeal and pre-bake rest is a pizza peel, but a cutting board or baking sheet would work just as well. We never slide bread off our peel anyways; we just gently pick the loaves up and place them on the stone.
  • You can recapture extra cornmeal with a flexible cutting board and then pour it back into whatever you keep cornmeal in (I buy bulk cornmeal from Henry’s and keep it in mason jars).
  • There are lots of baking/pizza stones out there, the ABin5 website warns against Pampered Chef baking stones, apparently they break a lot.
  • For a water/steam vessel, I use an old bread pan (rusty, and getting rustier). Best way to dry it out afterwards is to take it out with the bread and dump it, then its heat will dry it completely. Using pyrex is possible, but DO NOT add water to heated pyrex, it will explode.
  • For post-baking rest, if you have a non-flat-top stove you can put it on a burner like a pan, it will get plenty of air that way.
  • Make a habit of saving old bags and twisties from bread or hamburger buns etc, they can be reused many times.

Process:

  • If you can’t get all the flour wet, it’s OK. Different flours in different climates accept water slightly differently. Just mix in more water a small splash at a time, and next time consider 6.25 cups of flour, or maybe even just 6 cups.
  • You can vary the amount of salt or yeast.
  • Leave your bread knife in the middle of your resting loaves so you don’t forget to slash before baking; without a slash they will burst into irregular shapes.
  • If you schedule carefully, you can make bread all in the same day: for example, mix at 9, refrigerate at 11, shape at 4:20, bake from 5:00-5:30. You can even bake without ever refrigerating (mix at 3, shape at 5, bake from 5:40-6:10), but the loose and sticky dough will be very difficult to work with, and it will make a big mess.
  • You don’t have to bake the entire bucket at once. If you love fresh bread (and who doesn’t!) you can keep dough in the fridge and just bake 1-2 loaves for dinner whenever you want.
  • I maintain a number of buckets:
    • A bucket for storing flour (also for dropping dough into when shaping)
    • A “staging” bucket to measure 6.5 cups into, because I often lose count and can just dump it back out. Also so I can measure flour before my measuring cup gets wet from measuring water.
    • Two mixing buckets. If I am making two buckets for three sandwich loaves (see below), then I measure 6.5 c of flour into both the staging bucket and bucket 2, then bucket 2 gets dumped into 1 and staging into 2.
  • Ice cream buckets are not built to last forever. When the rim starts to break off, it can be demoted to staging. If you need a new bucket, eat some more ice cream!
  • A staging bucket can be stored with the flour bucket nested inside it.
  • DO NOT let used buckets with dough residue nest inside each other. Without air to dry it out, the trapped wet dough will rot and get really, really disgusting.
  • To avoid this, you can obviously wash the mixing buckets completely clean, or you can also let them air-dry. The remaining dough will dry and mostly flake off, and any other residue will be fine to mix into subsequent batches, it’s the same as propagating a favorite yeast strain with sourdough bread.

Variations:

  • 1 tsp of chopped Rosemary mixed in with the dough makes a delicious herbed bread. You can experiment with other herbs as well.
  • 1/4 bucket of dough is exactly the same as a bag of wet pizza dough like you can buy at Trader Joe’s. Flour and roll it out as you would think, lay it on a cornmealed pizza peel, add sauce and toppings, and bake at 450 on a stone until the cheese is melted and the crust is done. Two stones means two pizzas can bake at once. If your BBQ is the right size, you can also put your stone(s) on the grill and cook pizza(s) in the BBQ.
  • Divide a bucket into 5-6 slightly smaller round loaves, and this is perfect for making bread bowls for soup. Go with a thicker type of soup/stew — broccoli cheese is the best, but chicken noodle soup would likely leak.
  • This dough can make great sandwich bread
    • 1 loaf needs 2/3 of a bucket, so you need 2 buckets for 3 loaves; mix, rest, and refrigerate as above.
    • Spray Pam into three 9×5 bread pans
    • As before, cut out the dough (2/3 of one bucket, or 2/3 of the other bucket, combine the remaining one-thirds of both buckets), drop into the flour bucket to coat liberally
    • Shape by stretching the top around the back, but allow gravity to elongate the loaf to approximate the length of the pan
    • Drop three shaped loaves into the three pans
    • Cover loosely and rest for 2-3 hours
      • My usual schedule is to mix the dough on Saturday, refrigerate overnight, shape the loaves before I leave for Sunday evening church, and bake them when I get home
      • The best method I’ve found to cover is to put the pans side-by-side and put a half-sheet baking tray upside down on top of them, but you can also put the pans into bread bags or use saran wrap.
    • Preheat oven to 450: with a water vessel, but no baking stone
    • At the end of the resting, the dough should mostly fill the pans and might slightly stick to the cover, just lift gently.
    • Slash the tops with a line down the center, and bake for 45 minutes. “Oven spring” should cause the loaves to bake above the tops of the pans into a nice rounded-top sandwich-loaf shape.
    • Rest before slicing, 10 min–overnight. Enjoy one loaf within the next day or two, slice, bag, and freeze the rest.
    • When packing a lunch in the morning, frozen slices are OK, they’ll be fine by lunchtime.
    • To eat quickly, microwave 2 frozen slices for 30 sec, or 4 for 1 min, then toast.
  • Batch sizes:
    • Normal: 3c water, 1 1/2T yeast,salt, 6 1/2c flour. Makes 4 round loaves, 4 pizza crusts, 5 bread bowls, or 2/3 of a batch makes one sandwich loaf, and 2 batches makes 3 sandwich loaves.
    • 2/3 batch: 2c water, 1T yeast,salt, 4 1/3c flour. Makes 1 sandwich loaf
    • 4/3 batch: 4c water, 2T yeast,salt, 8 2/3c flour. Makes 2 sandwich loaves, but when it rises it will overflow a 5qt bucket. Maybe it would fit in a 6qt bucket?

Cost:

  • I buy Minnesota Girl Baker’s Flour from Costco, last I checked it was like $13.50 for a 50lb bag. The recipe calls for unbleached, but this hasn’t given me any trouble.
  • I store flour in the garage in this 18 gallon hopper with a hinged lid. If you go through the flour as fast as we do (50lb every couple months), that will work fine. But if your flour sits around for too long, it can develop bugs, because all flour has bug eggs in it. To prevent this, freeze the flour for a few days, in batches if necessary.
  • I buy Red Star yeast in a vacuum-sealed brick from Costco, last I checked it was $4.39 for 1lb?
  • I store the yeast in a jar in the freezer. It stays loose, will keep forever, and I can measure it straight from the freezer into the bucket.
  • At about 4.25 oz/cup, 50lb of flour is about 188 cups, and 188/6.5=29 buckets, so it’s about $13.50/29=$0.46/bucket in flour.
  • I would guesstimate that a brick of yeast lasts through about two bags of flour, so that’s $4.39/2/29=$0.08/bucket
  • Salt and water are even cheaper, we’ll call that another $0.01/bucket for a total of 55c/bucket.
  • At four round loaves or pizza crusts per bucket, that’s 14 cents each.
  • At five bread bowls per bucket, that’s 11 cents each.
  • At three sandwich loaves per two buckets, that’s 37 cents each.

Cutthroat Chopped!

The boys have been watching a lot of Food Network lately, such that #3 named himself Ted Brown and decided to host a combo of Cutthroat Kitchen and Chopped for dinner tonight.

Ted Brown got 62 dollars of play money out of his Allowance board game, and divided it equally between the contestants.

The theme was “Elevated Sandwich”. The contestants both faced a basket containing blueberry bagels, cream cheese, turkey lunchmeat, and leftover cous cous (full of dried fruits and nuts).

Mom got quickly to work on mixing cream cheese into the cous cous and forming patties that she started frying in the nonstick pan (which she was quick to grab before Dad had a chance!), and popped a bagel in toaster oven.

Dad assembled turkey, cream cheese, and jelly sandwiches on inside out bagels (except for one bagel half was not inside out), cracked an egg, dipped the sandwiches in and started frying his inside-out french-toasted Monte Cristo.

Auction one was to have to incorporate bagged iceberg salad. Dad figured he had no clue what to do with the cous cous and could make a combined salad with the lettuce so he didn’t bid it up too high.

Auction two was to do no cooking at all for 5 minutes. Dad let himself lose this one too because he was able to flip his sandwiches and put the pan on low, so they could survive for 5 minutes.

During the 5 minutes, Mom added some turkey to her pan to heat up, and then added turkey and cheese to her bagel halves in the toaster oven.

After the timeout was over, Dad made a bed of lettuce, and topped it with cous cous mixed with the fresh-chopped apple and balsamic vinaigrette; also he made a small bowl of dipping sauce with strawberry jelly and a touch of Log Cabin (to play off the French Toast theme). Unfortunately, due to cooking, the bagels were tough, and the cream cheese was slippery, so quartering the Monte Cristos was a massacre.

Mom plated her bagels as open-faced turkey melts with the cous-cous patties on top.

Since #3 was the host, #1 and #2 were the judges. As all the food was delicious, it was a tough decision. The sweet blueberry bagel was not the best for pairing with its savory toppings, but otherwise the patty was very good. The butchered Monte Cristo was seriously docked for presentation, and some judges found the whole thing too sweet. Also, the cous-cous salad was overdressed. It was for those reasons that Dad was Chopped, and Mom walked away with a $21 prize!

Hot Mexican Carrots

One of my favorite things about taco shacks has always been those little baggies of hot carrots. I finally did a little research and started making my own — can a snack get any healthier?

I developed my own method starting from here.

  • 3 lb carrots, peeled and sliced very diagonally about 1/4″ thick
  • Liquid: 2 parts white vinegar to 1 part water
  • 1/4c sugar
  • 1 onion, chopped into strips (half rings)
  • 2 cloves garlic, sliced wafer thin
  • 1-2 c (1 jar?) pickled (“nacho”) jalapeno slices
  • oregano
  • (optional) red pepper flakes

Bring 2c vinegar and 1c water and 1/4c sugar to a boil, make sure sugar is dissolved. Add carrots, onion, garlic. Add liquid (2:1 vinegar:water) just to cover. Sprinkle generously with oregano, covering the surface (optionally sprinkle on red pepper flakes for extra heat). Bring back to a boil. As soon as it begins boiling, add and gently stir in the jalapenos (some juice is ok), and keep the heat on to boil for 2min30sec. Turn off the heat, cover, and let it steep until room temperature. Move to container(s) (including liquid) and refrigerate.

This gets very economical (certainly compared to 50-75c for a dozen carrot slices in a baggie) if you buy 10lb bags of carrots and 1gal jars of jalapeno slices from Costco.

So this recipe is rather subjective, as well as progressive. Start with this basic method, and save and strain the liquid for subsequent batches (when making each batch, boiling = sterilizing), topping up with additional 2:1 vinegar:water. Adjust subsequent batches depending on how you liked the previous one. Was the previous batch too sweet? Then add no new sugar, and the previous liquid will get less sweet by topping up with fresh vinegar. Too vinegary? Add a couple spoonfuls of sugar with the fresh liquid. Not hot enough? Use more jalapenos and/or red pepper flakes. Carrots too crunchy for your preference? Try boiling for 3:00. Too soft? Try backing off to 2:00. Etc.

As you reuse the liquid for more batches, it will get more green, as more jalapeno mushes into it. But it will also get more rich (not so sharply vinegary). As you go along, you can manage it by balancing previous liquid with fresh liquid.

Another tip that you might find helpful. It took me a little bit to find the best way to slice the carrots. For maximum size, you want to slice them very much on the bias, almost completely lengthwise. The best way to do this is to hold the carrot in your left hand, angled about from 1:00 to 7:00 on the cutting board, and the knife in your right hand at 12:00. This way you’ll get the right angle without having to hold your knife in an awkward, dangerous position (that is, if you are right-handed. Reverse as appropriate). Try to find a side of the carrot that you can rest stably on the cutting board so it won’t rotate or slip when you cut.

EUREKA: Blitz this stuff (carrots, onions, jalapenos, garlic, after lightly draining of liquid) in a food processor, and this makes an awesome spicy carrot relish! Try it anywhere you might use salsa, it’s especially good with melted cheese: in quesadillas, in an egg&cheese breakfast burrito, on nachos — and for a real mind-melt, try spicy carrot relish on Ruffles-nachos!

Hoagies & Stogies: Death Penalty

Here are the recordings from last week’s Hoagies & Stogies: Death Penalty:

  • Part 1: Debate (1:05:55, 16MB)
  • Part 2: Audience Q&A (26:55, 6MB)

Thanks so much to Dr. Ron Gleason and Don Lowe for their spirited discussion! Everybody had a great time.

Don’t forget to check out Dr. Gleason’s book Death Penalty on Trialit’s available for a discounted price from the publisher this month.

Also, don’t forget to check out the new tasting room at Hess Brewing’s new digs in North Park (see here for hours and directions).

And finally, don’t forget to send in your ideas for potential speakers for this growing list of topics

Follow

Get every new post delivered to your Inbox.

Join 39 other followers