The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Photography & Images

Taking & making images.

  • HP8591 Spectrum Analyzer Screen Dump Sizes

    The script I use to fetch screen dumps from my HP8591 spectrum analyzer works fine, but it turns out that the screen images have (at least) two sizes.

    The hp2xx program converts the screen dumps from HP-GL text files to PNG bitmaps:

    for f in *hgl ; do hp2xx -m png -c 1436 "$f" ; done
    

    The usual size is 593x414 pixels:

    SMD 470 pF - Comm Spec
    SMD 470 pF – Comm Spec

    The other size is 593x395 pixels:

    SMD 470 pF - Surplus
    SMD 470 pF – Surplus

    As nearly as I can tell, the spectrum analyzer mashes the Y coordinate when any of the soft keys along the right edge have reverse-video highlights, which print as outlined boxes. There may be other sizes; those are the two I’ve stumbled over so far. This doesn’t much matter unless I’m using the images in a column, in which case it’s awkward to have two sizes: a one-size-fits-all script to trim off the soft keys doesn’t produce the proper results.

    Musing on how to figure this programmatically…

    The file command gives the pixel dimensions, with the file name (which may contain blanks: so sue me) set off  with a colon:

    file "SMD 470 pF - Surplus.png"
    SMD 470 pF - Surplus.png: PNG image, 593 x 395, 8-bit colormap, non-interlaced
    

    Judicious application of cut extracts the relevant numbers, albeit with a trailing comma that requires another pass through the grinder:

    file "SMD 470 pF - Surplus.png" | cut -d\: -f2 | cut -d\  -f4,6
    593 395,
    

    Although I think a sed script might be better, that requires more skull sweat than I have available right now.

    Given that, then an appropriate mogrify would crop off the softkey labels; the first one is what’s in the script right now:

    mogrify -crop "540x414+0+0" SMD\ 470\ pF\ -\ Comm\ Spec.png
    mogrify -crop "515x395+0+0" SMD\ 470\ pF\ -\ Surplus.png
    

    Which looks like this:

    SMD 470 pF - Comm Spec cropped
    SMD 470 pF – Comm Spec cropped

    The two sizes come out pretty close to the same 1.3 aspect ratio, but resizing the smaller one to match the larger doesn’t work well:

    convert -resize '540x414+0+0!' SMD\ 470\ pF\ -\ Surplus.png SMD\ 470\ pF\ -\ Surplus\ resized.png
    

    You need single quotes around the geometry parameter to prevent Bash (or Dash or whatever) from gnawing on the bang character (yes, that’s how you pronounce “!”).

    The images are lossless PNGs because they consist entirely of single-pixel lines and characters; alas, resizing by non-integer factors close to 1.0 introduces nasty picket-fence aliasing artifacts:

    Resize x 1.049
    Resize x 1.049

    I resize the pix by a nice, even factor of two (which also adds aliasing artifacts, but in small and very regular doses) and set the dots/inch value so the images print at about the right size without further hassle along the production pipeline:

    mogrify -density 300 -resize 200% whatever.png
    

    Which looks like this:

    Resize 2.00
    Resize 2.00

    Resizing from the smaller images to (roughly) the final size in one step doesn’t look quite so awful:

    convert -density 300 -resize 209% "SMD 470 pF - Surplus.png" "SMD 470 pF - Surplus large.png"
    

    Like this, still with a distinctly garbled dBm:

    Resize 2.09
    Resize 2.09

    But it’s decidedly better than this result from a two-step enlargement, although not as wonderful as one might like:

    Resize x 1.049 x 2.00
    Resize x 1.049 x 2.00

    So the script needs a tweak for the file sizes, but …

    Memo to Self: It’d be simpler to not have highlighted softkeys when doing screen dumps!

  • Christmas Cactus Time Lapse: Drat!

    With macro lens adapter in hand, I started taking a picture a day with the intent of making a time-lapse movie:

    Christmas Cactus flower bud
    Christmas Cactus flower bud

    Of course, that particular flower bud fell off after three days.

    We returned the plant to a cool location for the Christmas season. Evidently the kitchen was far too warm for best performance…

  • Canon SX230HS Close-up Adapter: Up-amped LED Ring

    Paralleling a 510 Ω resistor with each of the 180 Ω resistors on the LED ring light around the macro lens holder boosted the LED string current from 15 to 20 mA:

    LED ring light - paralleled resistors
    LED ring light – paralleled resistors

    The complete botch job in the lower right is what you get when you don’t wipe the soldering iron tip first.

    LED brightness being pretty nearly linearly proportional to current, the exposure gets another 0.4 EV that probably doesn’t matter in the least.

    A hand-held picture of the pile of SMD resistors (which willingly produced four of the five resistors and required enhanced interrogation to extract the last one):

    SX230HS - macro lens - 15 x 20 mA ring light
    SX230HS – macro lens – 15 x 20 mA ring light

    That’s pretty much overhead at f/8, so the depth of field is as good as it gets.

  • Vanilla Extract: Commercial Variations

    This look at the ingredients found in various commercial vanilla extracts (plus their prices) finally pushed me over the edge into brewing up that DIY vanilla extract.

    We’ve been using McCormick vanilla forever, mostly because it has the simplest and shortest list of ingredients:

    McCormick Vanilla
    McCormick Vanilla

    Nielson-Massey vanilla seemed about the same, although it’s not clear why it needs more sugar than those “vanilla bean extractives”:

    Nielsen-Massey Vanilla
    Nielsen-Massey Vanilla

    Wal-Mart vanilla doesn’t smell like vanilla, even though it has more “extractive” than corn syrup:

    Wal-Mart Vanilla
    Wal-Mart Vanilla

    All three extracts have “Pure” on the label, which (according to Wikipedia, anyway) means that they have at least 13.35 ounce of vanilla bean per gallon of extract. I didn’t weigh the three beans in my 8 ounces of hooch, but I suspect they weighed far less than the regulation 0.834 ounce. Next time, for sure, I’ll go for triple strength extract!

    Despite that, my DIY hooch has turned brown and smells pretty good…

    These full-frame pix used my new close-up lens gizmo; even with some vignetting the results seem perfectly usable. Normally I crop pix down to the central section, so this will be as bad as it gets.

  • SX230HS Adapter: Main Tube and Assembly

    The main tube connects the camera mounting plate and the snout on the front, so it’s a structural element of a sort. The ID fits over the non-moving lens turret base on the camera and the inner length is a few millimeters longer than the maximum lens turret extension:

    Camera mount tube - interior
    Camera mount tube – interior

    As you might expect by now, the front bulkhead has four alignment peg holes for the snout:

    Camera mount tube
    Camera mount tube

    The OpenSCAD code sets the wall thickness to 3 thread widths, but Skeinforge prints two adjacent threads with no fill at all. I think the polygon corners eliminate the one-thread-width fill and the perimeter threads wind up near enough to merge properly.

    I assembled snouts to main tubes first, because it was easier to clamp bare cylinders to the bench:

    Microscope eyepiece adapter - snout clamping
    Microscope eyepiece adapter – snout clamping

    Then glue the tube to the mounting plate using a couple of clamps:

    Microscope eyepiece adapter - baseplate clamping
    Microscope eyepiece adapter – baseplate clamping

    The alignment is pretty close to being right, but if when I do this again I’ll add alignment pegs along the trench in the mounting plate to make sure the tube doesn’t ease slightly to one side, thusly:

    SX230HS Macro Lens mount - solid model - exploded with pegs
    SX230HS Macro Lens mount – solid model – exploded with pegs

    You can see the entrance pupil isn’t quite filled in the last picture there, so a bit more attention to detail is in order. A bigger doublet lens would help, too.

    The current version of the OpenSCAD source code with those pegs:

    // Close-up lens mount & Microscope adapter for Canon SX230HS camera
    // Ed Nisley KE4ZNU - Nov 2011
    
    Mount = "LEDRing";			// End result: LEDRing Eyepiece
    
    Layout = "Show";			// Assembly: Show
    							// Parts: Plate Tube LEDRing Camera Eyepiece
    							// Build Plates: Build1..4
    
    Gap = 10;					// between "Show" objects
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
    
    //-------
    //- Extrusion parameters must match reality!
    //  Print with +1 shells, 3 solid layers, 0.2 infill
    
    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleFinagle = 0.2;
    HoleFudge = 1.00;
    
    function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
    
    Protrusion = 0.1;			// make holes end cleanly
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //-------
    // Dimensions
    
    // doublet lens
    
    LensDia = 25.0;
    LensRad = LensDia/2;
    LensClearance = 0.2;
    
    LensEdge = 6.7;
    LensThick = 8.6;
    LensRimThick = IntegerMultiple((2.0 + LensThick),ThreadThick);
    
    // LED ring light
    
    LEDRingOD = 50.0;
    LEDRingID = 36.0;
    LEDBoardThick = 1.5;
    LEDThick = 4.0;
    LEDRingClearance = 0.5;
    LEDWireHoleDia = 3.0;
    
    // microscope eyepiece
    
    EyepieceOD = 30.0;
    EyepieceID = 24.0;
    EyepieceLength = 25.0;
    
    // camera
    // Origin at base of [0] ring, Z+ along lens axis, X+ toward bottom, Y+ toward left
    
    CameraBodyWidth = 2*10.6;							// 2 x center-to-curve edge
    CameraBaseWidth = 15.5;								// flat part of bottom front to back
    CameraBaseRadius = (CameraBodyWidth - CameraBaseWidth)/2;	// edge rounding
    CameraBaseLength = 60.0;							// centered on lens axis
    CameraBaseHeight = 55.0;							// main body height
    CameraBaseThick = 0.9;								// downward from lens ring
    
    echo(str("Camera base radius: ",CameraBaseRadius));
    
    TripodHoleOffset = -19.0;							// mount screw wrt lens centerline
    TripodHoleDia = Clear025_20;						// clearance hole
    
    TripodScrewHeadDia = 14.5;							// recess for screw mounting camera
    TripodScrewHeadRad = TripodScrewHeadDia/2;
    TripodScrewHeadThick = 3.0;
    
    // main lens tube
    
    TubeDia = 		[53.0,	44.0,	40.0,	37.6];		// lens rings, [0] is fixed to body
    TubeLength = 	[8.1,	20.6,	17.6,	12.7];
    
    TubeEndClearance = 2.0;								// camera lens end to tube end
    TubeEndThickness = IntegerMultiple(1.5,ThreadThick);
    TubeInnerClearance = 0.5;
    
    TubeInnerLength = TubeLength[0] + TubeLength[1] + TubeLength[2] + TubeLength[3] +
    				  TubeEndClearance;
    TubeOuterLength = TubeInnerLength + TubeEndThickness;
    
    TubeID = TubeDia[0] + TubeInnerClearance;
    TubeOD = TubeID + 6*ThreadWidth;
    TubeWall = (TubeOD - TubeID)/2;
    TubeSides = 48;
    
    echo(str("Main tube outer length: ",TubeOuterLength));
    echo(str("          ID: ",TubeID," OD: ",TubeOD," wall: ",TubeWall));
    
    // camera mounting base
    
    BaseWidth = IntegerMultiple((CameraBaseWidth + 2*CameraBaseRadius),ThreadThick);
    BaseLength = 60.0;
    BaseThick = IntegerMultiple((1.0 + Nut025_20Thick + CameraBaseThick),ThreadThick);
    
    // LED ring mount
    
    LEDBaseThick = IntegerMultiple(2.0,ThreadThick);	// base under lens + LED ring
    LEDBaseRimWidth = IntegerMultiple(6.0,ThreadWidth);
    LEDBaseRimThick = IntegerMultiple(LensThick,ThreadThick);
    
    LEDBaseOD = max((LEDRingOD + LEDRingClearance + LEDBaseRimWidth),TubeOD);
    
    echo(str("LED Ring OD: ",LEDBaseOD));
    
    // alignment pins between tube and LED ring / microscope eyepiece
    
    AlignPinOD = 2.9;
    
    SnoutPins = 4;
    SnoutPinCircleDia = TubeOD - 2*TubeWall - 2*AlignPinOD;		// 2*PinOD -> more clearance
    
    // alignment pins between tube and base plate
    
    BasePins = 2;
    BasePinOffset = 10.0;
    BasePinSpacing = BaseLength/3;
    
    //-------
    
    module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes
    
      Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    
      FixDia = Dia / cos(180/Sides);
    
      cylinder(r=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
    }
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      Range = floor(50 / Space);
    
    	for (x=[-Range:Range])
    	  for (y=[-Range:Range])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //-------
    
    //- Camera body segment
    //	Including lens base and peg for tripod hole access
    //	Z=0 at edge of lens base ring, X=0 along lens axis
    
    module CameraBody() {
    
      translate([0,0,-CameraBaseThick])
    	rotate(90)
    	  union() {
    		translate([0,0,(CameraBaseHeight/2 + CameraBaseRadius)])
    		  minkowski() {
    			cube([CameraBaseWidth,
    				  (CameraBaseLength + 2*Protrusion),
    				  CameraBaseHeight],center=true);
    			rotate([90,0,0])
    			  cylinder(r=CameraBaseRadius,h=Protrusion,$fn=8);
    		  }
    
    		translate([0,0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			rotate(180/TubeSides)
    			  cylinder(r=(TubeDia[0]/2 + CameraBaseThick),
    					  h=(CameraBodyWidth/2 + Protrusion),
    					  $fn=TubeSides);
    
    		translate([CameraBodyWidth/2,0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[0]/2,h=TubeLength[0]);
    
    		translate([(TubeLength[0] + CameraBodyWidth/2),
    				  0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[1]/2,h=TubeLength[1]);
    
    		translate([(TubeLength[0] + TubeLength[1] + CameraBodyWidth/2),
    				  0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[2]/2,h=TubeLength[2]);
    
    		translate([(TubeLength[0] + TubeLength[1] + TubeLength[2] + CameraBodyWidth/2),
    				  0,(TubeDia[0]/2 + CameraBaseThick)])
    		  rotate([0,90,0])
    			cylinder(r=TubeDia[3]/2,h=TubeLength[3]);
    
    		translate([0,TripodHoleOffset,-BaseThick])
    		  PolyCyl(TripodHoleDia,(BaseThick + 2*Protrusion));
    
    	  }
    }
    
    //- Main tube
    
    module Tube() {
    
      difference() {
    	cylinder(r=TubeOD/2,h=TubeOuterLength,$fn=TubeSides);
    
    	translate([0,0,TubeEndThickness])
    	  PolyCyl(TubeID,(TubeInnerLength + Protrusion),TubeSides);
    
    	translate([0,0,-Protrusion]) {
    	  if (Mount == "LEDRing")
    		cylinder(r=LensRad,h=(TubeEndThickness + 2*Protrusion));
    	  if (Mount == "Eyepiece")
    		cylinder(r=EyepieceID/2,h=(TubeEndThickness + 2*Protrusion));
    	}
    
    	for (Index = [0:SnoutPins-1])
    	  rotate(Index*90)
    		translate([(SnoutPinCircleDia/2),0,-ThreadThick])
    		  rotate(180)			// flat sides outward
    			PolyCyl(AlignPinOD,TubeEndThickness);
    
    	for (Index = [0:BasePins-1])
    		translate([0,-(TubeOD/2 + Protrusion),
    				  (TubeOuterLength - BasePinOffset - Index*BasePinSpacing)])
    		  rotate([-90,90,0])					// y = flat toward camera
    			PolyCyl(AlignPinOD,(TubeWall + 2*Protrusion));
      }
    
    }
    
    //- Base plate
    
    module BasePlate() {
    
      union() {
    	difference() {
    		linear_extrude(height=BaseThick)
    		  hull() {
    			translate([-(BaseLength/2 - BaseWidth/2),0,0])
    			  circle(BaseWidth/2);
    			translate([ (BaseLength/2 - BaseWidth/2),0,0])
    			  circle(BaseWidth/2);
    			translate([0,(0.75*BaseLength),0])
    			  circle(BaseWidth/2);
    		  }
    
    		translate([0,0,BaseThick])
    		  CameraBody();
    
    		translate([0,(TubeOuterLength + CameraBodyWidth/2),
    				  (BaseThick + TubeDia[0]/2)])
    		  rotate([90,0,0])
    			PolyCyl(TubeOD,TubeOuterLength,$fn=TubeSides);
    
    		for (Index = [0:BasePins-1])
    			translate([0,(CameraBodyWidth/2 + BasePinOffset + Index*BasePinSpacing),
    					  3*ThreadThick])
    			  rotate(90)										// flat toward camera
    				PolyCyl(AlignPinOD,BaseThick);
    
    		translate([0,0,3*ThreadThick])
    		  PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6);	// dia across hex flats
    
    		translate([0,0,-Protrusion])
    		  PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));
    
    		translate([TripodHoleOffset,0,3*ThreadThick])
    		  PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6);	// dia across hex flats
    
    		translate([TripodHoleOffset,0,-Protrusion])
    		  PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));
    
    		translate([-TripodHoleOffset,0,-Protrusion])
    		  PolyCyl(TripodScrewHeadDia,(TripodScrewHeadThick + Protrusion));
    	}
    
    	translate([-TripodHoleOffset,0,0]) {				// support for tripod screw hole
    	  for (Index=[0:3])
    		rotate(Index*45)
    		  translate([-ThreadWidth,-TripodScrewHeadRad,0])
    			cube([2*ThreadWidth,TripodScrewHeadDia,TripodScrewHeadThick]);
    
    	  cylinder(r=0.4*TripodScrewHeadRad,h=(BaseThick - CameraBaseThick),$fn=9);
    	}
      }
    }
    
    //- LED mounting ring
    
    module LEDRing() {
    
      difference() {
    	cylinder(r=LEDBaseOD/2,h=LensRimThick,$fn=48);
    
    	translate([0,0,-Protrusion])
    	  PolyCyl((LensDia + LensClearance),
    			  (LensRimThick + 2*Protrusion));
    
    	translate([0,0,LEDBaseRimThick])
    	  difference() {
    		PolyCyl(LEDBaseOD,LensThick);
    		PolyCyl(LEDRingID,LensThick);
    	  }
    
    	translate([0,0,LEDBaseThick])
    	  difference() {
    		PolyCyl((LEDRingOD + LEDRingClearance),LensThick);
    		cylinder(r1=HoleAdjust(LEDRingID - LEDRingClearance)/2,
    				 r2=HoleAdjust(LensDia + LensClearance)/2 + 2*ThreadWidth,
    				 h=LensThick);
    	  }
    
    	for (Index = [0:SnoutPins-1])
    	  rotate(Index*90)
    		translate([(SnoutPinCircleDia/2),0,-ThreadThick])
    		  rotate(180)			// flat sides outward
    			PolyCyl(AlignPinOD,LEDBaseThick);
    
    	rotate(45)
    	  translate([0,LEDRingID/2,(LEDBaseThick + 1.2*LEDWireHoleDia/2)])
    		rotate([0,-90,0])			// flat side down
    		  rotate([-90,0,0])
    			PolyCyl(LEDWireHoleDia,2*LEDBaseRimWidth);
      }
    
    }
    
    //- Microscope eyepiece adapter
    
    module EyepieceMount() {
    
      difference() {
    	cylinder(r1=TubeOD/2,
    			 r2=(EyepieceOD + 8*ThreadWidth)/2,
    			 h=EyepieceLength,
    			 $fn=TubeSides);
    
    	translate([0,0,-Protrusion])
    	  PolyCyl(EyepieceOD,(EyepieceLength + 2*Protrusion));
    
    	for (Index = [0:SnoutPins-1])
    	  rotate(Index*90)
    		translate([(SnoutPinCircleDia/2),0,-ThreadThick])
    		  rotate(180)			// flat sides outward
    			PolyCyl(AlignPinOD,6*ThreadThick);
      }
    
    }
    
    //-------
    // Build it!
    
    if (Layout != "Show")
      ShowPegGrid();
    
    if (Layout == "Tube")
      Tube();
    
    if (Layout == "LEDRing")
      LEDRing();
    
    if (Layout == "Plate")
      BasePlate();
    
    if (Layout == "Camera")
      CameraBody();
    
    if (Layout == "Eyepiece")
      EyepieceMount();
    
    if (Layout == "Build1")
      translate([0,-BaseLength/3,0])
    	BasePlate();
    
    if (Layout == "Build2")
      Tube();
    
    if (Layout == "Build3")
      LEDRing();
    
    if (Layout == "Build4")
      EyepieceMount();
    
    if (Layout == "Show") {
      translate([0,TubeOuterLength,TubeDia[0]/2]) {
    	rotate([90,0,0])
    	  color(LTC) Tube();
    	translate([0,(Gap/2 - TubeEndThickness - Protrusion),0])
    	  rotate([-90,0,0])
    		for (Index = [0:SnoutPins-1])
    		  rotate(Index*90)
    			translate([(SnoutPinCircleDia/2),0,0])
    			  rotate(180)			// flat sides outward
    				PolyCyl(AlignPinOD,(TubeEndThickness + LEDBaseThick));
    
    	translate([0,Gap,0])
    	  rotate([-90,0,0]) {
    		if (Mount == "LEDRing")
    		  color(OOR) LEDRing();
    		if (Mount == "Eyepiece")
    		  color(OOR) EyepieceMount();
    	  }
      }
    
      translate([0,-CameraBodyWidth/2,0])
    	color(PG) CameraBody();
    
      color(PDA)
    	render()
    	translate([0,-CameraBodyWidth/2,-(BaseThick + Gap)])
    	  BasePlate();
    
      for (Index = [0:BasePins-1])
    	  translate([0,(BasePinOffset + Index*BasePinSpacing),
    				-Gap/2])
    		rotate([180,0,90])										// flat toward camera
    		  PolyCyl(AlignPinOD,BaseThick/2);
    
    }
    
  • SX230HS Adapter: Microscope Snout

    The microscope eyepiece adapter was easy enough: a cone with a cylinder punched out of it:

    Microscope eyepiece adapter - front view
    Microscope eyepiece adapter – front view

    The thin part of the tip is four threads wide around the eyepiece OD, which makes for good fill.

    The bottom has the usual four alignment pin holes:

    Microscope eyepiece adapter - bottom view
    Microscope eyepiece adapter – bottom view

    The main tube opening ID is equal to the diameter of the flat rim around the microscope eyepiece, which provides a positive stop with plenty of surface area.

    It looks about like you’d expect on the microscope, with the LED ring light from Planet Barbie around the objective lenses:

    SX230HS on microscope
    SX230HS on microscope

    The lowest magnification is much higher than I expected; it’s about 2.5 mm across. This is a 1206 SMD resistor:

    Microscope adapter - minimum magnification
    Microscope adapter – minimum magnification

    The highest magnification shows details of a laser etched numeral:

    Microscope adapter - maximum magnification
    Microscope adapter – maximum magnification

    The alignment obviously isn’t up to par and I think the exposure needs some work. All in all, though, it’s usable as-is.

  • SX230HS Adapter: Macro Lens Snout

    The macro lens adapter took shape around a nice 25 mm doublet lens from the Box o’ Lenses. The Canon SX230HS has a lens opening that’s just about 25 mm in diameter and a larger lens would be better, but at maximum zoom the image pretty much fills the camera’s entrance pupil. I ordered a pair of 50 mm LED ring lights from halfway around the planet and built the snout to hold the ring around the lens:

    Macro lens snout with LED ring light
    Macro lens snout with LED ring light

    That’s the first pass to get the sizes right and work out some details. In particular, that small white ring inside the aperture below the lens didn’t work at all, so I made the main tube opening a bit smaller.

    The solid model shows the details a bit better:

    Macro adapter snout - solid model - front view
    Macro adapter snout – solid model – front view

    The inner cone shields the lens edges from (most of the) scattered LED light. I considered angling the side walls to concentrate the ring light, but wasn’t convinced it’d be worth the effort because the LEDs have such a broad beamwidth anyway. The little hole is for the LED power cable, which goes to a 12 V switching wall wart. The 5 strings of 3 LEDs draw about 70 mA, which suggests I should hack the ballast resistors down a bit to boost the current up to 20 mA per string. FWIW, the resistors give 25 mA per string at 13.8 V, so I could probably goose the current a lot higher.

    The bottom has four shallow holes for the alignment pegs cut from ABS filament:

    Macro adapter snout - solid model - bottom view
    Macro adapter snout – solid model – bottom view

    The hole in the front end of the main tube is marginally smaller than the lens diameter, as I used the OpenSCAD cylinder primitive instead of the PolyCyl module that slightly enlarges holes to make the answer come out right. The difference is just enough to form a solid stop that aligns the lens and prevents it from sliding into the body before the glue cures.

    The whole affair looks pretty scary from the victim’s point of view:

    SX230HS macro adapter LED ring light - front view
    SX230HS macro adapter LED ring light – front view

    But the camera’s view seems OK, albeit with some vignetting:

    Canon NB-5L battery through macro lens adapter
    Canon NB-5L battery through macro lens adapter

    Limited by vignetting & entrance pupil filling, zooming controls the horizontal subject size from 25 mm to about 10 mm. Depth of field is a few mm, at best; the printing on the far end of that battery is fuzzier than it seems.

    Best results so far come from:

    • Manual aperture at f/8
    • Manual focus at infinity (move the subject to focus)
    • Shutter delay = 2 seconds to let the camera stop shaking