/********************************************************************** * 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 #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