оконный дискриминатор

 

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; }

Контакты

Адрес: 117105, Москва, Варшавское шоссе, д. 5, корп. 4, стр. 2

Многоканальный телефон:
+7 (495) 785-95-25
Факс: +7 (495) 785-95-14

Отдел продаж: sale@lcard.ru
Техническая поддержка: support@lcard.ru

Время работы: с 9-00 до 19-00 мск