Reputation: 185
The code below stems from work on a Euclidean Distance algorithm. The color table was simply a vehicle to test the algorithm. It is perhaps reinventing the wheel, however it is useful in itself. Any 3 RGB integers (0-255) can be associated with the nearest X11 color name. Many thanks to svick for his insights.
In the current code, the ColorTable is initialized via the AddColor method after the instance is created. However, the loadrgb/colorinfo combination can be used to pull down an X11 color table off the web.
I'm having one last problem in initializing the color table from the online version of the X11 rgb.txt file. I need to parse the text into a {Name: Values:} list. Currently, the results are in a tuple of strings. I'm working to have "colorinfo" load the "ColorTable".
// currently the color table is create via the AddColor method, however
// the initial values should be created with the loadrgb and colorinfo members
type MyFSColorTable() =
// pull the X11 rgb.txt color table off the web in text format
static let loadrgb =
let url = "http://people.csail.mit.edu/jaffer/Color/rgb.txt"
let req = WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let txt = reader.ReadToEnd()
txt
// parse the text of the rgb.txt color table into a Name: Values: list
static let colorinfo =
loadrgb.Split([|'\n'|])
|> Seq.skip 1
|> Seq.map (fun line -> line.Split([|'\t'|]))
|> Seq.filter (fun values -> values |> Seq.length = 3)
|> Seq.map (fun values -> string values.[0], string values.[2])
|> Seq.map (fun (rgb, name) -> rgb.Split([|' '|]), name)
|> Seq.map (fun (rgb, name) -> [|name, rgb.[0], rgb.[1], rgb.[2]|])
// Mutable Color Table will be defined on-the-fly
let mutable ColorTable = []
// Euclidean distance between 2 vectors - float is overkill here
static let Dist (V1: float[]) V2 =
Array.zip V1 V2
|> Array.map (fun (v1, v2) -> pown (v1 - v2) 2)
|> Array.sum
// Add new colors to the head of the ColorTable
member x.AddColor name rgb = ColorTable <- {Name = name; Values = rgb}::ColorTable
// Find nearest color by calculating euclidean distance of all colors,
// then calling List.minBy for the smallest
member x.FindNearestColor (rgb : float[]) =
let nearestColor =
ColorTable |> List.minBy (fun color -> Dist rgb color.Values)
nearestColor.Name
Upvotes: 0
Views: 470
Reputation: 47904
You can shorten your code, and return {Name: string; Values: float[]} list
as desired, with the following:
static let colorinfo =
loadrgb.Split('\n')
|> Seq.skip 1
|> Seq.choose (fun line ->
match line.Split('\t') with
| [|rgb; _; name|] ->
let values = rgb.Split(' ') |> Array.map float
Some({Name=name; Values=values})
| _ -> None)
|> Seq.toList
Upvotes: 0
Reputation: 243061
At the moment, your code that constructs colorinfo
produces a sequence containing an array with just a single element which is a tuple (containing four strings). This means that the type of the overall result is - in your current version - seq<(string * string * string * string) []>
:
(...)
|> Seq.map (fun (rgb, name) ->
[|name, rgb.[0], rgb.[1], rgb.[2] |]
This is probably not intended - if you wanted to create an array with four strings, you need to use semicolon instead of comma [| name; rgb.[0]; ... |]
and if you wanted to create four-element tuple, then you can just omit the [|
and |]
around the tuple.
There is no way to automatically turn array or a tuple into a named record type (assuming you have a record with Name:string
and Values:float[]
), so the best option is to do this transformation in the last step of your pipeline. You can replace the above with:
|> Seq.map (fun (rgb, name) ->
{ Name = name
Values = [| float rgb.[0]; float rgb.[1]; float rgb.[2] |] })
|> List.ofSeq
I also added List.ofSeq
to the end of the snippet so that you get a list back - that is the same type you're using for ColorTable
at the moment.
(Alternatively, you could say Values = Array.map float rgb
, which converts all strings in rgb
to floats and works for any lenght of the array.)
Upvotes: 2