blob: 6d230f672d00de39d80cdbce0d1b758426a4f78d [file] [log] [blame]
/**********************************************************************
* File: lmedsq.cpp (Formerly lms.c)
* Description: Code for the LMS class.
* Author: Ray Smith
* Created: Fri Aug 7 09:30:53 BST 1992
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
#include <stdlib.h>
#include "statistc.h"
#include "memry.h"
#include "statistc.h"
#include "lmedsq.h"
#define EXTERN
EXTERN INT_VAR (lms_line_trials, 12, "Number of linew fits to do");
#define SEED1 0x1234 //default seeds
#define SEED2 0x5678
#define SEED3 0x9abc
#define LMS_MAX_FAILURES 3
#ifndef __UNIX__
uinT32 nrand48( //get random number
uinT16 *seeds //seeds to use
) {
static uinT32 seed = 0; //only seed
if (seed == 0) {
seed = seeds[0] ^ (seeds[1] << 8) ^ (seeds[2] << 16);
srand(seed);
}
//make 32 bit one
return rand () | (rand () << 16);
}
#endif
/**********************************************************************
* LMS::LMS
*
* Construct a LMS class, given the max no of samples to be given
**********************************************************************/
LMS::LMS ( //constructor
inT32 size //samplesize
):samplesize (size) {
samplecount = 0;
a = 0;
m = 0.0f;
c = 0.0f;
samples = (FCOORD *) alloc_mem (size * sizeof (FCOORD));
errors = (float *) alloc_mem (size * sizeof (float));
line_error = 0.0f;
fitted = FALSE;
}
/**********************************************************************
* LMS::~LMS
*
* Destruct a LMS class.
**********************************************************************/
LMS::~LMS ( //constructor
) {
free_mem(samples);
free_mem(errors);
}
/**********************************************************************
* LMS::clear
*
* Clear samples from array.
**********************************************************************/
void LMS::clear() { //clear sample
samplecount = 0;
fitted = FALSE;
}
/**********************************************************************
* LMS::add
*
* Add another sample. More than the constructed number will be ignored.
**********************************************************************/
void LMS::add( //add sample
FCOORD sample //sample coords
) {
if (samplecount < samplesize)
//save it
samples[samplecount++] = sample;
fitted = FALSE;
}
/**********************************************************************
* LMS::fit
*
* Fit a line to the given sample points.
**********************************************************************/
void LMS::fit( //fit sample
float &out_m, //output line
float &out_c) {
inT32 index; //of median
inT32 trials; //no of medians
float test_m, test_c; //candidate line
float test_error; //error of test line
switch (samplecount) {
case 0:
m = 0.0f; //no info
c = 0.0f;
line_error = 0.0f;
break;
case 1:
m = 0.0f;
c = samples[0].y (); //horiz thru pt
line_error = 0.0f;
break;
case 2:
if (samples[0].x () != samples[1].x ()) {
m = (samples[1].y () - samples[0].y ())
/ (samples[1].x () - samples[0].x ());
c = samples[0].y () - m * samples[0].x ();
}
else {
m = 0.0f;
c = (samples[0].y () + samples[1].y ()) / 2;
}
line_error = 0.0f;
break;
default:
pick_line(m, c); //use pts at random
compute_errors(m, c); //from given line
index = choose_nth_item (samplecount / 2, errors, samplecount);
line_error = errors[index];
for (trials = 1; trials < lms_line_trials; trials++) {
//random again
pick_line(test_m, test_c);
compute_errors(test_m, test_c);
index = choose_nth_item (samplecount / 2, errors, samplecount);
test_error = errors[index];
if (test_error < line_error) {
//find least median
line_error = test_error;
m = test_m;
c = test_c;
}
}
}
fitted = TRUE;
out_m = m;
out_c = c;
a = 0;
}
/**********************************************************************
* LMS::fit_quadratic
*
* Fit a quadratic to the given sample points.
**********************************************************************/
void LMS::fit_quadratic( //fit sample
float outlier_threshold, //min outlier size
double &out_a, //x squared
float &out_b, //output line
float &out_c) {
inT32 trials; //no of medians
double test_a;
float test_b, test_c; //candidate line
float test_error; //error of test line
if (samplecount < 3) {
out_a = 0;
fit(out_b, out_c);
return;
}
pick_quadratic(a, m, c);
line_error = compute_quadratic_errors (outlier_threshold, a, m, c);
for (trials = 1; trials < lms_line_trials * 2; trials++) {
pick_quadratic(test_a, test_b, test_c);
test_error = compute_quadratic_errors (outlier_threshold,
test_a, test_b, test_c);
if (test_error < line_error) {
line_error = test_error; //find least median
a = test_a;
m = test_b;
c = test_c;
}
}
fitted = TRUE;
out_a = a;
out_b = m;
out_c = c;
}
/**********************************************************************
* LMS::constrained_fit
*
* Fit a line to the given sample points.
* The line must have the given gradient.
**********************************************************************/
void LMS::constrained_fit( //fit sample
float fixed_m, //forced gradient
float &out_c) {
inT32 index; //of median
inT32 trials; //no of medians
float test_c; //candidate line
static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
//for nrand
float test_error; //error of test line
m = fixed_m;
switch (samplecount) {
case 0:
c = 0.0f;
line_error = 0.0f;
break;
case 1:
//horiz thru pt
c = samples[0].y () - m * samples[0].x ();
line_error = 0.0f;
break;
case 2:
c = (samples[0].y () + samples[1].y ()
- m * (samples[0].x () + samples[1].x ())) / 2;
line_error = m * samples[0].x () + c - samples[0].y ();
line_error *= line_error;
break;
default:
index = (inT32) nrand48 (seeds) % samplecount;
//compute line
c = samples[index].y () - m * samples[index].x ();
compute_errors(m, c); //from given line
index = choose_nth_item (samplecount / 2, errors, samplecount);
line_error = errors[index];
for (trials = 1; trials < lms_line_trials; trials++) {
index = (inT32) nrand48 (seeds) % samplecount;
test_c = samples[index].y () - m * samples[index].x ();
//compute line
compute_errors(m, test_c);
index = choose_nth_item (samplecount / 2, errors, samplecount);
test_error = errors[index];
if (test_error < line_error) {
//find least median
line_error = test_error;
c = test_c;
}
}
}
fitted = TRUE;
out_c = c;
a = 0;
}
/**********************************************************************
* LMS::pick_line
*
* Fit a line to a random pair of sample points.
**********************************************************************/
void LMS::pick_line( //fit sample
float &line_m, //output gradient
float &line_c) {
inT16 trial_count; //no of attempts
static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
//for nrand
inT32 index1; //picked point
inT32 index2; //picked point
trial_count = 0;
do {
index1 = (inT32) nrand48 (seeds) % samplecount;
index2 = (inT32) nrand48 (seeds) % samplecount;
line_m = samples[index2].x () - samples[index1].x ();
trial_count++;
}
while (line_m == 0 && trial_count < LMS_MAX_FAILURES);
if (line_m == 0) {
line_c = (samples[index2].y () + samples[index1].y ()) / 2;
}
else {
line_m = (samples[index2].y () - samples[index1].y ()) / line_m;
line_c = samples[index1].y () - samples[index1].x () * line_m;
}
}
/**********************************************************************
* LMS::pick_quadratic
*
* Fit a quadratic to a random triplet of sample points.
**********************************************************************/
void LMS::pick_quadratic( //fit sample
double &line_a, //x suaread
float &line_m, //output gradient
float &line_c) {
inT16 trial_count; //no of attempts
static uinT16 seeds[3] = { SEED1, SEED2, SEED3 };
//for nrand
inT32 index1; //picked point
inT32 index2; //picked point
inT32 index3;
FCOORD x1x2; //vector
FCOORD x1x3;
FCOORD x3x2;
double bottom; //of a
trial_count = 0;
do {
if (trial_count >= LMS_MAX_FAILURES - 1) {
index1 = 0;
index2 = samplecount / 2;
index3 = samplecount - 1;
}
else {
index1 = (inT32) nrand48 (seeds) % samplecount;
index2 = (inT32) nrand48 (seeds) % samplecount;
index3 = (inT32) nrand48 (seeds) % samplecount;
}
x1x2 = samples[index2] - samples[index1];
x1x3 = samples[index3] - samples[index1];
x3x2 = samples[index2] - samples[index3];
bottom = x1x2.x () * x1x3.x () * x3x2.x ();
trial_count++;
}
while (bottom == 0 && trial_count < LMS_MAX_FAILURES);
if (bottom == 0) {
line_a = 0;
pick_line(line_m, line_c);
}
else {
line_a = x1x3 * x1x2 / bottom;
line_m = x1x2.y () - line_a * x1x2.x ()
* (samples[index2].x () + samples[index1].x ());
line_m /= x1x2.x ();
line_c = samples[index1].y () - samples[index1].x ()
* (samples[index1].x () * line_a + line_m);
}
}
/**********************************************************************
* LMS::compute_errors
*
* Compute the squared error from all the points.
**********************************************************************/
void LMS::compute_errors( //fit sample
float line_m, //input gradient
float line_c) {
inT32 index; //picked point
for (index = 0; index < samplecount; index++) {
errors[index] =
line_m * samples[index].x () + line_c - samples[index].y ();
errors[index] *= errors[index];
}
}
/**********************************************************************
* LMS::compute_quadratic_errors
*
* Compute the squared error from all the points.
**********************************************************************/
float LMS::compute_quadratic_errors( //fit sample
float outlier_threshold, //min outlier
double line_a,
float line_m, //input gradient
float line_c) {
inT32 outlier_count; //total outliers
inT32 index; //picked point
inT32 error_count; //no in total
double total_error; //summed squares
total_error = 0;
outlier_count = 0;
error_count = 0;
for (index = 0; index < samplecount; index++) {
errors[error_count] = line_c + samples[index].x ()
* (line_m + samples[index].x () * line_a) - samples[index].y ();
errors[error_count] *= errors[error_count];
if (errors[error_count] > outlier_threshold) {
outlier_count++;
errors[samplecount - outlier_count] = errors[error_count];
}
else {
total_error += errors[error_count++];
}
}
if (outlier_count * 3 < error_count)
return total_error / error_count;
else {
index = choose_nth_item (outlier_count / 2,
errors + samplecount - outlier_count,
outlier_count);
//median outlier
return errors[samplecount - outlier_count + index];
}
}
/**********************************************************************
* LMS::plot
*
* Plot the fitted line of a LMS.
**********************************************************************/
#ifndef GRAPHICS_DISABLED
void LMS::plot( //plot fit
ScrollView* win, //window
ScrollView::Color colour //colour to draw in
) {
if (fitted) {
win->Pen(colour);
win->SetCursor(samples[0].x (),
c + samples[0].x () * (m + samples[0].x () * a));
win->DrawTo(samples[samplecount - 1].x (),
c + samples[samplecount - 1].x () * (m +
samples[samplecount -
1].x () * a));
}
}
#endif