So far I've managed to implement Dolby's Matrix Decoder based on the following specifications:
Left Right
Center 0.707 0.707
Left 1 0
Right 0 1
SLeft 0.871 0.489
SRight 0.489 0.871
But after testing my matrix I've discovered there's a lot clipping, and it sounds nothing like Dolby's decoder. I'm relatively new to DSP, although I'm pretty sure I understand most of the basics; but I'm still left clueless as to what's causing this to happen, am I missing something in the specifications, or is it just my code?
My current matrix decoder,
private static void DolbyProLogicII(List<float> leftSamples, List<float> rightSamples, int sampleRate, string outputDirectory)
{
// WavFileWrite is a wrapper class for NAudio to create Wav files.
var meta = new WaveFormat(sampleRate, 16, 1);
var c = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "c.wav") };
var l = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "l.wav") };
var r = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "r.wav") };
var sl = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "sl.wav") };
var sr = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "sr.wav") };
var ii = (leftSamples.Count > rightSamples.Count ? rightSamples.Count : leftSamples.Count);
// Process center channel.
for (var i = 0; i < ii; i++)
{
c.MonoChannelAudioData.Add((leftSamples[i] * 0.707) + (rightSamples[i] * 0.707));
}
c.Flush();
// Process left channel.
l.MonoChannelAudioData = leftSamples;
l.Flush();
// Process right channel.
r.MonoChannelAudioData = rightSamples;
r.Flush();
// Process surround left channel.
for (var i = 0; i < ii - 1; i++)
{
sl.MonoChannelAudioData.Add((leftSamples[i] * 0.871) + (rightSamples[i] * 0.489));
}
sl.Flush();
// Process surround right channel.
for (var i = 0; i < ii - 1; i++)
{
sr.MonoChannelAudioData.Add((leftSamples[i] * 0.489) + (rightSamples[i] * 0.871));
}
sr.Flush();
}
Tl;dr
To strictly compare your implementation with Dolby's specifications, you're missing several things,
The decoder's mixing levels (not the ratios) are incorrect,
The LFE channel isn't being processed,
The surround left & surround right channels aren't phase shifted, delayed, or passed through a HPF,
And the center channel isn't passed through a Bandpass.
The right mix
The clipping problem is the result of your maxtrix mixing levels (the ratios are fine), for example, the center channel is being over amp'd by 41.4% since 0.707 + 0.707 = 1.414; so, to retain the correct ratios simply halve your mixing levels.
With that in mind, your maxtrix should now look like this,
Left Right
Center 0.354 0.354
Left 0.5 0
Right 0 0.5
SLeft 0.436 0.245
SRight 0.245 0.436
Adding the LFE
If you're not intentionally leaving out the LFE channel, you can decode it just like the center channel, but you must apply an LPF at 120Hz (Dolby's standard).
Making the surround channels actually "surround"
To process the surround left and surround right channels, you'll need to pass the left and right channels through a HPF at 100 Hz then apply a 90o phase shift (then invert the left side); mix, and then add a delay (I believe Dolby doesn't specify the exact amount of time, but after some testing I've found the "sweetspot" appears to be around 10 milliseconds).
(If you're going to play around with the delay, I'd recommend doing so between 5ms to 12.5ms (feel free to experiment). If the delay is too small you'll end up with a "compressed/condensed" sounding mix, too long, and you'll end up with an awful echoing of higher frequencies in the background. Ideally, the end result should sound as "airy/open" as possible, but without any hint of echoing/reverberation.)
leftSamples -> HPF -> phase shift -> invert -> mix \
-> delay
rightSamples -> HPF -> phase shift -> mix /
The Center channel
Dolby also specifies that the center channel needs to be passed through a bandpass at 70Hz to 20kHz (after mixing).
Finally...
So, putting that all together you should end up with the following,
public void DolbyProLogicII(float[] leftSamples, float[] rightSamples, int sampleRate)
{
var ii = Math.Min(leftSamples.Length, rightSamples.Length);
var c = new float[ii];
var l = new float[ii];
var r = new float[ii];
var sl = new float[ii];
var sr = new float[ii];
var lfe = new float[ii];
// Process center channel
for (var i = 0; i < ii; i++)
{
// Best to be as precise as possible.
c[i] = (leftSamples[i] * 0.35355339059327376220042218105242f) + (rightSamples[i] * 0.35355339059327376220042218105242f);
}
c = LinkwitzRileyHighPass(c, sampleRate, 70);
c = LinkwitzRileyLowPass(c, sampleRate, 20000);
// Process front left channel
for (var i = 0; i < ii; i++)
{
l[i] = leftSamples[i] * 0.5f;
}
// Process front right channel
for (var i = 0; i < ii; i++)
{
r[i] = rightSamples[i] * 0.5f;
}
// Process left samples for SL channel
var slL = new float[ii];
for (var i = 0; i < ii; i++)
{
slL[ii] = leftSamples[i] * 0.43588989435406735522369819838596f;
}
slL = LinkwitzRileyHighPass(slL, sampleRate, 100);
slL = PhaseShift(slL, sampleRate, true);
// Process right samples for SL channel.
var slR = new float[ii];
for (var i = 0; i < ii; i++)
{
slR[i] = rightSamples[i] * 0.24494897427831780981972840747059f;
}
slR = LinkwitzRileyHighPass(slR, sampleRate, 100);
slR = PhaseShift(slR, sampleRate);
// Combine new left & right samples for SL channel
for (var i = 0; i < ii - 1; i++)
{
sl[i] = slL[i] + slR[i];
}
sl = Delay(sl, sampleRate, 10);
// Process left samples for SR channel
var srL = new float[ii];
for (var i = 0; i < ii; i++)
{
srL[i] = leftSamples[i] * 0.24494897427831780981972840747059f;
}
srL = LinkwitzRileyHighPass(srL, sampleRate, 100);
srL = PhaseShift(srL, sampleRate, true);
// Process right samples for SR channel
var srR = new float[ii];
for (var i = 0; i < ii; i++)
{
srR[i] = rightSamples[i] * 0.43588989435406735522369819838596f;
}
srR = LinkwitzRileyHighPass(srR, sampleRate, 100);
srR = PhaseShift(srR, sampleRate);
// Combine new left & right samples for SR channel
for (var i = 0; i < ii - 1; i++)
{
sr[i] = srL[i] + srR[i];
}
sr = Delay(sr, sampleRate, 10);
// Process LFE channel.
for (var i = 0; i < ii; i++)
{
lfe[i] = (leftSamples[i] * 0.35355339059327376220042218105242f) + (rightSamples[i] * 0.35355339059327376220042218105242f);
}
lfe = LinkwitzRileyLowPass(lfe, sampleRate, 120);
}
public float[] PhaseShift(float[] samples, double sampleRate, bool invertOutput = false)
{
var depth = 4.0;
var delay = 100.0;
var rate = 0.1;
var newSamples = new float[samples.Length];
double wp, min_wp, max_wp, range, coef, sweepfac;
double inval, x1, outval = 0.0;
double lx1 = 0.0, ly1 = 0.0, lx2 = 0.0, ly2 = 0.0, lx3 = 0.0, ly3 = 0.0, lx4 = 0.0, ly4 = 0.0;
// calc params for sweeping filters
wp = min_wp = (Math.PI * delay) / sampleRate;
range = Math.Pow(2.0, depth);
max_wp = (Math.PI * delay * range) / sampleRate;
rate = Math.Pow(range, rate / (sampleRate / 2));
sweepfac = rate;
for (var i = 0; i < samples.Length; i++)
{
coef = (1.0 - wp) / (1.0 + wp); // calc coef for current freq
x1 = (inval = (double)samples[i]);
ly1 = coef * (ly1 + x1) - lx1; // do 1st filter
lx1 = x1;
ly2 = coef * (ly2 + ly1) - lx2; // do 2nd filter
lx2 = ly1;
ly3 = coef * (ly3 + ly2) - lx3; // do 3rd filter
lx3 = ly2;
ly4 = coef * (ly4 + ly3) - lx4; // do 4th filter
lx4 = ly3;
// final output
outval = ly4;
if (invertOutput)
{
newSamples[i] = -(float)outval;
}
else
{
newSamples[i] = (float)outval;
}
wp *= sweepfac; // adjust freq of filters
if (wp > max_wp) // max?
{
sweepfac = 1.0 / rate; // sweep back down
}
else
{
if (wp < min_wp) // min?
{
sweepfac = rate; // sweep back up
}
}
}
return newSamples;
}
public float[] Delay(float[] samples, int sampleRate, double milliseconds)
{
var output = new List<float>(samples);
var ii = (sampleRate / 1000) * milliseconds;
for (var i = 0; i < ii; i++)
{
output.Insert(0, 0);
}
return output.ToArray();
}
public float[] LinkwitzRileyHighPass(float[] samples, int sampleRate, double cutoff)
{
if (cutoff <= 0 && cutoff >= sampleRate / 2)
{
throw new ArgumentOutOfRangeException("cutoff", "The cutoff frequency must be between 0 and \"sampleRate\" / 2.");
}
if (sampleRate <= 0)
{
throw new ArgumentOutOfRangeException("sampleRate", "The sample rate must be more than 0.");
}
if (samples == null || samples.Length == 0)
{
throw new ArgumentNullException("samples", "\"samples\" can not be null or empty.");
}
var newSamples = new float[samples.Length];
var wc = 2 * Math.PI * cutoff;
var wc2 = wc * wc;
var wc3 = wc2 * wc;
var wc4 = wc2 * wc2;
var k = wc / Math.Tan(Math.PI * cutoff / sampleRate);
var k2 = k * k;
var k3 = k2 * k;
var k4 = k2 * k2;
var sqrt2 = Math.Sqrt(2);
var sq_tmp1 = sqrt2 * wc3 * k;
var sq_tmp2 = sqrt2 * wc * k3;
var a_tmp = 4 * wc2 * k2 + 2 * sq_tmp1 + k4 + 2 * sq_tmp2 + wc4;
var b1 = (4 * (wc4 + sq_tmp1 - k4 - sq_tmp2)) / a_tmp;
var b2 = (6 * wc4 - 8 * wc2 * k2 + 6 * k4) / a_tmp;
var b3 = (4 * (wc4 - sq_tmp1 + sq_tmp2 - k4)) / a_tmp;
var b4 = (k4 - 2 * sq_tmp1 + wc4 - 2 * sq_tmp2 + 4 * wc2 * k2) / a_tmp;
var a0 = k4 / a_tmp;
var a1 = -4 * k4 / a_tmp;
var a2 = 6 * k4 / a_tmp;
var a3 = a1;
var a4 = a0;
double ym1 = 0.0, ym2 = 0.0, ym3 = 0.0, ym4 = 0.0, xm1 = 0.0, xm2 = 0.0, xm3 = 0.0, xm4 = 0.0, tempy = 0.0;
for (var i = 0; i < samples.Length; i++)
{
var tempx = samples[i];
tempy = a0 * tempx + a1 * xm1 + a2 * xm2 + a3 * xm3 + a4 * xm4 - b1 * ym1 - b2 * ym2 - b3 * ym3 - b4 * ym4;
xm4 = xm3;
xm3 = xm2;
xm2 = xm1;
xm1 = tempx;
ym4 = ym3;
ym3 = ym2;
ym2 = ym1;
ym1 = tempy;
newSamples[i] = (float)tempy;
}
return newSamples;
}
public float[] LinkwitzRileyLowPass(float[] samples, int sampleRate, double cutoff)
{
if (cutoff <= 0 && cutoff >= sampleRate / 2)
{
throw new ArgumentOutOfRangeException("cutoff", "The cutoff frequency must be between 0 and \"sampleRate\" / 2.");
}
if (sampleRate <= 0)
{
throw new ArgumentOutOfRangeException("sampleRate", "The sample rate must be more than 0.");
}
if (samples == null || samples.Length == 0)
{
throw new ArgumentNullException("samples", "\"samples\" can not be null or empty.");
}
var newSamples = new float[samples.Length];
var wc = 2 * Math.PI * cutoff;
var wc2 = wc * wc;
var wc3 = wc2 * wc;
var wc4 = wc2 * wc2;
var k = wc / Math.Tan(Math.PI * cutoff / sampleRate);
var k2 = k * k;
var k3 = k2 * k;
var k4 = k2 * k2;
var sqrt2 = Math.Sqrt(2);
var sq_tmp1 = sqrt2 * wc3 * k;
var sq_tmp2 = sqrt2 * wc * k3;
var a_tmp = 4 * wc2 * k2 + 2 * sq_tmp1 + k4 + 2 * sq_tmp2 + wc4;
var b1 = (4 * (wc4 + sq_tmp1 - k4 - sq_tmp2)) / a_tmp;
var b2 = (6 * wc4 - 8 * wc2 * k2 + 6 * k4) / a_tmp;
var b3 = (4 * (wc4 - sq_tmp1 + sq_tmp2 - k4)) / a_tmp;
var b4 = (k4 - 2 * sq_tmp1 + wc4 - 2 * sq_tmp2 + 4 * wc2 * k2) / a_tmp;
var a0 = wc4 / a_tmp;
var a1 = 4 * wc4 / a_tmp;
var a2 = 6 * wc4 / a_tmp;
var a3 = a1;
var a4 = a0;
double ym1 = 0.0, ym2 = 0.0, ym3 = 0.0, ym4 = 0.0, xm1 = 0.0, xm2 = 0.0, xm3 = 0.0, xm4 = 0.0, tempy = 0.0;
for (var i = 0; i < samples.Length; i++)
{
var tempx = samples[i];
tempy = a0 * tempx + a1 * xm1 + a2 * xm2 + a3 * xm3 + a4 * xm4 - b1 * ym1 - b2 * ym2 - b3 * ym3 - b4 * ym4;
xm4 = xm3;
xm3 = xm2;
xm2 = xm1;
xm1 = tempx;
ym4 = ym3;
ym3 = ym2;
ym2 = ym1;
ym1 = tempy;
newSamples[i] = (float)tempy;
}
return newSamples;
}
Related
I am trying to place Weibull distributed random numbers in a sector of a circle.
I generated the random number, then distributed it in Weibull distribution. Now I want to use these Weibull distributed random numbers.
RadarSpace GetWeibullClutter()
{
Random _randomNumberGenerator = new Random();
Weibull myweibull = new Weibull(3,2,_randomNumberGenerator);
int n = 50; // number of clutter elements
var maxRange = _detectionModel.MaximumRange;
var centreX = 0; // Centre of the field of view -- X coordinates
var centreY = 0; // Centre of the field of view -- Y coordinates
var minimumAngle = Math.PI / 4; // _detectionModel.MinimumPhi;
var maximumAngle = (3 * Math.PI) / 4; // _detectionModel.MaximumPhir;
var theta = (maximumAngle - minimumAngle) * myweibull + minimumAngle;
var r = maxRange * Math.Sqrt(_randomNumberGenerator.Next(n));
var x = centreX + r * Math.Cos(theta);
var y = centreY + r * Math.Sin(theta);
I want the Weibull distributed random number to be multiplied in the var theta, but it rather says
operator '*' cannot be applied to opperands of type 'double' and 'weibull'
the updated code is
RadarSpace GetWeibullClutter()
{
Random _randomNumberGenerator = new Random();
Weibull myweibull = new Weibull(3,2,_randomNumberGenerator);
int n = 50; // number of clutter elements
var maxRange = _detectionModel.MaximumRange;
var centreX = 0; // Centre of the field of view -- X coordinates
var centreY = 0; // Centre of the field of view -- Y coordinates
var minimumAngle = Math.PI / 4; // _detectionModel.MinimumPhi;
var maximumAngle = (3 * Math.PI) / 4; // _detectionModel.MaximumPhir;
var theta = 0.0;
var r = 0.0;
var randomNumbers = new double[n];
myweibull.Samples(randomNumbers);
for (int i = 0; i < n; i++)
{
theta = (maximumAngle - minimumAngle) * randomNumbers[i] + minimumAngle;
r = maxRange * Math.Sqrt(randomNumbers[i]);
}
//var theta = (maximumAngle - minimumAngle) * myweibull.Sample() + minimumAngle;
//var r = maxRange * Math.Sqrt(_randomNumberGenerator.Next(n));
var x = centreX + r * Math.Cos(theta);
var y = centreY + r * Math.Sin(theta);
It looks like you're using the Math.NET Numerics library. The Weibull distribution class implements the IContinuousDistribution interface, which offers the properties and methods:
double Mode { get; }
double Minimum { get; }
double Maximum { get; }
double Density(double x);
double DensityLn(double x);
double Sample();
void Samples(double[] values);
IEnumerable<double> Samples();
Your variable myweibull contains an instance of the Weibull class, so you can't multiply it with a double.
You said that you generated the random number, but you didn't. For that, use the Sample() method:
var theta = (maximumAngle - minimumAngle) * myweibull.Sample() + minimumAngle;
This will give you one random number that is Weibull distributed. If you need more random numbers, either call Sample() repeatedly:
for( int i = 0; i < n; i++ )
{
var theta = (maximumAngle - minimumAngle) * myweibull.Sample() + minimumAngle;
...
}
or generate multiple random numbers at once using Samples()
var randomNumbers = new double[n];
myweilbull.Samples(randomNumbers);
for( int i = 0; i < n; i++ )
{
var theta = (maximumAngle - minimumAngle) * randomNumbers[i] + minimumAngle;
...
}
Edit for the updated question
For your problem, r and theta must be independent, otherwise the angle and the radius will be completely correlated and all generated points are on a line.
for( int i = 0; i < n; i++ )
{
var theta= (maximumAngle - minimumAngle) * myweibull.Sample() + minimumAngle;
var r = maxRange * Math.Sqrt( myweibull.Sample() );
var x = centreX + r * Math.Cos(theta);
var y = centreY + r * Math.Sin(theta);
// Do something with your generated point (x, y)
}
If you just generate 50 random values for theta and r but only calculate x and y once, you will only have one random point.
But I'm still wondering what you're trying to achieve, because the points won't be equally distributed in the sector, but Weibull distributed.
I've been struggling to convert northing/eastings to lat/long without using a nuget package. I'm sure my UTM zone is 30U (Great Britain).
How would one go about doing this in C#?
When manually done using this site. It provides the correct values and locations.
Easting: 426342 Northing: 505339
Lat/Long should be: 54.44277977022131, -1.5953328509040021
I had previously found some code on stackoverflow but it's giving me the wrong values.
utmZone = "30U"
public static LatLng ToLatLon(double utmX, double utmY, string utmZone)
{
bool isNorthHemisphere = utmZone.Last() >= 'N';
var diflat = -0.00066286966871111111111111111111111111;
var diflon = -0.0003868060578;
var zone = int.Parse(utmZone.Remove(utmZone.Length - 1));
var c_sa = 6378137.000000;
var c_sb = 6356752.314245;
var e2 = Math.Pow((Math.Pow(c_sa, 2) - Math.Pow(c_sb, 2)), 0.5) / c_sb;
var e2cuadrada = Math.Pow(e2, 2);
var c = Math.Pow(c_sa, 2) / c_sb;
var x = utmX - 500000;
var y = isNorthHemisphere ? utmY : utmY - 10000000;
var s = ((zone * 6.0) - 183.0);
var lat = y / (c_sa * 0.9996);
var v = (c / Math.Pow(1 + (e2cuadrada * Math.Pow(Math.Cos(lat), 2)), 0.5)) * 0.9996;
var a = x / v;
var a1 = Math.Sin(2 * lat);
var a2 = a1 * Math.Pow((Math.Cos(lat)), 2);
var j2 = lat + (a1 / 2.0);
var j4 = ((3 * j2) + a2) / 4.0;
var j6 = ((5 * j4) + Math.Pow(a2 * (Math.Cos(lat)), 2)) / 3.0;
var alfa = (3.0 / 4.0) * e2cuadrada;
var beta = (5.0 / 3.0) * Math.Pow(alfa, 2);
var gama = (35.0 / 27.0) * Math.Pow(alfa, 3);
var bm = 0.9996 * c * (lat - alfa * j2 + beta * j4 - gama * j6);
var b = (y - bm) / v;
var epsi = ((e2cuadrada * Math.Pow(a, 2)) / 2.0) * Math.Pow((Math.Cos(lat)), 2);
var eps = a * (1 - (epsi / 3.0));
var nab = (b * (1 - epsi)) + lat;
var senoheps = (Math.Exp(eps) - Math.Exp(-eps)) / 2.0;
var delt = Math.Atan(senoheps / (Math.Cos(nab)));
var tao = Math.Atan(Math.Cos(delt) * Math.Tan(nab));
double longitude = ((delt * (180.0 / Math.PI)) + s) + diflon;
double latitude = ((lat + (1 + e2cuadrada * Math.Pow(Math.Cos(lat), 2) - (3.0 / 2.0) * e2cuadrada * Math.Sin(lat) * Math.Cos(lat) * (tao - lat)) * (tao - lat)) * (180.0 / Math.PI)) + diflat;
return new LatLng
{
Latitude = latitude,
Longitude = longitude
};
}
Refer the below code and run LatLonConversions.ConvertOSToLatLon(426342, 505339)
public class LatLonConversions
{
const double a = 6377563.396;
const double b = 6356256.91;
const double e2 = (a - b) / a;
const double n0 = -100000;
const double e0 = 400000;
const double f0 = 0.999601272;
const double phi0 = 0.855211333;
const double lambda0 = -0.034906585;
const double n = (a - b) / (a + b);
static double lat, lng;
private LatLonConversions() { }
private static double Deg2Rad(double x)
{
return x * (Math.PI / 180);
}
private static double Rad2Deg(double x)
{
return x * (180 / Math.PI);
}
private static double SinSquared(double x)
{
return Math.Sin(x) * Math.Sin(x);
}
private static double TanSquared(double x)
{
return Math.Tan(x) * Math.Tan(x);
}
private static double Sec(double x)
{
return 1.0 / Math.Cos(x);
}
private static void OSGB36ToWGS84()
{
var airy1830 = new RefEll(6377563.396, 6356256.909);
var a = airy1830.maj;
var b = airy1830.min;
var eSquared = airy1830.ecc;
var phi = Deg2Rad(lat);
var lambda = Deg2Rad(lng);
var v = a / (Math.Sqrt(1 - eSquared * SinSquared(phi)));
var H = 0; // height
var x = (v + H) * Math.Cos(phi) * Math.Cos(lambda);
var y = (v + H) * Math.Cos(phi) * Math.Sin(lambda);
var z = ((1 - eSquared) * v + H) * Math.Sin(phi);
var tx = 446.448;
var ty = -124.157;
var tz = 542.060;
var s = -0.0000204894;
var rx = Deg2Rad(0.00004172222);
var ry = Deg2Rad(0.00006861111);
var rz = Deg2Rad(0.00023391666);
var xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z);
var yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z);
var zB = tz + (-ry * x) + (rx * y) + (z * (1 + s));
var wgs84 = new RefEll(6378137.000, 6356752.3141);
a = wgs84.maj;
b = wgs84.min;
eSquared = wgs84.ecc;
var lambdaB = Rad2Deg(Math.Atan(yB / xB));
var p = Math.Sqrt((xB * xB) + (yB * yB));
var phiN = Math.Atan(zB / (p * (1 - eSquared)));
for (var i = 1; i < 10; i++)
{
v = a / (Math.Sqrt(1 - eSquared * SinSquared(phiN)));
double phiN1 = Math.Atan((zB + (eSquared * v * Math.Sin(phiN))) / p);
phiN = phiN1;
}
var phiB = Rad2Deg(phiN);
lat = phiB;
lng = lambdaB;
}
public static LatLon ConvertOSToLatLon(double easting, double northing)
{
RefEll airy1830 = new RefEll(6377563.396, 6356256.909);
double OSGB_F0 = 0.9996012717;
double N0 = -100000.0;
double E0 = 400000.0;
double phi0 = Deg2Rad(49.0);
double lambda0 = Deg2Rad(-2.0);
double a = airy1830.maj;
double b = airy1830.min;
double eSquared = airy1830.ecc;
double phi = 0.0;
double lambda = 0.0;
double E = easting;
double N = northing;
double n = (a - b) / (a + b);
double M = 0.0;
double phiPrime = ((N - N0) / (a * OSGB_F0)) + phi0;
do
{
M =
(b * OSGB_F0)
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n))
* (phiPrime - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.Sin(phiPrime - phi0)
* Math.Cos(phiPrime + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.Sin(2.0 * (phiPrime - phi0))
* Math.Cos(2.0 * (phiPrime + phi0)))
- (((35.0 / 24.0) * n * n * n)
* Math.Sin(3.0 * (phiPrime - phi0))
* Math.Cos(3.0 * (phiPrime + phi0))));
phiPrime += (N - N0 - M) / (a * OSGB_F0);
} while ((N - N0 - M) >= 0.001);
var v = a * OSGB_F0 * Math.Pow(1.0 - eSquared * SinSquared(phiPrime), -0.5);
var rho =
a
* OSGB_F0
* (1.0 - eSquared)
* Math.Pow(1.0 - eSquared * SinSquared(phiPrime), -1.5);
var etaSquared = (v / rho) - 1.0;
var VII = Math.Tan(phiPrime) / (2 * rho * v);
var VIII =
(Math.Tan(phiPrime) / (24.0 * rho * Math.Pow(v, 3.0)))
* (5.0
+ (3.0 * TanSquared(phiPrime))
+ etaSquared
- (9.0 * TanSquared(phiPrime) * etaSquared));
var IX =
(Math.Tan(phiPrime) / (720.0 * rho * Math.Pow(v, 5.0)))
* (61.0
+ (90.0 * TanSquared(phiPrime))
+ (45.0 * TanSquared(phiPrime) * TanSquared(phiPrime)));
var X = Sec(phiPrime) / v;
var XI =
(Sec(phiPrime) / (6.0 * v * v * v))
* ((v / rho) + (2 * TanSquared(phiPrime)));
var XII =
(Sec(phiPrime) / (120.0 * Math.Pow(v, 5.0)))
* (5.0
+ (28.0 * TanSquared(phiPrime))
+ (24.0 * TanSquared(phiPrime) * TanSquared(phiPrime)));
var XIIA =
(Sec(phiPrime) / (5040.0 * Math.Pow(v, 7.0)))
* (61.0
+ (662.0 * TanSquared(phiPrime))
+ (1320.0 * TanSquared(phiPrime) * TanSquared(phiPrime))
+ (720.0
* TanSquared(phiPrime)
* TanSquared(phiPrime)
* TanSquared(phiPrime)));
phi =
phiPrime
- (VII * Math.Pow(E - E0, 2.0))
+ (VIII * Math.Pow(E - E0, 4.0))
- (IX * Math.Pow(E - E0, 6.0));
lambda =
lambda0
+ (X * (E - E0))
- (XI * Math.Pow(E - E0, 3.0))
+ (XII * Math.Pow(E - E0, 5.0))
- (XIIA * Math.Pow(E - E0, 7.0));
lat = Rad2Deg(phi);
lng = Rad2Deg(lambda);
// convert to WGS84
OSGB36ToWGS84();
return new LatLon(lat, lng);
}
}
public class RefEll
{
public double maj, min, ecc;
public RefEll(double major, double minor)
{
maj = major;
min = minor;
ecc = ((major * major) - (minor * minor)) / (major * major);
}
}
public class LatLon
{
public double Latitude;
public double Longitude;
public LatLon()
{
Latitude = 0;
Longitude = 0;
}
public LatLon(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}
}
I ran into the same issue. Ended up using this library: https://github.com/Tronald/CoordinateSharp
This sample will return the exact position in latlon decimal.
UniversalTransverseMercator utm = new UniversalTransverseMercator("T", 30, 581177.3879, 4794824.5279);
Coordinate c = UniversalTransverseMercator.ConvertUTMtoLatLong(utm);
c.FormatOptions.Format = CoordinateFormatType.Decimal;
c.FormatOptions.Round = 7;
Debug.Log($"({c.Latitude}, {c.Longitude})");
In reference to the widely used CalculateRamp code at:
Changing image contrast with GDI+ and C#
public static void CalculateRamp(double level, double gamma, double brightness, double contrast)
{
ramp.Red = new ushort[256];
ramp.Green = new ushort[256];
ramp.Blue = new ushort[256];
gamma /= 10;
brightness = 1 + (((brightness - 50) / 100) * 65535);
contrast = 1 + ((contrast - 50) / 100);
level = 1 + ((level - 50) / 100);
for (int i = 0; i < 256; i++)
{
double value = i * 256;
value = (Math.Pow(value / 65535, 1 / gamma) * 65535) + 0.5;
value = ((((value / 65535) - 0.5) * contrast) + 0.5) * 65535;
value = value += brightness;
value *= level;
ramp.Red[i] = ramp.Green[i] = ramp.Blue[i] = (ushort)Math.Min((double)65535, Math.Max((double)0, value));
}
SetDeviceGammaRamp(GetDC(IntPtr.Zero), ref ramp);
}
How would you calculate the input values (arguments) for this method directly from the existing Gamma Ramp? In other words, what would be the reverse method?
If I am going to provide these utilities to the user, I need to present them with their current settings prior to their making the adjustments.
I have tried for three days to come up with a solution, and must admit that this is beyond my capabilities.
Update Sept 8, 2016 - My best solution...
While I worked long and hard looking for a mathematical solution, I finally went back and played around with the iteration method.
A blunt force iteration that calculates each and every possible setting and then checks for a match will run for more than 20 minutes; which is unacceptable.
I found that I could take a single sample point in the middle of the array and quickly iterate through all possible settings to find a match for that one sample; and then perform all 256 of the calculations for the array to see if we had a complete match. This operation generally runs in less than a second.
But I realized that there was yet another problem. If the Gamma Ramp was not programmed using the CalculateRamp algorithm (such as factory setting) then it is unlikely that we will find an exact match for our settings; and our solution will fail.
So I devised a backup plan that uses four sample points from across the gamma ramp array; and then set up a "span" of acceptable values that would provide a close approximation for our settings. The criteria for "span" would broaden after each failure, and re-enter the search. This scenario will produce an approximation of the Gamma Ramp Settings usually within about three seconds. This is acceptable since my main program can perform this operation during the program startup sequence.
Here is my C++ solution...
//---------------------------------------------------------------------------
int __fastcall TForm1::Round( double flt ){
int num = (int) flt;
if( flt >= 0 ){
if( flt - num >= .5 ){ num++;}
}else{
if( flt - num <= -.5 ){ num--;}
}
return( num );
}
//----------------------------------------------------------------------------
void __fastcall TForm1::GetGammaRampSettings(void){
// level should be between 2 and 100
// gamma should be between 2 and 50
// brigntess should be between 0 and 100
// contrast should be between 0 and 100
double i,j,k,m;
double gamma,bright,cntrst,level;
double v1,v2,v3,v4;
double c1,c2,c3,c4;
double a1,a2,a3,a4,b1,b2,b3,b4; //for best estimate criteria
int x1,x2,x3,x4,x5;
int d,n;
WORD span = 8;
bool tog = false;
TDateTime strt, end;
WORD GammaArray[3][256]; //Gamma Ramp Array buffer
WORD CompArray[256]; //Array buffer for comparison
HDC GammaDC = GetDC( NULL ); //fetch the gamma ramp array
GetDeviceGammaRamp( GammaDC, GammaArray );
ReleaseDC( NULL, GammaDC );
strt=Now();
//Find two endpoints for the GammaArray
for( x1 = 0; x1 < 256; x1++ ){
if( GammaArray[0][x1] > 0 ){ break;}
}
for( x2 = 0; x2 < 256; x2++ ){
if( GammaArray[0][x2] == 65535 ){ break;}
}
if( x2 == 256 ){ x2 = 255; }
x5 = x1 + Round( (double)( x2 - x1 ) / 2.0 );
Memo1->Lines->Add( "x1 = " + IntToStr( x1 )); //start
Memo1->Lines->Add( "x2 = " + IntToStr( x2 )); //end
Memo1->Lines->Add( "x5 = " + IntToStr( x5 )); //mid
//neutral settings are as follows...
//Bright = 50
//Contrast = 50
//Level = 50
//Gamma = 10
//The following code will look for an exact match to the settings
//but it may fail if the gamma ramp was not programmed using the
//original CalculateRamp code. ie: A factory setting, or other
for(n=0;n<=40;){//gamma
if( n < 9 ){ //gamma begins with a neutral setting of 10 and
if( tog ){ //then increments high and low depending on tog
i = ( 10 - n );
tog = false; n++;
if( i == 10 ){ continue;}
}else{
i = ( 10 + n );
tog = true;
}
}else{
i = ( 10 + n++ );
}
gamma = i / 10;
v1 = ( pow( (double)(x5 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test only the mid-line value at first
//To see if a complete comparison is warranted
c1 = (((( v1 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c1 = c1 += bright;
c1 *= level;
if( c1 > 65535){ c1 = 65535; }
if( c1 < 0 ){ c1 = 0; }
if( (WORD) c1 == GammaArray[0][x5] ){
for( d = 0; d < 256; d++ ){
c1 = ( pow( (double)(d * 256) / 65535, 1 / gamma) * 65535) + 0.5;
c1 = ((( (c1 / 65535) - 0.5) * cntrst) + 0.5) * 65535;
c1 = c1 += bright;
c1 *= level;
if( c1 > 65535 ){ c1 = 65535;}
if( c1 < 0 ){ c1 = 0;}
CompArray[d] = (WORD) c1;
}
if(memcmp( &CompArray[0], &GammaArray[0][0], 2*256) == 0){
goto ENDIT;
}
}
}
}
}
}
//Since an exact match could not be found for the current
//gamma ramp settings, we will broaden our criteria and
//search for the best match for our gamma ramp settings.
//Here our search is based on the start and end values,
//plus two additional values that are 1/4 of the overall distance
//above the start point; and 1/4 the distance below the end point.
Memo1->Lines->Add("Values NOT Discovered!!!");
Memo1->Lines->Add("Widening the Search....");
x3 = x1 + Round( (double)( x2 - x1 ) / 4.0 );
x4 = x1 + Round(( (double)( x2 - x1 ) / 4.0 )*3);
Memo1->Lines->Add( "x1 = " + IntToStr( x1 )); //start
Memo1->Lines->Add( "x2 = " + IntToStr( x2 )); //end
Memo1->Lines->Add( "x3 = " + IntToStr( x3 )); // 1/4
Memo1->Lines->Add( "x4 = " + IntToStr( x4 )); // 3/4
BROADEN:
a1 = GammaArray[0][x1]+span; //these are our high and low
b1 = GammaArray[0][x1]-span; //values that the ramp values must
//fall within to be accepted
a2 = GammaArray[0][x2]+span;
b2 = GammaArray[0][x2]-span; //span begins at 8 and increments upwards
//with each loop, ever widening the
a3 = GammaArray[0][x3]+span; //the search criteria
b3 = GammaArray[0][x3]-span;
a4 = GammaArray[0][x4]+span;
b4 = GammaArray[0][x4]-span;
tog=false;
for(n=0;n<=40;){//gamma
if( n < 9 ){ //gamma begins with a neutral setting of 10 and
if( tog ){ //then increments high and low depending on tog
i = ( 10 - n );
tog = false; n++;
if( i == 10 ){ continue;} //prevents two instances of i == 10
}else{
i = ( 10 + n );
tog = true;
}
}else{
i = ( 10 + n++ );
}
gamma = i / 10;
v1 = ( pow( (double)(x1 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
v2 = ( pow( (double)(x2 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
v3 = ( pow( (double)(x3 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
v4 = ( pow( (double)(x4 * 256) / 65535, 1 / gamma) * 65535) + 0.5;
for( j = 2; j < 101 ; j++ ){//level //980,000 possible iterations
level = 1 + ((j - 50) / 100);
for( k = 0; k < 101; k++ ){//bright
bright = 1 + (((k - 50) / 100) * 65535);
for( m = 0; m < 101; m++ ){//contrast
cntrst = 1 + ((m - 50) / 100);
//Test the four sample values to see if the
//results falls within our acceptance criteria.
c1 = (((( v1 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c1 = c1 += bright;
c1 *= level;
c2 = (((( v2 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c2 = c2 += bright;
c2 *= level;
c3 = (((( v3 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c3 = c3 += bright;
c3 *= level;
c4 = (((( v4 / 65535 ) - 0.5) * cntrst) + 0.5) * 65535;
c4 = c4 += bright;
c4 *= level;
if( c1 <= a1 && c1 >= b1 && c2 <= a2 && c2 >= b2 &&
c3 <= a3 && c3 >= b3 && c4 <= a4 && c4 >= b4 ){
Memo1->Lines->Add("Best Estimate +/- " + IntToStr( span ));
goto ENDIT;
}
}
}
}
}
if( span < 256){
span *= 2;
goto BROADEN;
}else if( span <= 4096 ){ //set the maximum allowance for "span"
span += 128; //which is the basis for the allowable
goto BROADEN; //search criteria
}
//if we get to this point then we have utterly failed.
Memo1->Lines->Add("Values STILL NOT Discovered.");
Memo1->Lines->Add("GetGammaRampSettings Failed.");
return;
ENDIT:
end=Now();
Memo1->Lines->Add("Execution duration was "+
FormatDateTime("nn:ss:zzz",end-strt));
//Report on findings...
Memo1->Lines->Add("Bright = " + IntToStr( (int) k ));
Memo1->Lines->Add("Cntrst = " + IntToStr( (int) m ));
Memo1->Lines->Add("Level = " + IntToStr( (int) j ));
Memo1->Lines->Add("Gamma = " + IntToStr( (int) i ));
}
//---------------------------------------------------------------------------
I am using this site as a reference: http://www.rapidtables.com/convert/color/rgb-to-cmyk.htm
My code:
public void Convert2CMYK()
{
float c, m, y, k;
if (inRed == 0 && inGreen == 0 && inBlue == 0)
{
tbCyan.Text = "0";
tbMagenta.Text = "0";
tbYellow.Text = "0";
tbBlack.Text = "1";
}
c = 1 - (inRed / 255f);
m = 1 - (inGreen / 255f);
y = 1 - (inBlue / 255f);
var minCMY = Math.Min(c, Math.Min(m, y));
c = (c - minCMY) / (1 - minCMY) * 100;
m = (m - minCMY) / (1 - minCMY) * 100;
y = (y - minCMY) / (1 - minCMY) * 100;
k = minCMY * 100;
tbCyan.Text = c.ToString();
tbMagenta.Text = m.ToString();
tbYellow.Text = y.ToString();
tbBlack.Text = k.ToString();
}
On the site, R=25, G=25, B=25 results in C=0, M=0, Y=0, K=0.902
In the app (my code), R=25, G=25, B=25 results in C=0, M=0, Y=0, K=90.19608
What do I have to modify to ensure my results are accurate.
Thanks to everyone for their help. Here are the final equations which did the trick:
c = Math.Round((c - minCMY) / (1 - minCMY), 3);
m = Math.Round((m - minCMY) / (1 - minCMY), 3);
y = Math.Round((y - minCMY) / (1 - minCMY), 3);
k = Math.Round(minCMY, 3);
How can I access a matrix with a specified row, column and channel?
Matrix<double> tensor = new Matrix<double>(yMax + 1, xMax, 4); //4 channels
CvInvoke.cvZero(tensor);
for(int k = 0; k<x.Count; ++k)
{
double gx = Math.Cos(angle[k] * Math.PI / 180 + 90 * Math.PI / 180 + Math.PI);
double gy = Math.Sin(angle[k] * Math.PI / 180 + 90 * Math.PI / 180 + Math.PI);
tensor[y[k], x[k]] = gx * gx; //How can I access other channels?
tensor.Data[y[k], x[k] + 1] = gx * gy; //How can I access other channels?
tensor.Data[y[k], x[k] + 2] = gx * gy; //How can I access other channels?
tensor.Data[y[k], x[k] + 3] = gy * gy; //How can I access other channels?
}
I think you should give a look at Split() method and then loop on channels array.
Here is an example of getting pixel value from two separate channels of Mat:
var flowResult = new Mat();
CvInvoke.CalcOpticalFlowFarneback(_scaledDownFrameOneColorImagePrev, scaledDownFrameOneColorImage, flowResult, 0.25, 3, 15, 5, 1, 1.2, OpticalflowFarnebackFlag.Default);
var flowResultChannels = flowResult.Split();
var flowResultX = flowResultChannels[0];
var flowResultY = flowResultChannels[1];
for (var r = 0; r < flowResult.Rows; r++)
{
for (var c = 0; c < flowResult.Cols; c++)
{
var xValue = new float[1];
Marshal.Copy(flowResultX.DataPointer + (((r * flowResultX.Cols) + c) * flowResultX.ElementSize), xValue, 0, 1);
var yValue = new float[1];
Marshal.Copy(flowResultY.DataPointer + (((r * flowResultY.Cols) + c) * flowResultY.ElementSize), yValue, 0, 1);
if (Math.Abs(xValue[0]) > 3 || Math.Abs(yValue[0]) > 3)
{
Console.WriteLine("{0} {1}", xValue[0], yValue[0]);
}
}
}