Sneaky Electrons

Miscellaneous Technical Stuff.

ThinkPad R61 With an External Power Switch

I had an old ThinkPad R61 motherboard lying around which was just begging to be re-purposed as a music system / home automation thingie. It’s small enough and plenty powerful for the task. Slap it with a touchscreen, USB DAC, some other peripherals and it would be almost perfect. The only drawback is that the power button is located on the keyboard, and basically, there is no way to turn it on without resorting to Wake-on-LAN when keyboard is not attached.
At first, I thought it would be easier or at least less destructive just to use a keyboard connector on a piece of breakout board with a power switch attached to it.
The connector used on ThinkPad R61 (and on plenty of other models of similar age) is JAE AA01B-P040VA1. However, as it turns out, it’s nearly impossible to procure this thing. They seem to be available only in bulk through some Chinese distributors.

Seems like the only thing left is to solder the button straight to the motherboard. This can be done by soldering a push button to 10th top-left pin and to any of ground pads at the keyboard connector:

However, since R61 comes with an unpopulated docking station connector which also exposes the power button pad, it’s actually easier to use it instead. The pads are covered with solder mask, so it should be removed with sandpaper before soldering. Here we would need to connect 2nd bottom-right pad with any of the ground pads:

Iriver E30/E50 Recovery

Yesterday my rather old, but still totally awesome, iriver E30 stopped working – it would just display “designed by iriver” briefly before shutting down completely. Actually, it started behaving erratically a few month before. Occasionally it would un-mount or wouldn’t connect at all. I just blamed it on the OS at the time, but as it turns out, it’s rather a common problem with iriver players and can be fixed by re-flashing the firmware in recovery mode.

If you have Windows XP laying around then just download firmware package for E30 and follow the steps bellow. On Windows 7 the updater bundled with E30 wouldn’t work, but the one from E40 firmware package can be used instead. Just overwrite updater.sb and E40.Hex in the E40 firmware package with updater.sb and E30.Hex from E30 package.

By the way, an actual Windows machine might be needed for this, it didn’t work for me with VirtualBox under Linux.

  1. Take the player apart. There are four screws on the bottom holding the circuit board down and two under the LCD. You’ll need to lift the LCD to remove those.
  2. Desolder the battery. Speaking of which, this could be a good opportunity to replace it with a newer one. I was unable to find a replacement with exactly the same dimensions: 2.8mm thick x 30mm wide x 48mm long (hence the model number PR-283048N), but slightly thicker 303048 worked just as fine.
  3. Short out bottom right 5th and 6th pin (see the picture) of the flash memory chip. Just use something conductive, it doesn’t need to permanent.
  4. Connect the player to computer.
  5. Wait for Windows to install drivers for the newly found devices (should display as HID-compatibale and USB input devices under system manager).
  6. Remove the short-out and run StUpdaterApp_E40.exe.
  7. Click Start and wait for it to finish.
  8. Repeat steps 3-7 if something goes wrong…

Matching Image to a Collection of Images With Emgu CV

One way for finding matching image within a collection of images (let’s say using SURF algorithm) is to extract features from the query image and all the images in the collection, and then find matching features one by one. While this might work for small collections, it will have horrible performance for collections of considerable size.
For a better performing approach we can use Fast Approximate Nearest Neighbor (FLANN) search which was created for the purpose of doing fast nearest neighbor search in large data-sets.
So let’s get straight to it.

