Плагин: оконный дискриминатор
1. Назначение
Плагин исходно предназначался для автоматического определения R-R интервалов в электрокардиограмме. Однако он также позволяет выделять в потоке любых данных пики с определенными параметрами и рассчитывать расстояние между ними.
- Работа как в реальном масштабе времени, так и в режиме просмотра файла.
- Независимая установка верхнего и нижнего порогов дискриминации.
- Маркировка отобранных пиков.
- Возможность настраивать отбор пиков не только по амплитудным характеристикам, но и по длительности части пика, попадающей в окно дискриминатора.
- Возможность настраивать отбор пиков с учетом статистических характеристик длительности пиков.
- Возможность настраивать предварительную цифровую фильтрацию сигнала фильтром верхних частот для фильтрации постоянной составляющей.
2. Исходный текст плагина
/*
Плагин оконного дискриминатора.
Среда разработки LabWindows CVI 9.0.
*/
#include
#include
#include
#include
#include "..\\include\\plugin.h"
// индексы визуальных элементов
#define VISUAL_INDEX_GRAPH 0 // график
#define VISUAL_INDEX_Y1 1 // положение по вертикали первого курсора
#define VISUAL_INDEX_Y2 2 // положение по вертикали второго курсора
#define VISUAL_N 3 // число найденных экстремумов
#define VISUAL_T_MX 4 // среднее
#define VISUAL_TABLE 5 // табличный элемент
// индексы параметров
#define PARM_PERCENT_VALUE 0 // максимальное отклонение в процентах
#define PARM_PERCENT_ENABLE 1 // разрешение проверки дельта времени на допустимый процент отклонения от среднего
#define PARM_TMIN_VALUE 2 // минимальная длительность
#define PARM_TMIN_ENABLE 3 // разрешение проверки на минимальную длительность дельта времени
#define PARM_TMIN_SCALE 4 // масштаб минимальной длительности (0 мкс, 1 мс, 2 секунды)
#define PARM_FILTER 5 // тип фильтра
#define PARM_FILTER_ORDER 6 // порядок фильтра
#define PARM_CUTOFF_F 7 // частота среза фильтра
#define PARM_DELETE_TIME 8 // сколько удалить данных с начала после фильтрации
#define PARM_INVERSE 9 // искать точки не выше курсоров, а ниже
#define MAX_SIZE 1000000 // максимальное число кадров, которые можно обработать за один раз
#define MAX_N 1000 // максимальное число объектов
struct ObjectStr {
int x1_index; // начальная координата объекта
int x2_index; // конечная координата объекта
double y_max; // пиковое значение максимума
double integral; // интеграл от x1 до x2
double x_max; // координата по горизонтали
int delta_t_good; // признак успешно найденного временного интервала
double delta_t; // значение временного интервала
};
static struct ObjectStr Objects[MAX_N]; // массив структур с информацией об объектах
static int ObjectsN; // число найденных объектов
static int GoodDeltaTAmount; // число успешно найденных временных интервалов
static struct PluginDataInfoStr DataInfo; // структура с настройками АЦП
struct PluginVisualMainStr LgraphVisual; // структура с настройками LGraph2
static struct TableStr *table_object; // табличный элемент
static int device_index=0; // работаем с первым модулем АЦП
static double *AdcData, *XData, *AdcFilteredData; // указатели на временные буфера
int AdcDataSize; // размер обрабатываемых данных (в кадрах)
// коэффициенты пересчета X для разных масштабов
double x_scale_k[7]={1., 1000., 1000000., 1000000.*60, 1000000.*60*24, 1000000., 1000000.};
double TminK[3]={1, 1000, 1e6};
static double y1, y2;
static struct GraphObjectStr *graph_objects;
static struct TableDataRowStr *table_rows;
// *********************************************************************************************************
// главная функция обмена данными
void __stdcall PluginDataExchange(struct PluginDataStr *data_str)
{
int i, j, chan, index, n, mode, imax, imin, good, inverse_flag;
WindowConst WinConst;
double df, *x, dt, y_min, y_max, porog_max, porog_min, max, min, t_min, f, average_t;
char Unit[64];
int chan1;
chan1=DataInfo.adc_channels[0]; // запомним номер канала плагина (от 0 до 31)
n=data_str->n; // сколько кадров будем обрабатывать
if(!n) // если это вызов из меню (пользователь двигает курсор)
{
y1=data_str->cursor_y[0][0]; // курсор Y1
y2=data_str->cursor_y[0][1]; // курсор Y2
data_str->slow_data[VISUAL_INDEX_Y1]=y1;
data_str->slow_data[VISUAL_INDEX_Y2]=y2;
return;
}
y1=data_str->cursor_y[0][0]; // курсор Y1
y2=data_str->cursor_y[0][1]; // курсор Y2
data_str->slow_data[VISUAL_INDEX_Y1]=y1;// покажем значение курсора Y1
data_str->slow_data[VISUAL_INDEX_Y2]=y2;// покажем значение курсора Y2
dt=1000000./DataInfo.rate[0]; // вычислим интервал между кадрами в мкс
if(AdcDataSize < n) // проверим хватит ли выделенной памяти на обрабатываемое число точек
{
// перевыделим память
AdcData=realloc(AdcData, n*sizeof(double));
if(AdcData == NULL) { free(XData); strcpy(data_str->error, "Не хватает памяти"); AdcDataSize=0; return; }
AdcFilteredData=realloc(AdcFilteredData, n*sizeof(double));
if(AdcFilteredData == NULL) { free(XData); strcpy(data_str->error, "Не хватает памяти"); AdcDataSize=0; return; }
XData=realloc(XData, n*sizeof(double));
if(XData == NULL) { free(AdcData); strcpy(data_str->error, "Не хватает памяти"); AdcDataSize=0; return; }
AdcDataSize=n;
}
// переложим в AdcData данные от обрабатываемого канала
index=DataInfo.chan_kadr_offset[device_index][chan1];
for(i=0; i < n; i++, index += DataInfo.nch[device_index]) AdcData[i]=data_str->data_to_plugin[index];
if(DataInfo.parameters_int[PARM_FILTER]) // если включена предварительная фильтрация ФВЧ
{
int delete_points_n;
switch(DataInfo.parameters_int[PARM_FILTER]) // отфильтруем
{
case 1: // Баттерворта
Bw_HPF (AdcData, n, DataInfo.rate[0], DataInfo.parameters_dbl[PARM_CUTOFF_F],
DataInfo.parameters_int[PARM_FILTER_ORDER], AdcFilteredData);
break;
case 2: // Чебышева
Ch_HPF (AdcData, n, DataInfo.rate[0], DataInfo.parameters_dbl[PARM_CUTOFF_F], 0.1,
DataInfo.parameters_int[PARM_FILTER_ORDER], AdcFilteredData);
break;
case 3: // Инверсный Чебышева
InvCh_HPF (AdcData, n, DataInfo.rate[0], DataInfo.parameters_dbl[PARM_CUTOFF_F], 40.0,
DataInfo.parameters_int[PARM_FILTER_ORDER], AdcFilteredData);
break;
case 4: // Эллиптический
Elp_HPF (AdcData, n, DataInfo.rate[0], DataInfo.parameters_dbl[PARM_CUTOFF_F], 0.1, 40.0,
DataInfo.parameters_int[PARM_FILTER_ORDER], AdcFilteredData);
break;
}
// отбросим заданные пользователем кадры от начала
delete_points_n=(DataInfo.parameters_dbl[PARM_DELETE_TIME]*TminK[DataInfo.parameters_int[PARM_TMIN_SCALE]])/
(1e6/DataInfo.rate[0]);
if(delete_points_n >= n) n=0;
else
{
n -= delete_points_n;
memcpy(AdcData, AdcFilteredData+delete_points_n, n*sizeof(double));
}
}
// сохраним обработанные данные для передачи в ЛГраф
data_str->n_from_graph[0]=n;
data_str->data_from_graph[0]=AdcData;
// сформируем ось X в том же масштабе, что и в окне исходных данных ЛГраф
for(i=0, x=XData; i < n; i++) *x++=((data_str->kadr_number+i)*dt)/x_scale_k[LgraphVisual.time_scale_index];
data_str->x_data[0]=XData;
//*********************** АЛГОРИТМ ПОИСКА ОБЪЕКТОВ ***********************************************
// поищем объекты
if(y1 > y2) { porog_min=y2; porog_max=y1; }
else { porog_min=y1; porog_max=y2; }
if(DataInfo.parameters_int[PARM_TMIN_ENABLE]) // если включен анализ на минимальный интервал
{
t_min=DataInfo.parameters_dbl[PARM_TMIN_VALUE];
t_min *= TminK[DataInfo.parameters_int[PARM_TMIN_SCALE]];
}
inverse_flag=DataInfo.parameters_int[PARM_INVERSE];
for(ObjectsN=index=mode=GoodDeltaTAmount=0; ObjectsN < MAX_N && index < n; index++) // цикл по всему массиву
{
if(porog_min == porog_max) break; // при совпадении порогов выйдем
if(!mode) // если ждем X1 (первое превышение минимального порога)
{
if((!inverse_flag && (AdcData[index] >= porog_min && AdcData[index] <= porog_max))
|| (inverse_flag &&(AdcData[index] <= porog_max && AdcData[index] >= porog_min)))
{
// нашли превышение минимального порога и ниже максимального
mode=1;
Objects[ObjectsN].x1_index=index;
}
}
else if(mode == 2) // ждем спуска ниже минимального порога
{
if((!inverse_flag && AdcData[index] <= porog_min) ||
(inverse_flag && AdcData[index] >= porog_max)) { mode=0; continue; }
}
else // ждем X2 (ждем спуска ниже минимального порога)
{
// проверим нет ли точки превышающей максимальный порог
if((!inverse_flag && AdcData[index] > porog_max) ||
(inverse_flag && AdcData[index] < porog_min)) { mode=2; continue; }
if((!inverse_flag && AdcData[index] <= porog_min) ||
(inverse_flag && AdcData[index] >= porog_max))
{
// нашли объект
mode=0; // режим поиска следующего объекта
good=1;
if(DataInfo.parameters_int[PARM_TMIN_ENABLE]) // если включена проверка на минимальный интервал
{
double t;
t=XData[index]-XData[Objects[ObjectsN].x1_index];
t *= x_scale_k[LgraphVisual.time_scale_index];
if(t < t_min) good=0; // увы, проверка не пройдена
}
if(good) // если все хорошо, запомним параметры найденной точки
{
Objects[ObjectsN].delta_t_good=0;
Objects[ObjectsN].x2_index=index;
MaxMin1D (&AdcData[Objects[ObjectsN].x1_index], index-Objects[ObjectsN].x1_index, &max, &imax, &min, &imin);
if(inverse_flag) { max=min; imax=imin; }
Objects[ObjectsN].y_max=max;
Objects[ObjectsN].x_max=XData[imax+Objects[ObjectsN].x1_index];
ObjectsN++;
}
}
}
}
if(ObjectsN > 1) // теперь поищем временные интервалы между соседними найденными точками
{
double mx;
// определим среднее
for(i=mx=0; i < ObjectsN-1; i++) mx += Objects[i+1].x_max-Objects[i].x_max;
mx /= ObjectsN-1;
if(mx != 0) for(i=0; i < ObjectsN-1; i++)
{
good=1;
// если включена проверка на процентное отклонение от среднего
if(DataInfo.parameters_int[PARM_PERCENT_ENABLE])
{
f=fabs((Objects[i+1].x_max-Objects[i].x_max)-mx)*100./mx;
if(f > DataInfo.parameters_dbl[PARM_PERCENT_VALUE]) good=0;
}
if(good) // если все хорошо, запомним параметры найденного временного интервала
{
Objects[i].delta_t=Objects[i+1].x_max-Objects[i].x_max;
Objects[i].delta_t_good=1;
GoodDeltaTAmount++;
}
}
// если предпоследний интервал был успешно определен, то пометим последнюю точку как успешную
if(Objects[ObjectsN-2].delta_t_good) Objects[ObjectsN-1].delta_t_good=1;
}
// найдем среднее значение delta t
for(i=average_t=0; i < ObjectsN; i++) if(Objects[i].delta_t_good) average_t += Objects[i].delta_t;
if(GoodDeltaTAmount) average_t /= GoodDeltaTAmount;
// сформируем список графических объектов для пометки найденных максимумов
data_str->slow_data[VISUAL_N]=GoodDeltaTAmount;
data_str->slow_data[VISUAL_T_MX]=average_t;
for(i=0; i < ObjectsN; i++)
{
graph_objects[i].type=VAL_OBJECT_POINT;
graph_objects[i].x1=Objects[i].x_max;
graph_objects[i].y1=Objects[i].y_max;
graph_objects[i].mode=VAL_SOLID_CIRCLE;
graph_objects[i].control_index=0;
graph_objects[i].color=(Objects[i].delta_t_good) ? VAL_BLACK : VAL_GREEN;
}
data_str->graph_objects_n=ObjectsN;
data_str->graph_objects=graph_objects;
data_str->control_index[VISUAL_INDEX_GRAPH]=0; // номер графического элемента, в котором будет нарисован график
data_str->color[VISUAL_INDEX_GRAPH]=VAL_RED; // цвет графика
data_str->line_type[VISUAL_INDEX_GRAPH]=VAL_SOLID; // тип линии (сплошной, точки и т.п.)
data_str->line_mode[VISUAL_INDEX_GRAPH]=VAL_FAT_LINE; // тип графика
sprintf(data_str->name[VISUAL_INDEX_GRAPH], "Канал %u", chan1+1); // имя графика (для легенды)
if(DataInfo.file_mode) // таблицу обновляем только в режиме просмотра файла
{
data_str->table_rows_n=GoodDeltaTAmount;
data_str->table_rows=table_rows;
for(i=0; i < ObjectsN; i++)
{
if(!Objects[i].delta_t_good) continue;
sprintf(table_rows[i].text[0], "%.3f", Objects[i].x_max);
sprintf(table_rows[i].text[1], "%.3f", Objects[i].y_max);
sprintf(table_rows[i].text[2], "%.3f", Objects[i].delta_t);
}
}
}
//*********************************************************************************************************
// информационная функция
void __stdcall PluginInfo(struct PluginInfoStr *p_info)
{
int i;
char *filter_names[]={"Отключен", "Баттерворта", "Чебышева", "Инверсный Чебышева", "Эллиптический"};
// установим общие переменные
strcpy(p_info->name, "Discrimnator1"); // название плагина
p_info->version=0x00010000; // версия 1.0
p_info->lgraph_version=0x221; // плагин разработан для версии 2.33
p_info->max_nch=p_info->min_nch=1; // максимальное число каналов, которые может обработать плагин 2
// установим параметры входных каналов плагина
strcpy(p_info->channel_names[0], "Канал для анализа");
p_info->parameters=10; // всего 10 параметров
// разрешение проверки дельта времени на допустимый процент отклонения от среднего
strcpy(p_info->parameters_names[PARM_PERCENT_ENABLE], "Проверять допустимое отклонение по длительности");
p_info->parameters_type[PARM_PERCENT_ENABLE]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_PERCENT_ENABLE][0], "Нет", 63);
strncpy(p_info->ring_names[PARM_PERCENT_ENABLE][1], "Да", 63);
p_info->default_parameters_int[PARM_PERCENT_ENABLE]=0;
// максимальное отклонение в процентах
strcpy(p_info->parameters_names[PARM_PERCENT_VALUE], "Максимальное отклонение по длительности, %");
p_info->parameters_type[PARM_PERCENT_VALUE]=L_TYPE_DOUBLE;
p_info->default_parameters_dbl[PARM_PERCENT_VALUE]=20.0;
p_info->min_parameters_dbl[PARM_PERCENT_VALUE]=0.1;
p_info->max_parameters_dbl[PARM_PERCENT_VALUE]=75.0;
// разрешение проверки на минимальную длительность дельта времени
strcpy(p_info->parameters_names[PARM_TMIN_ENABLE], "Проверять минимальную длительность пика");
p_info->parameters_type[PARM_TMIN_ENABLE]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_TMIN_ENABLE][0], "Нет", 63);
strncpy(p_info->ring_names[PARM_TMIN_ENABLE][1], "Да", 63);
p_info->default_parameters_int[PARM_TMIN_ENABLE]=0;
// минимальная длительность
strcpy(p_info->parameters_names[PARM_TMIN_VALUE], "Минимальная длительность пика");
p_info->parameters_type[PARM_TMIN_VALUE]=L_TYPE_DOUBLE;
p_info->default_parameters_dbl[PARM_TMIN_VALUE]=0.1;
// масштаб минимальной длительности (0 мкс, 1 мс, 2 секунды)
strcpy(p_info->parameters_names[PARM_TMIN_SCALE], "Масштаб минимальной длительности");
p_info->parameters_type[PARM_TMIN_SCALE]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_TMIN_SCALE][0], "микросекунды", 63);
strncpy(p_info->ring_names[PARM_TMIN_SCALE][1], "миллисекунды", 63);
strncpy(p_info->ring_names[PARM_TMIN_SCALE][2], "секунды", 63);
p_info->default_parameters_int[PARM_TMIN_SCALE]=2;
// Параметр выбора типа фильтра
strcpy(p_info->parameters_names[PARM_FILTER], "Фильтр ФВЧ");
p_info->parameters_type[PARM_FILTER]=L_TYPE_RING;
for(i=0; i < 5; i++) strncpy(p_info->ring_names[PARM_FILTER][i], filter_names[i], 63);
// Параметр порядок фильтра
strcpy(p_info->parameters_names[PARM_FILTER_ORDER], "Порядок фильтра (от 2 до 10)");
p_info->parameters_type[PARM_FILTER_ORDER]=L_TYPE_INT;
p_info->default_parameters_int[PARM_FILTER_ORDER]=3;
p_info->min_parameters_int[PARM_FILTER_ORDER]=2;
p_info->max_parameters_int[PARM_FILTER_ORDER]=10;
// Нижняя полоса среза
strcpy(p_info->parameters_names[PARM_CUTOFF_F], "Полоса среза, Гц");
p_info->parameters_type[PARM_CUTOFF_F]=L_TYPE_DOUBLE;
p_info->default_parameters_dbl[PARM_CUTOFF_F]=1;
// минимальная длительность
strcpy(p_info->parameters_names[PARM_DELETE_TIME], "Сколько отбросить после фильтрации");
p_info->parameters_type[PARM_DELETE_TIME]=L_TYPE_DOUBLE;
p_info->default_parameters_dbl[PARM_DELETE_TIME]=1.0;
// Параметр выбора типа фильтра
strcpy(p_info->parameters_names[PARM_INVERSE], "Поиск отрицательных пиков");
p_info->parameters_type[PARM_INVERSE]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_INVERSE][0], "Выключен", 63);
strncpy(p_info->ring_names[PARM_INVERSE][1], "Включен", 63);
// выделим память под разные объекты и массивы
if(table_object == NULL) table_object=malloc(sizeof(struct TableStr));
if(table_object == NULL) { strcpy(p_info->error, "Не хватает памяти"); return; }
if(table_rows == NULL) table_rows=malloc(sizeof(struct TableDataRowStr)*MAX_N);
if(table_rows == NULL) { strcpy(p_info->error, "Не хватает памяти"); return; }
if(graph_objects == NULL) graph_objects=malloc(MAX_N*sizeof(struct GraphObjectStr));
if(graph_objects == NULL) { strcpy(p_info->error, "Не хватает памяти"); return; }
}
// *********************************************************************************************************
// обработка данных о параметрах модулей АЦП от LGraph
void __stdcall PluginDataInfo(struct PluginDataInfoStr *d_info)
{
int chan1;
if(!d_info->devices) { strcpy(d_info->error, "Нет модуля АЦП"); return; }
if(!d_info->nch[device_index]) { strcpy(d_info->error, "Не выбраны каналы АЦП"); return; }
DataInfo=*d_info; // запомним параметры АЦП
chan1=d_info->adc_channels[0]; // запомним номер канала (от 0 до 31)
// проверим включен ли канал
if(!d_info->chan_on[device_index][chan1]) { sprintf(d_info->error, "Не выбран канал %u", chan1+1); return; }
d_info->input_kadrs_min=100; // минимальный размер получаемых данных 100
d_info->input_kadrs_max=MAX_SIZE; // максимальный размер получаемых данных
// выделим для начала память на 500000 точек на массив для данных
if(!AdcDataSize)
{
AdcData=malloc(500000*sizeof(double));
if(AdcData == NULL) { strcpy(d_info->error, "Не хватает памяти"); return; }
AdcFilteredData=malloc(500000*sizeof(double));
if(AdcFilteredData == NULL) { strcpy(d_info->error, "Не хватает памяти"); return; }
XData=malloc(500000*sizeof(double));
if(XData == NULL) { free(AdcData); strcpy(d_info->error, "Не хватает памяти"); return; }
AdcDataSize=500000;
}
}
//*********************************************************************************************************
// настройка графиков
void __stdcall PluginVisualSetting(struct PluginVisualMainStr *main_visual_settings, struct PluginVisualStr p_visual[])
{
int graph_height, graph_width, m_top;
LgraphVisual=*main_visual_settings;
main_visual_settings->n=6; // создаем 6 визуальных элементов
main_visual_settings->plugin_height=main_visual_settings->height*0.7; // высота 0.7 экрана
// ********** НАСТРОЙКИ ГРАФИКА
m_top=main_visual_settings->plugin_height-120;
graph_height=main_visual_settings->plugin_height-160;
graph_width=main_visual_settings->width-20;
p_visual[VISUAL_INDEX_GRAPH].type=L_VISUAL_GRAPH;
// настроим графические координаты
p_visual[VISUAL_INDEX_GRAPH].top=10; // координата по вертикали графика
p_visual[VISUAL_INDEX_GRAPH].height=graph_height; // высота графика
p_visual[VISUAL_INDEX_GRAPH].width=graph_width; // ширина графика
p_visual[VISUAL_INDEX_GRAPH].left=10; // горизонтальная координата
// сконфигурируем ось X (частота)
p_visual[VISUAL_INDEX_GRAPH].x_axis_mode=1; // по умолчанию включим автомасштаб по оси X
p_visual[VISUAL_INDEX_GRAPH].x_scale_format=VAL_FLOATING_PT_FORMAT;
if(main_visual_settings->time_scale_index == LGRAPH_X_TIME_REL ||
main_visual_settings->time_scale_index == LGRAPH_X_TIME_ABS)
p_visual[VISUAL_INDEX_GRAPH].x_scale_format=VAL_RELATIVE_TIME_FORMAT;
p_visual[VISUAL_INDEX_GRAPH].x_show_ms=main_visual_settings->show_ms;
// сконфигурируем ось Y (дБ)
p_visual[VISUAL_INDEX_GRAPH].y_axis_mode=1; // по умолчанию включим автомасштаб по оси X
// сконфигурируем курсоры, задающие полосы
p_visual[VISUAL_INDEX_GRAPH].cursors_n=2; // включим 2 активных курсора
p_visual[VISUAL_INDEX_GRAPH].cursor_color[0]=p_visual[VISUAL_INDEX_GRAPH].cursor_color[1]=VAL_GREEN;// цвета курсора
p_visual[VISUAL_INDEX_GRAPH].cursot_hair[0]=p_visual[VISUAL_INDEX_GRAPH].cursot_hair[1]=VAL_HORIZONTAL_LINE;
p_visual[VISUAL_INDEX_GRAPH].cursor_enabled[0]=p_visual[VISUAL_INDEX_GRAPH].cursor_enabled[1]=1;
p_visual[VISUAL_INDEX_GRAPH].cursor_mode[0]=p_visual[VISUAL_INDEX_GRAPH].cursor_mode[1]=VAL_FREE_FORM;
p_visual[VISUAL_INDEX_GRAPH].cursor_point_style[0]=p_visual[VISUAL_INDEX_GRAPH].cursor_point_style[1]=VAL_BOLD_CROSS;
//************ настройка остальных визуальных элементов
// Y1 (значение первого курсора по вертикали)
p_visual[VISUAL_INDEX_Y1].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_Y1].label_text, "Y1");
p_visual[VISUAL_INDEX_Y1].top=m_top; // координата по вертикали
p_visual[VISUAL_INDEX_Y1].left=40; // координата по горизонтали
p_visual[VISUAL_INDEX_Y1].y_precision=3;
// Y2 (значение второго курсора по вертикали)
p_visual[VISUAL_INDEX_Y2].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_Y2].label_text, "Y2");
p_visual[VISUAL_INDEX_Y2].top=m_top; // координата по вертикали
p_visual[VISUAL_INDEX_Y2].left=40+100; // координата по горизонтали
p_visual[VISUAL_INDEX_Y2].y_precision=3;
// число найденных объектов
p_visual[VISUAL_N].type=L_VISUAL_NUMERIC; // тип визуального элемента определяет пользователь
strcpy(p_visual[VISUAL_N].label_text, "N");
p_visual[VISUAL_N].top=m_top; // координата по вертикали
p_visual[VISUAL_N].left=40+100*2; // координата по горизонтали
p_visual[VISUAL_N].y_precision=0;
// среднее значение delta t
p_visual[VISUAL_T_MX].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_T_MX].label_text, "Average T");
p_visual[VISUAL_T_MX].top=m_top; // координата по вертикали
p_visual[VISUAL_T_MX].left=40+100*3; // координата по горизонтали
p_visual[VISUAL_T_MX].y_precision=3;
// таблица для вывода результата
p_visual[VISUAL_TABLE].type=L_VISUAL_TABLE;
p_visual[VISUAL_TABLE].top=m_top-20; // координата по вертикали
p_visual[VISUAL_TABLE].left=40+100*4; // координата по горизонтали
p_visual[VISUAL_TABLE].height=125; // высота таблицы
p_visual[VISUAL_TABLE].width=250; // координата по горизонтали
p_visual[VISUAL_TABLE].table_object=table_object;
table_object->scroll_bars=VAL_VERT_SCROLL_BAR; // включим только вертикальный скроллинг
table_object->columns=3; // всего 3 колонки
table_object->columns_width[0]=50;
table_object->columns_width[1]=60;
table_object->columns_width[2]=70;
strcpy(table_object->columns_names[0], "X");
strcpy(table_object->columns_names[1], "Y");
strcpy(table_object->columns_names[2], "T");
}
// Функция сообщает плагину, что начался/закончился сбор данных (в данном плагине не используем)
void __stdcall PluginStartInput(struct PluginDataStr *data_str) {}
void __stdcall PluginStopInput(struct PluginDataStr *data_str) {}
//*********************************************************************************************************
// Функция вызываемая при загрузке - выгрузке DLL плагина
int __stdcall DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
if (InitCVIRTE (hinstDLL, 0, 0) == 0) return 0;
break;
case DLL_PROCESS_DETACH:
if(AdcFilteredData != NULL) free(AdcFilteredData);
if(AdcData != NULL) free(AdcData);
if(graph_objects != NULL) free(graph_objects);
if (!CVIRTEHasBeenDetached ()) CloseCVIRTE ();
break;
}
return 1;
}



