HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Locating a bitmap inside another (larger) bitmap with F#

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
largerwithlocatinganotherbitmapinside

Problem

This is my firstish (heavily rewritten) go at the completed project that I've been working on with CodeReview assistance, so further advice is appreciated! See here, here and here for the past history of the project.

I strongly suspect there's a much more functional way to manage my loops, so assistance with that area especially is what I would like.

I have a separate set of unit tests (visible here) which confirm that this code works with my two sample image sets.

```
namespace MathAlgorithms

module ArrayFunctions =
// Generic because it makes testing easier, yay math
let SearchSubset (tSmallArray:'a[][]) (tLargeArray:'a[][]) (pCoordinate:(int * int)) =
let tSmallHeight = tSmallArray.Length
let tSmallWidth = tSmallArray.[0].Length

let tHeightIndex = fst pCoordinate
let tWidthIndex = snd pCoordinate
let mutable tSmallHeightIndex = 0
let mutable tSmallWidthIndex = 0
let mutable tMatch = true

try
while ( tSmallHeightIndex false

namespace FsharpImaging

open System
open System.Drawing
open System.Drawing.Imaging
open System.Runtime.InteropServices
open MathAlgorithms

module ImageFunctions =
let LoadBitmapIntoArray (pBitmap:Bitmap) =
let tBitmapData = pBitmap.LockBits( Rectangle(Point.Empty, pBitmap.Size),
ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb )
let tImageArrayLength = Math.Abs(tBitmapData.Stride) * pBitmap.Height
let tImageDataArray = Array.zeroCreate tImageArrayLength

Marshal.Copy(tBitmapData.Scan0, tImageDataArray, 0, tImageArrayLength)
pBitmap.UnlockBits(tBitmapData)

( pBitmap.Width, pBitmap.Height, tBitmapData.Stride ), tImageDataArray

// Notes:
// Image pixel data is stored BGR ( blue green red )
// Image data is padded to be divisible by 4 (int32 width)

let Transform2D ( (pDimensi

Solution

try ... with | _ -> false is bad. Like, very bad. The equivalent in C# is

try
{
    ...
}
catch (Exception)
{
    return false;
}


See this answer on SO for good advice on exception handling, and this one line in particular:


Only catch what you can handle and recover from.

In that particular code, the only exception I can imagine being thrown is an IndexOutOfRangeException, which you definitely want to know about. If it's thrown, it means your code is wrong.

I mentioned in a previous answer that Hungarian notation is dead and buried, and I feel that I'm not going to change your mind, but I will say it again just in case :) tImagine pIf tMy tReview pLooked tLike tThis...

You can use pattern matching to simplify this

let tHeightIndex = fst pCoordinate
let tWidthIndex = snd pCoordinate


to this

let heightIndex, widthIndex = coordinate


Or since coordinate is only used for these values, you can change the function signature to

let SearchSubset (smallArray : 'a[][]) (largeArray : 'a[][]) (heightIndex : int, widthIndex : int)


While we're looking at this, SearchSubset is a misleading name. I would suggest isSubmatrix.

I'm not sure, but it looks like you have off-by-one errors in these lines:

while ( tSmallHeightIndex < tSmallHeight - 1 ) && tMatch do
    while ( tSmallWidthIndex < tSmallWidth - 1 ) && tMatch do


I would think smallHeightIndex should range over [0, smallHeight), and similarly for smallWidthIndex.

Now as for the loops... I'm going to sidestep the entire issue by suggesting another way of writing this. F# has nice support for array slices and structural equality, which allows us to write the following:

let isSubmatrix (small : 'a[][]) (large : 'a[][]) (y : int, x : int) : bool =
    // TODO: bounds checking.
    let yMax = y + small.Length - 1
    let xMax = x + small.[0].Length - 1
    large.[y .. yMax] |> Array.map (fun row -> row.[x .. xMax]) = small


And some sample tests

let large = [| [| 0; 1; 2 |]
             ; [| 3; 4; 5 |]
             ; [| 6; 7; 8 |]
             ; [| 9; 10; 11 |] |]

printfn "%A" <| isSubmatrix [| [| 6; 7 |] ; [| 9 ; 10 |] |] large (2, 0)
printfn "%A" <| isSubmatrix [| [| 6; 7; 8 |] ; [| 9 ; 10; 11 |] |] large (2, 0)
printfn "%A" <| isSubmatrix [| [| 1 |] ; [| 4 |] |] large (0, 1)


OK, so what's going on here? Let's look at what the slices are doing.

large.[y .. yMax] will give us \$n\$ rows of large starting at row y, where \$n\$ is the number of rows in small.

Array.map (fun row -> row.[x .. xMax]) will then give us \$m\$ elements of each row, starting at column x, where \$m\$ is the number of columns in small.

For example,

printfn "%A" (large.[2 .. 3] |> Array.map (fun row -> row.[1 .. 2]))


will print

[|[|7; 8|]; [|10; 11|]|]


F# even supports slicing 2D arrays, which is nice:

let isSubmatrix (small : 'a[,]) (large : 'a[,]) (y : int, x : int) : bool =
    // TODO: bounds checking.
    let yMax = y + Array2D.length1 small - 1
    let xMax = x + Array2D.length2 small - 1
    large.[y .. yMax, x .. xMax] = small

Code Snippets

try
{
    ...
}
catch (Exception)
{
    return false;
}
let tHeightIndex = fst pCoordinate
let tWidthIndex = snd pCoordinate
let heightIndex, widthIndex = coordinate
let SearchSubset (smallArray : 'a[][]) (largeArray : 'a[][]) (heightIndex : int, widthIndex : int)
while ( tSmallHeightIndex < tSmallHeight - 1 ) && tMatch do
    while ( tSmallWidthIndex < tSmallWidth - 1 ) && tMatch do

Context

StackExchange Code Review Q#57882, answer score: 6

Revisions (0)

No revisions yet.