Implementation Concept

  1. For each image in the collection compute descriptors with the algorithm best fitting your requirements. I am using SURF, however depending on the required license, performance or image nuances a different algorithm might be more appropriate.

  2. Combine all the descriptors into one big matrix while keeping track which descriptor range within the matrix came from what image. Basically we concatenate rows from all matrices and save the row number where the newly appended descriptors start and end.

  3. Build a FLANN index from the concatenated matrix.

  4. Compute descriptors for the query image.

  5. Run KNN search over the FLANN index. A K-Nearest-Neighbours search computes the distance between a query descriptor and all of the image collection descriptors, and returns the K pairs with lowest distance. KNN search will produce two matrices – Indeces matrix and Distances matrix containing indices of descriptors within the concatenated matrix which had the lowest distance and the distances themselves, respectively.

  6. Filter out all the inadequate matches computed in the previous step.

  7. Now we can find images matching the query image. Since we know which descriptors within the concatenated matrix belong to what image, the best matching image will be one that has the higher number of descriptors, or more precisely descriptor indexes in the Indices matrix.

Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Main method.
/// </summary>
public IList<IndecesMapping> Match()
{
    string[] dbImages = {"1.jpg", "2.jpg", "3.jpg"};
    string queryImage = "query.jpg";

    IList<IndecesMapping> imap;

    // compute descriptors for each image
    var dbDescsList = ComputeMultipleDescriptors(dbImages, out imap);

    // concatenate all DB images descriptors into single Matrix
    Matrix<float> dbDescs = ConcatDescriptors(dbDescsList);

    // compute descriptors for the query image
    Matrix<float> queryDescriptors = ComputeSingleDescriptors(queryImage);

    FindMatches(dbDescs, queryDescriptors, ref imap);

    return imap;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// Computes image descriptors.
/// </summary>
/// <param name="fileName">Image filename.</param>
/// <returns>The descriptors for the given image.</returns>
public Matrix<float> ComputeSingleDescriptors(string fileName)
{
    Matrix<float> descs;

    using (Image<Gray, Byte> img = new Image<Gray, byte>(fileName))
    {
        VectorOfKeyPoint keyPoints = detector.DetectKeyPointsRaw(img, null);
        descs = detector.ComputeDescriptorsRaw(img, null, keyPoints);
    }

    return descs;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// Convenience method for computing descriptors for multiple images.
/// On return imap is filled with structures specifying which descriptor ranges in the concatenated matrix belong to what image. 
/// </summary>
/// <param name="fileNames">Filenames of images to process.</param>
/// <param name="imap">List of IndecesMapping to hold descriptor ranges for each image.</param>
/// <returns>List of descriptors for the given images.</returns>
public IList<Matrix<float>> ComputeMultipleDescriptors(string[] fileNames, out IList<IndecesMapping> imap)
{
    imap = new List<IndecesMapping>();

    IList<Matrix<float>> descs = new List<Matrix<float>>();

    int r = 0;

    for (int i = 0; i < fileNames.Length; i++)
    {
        var desc = ComputeSingleDescriptors(fileNames[i]);
        descs.Add(desc);

        imap.Add(new IndecesMapping()
        {
            fileName = fileNames[i],
            IndexStart = r,
            IndexEnd = r + desc.Rows - 1
        });

        r += desc.Rows;
    }

    return descs;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// <summary>
/// Computes 'similarity' value (IndecesMapping.Similarity) for each image in the collection against our query image.
/// </summary>
/// <param name="dbDescriptors">Query image descriptor.</param>
/// <param name="queryDescriptors">Consolidated db images descriptors.</param>
/// <param name="images">List of IndecesMapping to hold the 'similarity' value for each image in the collection.</param>
public void FindMatches(Matrix<float> dbDescriptors, Matrix<float> queryDescriptors, ref IList<IndecesMapping> imap)
{
    var indices = new Matrix<int>(queryDescriptors.Rows, 2); // matrix that will contain indices of the 2-nearest neighbors found
    var dists = new Matrix<float>(queryDescriptors.Rows, 2); // matrix that will contain distances to the 2-nearest neighbors found

    // create FLANN index with 4 kd-trees and perform KNN search over it look for 2 nearest neighbours
    var flannIndex = new Index(dbDescriptors, 4);
    flannIndex.KnnSearch(queryDescriptors, indices, dists, 2, 24);

    for (int i = 0; i < indices.Rows; i++)
    {
        // filter out all inadequate pairs based on distance between pairs
        if (dists.Data[i, 0] < (0.6 * dists.Data[i, 1]))
        {
            // find image from the db to which current descriptor range belongs and increment similarity value.
            // in the actual implementation this should be done differently as it's not very efficient for large image collections.
            foreach (var img in imap)
            {
                if (img.IndexStart <= i && img.IndexEnd >= i)
                {
                    img.Similarity++;
                    break;
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Concatenates descriptors from different sources (images) into single matrix.
/// </summary>
/// <param name="descriptors">Descriptors to concatenate.</param>
/// <returns>Concatenated matrix.</returns>
public Matrix<float> ConcatDescriptors(IList<Matrix<float>> descriptors)
{
    int cols = descriptors[0].Cols;
    int rows = descriptors.Sum(a => a.Rows);

    float[,] concatedDescs = new float[rows, cols];

    int offset = 0;

    foreach (var descriptor in descriptors)
    {
        // append new descriptors
        Buffer.BlockCopy(descriptor.ManagedArray, 0, concatedDescs, offset, sizeof(float) * descriptor.ManagedArray.Length);
        offset += sizeof(float) * descriptor.ManagedArray.Length;
    }

    return new Matrix<float>(concatedDescs);
}
1
2
3
4
5
6
7
public class IndecesMapping
{
  public int IndexStart { get; set; }
  public int IndexEnd { get; set; }
  public int Similarity { get; set; }
  public string fileName { get; set; }
}
1
2
3
private const double surfHessianThresh = 300;
private const bool surfExtendedFlag = true;
private SURFDetector detector = new SURFDetector(surfHessianThresh, surfExtendedFlag);

EIA Standard Resistor Value Approximation in JavaScript

While working on my bi-directional resistor color code calculator, one of the first things I stumbled upon was how to normalize the user supplied resistance value.
Generally, resistors come in predefined values, or “preferred values”. These values were defined by Electronic Industries Association (EIA) (IEC 60038) and consist of about 6 different series with each series corresponding to particular tolerances:

E6    20% tolerance
E12   10% tolerance
E24   5% tolerance
E48   2% tolerance
E96   1% tolerance
E192  0.5, 0.25, 0.1% tolerances

Full table

Such division ensures that substituting an arbitrary value with nearest preferred value wouldn’t exceed the required tolerance. In other words, given a tolerance and an arbitrary resistance value we can calculate the preferred value using the above table.

Fortunately there is much neater way to do this. It is possible to approximate E-series preferred values (which is basically Renard series with different interval steeping) with the following equation (author unknown):

Where R is the resistance, round() is round to nearest integer function, and round2decimal() is round to 2 decimal places function.
The equation seemed to work for E48 and E96 series, but gave wrong values for other. The code below is its adaptation for JavaScript and for the rest of the series E12/24/192. The function accepts resistance value in Ohm plus tolerance and produces closest preferred value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   function log10(val) {
      return Math.log(val) / Math.LN10;
  }

        function EIAValues(val, tolerance) {
            var series;

            if (tolerance == 20) series = 6;
            else if (tolerance == 10) series = 12;
            else if (tolerance == 5) series = 24;
            else if (tolerance == 2) series = 48;
            else if (tolerance == 1) series = 96;
            else series = 192;

            var l = log10(val);

            var decplaces = series < 48 ? 10 : 100;

            var pref_val = (Math.round((Math.pow(10, (Math.round(series * l) / series)) / Math.pow(10, Math.floor(log10((Math.pow(10, (Math.round(series * l) / series))))))) * decplaces) / decplaces) * Math.pow(10, Math.floor(log10((Math.pow(10, (Math.round(series * l) / series))))));

            // compensate for possible precision loss in the above calculation
            var rounded = Math.round(pref_val);
            var abs = Math.abs(rounded - pref_val);
            if (abs > 0.999 || abs < 0.0001)
                pref_val = rounded;

            if (pref_val >= 260 && pref_val <= 460) pref_val += 10; // fix for E24/E12/E6 series   
            else if (pref_val == 830) pref_val -= 10;               // fix for E24/E12/E6 series
            else if (pref_val == 919) pref_val++;                   // fix for E192 series

            return pref_val;
        }