diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2ea1a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.hg* +*~ +*.bak diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d25dc3f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.6) +set(PROJ fitsview) +project(${PROJ}) +set(CMAKE_COLOR_MAKEFILE ON) +set(DEBUG 1) # закомментировать эту строку по окончании "разработки" +if(NOT DEFINED NO_CUDA) + message("Try to use CUDA") + find_package(CUDA) + if(CUDA_FOUND) + add_definitions(-DCUDA_FOUND) + endif() +endif() +if(NOT DEFINED PROCESSOR_COUNT) + set(PROCESSOR_COUNT 2) # by default 2 cores + set(cpuinfo_file "/proc/cpuinfo") + if(EXISTS "${cpuinfo_file}") + file(STRINGS "${cpuinfo_file}" procs REGEX "^processor.: [0-9]+$") + list(LENGTH procs PROCESSOR_COUNT) + endif() +endif() +add_definitions(-DTHREAD_NUMBER=${PROCESSOR_COUNT}) +message("In multithreaded operations will use ${PROCESSOR_COUNT} threads") +subdirs(src) + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..256a913 --- /dev/null +++ b/COPYING @@ -0,0 +1,2 @@ +eddy@sao.ru + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..fc658ec --- /dev/null +++ b/ChangeLog @@ -0,0 +1,99 @@ +01.12.2011 Edward V. Emelianoff + + * Изменена функция определения изолиний. Теперь медлненно, но надежно. Планирую ускорить + - обнаружены ошибки при открывании файла сразу в 3D + - неверно отображаются изолинии на 3D при выборе части изображения + + +06.09.2011 Edward V. Emelianoff + + * добавлен поиск изолиний (с различными типами шкалы высот) + - АЛГОРИТМ ЖУТКО ГЛЮЧИТ + - сегфолт на экспоненциальной шкале высот + - почему-то некоторые контуры получаются разорванными, контуры выходят за пределы рисунка + + +29.08.2011 Edward V. Emelianoff + + * добавлен медианный фильтр + + +22.08.2011 Edward V. Emelianoff + + * убран сегфолт при попытке открыть битую симв. ссылку + * начал добавлять фильтры + + +14.08.2011 Edward V. Emelianoff + + * перешел на mercurial + + +18.07.2011 Edward V. Emelianoff + + - исправить глюки с гистограммами и Хафом + - переписать функции рассчета вершин и нормалей на CUDA + - сделать нормальные пределы на гистограмме (а не [0, 1]) + - скорректировать линейки на графическом окне + + +30.05.2011 Edward V. Emelianoff + + * исправлена ошибка с чтением шапок фитс-файлов + * разделены операции обнаружения пятен и распознавания гартманограммы + * автоматическое распознавание пред- и зафокальных гартманограмм + * сохранение списка координат обнаруженных пятен + + +10.04.2011 Edward V. Emelianoff + + -+частично реализовано сохранение FITSов (и шапок) + -+частично реализовано вращение 3D мышью + + +04.04.2011 Edward V. Emelianoff + + * пункт меню "открыть" в окне 3D (без навигации pgUp/pgDwn) + * прокрутка в редактировании/просмотре шапки фитсов + * пункты меню "открыть в новом окне" и "открыть в 3D" + + +31.03.2011 Edward V. Emelianoff + + * навигация по файлам в текущей директории + + +31.03.2011 Edward V. Emelianoff + +Пре-альфа версия, на ближайшее время реализовать: + - отображение имени файла в строке состояния (а то непонятно, что за файл открыт) + - глюк: "увеличение рамкой" не отменяется + - исправить глюки при открывании нового файла (остаются старые режимы, spots и т.п.) + - предлагать на выбор методы определения центров тяжести (аппр. гауссом, параболой по логарифму, "тупой", параболой по макушке, "тупой" с порогами/весами, аппр. параболоидом...) + - отображать отмеченные треки, границы на гистограмме и т.п. + - редактировать границы треков + - разбор аргументов командной строки + - batch-режим + - вращение 3D мышью + - сохранение FITSов (и шапок) преобразованного изображения + - интерактивный Хаф + - распознавание окружностей + - расчет поверхности зеркала по гартманограммам (случай 1 и пары снимков) + - фильтрация изображений (свертки/Фурье, вейвлеты) + - корреляционный анализ + - визуальное сравнение двух изображений (верхнее - полупрозрачное) + - добавить в 3D NURBS'ы + - "лупа", копипаст, редактирование (с сохранением) + - полная статистика + - вычисление экв. ширины линии на срезе + - отождествление кривых (например, сп. порядков или теллур. линий) и "выпрямление" изображения + - арифметические операции над изображениями + - отображение комплексных изображений (варианты: амплитуда, фаза, Re, Im) и комплексные операции (Фурье, арифм., Фурье-фильтры, градиентные фильтры и т.п.) + - глючит редактирование (где-то лишнее free, не снимается выделение с ячеек) + - сохранение FITSов (и шапок) + - вращение 3D мышью + - исправить глюки с гистограммами и Хафом + - переписать функции рассчета вершин и нормалей на CUDA + - сделать нормальные пределы на гистограмме (а не [0, 1]) + - скорректировать линейки на графическом окне + - при открывании нового изображения старое не удаляется из памяти - УТЕЧКА diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..e69de29 diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..621e94f --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +none diff --git a/README b/README new file mode 100644 index 0000000..ac7090e --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +Ключи cmake: +DEBUG=1 - отладочный режим +NO_CUDA=1 - не использовать CUDA, даже если она поддерживается системой +PROCESSOR_COUNT=X - в многопоточных задачах использовать X потоков +NO_LEPTONICA=1 - не использовать лептонику, даже если она есть в системе +NO_GSL=1 - не использовать GSL, даже если она есть в системе diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..c24c61c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 2.6) +if(DEFINED DEBUG) + add_definitions(-DEBUG) +endif() +set(SRC ${CMAKE_SOURCE_DIR}/src) +aux_source_directory(${SRC} SOURCES) +set(NOCUFILE ${SRC}/NOCUDA.c) +set(CUFILE ${SRC}/CUDA.cu) +set(CFLAGS -O3 -Wall -Werror -W -std=c99) +set(LCPATH ${SRC}/locale/ru) +set(PO_FILE ${LCPATH}/messages.po) +set(MO_FILE ${LCPATH}/LC_MESSAGES/${PROJ}.mo) +set(RU_FILE ${LCPATH}/ru.po) +find_package(PkgConfig REQUIRED) +find_package(OpenGL REQUIRED) +find_package(GTK2 REQUIRED) +pkg_check_modules(${PROJ} REQUIRED + #glib-2.0>=2.10 + gtkglext-1.0>=0.7.0 + gtkglext-x11-1.0>=0.7.0 + #gtk+-2.0>=2.6.0 + cfitsio>=3.0 + fftw3>=3.2.0 + ) +include(FindOpenMP) +if(OPENMP_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") +endif() +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lfftw3_threads") +if(NOT DEFINED NO_LEPTONICA) + pkg_check_modules(LIBLEPT liblept) +endif() +if(NOT DEFINED NO_GSL) + pkg_check_modules(GSL gsl) +endif() +if(NOT DEFINED GSL_VERSION) + message("GSL not found, some mathematics functions wouldn't be avialable") +else() + add_definitions(-DGSL_FOUND) +endif() +if(NOT DEFINED LIBLEPT_VERSION) + message("Leptonica library not found, some functions wouldn't be avialable") +else() + add_definitions(-DLEPTONICA_FOUND) +endif() +if(CUDA_FOUND) + list(REMOVE_ITEM SOURCES ${NOCUFILE}) + list(APPEND CUDA_NVCC_FLAGS --use_fast_math) + cuda_include_directories(include) + cuda_add_executable(${PROJ} ${SOURCES} ${CUFILE} ${PO_FILE} ${MO_FILE} ui.h) + target_link_libraries( ${PROJ} ${${PROJ}_LIBRARIES} + ${GSL_LIBRARIES} ${LIBLEPT_LIBRARIES} + ${CUDA_CUFFT_LIBRARIES} -lcuda) +else(CUDA_FOUND) + find_package(Threads) + add_executable(${PROJ} ${SOURCES} ${PO_FILE} ${MO_FILE} ui.h) + target_link_libraries( ${PROJ} ${${PROJ}_LIBRARIES} + ${GSL_LIBRARIES} ${LIBLEPT_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ) +endif(CUDA_FOUND) +include_directories(${SRC}/include ${${PROJ}_INCLUDE_DIRS} ${GSL_INCLUDE_DIRS} ${LIBLEPT_INCLUDE_DIRS}) +link_directories(${${PROJ}_LIBRARY_DIRS} ${GSL_LIBRARY_DIRS} ${LIBLEPT_LIBRARY_DIRS}) +add_definitions(-DPACKAGE_VERSION=\"0.0.1\" -DGETTEXT_PACKAGE=\"${PROJ}\" + -DLOCALEDIR=\"~/.local/share/locale\" ${CFLAGS}) + +#if(DEFINED DEBUG) + find_package(Gettext REQUIRED) + find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) + if(NOT GETTEXT_XGETTEXT_EXECUTABLE ) + message(FATAL_ERROR "xgettext not found") + endif(NOT GETTEXT_XGETTEXT_EXECUTABLE ) +add_custom_command( + OUTPUT ${PO_FILE} + COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} --from-code=koi8-r ${SOURCES} ${SRC}/${PROJ}.glade -k_ -kN_ -o ${PO_FILE} + DEPENDS ${SOURCES}) +add_custom_command( + OUTPUT ${MO_FILE} + COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ${RU_FILE} -o ${MO_FILE} + DEPENDS ${RU_FILE}) +add_custom_command( + OUTPUT ui.h + COMMAND gtk-builder-convert ${SRC}/${PROJ}.glade ${PROJ}.ui + COMMAND ${SRC}/scripts/genh ${PROJ}.ui + DEPENDS ${PROJ}.glade) +#endif(DEFINED DEBUG) diff --git a/src/CUDA.cu b/src/CUDA.cu new file mode 100644 index 0000000..c72fe57 --- /dev/null +++ b/src/CUDA.cu @@ -0,0 +1,693 @@ +/* + * CUDA.cu - subroutines for GPU + * + * Copyright 2011 Edward V. Emelianoff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#define _CUDA_CU_ +#include "include/CUtools.h" + +#include +#include +//#include +//#include +//#include + +const int SHMEMSZ = 16383; // default constants, changed runtime +const int QBLKSZ = 16; // QBLKSZ = sqrt(LBLKSZ) +const int LBLKSZ = 512; + +// static arrays for sines & cosines values +static float *Sin_d = NULL, *Cos_d = NULL; +// array size +static int sincosize = 0; + +cudaError_t CUerr; +inline int CUERROR(char *str){ + if(CUerr != cudaSuccess){ + fprintf(stderr, "%s, %s\n", str, cudaGetErrorString(CUerr)); + return 1; + }else return 0; +} +// error macro (by default - nothing) +#define RETMACRO return +// memory macros +#define CUALLOC(var, size) do{ \ + CUerr = cudaMalloc((void**)&var, size); \ + if(CUERROR("CUDA: can't allocate memory")){ \ + RETMACRO; \ +}}while(0) +#define CUMOV2DEV(dest, src, size) do{ \ + CUerr = cudaMemcpy(dest, src, size, \ + cudaMemcpyHostToDevice); \ + if(CUERROR("CUDA: can't copy data to device")){\ + RETMACRO;} \ +}while(0) +#define CUMOV2HOST(dest, src, size) do{ \ + CUerr = cudaMemcpy(dest, src, size, \ + cudaMemcpyDeviceToHost); \ + if(CUERROR("CUDA: can't copy data to host")){\ + RETMACRO;} \ +}while(0) +#define CUFREE(var) do{cudaFree(var); var = NULL; }while(0) +#define CUFFTCALL(fn) do{ \ + cufftResult fres = fn; \ + if(CUFFT_SUCCESS != fres){ \ + fprintf(stderr, "CUDA fft error %d\n", fres);\ + RETMACRO;} \ +}while(0) + +#ifdef EBUG + #define FNAME() fprintf(stderr, "\n%s (%s, line %d)\n", __func__, __FILE__, __LINE__) + #define DBG(...) do{fprintf(stderr, "%s (%s, line %d): ", __func__, __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n");} while(0) +#else + #define FNAME() do{}while(0) + #define DBG(...) do{}while(0) +#endif //EBUG + +// getting the videocard parameters +extern "C" void getprops(){ + cudaDeviceProp dP; + CUdevice dev; CUcontext ctx; + cudaGetDeviceProperties(&dP, 0); + cuDeviceGet(&dev,0); + cuCtxCreate(&ctx, 0, dev); + printf("\nDevice: %s, totalMem=%zd, memPerBlk=%zd,\n", dP.name, dP.totalGlobalMem, dP.sharedMemPerBlock); + printf("warpSZ=%d, TPB=%d, TBDim=%dx%dx%d\n", dP.warpSize, dP.maxThreadsPerBlock, + dP.maxThreadsDim[0],dP.maxThreadsDim[1],dP.maxThreadsDim[2]); + printf("GridSz=%dx%dx%d, MemovrLap=%d, GPUs=%d\n", dP.maxGridSize[0], + dP.maxGridSize[1],dP.maxGridSize[2], + dP.deviceOverlap, dP.multiProcessorCount); + printf("canMAPhostMEM=%d\n", dP.canMapHostMemory); + printf("compute capability %d.%d.\n\n", dP.major, dP.minor); + if(dP.major > 1){ + // SHMEMSZ = 49151; QBLKSZ = 32; LBLKSZ = 1024; + } + size_t theFree, theTotal; + CUresult aaa = cuMemGetInfo( &theFree, &theTotal ); + printf("CARD returns(err=%d): free mem:%zd, total mem:%zd\n", aaa, theFree, theTotal); + cuCtxDetach(ctx); +} + +// normalisation of array arr with size arrsize +__global__ void normalize_vec(float *arr, int arrsize){ + __shared__ float max[LBLKSZ]; + int idx = threadIdx.x; + int blksize = (arrsize + blockDim.x - 1) / blockDim.x; + int b_beg = idx * blksize; + if(b_beg >= arrsize) return; + int b_end = b_beg + blksize; + if(b_end > arrsize) b_end = arrsize; + int i; float *ptr = &arr[b_beg]; + float mm = *ptr++; + for(i = b_beg +1 ; i < b_end; i++, ptr++) + if(mm < *ptr) mm = *ptr; + max[idx] = mm; + __syncthreads(); + if(idx == 0){ + mm = max[0]; + for(i = 1; i < LBLKSZ; i++) + if(mm < max[i]) mm = max[i]; + max[0] = mm; + } + __syncthreads(); + ptr = &arr[b_beg]; + mm = max[0]; + if(mm != 0.f) + for(i = b_beg ; i < b_end; i++, ptr++) *ptr /= mm; +} +/* +__global__ void fill_zeros(float *arr, int arrsize, int W, int H){ + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + if(x > W || y > H) return; + arr[x + y*W] = 0.f; +}*/ + +// kernel of function for sin/cos array initialisation +__global__ void fill_sincos(int angles, + float *Sin_d, float *Cos_d, + float anglestep, float conv){ + int k = blockIdx.x * blockDim.x + threadIdx.x; + if(k >= angles) return; + float theta = ((float)k / anglestep - 90.f) * conv; + sincosf(theta, &Sin_d[k], &Cos_d[k]); +} + +// initialisation of sin/cos arrays +extern "C" int init_sincos(int angles){ + #undef RETMACRO + #define RETMACRO return 0 + // the value reciprocal for angle step + float anglestep = (float)angles / 270.f; + float conv = M_PI / 180.f; + int blks = (angles + QBLKSZ - 1) / QBLKSZ; + int threads = LBLKSZ; + // first time we initialize arrays + if(!Sin_d || !Cos_d || angles != sincosize){ + CUFREE(Cos_d); + CUFREE(Sin_d); + CUALLOC(Sin_d, angles*sizeof(float)); + CUALLOC(Cos_d, angles*sizeof(float)); + fill_sincos<<>>(angles, Sin_d, Cos_d, anglestep, conv); + sincosize = angles; + } + cudaThreadSynchronize(); + return 1; + #undef RETMACRO + #define RETMACRO return +} + +/* + * Lines Hough transform kernel + * ima_d - device array with the image + * imW, imH, min, max - width, height of the image and extreme values of its histogram + * Sin_d, Cos_d - device array with sines and cosines of angles (-90..180degr increments 270/angles) + * Rmax - the maximum range for the desired lines + * angles - the number of angles in the range -90 .. 180 + * treshold - lower threshold of intensity (in relative units: I=tres*(max-min)+min) for inclusion of point into array + * hough_d - output array with Hough transform + */ +__global__ void fill_lin_hough_array(float *ima_d, + int imW, int imH, + float min, float max, + float *Sin_d, float *Cos_d, + int Rmax, int angles, float treshold, + float *hough_d){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + int i = xi + imW * yi; + float x = (float)xi; + float y = (float)yi; + int k, R; + if(xi >= imW || yi >= imH) return; + float wd = max-min; if(wd == 0.f) wd = 1.f; + float ima = (ima_d[i]-min)/wd; + if(ima > treshold){ + for(k = 0; k < angles; k++){ + // R = x*cos(theta) + y*sin(theta) + R = (int)(0.5f + x * Cos_d[k] + y * Sin_d[k]); + // THIS IS VERY BAD, BUT atomicAdd doesn't work in old devices + if(R > 0 && R < Rmax) hough_d[R + Rmax*k] += ima; + //if(R > 0 && R < Rmax) atomicAdd(&hough_d[R + Rmax*k], ima); + } + } +} + +/* + * Build Hough transform to find lines + * Input: + * ima - the image data + * min, max - range of the data in it + * imW, imH - image width and height + * Rmax - the maximum value of R + * Angles - the array size of angles (the angle of pitch is 180/angles degrees) + * Output: + * hough - array initialized by an external function, + * in which the Hough transform will be + * !!! array must be initialized with zeros before calling this function + * Output array is normalized to unity + */ +extern "C" int fill_hough_lines(float *ima, float min, float max, int imW, int imH, + int Rmax, int angles, float *hough){ + #undef RETMACRO + #define RETMACRO do{ ret = 0; goto free_all; }while(0) + int sz, ret = 1; + int lblksz = LBLKSZ; + float *ima_d = NULL, *hough_d = NULL; + float treshold = 0.1f; + sz = imW * imH; + getprops(); + dim3 blkdim(QBLKSZ, QBLKSZ); + dim3 griddim((imW+QBLKSZ-1)/QBLKSZ, (imH+QBLKSZ-1)/QBLKSZ); +// dim3 hgriddim((Rmax+QBLKSZ-1)/QBLKSZ, (angles+QBLKSZ-1)/QBLKSZ); + if(!init_sincos(angles)) RETMACRO; + CUALLOC(ima_d, sz*sizeof(float)); + CUMOV2DEV(ima_d, ima, sz*sizeof(float)); + sz = Rmax * angles; + CUALLOC(hough_d, sz*sizeof(float)); + cudaMemset(hough_d, 0, sz*sizeof(float)); +// fill_zeros<<>>(hough_d, sz, Rmax, angles); + cudaThreadSynchronize(); + CUMOV2DEV(hough_d, hough, sz*sizeof(float)); + fill_lin_hough_array<<>>(ima_d, imW,imH, min,max, Sin_d,Cos_d, + Rmax, angles, treshold, hough_d); + cudaThreadSynchronize(); + normalize_vec<<<1, lblksz>>>(hough_d, sz); + cudaThreadSynchronize(); + CUMOV2HOST(hough, hough_d, sz*sizeof(float)); +free_all: + CUFREE(hough_d); + CUFREE(ima_d); + return ret; + #undef RETMACRO + #define RETMACRO return +} + +/* + * Kernels of the threshold filtering + * in, out - in and out + * stepfn - a pointer to a function of conversion + * sizex, sizey - image size + * min - the minimum intensity + * wd - range of the data + * step - a step for stepfn + */ +// uniform intensity distribution +__global__ void Funiform(float *in, int sizex, int sizey, float min, float step){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + int i = xi + yi*sizex; + if(xi >= sizex || yi >= sizey) return; + in[i] = floor((in[i]-min)/step); +} +// logarithm distribution +__global__ void Flog(float *in, int sizex, int sizey, float min, float step){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + int i = xi + yi*sizex; + if(xi >= sizex || yi >= sizey) return; + in[i] = floor(logf(in[i]-min+1.f)/step); +} +// exponential distribution +__global__ void Fexp(float *in, int sizex, int sizey, float min, float wd, float step){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + int i = xi + yi*sizex; + if(xi >= sizex || yi >= sizey) return; + in[i] = floor(expf((in[i]-min)/wd)/step); +} +// distribution of a square root +__global__ void Fsqrt(float *in, int sizex, int sizey, float min, float step){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + int i = xi + yi*sizex; + if(xi >= sizex || yi >= sizey) return; + in[i] = floor(sqrtf(in[i]-min)/step); +} +// distribution of a x^2 +__global__ void Fpow(float *in, int sizex, int sizey, float min, float step){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + int i = xi + yi*sizex; + if(xi >= sizex || yi >= sizey) return; + in[i] = floor((in[i]-min)*(in[i]-min)/step); +} + +// functions for calculation of output scale +float Suniform(float in, float min, float wd, float step){ + return step*in + min; +} +float Slog(float in, float min, float wd, float step){ + return expf(in*step) + min - 1.f; +} +float Sexp(float in, float min, float wd, float step){ + return wd*logf(in*step) + min; +} +float Ssqrt(float in, float min, float wd, float step){ + return in*in*step*step + min; +} +float Spow(float in, float min, float wd, float step){ + return sqrtf(in*step) + min; +} + +/* + * Threshold filtering ("posterization") + * Input: + * ima - picture (free() must be executed in the caller) + * f - filter: + * f-> w - number of levels of posterization, [2.255] + * f-> h - type of posterization (0 - uniform) + * sizex, sizey - image size + * min, max - minimum and maximum intensity of the image + * Output: + * result - filtered image, the memory is allocated in this procedure + * scale - the scale of intensities, the memory is allocated here (if the scale!=NULL) + * + * TODO: save the result in the char, not float; learn display function + */ +extern "C" int StepFilter(float *ima, float **result, + Filter *f, int sizex, int sizey, + float min, float max, float **scale){ + #undef RETMACRO + #define RETMACRO do{ ret = 0; goto free_all; }while(0) + int ret = 1; + float wd = max - min; + int y; + float Nsteps = (float)f->w; // number of intervals + float step; + float *in=NULL; // image and result array + int sz = sizex*sizey*sizeof(float); + dim3 blkdim(QBLKSZ, QBLKSZ); + dim3 griddim((sizex+QBLKSZ-1)/QBLKSZ, (sizey+QBLKSZ-1)/QBLKSZ); + *result = (float*)malloc(sz); + if(!result) RETMACRO; + float (*scalefn)(float,float,float,float); + if(f->w < 2 || f->w > 255) return 0; + if(wd == 0.f) return 0; + CUALLOC(in, sz); + CUMOV2DEV(in, ima, sz); + switch(f->h){ // filter type + case LOG: // logarithm + scalefn = Slog; + step = logf(max-min+1.f)/Nsteps; + Flog<<>>(in, sizex, sizey, min, step); + break; + case EXP: // exponential + scalefn = Sexp; + step = expf(1.f)/Nsteps; + Fexp<<>>(in, sizex, sizey, min, wd, step); + break; + case SQRT: // square root + scalefn = Ssqrt; + step = sqrtf(wd)/Nsteps; + Fsqrt<<>>(in, sizex, sizey, min, step); + break; + case POW: // power of two + scalefn = Spow; + step = wd*wd/Nsteps; + Fpow<<>>(in, sizex, sizey, min, step); + break; + default: // uniform + scalefn = Suniform; + step = wd/Nsteps; + Funiform<<>>(in, sizex, sizey, min, step); + } + cudaThreadSynchronize(); + CUMOV2HOST(*result, in, sz); + if(scale){ + int M = f->w; + *scale = (float*)calloc(M, sizeof(float)); + if(*scale) for(y = 0; y < M; y++){ + (*scale)[y] = scalefn(y+1,min,wd,step); + } + } +free_all: + CUFREE(in); + return ret; + #undef RETMACRO + #define RETMACRO return +} + + +/* + * A set of functions for constructing differential filters + */ +int p2oi(int i){ + unsigned int v = (unsigned int)i - 1; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return (int) v; +} +int nextpow2(int i, int j){ + int p1 = p2oi(i), p2 = p2oi(j); + return (p1 > p2)? p1 : p2; +} +// multiplication of two complex matrices with size x size +// result in the entry of the first matrix +__global__ void ComplexMul(cufftComplex *inout, cufftComplex *in, int size){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + if(xi >= size || yi >= size) return; + int i = xi + yi*size; + cufftComplex a = inout[i], b = in[i]; + inout[i].x = a.x * b.x - a.y * b.y; + inout[i].y = a.x * b.y + a.y * b.x; +} + +// restore coordinates of the Fourier transform +__global__ void fftshift(int size, float *m){ + int h = size/2; + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + if(xi >= h || yi >= h) return; + // k - point in left upper quadrant, k1 - in right upper + int k = yi * size+xi, k1 = k + h; + // p - point in right lower quadrant, p1 - in left lower + int p = k + (size+1)*h, p1 = k1 + (size-1)*h; + float tmp; + tmp = m[k]; m[k] = m[p]; m[p] = tmp; + tmp = m[k1]; m[k1] = m[p1]; m[p1] = tmp; +} +// data copying float->cufftReal (need because different sizes of picture and Fourier image) +__global__ void f2r(cufftReal *out, float *in, int sizex, int sizey, int size2){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + if(xi >= sizex || yi >= sizey) return; + out[xi+yi*size2] = (cufftReal) in[xi+yi*sizex]; +} +// data copying cufftReal->float +__global__ void r2f(float *out, cufftReal *in, int sizex, int sizey, int size2){ + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + if(xi >= sizex || yi >= sizey) return; + out[xi+yi*sizex] = (float) in[xi+yi*size2]; +} + +/* + * The kernel of the Laplacian of Gaussian + * Output: + * mask - the filled array + * Input: + * size - array size (size x size) + * x0, x1 - array bounds on x: [x0, x1) (outside this array filled by zeros) + * y0, y1 - -//- on y + * half - half the size of the array + * ss - normalizing factor + * sx2, sy2 - the variance of the filter in x and y + */ +__global__ void LGf_kernel(cufftReal *mask, int size, int x0, int x1, + int y0, int y1, float half, float ss, + float sx2, float sy2){ + int xi = blockIdx.x * blockDim.x + threadIdx.x + x0; + int yi = blockIdx.y * blockDim.y + threadIdx.y + y0; + if(xi >= x1 || yi >= y1) return; + int i = xi + yi*size; + float x2 = (float)xi + half; + float y2 = (float)yi + half; + x2 = x2*x2 / sx2; y2 = y2*y2 / sy2; + mask[i] = (cufftReal)(ss * ((x2-1.f)/sx2+(y2-1.f)/sy2)*expf(-(x2+y2)/2.f)); +} +// The kernel of Gaussian filter +__global__ void Gf_kernel(cufftReal *mask, int size, int x0, int x1, + int y0, int y1, float half, float ss, + float sx2, float sy2){ + int xi = blockIdx.x * blockDim.x + threadIdx.x + x0; + int yi = blockIdx.y * blockDim.y + threadIdx.y + y0; + if(xi >= x1 || yi >= y1) return; + int i = xi + yi*size; + float x2 = (float)xi + half; + float y2 = (float)yi + half; + x2 = x2*x2 / sx2; y2 = y2*y2 / sy2; + mask[i] = (cufftReal)(ss * expf(-(x2+y2)/2.f)); +} +/* + * Building mask of Gaussian or Laplasian of Gaussian + * Output: + * mask - filter array + * Input: + * size - mask size (size x size) + * f - filter parameters + */ +void build_GLG_filter(cufftReal *mask, int size, Filter *f){ + int y0=0,y1=size, x0=0, x1=size; + float sx2 = f->sx * f->sx, sy2 = f->sy * f->sy; + float half; + dim3 blkdim(QBLKSZ, QBLKSZ); + dim3 griddim((size+QBLKSZ-1)/QBLKSZ, (size+QBLKSZ-1)/QBLKSZ); + if(f->w < size && f->w > 0){ + x0 = (size - f->w + 1) / 2; + x1 = x0 + f->w; + } + if(f->h < size && f->h > 0){ + y0 = (size - f->h + 1) / 2; + y1 = y0 + f->h; + } + half = -(float)size / 2.f; + float ss = 3.f / half / half / sqrt(-half); + switch(f->FilterType){ + case LAPGAUSS: + LGf_kernel<<>>(mask, size, x0,x1, y0,y1, half, ss, sx2, sy2); + break; + case GAUSS: + Gf_kernel<<>>(mask, size, x0,x1, y0,y1, half, ss, sx2, sy2); + break; + default: + fprintf(stderr, "Error: bad filter\n"); + } + cudaThreadSynchronize(); + DBG("size=%d, x0=%d,x1=%d, y0=%d,y1=%d, half=%g, ss=%g, sx2=%g, sy2=%g", + size, x0,x1, y0,y1, half, ss, sx2, sy2); +} + +/* + * Building of elementary filter mask + * Output: + * mask - filter array + * Input: + * size - mask size (size x size) + * f - filter parameters + */ +void build_S_filter(cufftReal *mask, int size, Filter *f){ + int y, a0, a1; + float hh, Y, pt = 0.f; + a0 = (size - 2) / 2; + a1 = a0 + 3; + hh = -(float)(size / 2); + float ss = 1.f / (M_PI*2.f) / hh / hh / sqrtf(-hh); + Y = -1.f; + for(y = a0; y < a1; y++, Y+=1.f){ + float X = -1.f; + int str, x; + str = y * size; + for(x = a0; x < a1; x++, X+=1.f){ + switch(f->FilterType){ + case SOBELH: + pt = -X*(2.f-fabs(Y)); + break; + case SOBELV: + pt = -Y*(2.f-fabs(X)); + break; + case PREWITTH: + pt = X; + break; + case PREWITTV: + pt = Y; + break; + } + cufftReal tmppar = (cufftReal)ss*pt; + cudaMemcpy(&mask[str + x], &tmppar, sizeof(cufftReal), cudaMemcpyHostToDevice); + } + } +} +/* + * Convolution filtering (convolution by FFT) + * Input: + * ima - picture, that need to be filtering + * f - filter parameters + * Output: + * result - memory area, allocated by this function, + * where the filtered picture to be store + * return: TRUE if the filtering succeed + */ +extern "C" int DiffFilter(float *ima, float **result, + Filter *f, int sizex, int sizey){ + #undef RETMACRO + #define RETMACRO do{ ret = 0; goto free_all; }while(0) + int ssize, ret = 0, size2; + float *tmp; + size2 = nextpow2(sizex, sizey); + ssize = size2 * size2; // Fourier image size + dim3 blkdim(QBLKSZ, QBLKSZ); + dim3 griddim((size2+QBLKSZ-1)/QBLKSZ, (size2+QBLKSZ-1)/QBLKSZ); + dim3 halfgriddim((size2/2+QBLKSZ-1)/QBLKSZ, (size2/2+QBLKSZ-1)/QBLKSZ); + dim3 imgriddim((sizex+QBLKSZ-1)/QBLKSZ, (sizey+QBLKSZ-1)/QBLKSZ); + if(!result || !*result || !ima || !f){ + fprintf(stderr, "DiffFilter: bad parameters\n"); + return 0; + } + cufftHandle plan; + cufftComplex *Fmask=NULL, *Fimg=NULL; + cufftReal *mask=NULL, *img=NULL, *resm=NULL; + #ifdef EBUG + getprops(); + #endif + // Allocate memory for new objects + DBG("allocate"); + CUALLOC(img, ssize*sizeof(cufftReal)); + // fill it zeros + cudaMemset(img, 0, ssize*sizeof(cufftReal)); + // copy ima -> img + DBG("copy image to dev"); + CUALLOC(tmp, sizex*sizey*sizeof(float)); + CUMOV2DEV(tmp, ima, sizex*sizey*sizeof(float)); + f2r<<>>(img, tmp, sizex, sizey, size2); + cudaThreadSynchronize(); + CUFREE(tmp); + CUALLOC(Fimg, ssize*sizeof(cufftComplex)); + // make FFT + DBG("doing image FFT"); + CUFFTCALL(cufftPlan2d(&plan, size2, size2, CUFFT_R2C)); + CUFFTCALL(cufftExecR2C(plan, img, Fimg)); + CUFREE(img); + DBG("allocate"); + CUALLOC(mask, ssize*sizeof(cufftReal)); + cudaMemset(mask, 0, ssize*sizeof(cufftReal)); + CUALLOC(Fmask, ssize*sizeof(cufftComplex)); + switch(f->FilterType){ + case LAPGAUSS: + case GAUSS: + build_GLG_filter(mask, size2, f); + break; + case SOBELH: + case SOBELV: + case PREWITTH: + case PREWITTV: + build_S_filter(mask, size2, f); + break; + default: + fprintf(stderr, "Error: bad filter\n"); + RETMACRO; + } + // swap filter quadrants + fftshift<<>>(size2, mask); + cudaThreadSynchronize(); + // make FFT + DBG("doing filter FFT"); + CUFFTCALL(cufftExecR2C(plan, mask, Fmask)); + CUFFTCALL(cufftDestroy(plan)); + CUFREE(mask); + // make convolution in Fourier space + DBG("multiplication"); + ComplexMul<<>>(Fimg, Fmask, size2); + cudaThreadSynchronize(); + CUFREE(Fmask); + // Inverse FFT + DBG("doing inverse FFT"); + CUALLOC(resm, ssize*sizeof(cufftReal)); + CUFFTCALL(cufftPlan2d(&plan, size2, size2, CUFFT_C2R)); + CUFFTCALL(cufftExecC2R(plan, Fimg, resm)); + CUFFTCALL(cufftDestroy(plan)); + CUFREE(Fimg); + DBG("allocate"); + CUALLOC(tmp, ssize*sizeof(float)); + *result = (float*)calloc(sizex*sizey, sizeof(float)); + if(!*result) RETMACRO; + // copy iFFT -> res + DBG("copy to host"); + r2f<<>>(tmp, resm, sizex, sizey, size2); + cudaThreadSynchronize(); + CUMOV2HOST(*result, tmp, sizex*sizey*sizeof(float)); + ret = 1; +free_all: + CUFREE(Fmask); CUFREE(Fimg); CUFREE(img); + CUFREE(mask); CUFREE(resm); CUFREE(tmp); + #ifdef EBUG + getprops(); + #endif + return ret; + #undef RETMACRO + #define RETMACRO return +} +extern "C" int MedFilter(float *ima, float **result, Filter *f, int sizex, int sizey){return 0;} +extern "C" int GradFilterSimple(float *ima, float **result, Filter *f, int sizex, int sizey){return 0;} diff --git a/src/NOCUDA.c b/src/NOCUDA.c new file mode 100644 index 0000000..fef4410 --- /dev/null +++ b/src/NOCUDA.c @@ -0,0 +1,816 @@ +// NOCUDA.c - CPU-variants when there's no CUDA +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. + +#include "fitsview.h" +#include "gtk.h" +#include "CUtools.h" +#include +#include + +static float *Sin_d = NULL, *Cos_d = NULL; +static int sincosize = 0; + +#ifndef THREAD_NUMBER + #define THREAD_NUMBER 2 // default - 2 threads +#endif + +typedef struct{ + float *image; + float min; + float max; + float *hough; + int w; + int Y0; + int Y1; + int Rmax; + int angles; + pthread_mutex_t *mutex; +}Hough_kernel_args; + +// sin/cos ini, [-90,+180) +void init_sincos(int angles){ + float anglestep = (float)angles / 270.; // step by an angle + float conv = M_PI / 180.; + float *cp, *sp; + int k; + if(!Sin_d || !Cos_d || angles != sincosize){ // first time initialize arrays + free(Sin_d); + free(Cos_d); + Sin_d = malloc(angles * sizeof(float)); + Cos_d = malloc(angles * sizeof(float)); + cp = Cos_d; sp = Sin_d; + for(k = 0; k < angles; k++){ // fill arrays from -90 to +90 degr in radians + float theta = ((float)k / anglestep - 90.) * conv; + *sp++ = sinf(theta); + *cp++ = cosf(theta); + //sincosf(theta, sp++, cp++); + } + sincosize = angles; + } +} + +// Line Hough transform kernel +void *fill_lin_hough_array(void *data){ + Hough_kernel_args *HD = (Hough_kernel_args*) data; + float *ima = HD->image, *hough = HD->hough; + float min = HD->min, wd = HD->max - min; + pthread_mutex_t *mutex = HD->mutex; + int Y0 = HD->Y0, Y1 = HD->Y1, imW = HD->w, Rmax = HD->Rmax, angles = HD->angles; + int i, j, k, Y, R; + for(j = Y0; j < Y1; j++){ + for(i = 0.; i < imW; i++, ima++){ + float imdata = (*ima - min) / wd; + if(imdata > 0.1){ + Y = 0; + pthread_mutex_lock(mutex); + for(k = 0; k < angles; k++, Y+=Rmax){ + // R = x*cos(theta) + y*sin(theta) + R = (int)(0.5 + i*Cos_d[k] + j*Sin_d[k]); + if(R > 0 && R < Rmax) hough[R + Y] += imdata; + } + pthread_mutex_unlock(mutex); + } + } + } + return NULL; +} +/* + * Build of a Hough transform to find lines + * Input: + * ima - the image data + * min, max - range of the data in it + * imW, imH - image width and height + * Rmax - the maximum value of R + * angles - the array size of angles (the angle step is 180/angles degrees) + * Output: + * hough - initialized by an external function array for Hough transform + * !!! array must be initialized with zeros before calling this function !!! + * Output array is normalized to unity + */ +int fill_hough_lines(float *ima, float min, float max, int imW, int imH, int Rmax, int angles, float *hough){ + int i, Y0, Y1, dY; + float *hptr = hough, hmax; + Hough_kernel_args HD[THREAD_NUMBER]; + pthread_t threads[THREAD_NUMBER]; + pthread_mutex_t mutex; + pthread_mutex_init(&mutex, NULL); + init_sincos(angles); + dY = (imH + THREAD_NUMBER/2) / THREAD_NUMBER; + Y0 = 0; Y1 = dY; + for(i = 0; i < THREAD_NUMBER; i++, Y0+=dY, Y1+=dY){ + if(Y0 >= imW) break; + if(Y1 > imW) Y1 = imW; + HD[i].min = min; HD[i].max = max; + HD[i].image = ima; HD[i].hough = hough; + HD[i].Y0 = Y0; HD[i].Y1 = Y1; HD[i].w = imW; + HD[i].Rmax = Rmax; HD[i].angles = angles; + HD[i].mutex = &mutex; + pthread_create(&threads[i], NULL, fill_lin_hough_array, &HD[i]); + } + for(i = 0; i < THREAD_NUMBER; i++) + pthread_join(threads[i], NULL); + pthread_mutex_destroy(&mutex); + hmax = *hough++; + Y1 = Rmax * angles; + for(i = 1; i < Y1; i++, hough++) + if(*hough > hmax) hmax = *hough; + hough = hptr; + for(i = 0; i < Y1; i++, hough++) + *hough /= hmax; + return 1; +} + + + + +// from http://graphics.stanford.edu/%7Eseander/bithacks.html#RoundUpPowerOf2 +int nextpow2(int i, int j){ + inline int p2oi(int i){ + unsigned int v = (unsigned int)i - 1; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return (int) v; + } + int p1 = p2oi(i), p2 = p2oi(j); + return MAX(p1, p2); +} + +// FFT +fftw_plan fftimg, fftmask, ifft; +// FFT buffrers for image & filter +fftw_complex *Fmask = NULL, // FFT of a filter + *Fimg = NULL; // picture FFT +static double *mask = NULL, // filter mask + *img = NULL, // picture + *resm = NULL; + +void fftshift(int size, double *m){ + int h = size/2, ss, ss1, i, j, k, l, p; + double tmp; + ss = (2*h+1)*h; + ss1 = (2*h-1)*h; + for(j = 0; j < h; j++){ + k = j * size; + l = k + h; + for(i = 0; i < h; i++, k++, l++){ + p = k + ss; + tmp = m[k]; m[k] = m[p]; m[p] = tmp; + p = l + ss1; + tmp = m[l]; m[l] = m[p]; m[p] = tmp; + } + } +} + +// Lapgauss mask building +void build_LG_filter(int size, Filter *f){ + int y, y0=0,y1=size, x0=0, x1=size; + double sx2 = f->sx * f->sx, sy2 = f->sy * f->sy; + double hw, hh; + #ifdef EBUG + double t0=dtime(); + #endif + if(f->w < size && f->w > 0){ + x0 = (size - f->w + 1) / 2; + x1 = x0 + f->w; + } + if(f->h < size && f->h > 0){ + y0 = (size - f->h + 1) / 2; + y1 = y0 + f->h; + } + hh = -(double)size / 2.; + hw = -(double)size / 2.+(double)x0; + DBG("y0=%d, y1=%d, hw=%g, hh=%g",y0,y1,hw,hh); + double ss = 3. / hh / hh / sqrt(-hh); +// double ss = 1./sqrt(2*M_PI*f->sx*f->sy); + #pragma omp parallel for + for(y = y0; y < y1; y++){ + double X, Y, y2, x2, R; + int str, x; + X = hw; + str = y * size; + Y = ((double)y) + hh; + y2 = Y*Y/sy2; + for(x = x0; x < x1; x++, X+=1.){ + x2 = X*X/sx2; + R = x2 + y2; + mask[str + x] =ss * ((x2-1.)/sx2+(y2-1.)/sy2)*exp(-R/2.); + } + } + DBG("time=%f\n", dtime()-t0); +} + +// Gaussian mask building/ +void build_G_filter(int size, Filter *f){ + int y, y0=0,y1=size, x0=0, x1=size; + double sx2 = f->sx * f->sx, sy2 = f->sy * f->sy; + double hw, hh; + #ifdef EBUG + double t0=dtime(); + #endif + if(f->w < size && f->w > 0){ + x0 = (size - f->w + 1) / 2; + x1 = x0 + f->w; + } + if(f->h < size && f->h > 0){ + y0 = (size - f->h + 1) / 2; + y1 = y0 + f->h; + } + hh = -(double)size / 2.; + hw = -(double)size / 2.+(double)x0; + DBG("y0=%d, y1=%d, hw=%g, hh=%g",y0,y1,hw,hh); + double ss = 1. / (M_PI*2.) / sx2 / sy2 / hh / hh; + #pragma omp parallel for + for(y = y0; y < y1; y++){ + double X, Y, y2, x2, R; + int str, x; + X = hw; + str = y * size; + Y = ((double)y) + hh; + y2 = Y*Y/sy2; + for(x = x0; x < x1; x++, X+=1.){ + x2 = X*X/sx2; + R = x2 + y2; + mask[str + x] =ss * exp(-R/2.); + } + } + DBG("time=%f\n", dtime()-t0); +} + +// Elementary filter mask building +void build_S_filter(int size, Filter *f){ + int y, a0, a1; + double hh, Y, pt = 0.; + a0 = (size - 2) / 2; + a1 = a0 + 3; + hh = -(double)(size / 2); + double ss = 1. / (M_PI*2.) / hh / hh / sqrt(-hh); + Y = -1.; + for(y = a0; y < a1; y++, Y+=1.){ + double X = -1.; + int str, x; + str = y * size; + for(x = a0; x < a1; x++, X+=1.){ + switch(f->FilterType){ + case SOBELH: + pt = -X*(2.-fabs(Y)); + break; + case SOBELV: + pt = -Y*(2.-fabs(X)); + break; + case PREWITTH: + pt = X; + break; + case PREWITTV: + pt = Y; + break; + /* case : + pt = + break; + case : + pt = + break; + case : + pt = + break; + case : + pt = + break; + case : + pt = + break;*/ + } + mask[str + x] = ss*pt; + } + } + //mask[size*size/2]=0.; +} + +/* + * Filtering by convolution with a filter + * Input: + * ima - a pointer to picture data, which should be filtered + * f - filter parameters + * Output: + * result - allocated by this function memory area where + * the filtered image is placed + * Returns TRUE, if the filter succeeded + */ +int DiffFilter(float *ima, + float **result, + Filter *f, + int sizex, + int sizey + ){ + int ssize, i, j, k, l; + static int fftw_ini = 0; + int size2; + #ifdef EBUG + double t0 = dtime(); + #endif + size2 = nextpow2(sizex, sizey); + ssize = size2 * size2; // FFT image size + if(!fftw_ini) + if(!(fftw_ini = fftw_init_threads())){ + //g_err(_("FFTW error"); + return FALSE; + } + void free_all(){ + _FREE(mask); _FREE(Fmask); _FREE(img); + _FREE(Fimg); _FREE(resm); + } + free_all(); + DBG("img (%d x %d) -> (%d x %d), time=%f\n", sizex,sizey, size2,size2, dtime()-t0); + // allocate memory for objects + img = (double*)calloc(ssize, sizeof(double)); + Fimg = (fftw_complex*)calloc(ssize, sizeof(fftw_complex)); + if(!img || !Fimg){free_all(); return FALSE;} + // define direct FFT + fftw_plan_with_nthreads(THREAD_NUMBER); + fftimg = fftw_plan_dft_r2c_2d(size2, size2, img, Fimg, FFTW_ESTIMATE); + // copy ima -> img + for(j = 0; j < sizey; j++){ + k = j * size2; + l = j * sizex; + for(i = 0; i < sizex; i++, k++, l++) + img[k] = ima[l]; + } + fftw_execute(fftimg); + _FREE(img); + // build filter + DBG("build filter, time=%f\n", dtime()-t0); + mask = (double*)calloc(ssize, sizeof(double)); + Fmask = (fftw_complex*)calloc(ssize, sizeof(fftw_complex)); + if(!mask || !Fmask){free_all(); return FALSE;} + switch(f->FilterType){ + case LAPGAUSS: + build_LG_filter(size2, f); + break; + case GAUSS: + build_G_filter(size2, f); + break; + case SOBELH: + case SOBELV: + case PREWITTH: + case PREWITTV: + build_S_filter(size2, f); + break; + } + // define filter FFT + fftw_plan_with_nthreads(THREAD_NUMBER); + fftmask = fftw_plan_dft_r2c_2d(size2, size2, mask, Fmask, FFTW_ESTIMATE); + fftw_execute(fftmask); + _FREE(mask); + // filtered picture: + DBG("filter image, time=%f\n", dtime()-t0); + resm = (double*)calloc(ssize, sizeof(double)); + if(!resm){free_all(); return FALSE;} + // define inverse FFT + fftw_plan_with_nthreads(THREAD_NUMBER); + ifft = fftw_plan_dft_c2r_2d(size2, size2, Fimg, resm, FFTW_ESTIMATE); + // DON'T PARALLEL THIS, it will be slower + for(i=0; i result + for(j = 0; j < sizey; j++){ + k = j * size2; + l = j * sizex; + for(i = 0; i < sizex; i++, k++, l++) + tmp[l] = resm[k]; + } + _FREE(resm); + fftw_destroy_plan(fftimg); + fftw_destroy_plan(ifft); + fftw_destroy_plan(fftmask); + fftw_cleanup_threads(); + DBG("time=%f\n", dtime()-t0); + return TRUE; +} + +/* + * Simple gradient filter based on two Sobel filters + * output = sqrt(SobelH(input)^2+SobelV(input)^2) + */ +int GradFilterSimple(float *ima, float **result, Filter *f, int w, int h){ + float *dst1, *dst2; + int res = FALSE, y; + #ifdef EBUG + double t0 = dtime(); + #endif + f->FilterType = SOBELH; + res = DiffFilter(ima, &dst1, f, w, h); + if(!res) return FALSE; + f->FilterType = SOBELV; + res = DiffFilter(ima, &dst2, f, w, h); + if(!res){free(dst1); return FALSE;} + *result = dst1; + #pragma omp parallel for + for(y = 0; y < h; y++){ + int x; + float *in, *out; + in = dst2 + y*w; + out = dst1 + y*w; + for(x = 0; x < w; x++, in++, out++) + *out = sqrtf((*in)*(*in) + (*out)*(*out)); + } + free(dst2); + DBG("time=%f\n", dtime()-t0); + return TRUE; +} + +/* + * Quick median functions stolen from + * + * Fast median search: an ANSI C implementation + * Nicolas Devillard - ndevilla AT free DOT fr + * July 1998 + */ +#define PIX_SORT(a,b) { if ((*a)>(*b)) ELEM_SWAP((a),(b)); } +#define ELEM_SWAP(a,b) { register float *t=(a);(a)=(b);(b)=t; } +float opt_med3(float **p, int n __attribute__((unused))){ + PIX_SORT(p[0],p[1]) ; PIX_SORT(p[1],p[2]) ; PIX_SORT(p[0],p[1]) ; + return(*p[1]) ; +}float opt_med5(float **p, int n __attribute__((unused))){ + PIX_SORT(p[0],p[1]) ; PIX_SORT(p[3],p[4]) ; PIX_SORT(p[0],p[3]) ; + PIX_SORT(p[1],p[4]) ; PIX_SORT(p[1],p[2]) ; PIX_SORT(p[2],p[3]) ; + PIX_SORT(p[1],p[2]) ; return(*p[2]) ; +}float opt_med7(float **p, int n __attribute__((unused))){ + PIX_SORT(p[0], p[5]) ; PIX_SORT(p[0], p[3]) ; PIX_SORT(p[1], p[6]) ; + PIX_SORT(p[2], p[4]) ; PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[5]) ; + PIX_SORT(p[2], p[6]) ; PIX_SORT(p[2], p[3]) ; PIX_SORT(p[3], p[6]) ; + PIX_SORT(p[4], p[5]) ; PIX_SORT(p[1], p[4]) ; PIX_SORT(p[1], p[3]) ; + PIX_SORT(p[3], p[4]) ; return (*p[3]) ; +}float opt_med9(float **p, int n __attribute__((unused))){ + PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ; + PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[4]) ; PIX_SORT(p[6], p[7]) ; + PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ; + PIX_SORT(p[0], p[3]) ; PIX_SORT(p[5], p[8]) ; PIX_SORT(p[4], p[7]) ; + PIX_SORT(p[3], p[6]) ; PIX_SORT(p[1], p[4]) ; PIX_SORT(p[2], p[5]) ; + PIX_SORT(p[4], p[7]) ; PIX_SORT(p[4], p[2]) ; PIX_SORT(p[6], p[4]) ; + PIX_SORT(p[4], p[2]) ; return(*p[4]) ; +} float opt_med25(float **p, int n __attribute__((unused))){ + PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[4]) ; PIX_SORT(p[2], p[4]) ; + PIX_SORT(p[2], p[3]) ; PIX_SORT(p[6], p[7]) ; PIX_SORT(p[5], p[7]) ; + PIX_SORT(p[5], p[6]) ; PIX_SORT(p[9], p[10]) ; PIX_SORT(p[8], p[10]) ; + PIX_SORT(p[8], p[9]) ; PIX_SORT(p[12], p[13]); PIX_SORT(p[11], p[13]) ; + PIX_SORT(p[11], p[12]); PIX_SORT(p[15], p[16]); PIX_SORT(p[14], p[16]) ; + PIX_SORT(p[14], p[15]); PIX_SORT(p[18], p[19]); PIX_SORT(p[17], p[19]) ; + PIX_SORT(p[17], p[18]); PIX_SORT(p[21], p[22]); PIX_SORT(p[20], p[22]) ; + PIX_SORT(p[20], p[21]); PIX_SORT(p[23], p[24]); PIX_SORT(p[2], p[5]) ; + PIX_SORT(p[3], p[6]) ; PIX_SORT(p[0], p[6]) ; PIX_SORT(p[0], p[3]) ; + PIX_SORT(p[4], p[7]) ; PIX_SORT(p[1], p[7]) ; PIX_SORT(p[1], p[4]) ; + PIX_SORT(p[11], p[14]); PIX_SORT(p[8], p[14]) ; PIX_SORT(p[8], p[11]) ; + PIX_SORT(p[12], p[15]); PIX_SORT(p[9], p[15]) ; PIX_SORT(p[9], p[12]) ; + PIX_SORT(p[13], p[16]); PIX_SORT(p[10], p[16]); PIX_SORT(p[10], p[13]) ; + PIX_SORT(p[20], p[23]); PIX_SORT(p[17], p[23]); PIX_SORT(p[17], p[20]) ; + PIX_SORT(p[21], p[24]); PIX_SORT(p[18], p[24]); PIX_SORT(p[18], p[21]) ; + PIX_SORT(p[19], p[22]); PIX_SORT(p[8], p[17]) ; PIX_SORT(p[9], p[18]) ; + PIX_SORT(p[0], p[18]) ; PIX_SORT(p[0], p[9]) ; PIX_SORT(p[10], p[19]) ; + PIX_SORT(p[1], p[19]) ; PIX_SORT(p[1], p[10]) ; PIX_SORT(p[11], p[20]) ; + PIX_SORT(p[2], p[20]) ; PIX_SORT(p[2], p[11]) ; PIX_SORT(p[12], p[21]) ; + PIX_SORT(p[3], p[21]) ; PIX_SORT(p[3], p[12]) ; PIX_SORT(p[13], p[22]) ; + PIX_SORT(p[4], p[22]) ; PIX_SORT(p[4], p[13]) ; PIX_SORT(p[14], p[23]) ; + PIX_SORT(p[5], p[23]) ; PIX_SORT(p[5], p[14]) ; PIX_SORT(p[15], p[24]) ; + PIX_SORT(p[6], p[24]) ; PIX_SORT(p[6], p[15]) ; PIX_SORT(p[7], p[16]) ; + PIX_SORT(p[7], p[19]) ; PIX_SORT(p[13], p[21]); PIX_SORT(p[15], p[23]) ; + PIX_SORT(p[7], p[13]) ; PIX_SORT(p[7], p[15]) ; PIX_SORT(p[1], p[9]) ; + PIX_SORT(p[3], p[11]) ; PIX_SORT(p[5], p[17]) ; PIX_SORT(p[11], p[17]) ; + PIX_SORT(p[9], p[17]) ; PIX_SORT(p[4], p[10]) ; PIX_SORT(p[6], p[12]) ; + PIX_SORT(p[7], p[14]) ; PIX_SORT(p[4], p[6]) ; PIX_SORT(p[4], p[7]) ; + PIX_SORT(p[12], p[14]); PIX_SORT(p[10], p[14]); PIX_SORT(p[6], p[7]) ; + PIX_SORT(p[10], p[12]); PIX_SORT(p[6], p[10]) ; PIX_SORT(p[6], p[17]) ; + PIX_SORT(p[12], p[17]); PIX_SORT(p[7], p[17]) ; PIX_SORT(p[7], p[10]) ; + PIX_SORT(p[12], p[18]); PIX_SORT(p[7], p[12]) ; PIX_SORT(p[10], p[18]) ; + PIX_SORT(p[12], p[20]); PIX_SORT(p[10], p[20]); PIX_SORT(p[10], p[12]) ; + return (*p[12]); +} +float quick_select(float **arr, int n){ + int low, high; + int median; + int middle, ll, hh; + float ret; + low = 0 ; high = n-1 ; median = (low + high) / 2; + for(;;){ + if(high <= low) /* One element only */ + break; + if(high == low + 1){ /* Two elements only */ + PIX_SORT(arr[low], arr[high]) ; + break; + } + /* Find median of low, middle and high items; swap into position low */ + middle = (low + high) / 2; + PIX_SORT(arr[middle], arr[high]) ; + PIX_SORT(arr[low], arr[high]) ; + PIX_SORT(arr[middle], arr[low]) ; + /* Swap low item (now in position middle) into position (low+1) */ + ELEM_SWAP(arr[middle], arr[low+1]) ; + /* Nibble from each end towards middle, swapping items when stuck */ + ll = low + 1; + hh = high; + for(;;){ + do ll++; while (*arr[low] > *arr[ll]); + do hh--; while (*arr[hh] > *arr[low]); + if(hh < ll) break; + ELEM_SWAP(arr[ll], arr[hh]) ; + } + /* Swap middle item (in position low) back into correct position */ + ELEM_SWAP(arr[low], arr[hh]) ; + /* Re-set active partition */ + if (hh <= median) low = ll; + if (hh >= median) high = hh - 1; + } + ret = *arr[median]; + return ret; +} +#undef PIX_SORT +#undef ELEM_SWAP + +/* +#define _0(x) (x & 0x7FF) // lower 11 bits +#define _1(x) (x >> 11 & 0x7FF) // middle 11 bits +#define _2(x) (x >> 22 ) // upper 10 bits +float hist_select(float **arr, int n){ + inline uint32_t FloatFlip(uint32_t f){ + uint32_t mask = -((int32_t)(f >> 31)) | 0x80000000; + return f ^ mask; + } + inline uint32_t IFloatFlip(uint32_t f){ + uint32_t mask = ((f >> 31) - 1) | 0x80000000; + return f ^ mask; + } + uint32_t b0 +} +#undef _0 +#undef _1 +#undef _2 +*/ + +/* + * Median filtering + * Input: + * ima - picture (free() should be in a caller) + * f - filter + * sizex, sizey - picture size + * Output: + * result - filtered picture, memory allocates in this routine. + * Image borders that didn't pass through the filter (+- 1/2 of filter size) + * filled zeros + */ +int MedFilter(float *ima, + float **result, + Filter *f, + int sizex, + int sizey + ){ + int xlow = f->w / 2, xhigh = f->w - xlow;// filter area borders [x0-xlow, x0+xhigh) + int ylow = f->h / 2, yhigh = f->h - ylow; // [y0-ylow, y0+yhigh) + int x, xm=sizex-xhigh, ym=sizey-yhigh; // xm,ym - upper boundaries if filtered picture + int ssize=sizex*sizey, fsz=f->w*f->h, H = f->h - 1; + #ifdef EBUG + double t0 = dtime(); + #endif + float (*medfn)(float **p, int n); + *result = calloc(ssize, sizeof(float)); + if(!result) return FALSE; + switch(f->w*f->h){ + case 3: + medfn = opt_med3; + break; + case 5: + medfn = opt_med5; + break; + case 7: + medfn = opt_med7; + break; + case 9: + medfn = opt_med9; + break; + case 25: + medfn = opt_med25; + break; + default: + medfn = quick_select; + break; + } + /* + * Selection of picture elements on the square wxh into an array arr + * x0, y0 - coordinates of the center of the square + * cntr - current replacement string, or -1 if it is necessary to fill the entire arr + * If cntr!=-1 next line replaces a string cntr + */ + float selarr0(int x0, int y0, float *arr, float **sel){ + int x,y,tx,ty; + float *tmp, *ptr; + tx=x0+xhigh; ty=y0+yhigh; + tmp = arr; + for(y=y0-ylow; yw]; // pointer to a changed line + ptr = &ima[(ty-1)*sizex+x0-xlow]; + for(x=x0-xlow; x H) *cntr = 0; + return medfn(sel, fsz); + } + #pragma omp parallel + { + float *arr = calloc(fsz, sizeof(float*)); // array to sample storage + float **sel = calloc(fsz, sizeof(float*));// array to sorted sample storage + for(x = 0; x < fsz; x++) sel[x] = arr + x; + if(arr){ + #pragma omp for + for(x = xlow; x < xm; x++){ + int y, cntr = 0; + (*result)[ylow*sizex + x] = selarr0(x,ylow, arr, sel); + for(y = ylow+1; y < ym; y++){ + (*result)[y*sizex + x] = selarr1(x,y, arr, sel, &cntr); + } + } + free(arr); + free(sel); + } + } + DBG("time=%f\n", dtime()-t0); + return TRUE; +} +/* + * Fill isolines' scale (an array) + * Input: + * f - filter for given method + * min - minimum value of intensity + * wd - max-min (dinamic range) + * Output: + * scale - a pointer to array (allocated in this function) + */ +int fillIsoScale(Filter *f, float **scale, float min, float wd){ + int M = f->w, y; + float (*scalefn)(float in); + float step, Nsteps = (float)f->w; // amount of intervals + float Suniform(float in){ + return step*in + min; + } + float Slog(float in){ + return expf(in*step) + min - 1.; + } + float Sexp(float in){ + return wd*logf(in*step) + min; + } + float Ssqrt(float in){ + return in*in*step*step + min; + } + float Spow(float in){ + return sqrtf(in*step) + min; + } + if(!scale) return FALSE; + + *scale = calloc(M, sizeof(float)); + if(!*scale) return FALSE; + switch(f->h){ + case LOG: + scalefn = Slog; step = logf(wd+1.)/Nsteps; + break; + case EXP: + scalefn = Sexp; step = expf(1.)/Nsteps; + break; + case SQRT: + scalefn = Ssqrt; step = sqrtf(wd)/Nsteps; + break; + case POW: + scalefn = Spow; step = wd*wd/Nsteps; + break; + default: + scalefn = Suniform; step = wd/Nsteps; + } + for(y = 0; y < M; y++){ + (*scale)[y] = scalefn(y+1); + DBG("level %d: I=%g", y, (*scale)[y]); + } + return TRUE; +} + +/* + * Threshold filtering ("posterization") + * Input: + * ima - picture (free() must be executed in the caller) + * f - filter: + * f-> w - number of levels of posterization, [2.255] + * f-> h - type of posterization (0 - uniform) + * sizex, sizey - image size + * min, max - minimum and maximum intensity of the image + * Output: + * result - filtered image, the memory is allocated in this procedure + * scale - the scale of intensities, the memory is allocated here (if the scale!=NULL) + * + * TODO: save the result in the char, not float; learn display function + */ +int StepFilter(float *ima, float **result, Filter *f, int sizex, int sizey, + float min, float max, float **scale){ + if(f->w < 2 || f->w > 255) return FALSE; + int y; + float Nsteps = (float)f->w; // amount of intervals + float step; + float wd = max - min; + if(fabs(wd) < FLT_EPSILON) return FALSE; + float (*stepfn)(float in); + float Funiform(float in){ + return floor((in-min)/step); + } + float Flog(float in){ + return floor(logf(in-min+1.)/step); + } + float Fexp(float in){ + return floor(expf((in-min)/wd)/step); + } + float Fsqrt(float in){ + return floor(sqrtf(in-min)/step); + } + float Fpow(float in){ + return floor((in-min)*(in-min)/step); + } + #ifdef EBUG + double t0 = dtime(); + #endif + switch(f->h){ + case LOG: + stepfn = Flog; + step = logf(max-min+1.)/Nsteps; + break; + case EXP: + stepfn = Fexp; + step = expf(1.)/Nsteps; + break; + case SQRT: + stepfn = Fsqrt; + step = sqrtf(wd)/Nsteps; + break; + case POW: + stepfn = Fpow; + step = wd*wd/Nsteps; + break; + default: + stepfn = Funiform; + step = wd/Nsteps; + } + *result = calloc(sizex*sizey, sizeof(float)); + if(!result) return FALSE; + #pragma omp parallel for + for(y = 0; y < sizey; y++){ + int x; float *out, *in; + in = ima + y*sizex; out = *result + y*sizex; + for(x = 0; x < sizex; x++, in++, out++){ + *out = stepfn(*in); + } + } + if(scale && !fillIsoScale(f, scale, min, wd)) g_err(_("No memory left")); + DBG("SF: %d sublevels, step=%g, time=%f\n", f->w, step, dtime()-t0); + return TRUE; +} + + + + diff --git a/src/contours.c b/src/contours.c new file mode 100644 index 0000000..ccb88e1 --- /dev/null +++ b/src/contours.c @@ -0,0 +1,506 @@ +// contours.c - find isophotos +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. + +#include "contours.h" +#include "opengl.h" + +// contours' minimum size limits +const int MIN_CONTOUR_SIZE = 4; +const int MIN_CLOSED_CONTOUR_SIZE = 7; +// maximum amount of contour levels +const int MAX_CONTOUR_LEVELS = 255; + +// frees contour's data +void free_contour(Contour **c){ + cPoint *pp, *p; + p = (*c)->first; + while(p){ // go through all points of current contour + pp = p; p = p->next; + _FREE(pp); + } + _FREE(*c); +} + +// delete contours' list for the picture +void free_contours(IMAGE *image){ + FNAME(); + int i; + cList *l; + Contour *cp, *c; + if(image->Ncontours < 1 || !image->contours) return; + for(i = 0; i < image->Ncontours; i++){ // go through all levels + l = image->contours[i]; + c = l->first; + while(c){ // go throug all contours of current level + cp = c; c = c->next; + free_contour(&cp); + } + } +} + +// create new element of contours' list +cList *new_clist(int lvl){ + DBG("level: %d", lvl); + cList *c = calloc(1, sizeof(cList)); + c->L = lvl; + return c; +} +// create new contour +Contour *new_contour(){ + Contour *c = calloc(1, sizeof(Contour)); + return c; +} +// create new point +cPoint *new_point(){ + cPoint *p = calloc(1, sizeof(cPoint)); + return p; +} +// add contour c to list +void cList_add(cList *cl, Contour *c){ + cl->N++; + //DBG("contour #%d added to list with levl %d", cl->N, cl->L); + if(!cl->first) + cl->first = c; + else + cl->last->next = c; + cl->last = c; +} +// add point p to contour c +// headflag == 0 - add to tail; ==1 - add to head +void c_add(Contour *c, cPoint *p, char headflag){ + c->N++; + if(!c->first || !c->last){ + c->first = p; + c->last = p; + }else{ + if(headflag){ // add point to head + c->first->prev = p; + p->next = c->first; + c->first = p; + } + else{ + c->last->next = p; // add point to tail + p->prev = c->last; + c->last = p; + } + } +} + +/* + * copy contours from image in to image out + * with shift (dX, dY): Xnew=Xold+dX, Ynew=Yold+dY + */ +int copy_contours(IMAGE *in, IMAGE *out, float dX, float dY){ + int i, N = in->Ncontours; + cList *l; + cPoint *pCur, *p; + Contour *cCur, *c; + if(N < 1 || !N) return TRUE; + if(!(out->contours = calloc(in->Ncontours, sizeof(Contour)))) return FALSE; + for(i = 0; i < N; i++){ // go through all levels + l = in->contours[i]; + if(!(out->contours[i] = new_clist(i))) goto badExit; + c = l->first; + while(c){ // go throug all contours of current level + if(!(cCur = new_contour())) goto badExit; + pCur = c->first; + while(pCur){ + if(!(p = new_point())) goto badExit; + p->x = pCur->x+dX; p->y = pCur->y+dY; + c_add(cCur, p, 0); + pCur = pCur->next; + } + cList_add(out->contours[i], cCur); + c = c->next; + } + } + out->Ncontours = in->Ncontours; + return TRUE; +badExit: + free_contours(out); return FALSE; +} + +// direction bits +enum{ + D_RIGHT = 1 + ,D_LEFT = 2 + ,D_DOWN = 4 + ,D_UP = 8 +}; + +// directions of next moving for mask values +// spetial values 6 & 9 also checks separately +int directions[16] = { + 0, // no isoline + D_RIGHT | D_DOWN, + D_LEFT | D_DOWN, + D_RIGHT | D_LEFT, + D_RIGHT | D_UP, + D_UP | D_DOWN, + 0, // special point 6 + D_LEFT | D_UP, + D_LEFT | D_UP, + 0, // special point 9 + D_UP | D_DOWN, + D_RIGHT | D_UP, + D_RIGHT | D_LEFT, + D_LEFT | D_DOWN, + D_RIGHT | D_DOWN, + 0 // no isoline +}; + +/* + * coordinate shifts {dx, dy} according to direction value bits + * their values are choosen so, that even if there would be some non-zero + * bits in direction mask, new direction of isoline search will be choosen + * as possible nearest to "right" or " down" direction + */ +int dxdy[9][2] = { + {0 , 0}, // 0 + {1 , 0}, // D_RIGHT + {-1, 0}, // D_LEFT + {0 , 0}, + {0 , 1}, // D_DOWN + {0 , 0}, + {0 , 0}, + {0 , 0}, + {0 ,-1} // D_UP +}; +// new directions in simplex case, 2nd dimension - head flag (if==1, rotate CCW) +int newdirs[16][2] = { + {0, 0}, // 0 + {D_RIGHT,D_RIGHT},// D_RIGHT + {D_LEFT, D_LEFT}, // D_LEFT + {D_RIGHT, D_LEFT},// D_RIGHT | D_LEFT + {D_DOWN, D_DOWN}, // D_DOWN + {D_RIGHT, D_DOWN},// D_DOWN | D_RIGHT + {D_DOWN, D_LEFT}, // D_DOWN | D_LEFT + {D_RIGHT, D_LEFT},// D_DOWN | D_RIGHT | D_LEFT + {D_UP, D_UP}, // D_UP + {D_RIGHT, D_UP}, // D_UP | D_RIGHT + {D_LEFT, D_UP}, // D_UP | D_LEFT + {D_RIGHT, D_UP}, // D_UP | D_RIGHT | D_LEFT + {D_DOWN, D_UP}, // D_UP | D_DOWN + {D_RIGHT, D_UP}, // D_UP | D_DOWN | D_RIGHT + {D_DOWN, D_UP}, // D_UP | D_DOWN | D_LEFT + {D_RIGHT, D_UP} // D_UP | D_DOWN | D_RIGHT | D_LEFT +}; + +/* + * Functions that calculate the position of the contour (linear interpolation) + * P - the point at which are recorded the coordinates (relative to the center of the upper left quadrant) + * to the resulting coordinates p shold be added the coordinates of the square to obtain the coordinates in image SC + * A, b, c, d - intensity in the UL, UR DL and DR corners of the square + * Lvl - contours level + */ +// right & up borders +void getRU(cPoint *p, float a, float b, float c __attribute__((unused)), float d, float I){ + float x = 0., y = 0.; + if(fabs(b-a) > FLT_EPSILON) x = (I-a)/(b-a); + if(fabs(d-b) > FLT_EPSILON) y = (I-b)/(d-b); + p->x = x/2.+0.5; p->y = y/2.; +} +// left & up +void getLU(cPoint *p, float a, float b, float c, float d __attribute__((unused)), float I){ + float x = 0., y = 0.; + if(fabs(b-a) > FLT_EPSILON) x = (I-a)/(b-a); + if(fabs(c-a) > FLT_EPSILON) y = (I-a)/(c-a); + p->x = x/2.; p->y = y/2.; +} +// left & down +void getLD(cPoint *p, float a, float b __attribute__((unused)), float c, float d, float I){ + float x = 0., y = 0.; + if(fabs(d-c) > FLT_EPSILON) x = (I-c)/(d-c); + if(fabs(c-a) > FLT_EPSILON) y = (I-a)/(c-a); + p->x = x/2.; p->y = y/2.+0.5; +} +// right & down +void getRD(cPoint *p, float a __attribute__((unused)), float b, float c, float d, float I){ + float x = 0., y = 0.; + if(fabs(d-c) > FLT_EPSILON) x = (I-c)/(d-c); + if(fabs(d-b) > FLT_EPSILON) y = (I-b)/(d-b); + p->x = x/2.+0.5; p->y = y/2.+0.5; +} +// up & down +void getUD(cPoint *p, float a, float b, float c, float d, float I){ + float x1 = 0., x2 = 1.; + if(fabs(d-c) > FLT_EPSILON) x1 = (I-c)/(d-c); + if(fabs(b-a) > FLT_EPSILON) x2 = (I-a)/(b-a); + p->x = (x1+x2)/2.; p->y = 0.5; +} +// right & left +void getRL(cPoint *p, float a, float b, float c, float d, float I){ + float y1 = 0., y2 = 1.; + if(fabs(d-b) > FLT_EPSILON) y1 = (I-b)/(d-b); + if(fabs(c-a) > FLT_EPSILON) y2 = (I-a)/(c-a); + p->x = 0.5; p->y = (y1+y2)/2.; +} +// bung for a case +void getNull(cPoint *p __attribute__((unused)), float a __attribute__((unused)), + float b __attribute__((unused)), float c __attribute__((unused)), + float d __attribute__((unused)), float I __attribute__((unused))){ + p->x = 0.; p->y = 0.; +} + +// array of a funtions to coordinates calculation +typedef void (*FnXY)(cPoint *p, float a, float b, float c, float d, float I); +FnXY getXY[16] = { + getNull, // 0 + getRD, + getLD, + getRL, + getRU, + getUD, + getNull,//6 must be getLU or get RD + getLU, + getLU, + getNull,//9 must be getLD or get RU + getUD, + getRU, + getRL,//12 + getLD, + getRD, + getNull +}; + +static float *isoscale = NULL; +static int w, w1, h, h1; +static cList **contours = NULL; + +/* + * filling of the contour line, beginning at the point x1, y1 + * flag==1 - add points not to the tail of the circuit but to the head + * cCur - contour to add the point + * imdata - the original picture + * lvl - isoline level number + * olddir - direction (D_...) to a previous point + * mask - pointer to current mask + */ +int proc_contour(int x1, int y1, int flag, + Contour *cCur, float *imdata, + int lvl, int olddir, + unsigned char *mask){ + int frst = flag; + float Lvl = isoscale[lvl]; // intensity at isoline level lvl + float *point; + int newdir; + do{ + int mid = y1*w1 + x1, iid = y1*w + x1; + unsigned char m = mask[mid]; + if(m == 0 || m > 14) break; + if(!frst){ + cPoint *p = new_point(); + if(!p) return FALSE; + c_add(cCur, p, flag); + // get coordinates p->x, p->y (by simplified linear approximation) + point = imdata + iid; // LU pixel for a square + // find coordinates + switch(m){ // check special points 6 & 9, their values depends on olddir + case 6: // m = 7/14, mask=14/7 + switch(olddir){ + case D_LEFT: // next - UP or LEFT + case D_UP: + m = 7; mask[mid] = 14; // clear only used point + break; + default: // D_DOWN/D_RIGHT, next - RIGHT or UP + m = 14; mask[mid] = 7; + } + break; + case 9: // m = 13/11, mask=11/13 + switch(olddir){ + case D_LEFT: // next - DOWN or LEFT + case D_DOWN: + m = 13; mask[mid] = 11; // clear only used point + break; + default: // D_UP/D_RIGHT, next - RIGHT or DOWN + m = 11; mask[mid] = 13; + } + break; + default: + mask[mid] = 0; // just clear used mask's point + } + getXY[m](p,point[0],point[1],point[w],point[w+1],Lvl); + if(p->x < -0.5 || p->x > 1.5) p->x = 0.5; + if(p->y < -0.5 || p->y > 1.5) p->y = 0.5; + // add .5, beacause of mask's shift + p->x += (float)x1 + .5; + p->y += (float)y1 + .5; + } + gboolean found = FALSE; // whether next isoline point was found + int x2, y2; + do{ // find next contour's point + unsigned char pp; + // check right new direction + newdir = newdirs[directions[m] & (~olddir)][flag]; + //newdir = directions[m] & (~olddir); + if(newdir > 8){DBG("WTF? m=%d, newdir=%d, olddir=%d",m,newdir, olddir); break;} // This can't be so, but WTF if? + x2 = x1 + dxdy[newdir][0]; + y2 = y1 + dxdy[newdir][1]; + if(x1 == x2 && y1 == y2) break; + // is next square out of an picture? + if(x2 < 0 || x2 >= w1 || y2 < 0 || y2 >= h1) break; + int ii = y2*w1+x2; + pp = mask[ii]; + // has the next square a contour points? + if(pp == 0 || pp > 14) break; + found = TRUE; + }while(0); + x1 = x2; y1 = y2; + if(!found){ + break; // end of isoline + } + switch(newdir){ // calculate old direction + case D_LEFT: olddir = D_RIGHT; break; + case D_RIGHT: olddir = D_LEFT; break; + case D_UP: olddir = D_DOWN; break; + case D_DOWN: olddir = D_UP; break; + default: olddir = 0; + } + frst = FALSE; + }while(1); // cycle through the contour + return TRUE; +} +/* + * Processing of the point with coordinates x, y + * imdata - the original picture + * lvl - number of contour's level + * mask - current mask + * return FALSE if failed + */ +int process_it_(int x, int y, int lvl, float *imdata, unsigned char *mask){ + Contour *cCur; + unsigned char pt0 = mask[y*w1+x]; + if(pt0 == 0 || pt0 > 14) return TRUE;; + do{ + if(!contours[lvl]){ // countour wasn't created - create it + contours[lvl] = new_clist(lvl); + if(!contours[lvl]){ + g_err(_("Can't allocate memory")); + return FALSE; + } + } + cCur = new_contour(); + if(!cCur){ + g_err(_("Can't allocate memory")); + return FALSE; + } + // start processing contour, adding points to its tail + // D_LEFT - beacause we check points from left to the right + if(!proc_contour(x, y, 0, cCur, imdata, lvl, D_LEFT, mask)) + return FALSE; + // continue processing contour, adding points to its head + // D_UP - beacause there's no more contour points upper + // if((y + 1) < w1) // mask(x,y) == 0, so we check down point + // if(!proc_contour(x, y+1, 1, cCur, imdata, lvl, D_UP, mask)) + // return FALSE; + if(cCur->N < MIN_CONTOUR_SIZE){ // contour is too short + free_contour(&cCur); + break; + } + if( (fabs(cCur->first->x - cCur->last->x) + + fabs(cCur->first->y - cCur->last->y)) < 2.){ // closed contour + cCur->closed = TRUE; + if(cCur->N < MIN_CLOSED_CONTOUR_SIZE){ // contour is too short + free_contour(&cCur); + break; + } + } + cList_add(contours[lvl], cCur); + }while(0); + return TRUE; +} + + +/* + * Find isophotos on picture window->image + * nLevl - amount of isophotos + * type - type of isophotos segmentation (the same as f->h for STEP filter) + */ +void find_contours(Window *window, + int nLevl, + int type){ + IMAGE *ima = window->image; + int y, level; + float *image = ima->data; + if(nLevl < 2 || nLevl > MAX_CONTOUR_LEVELS) return; + unsigned char *mask; // array for marching squares matrix + Filter f; + #ifdef EBUG + double t0 = dtime(); + #endif + w = ima->width; w1=w-1; h = ima->height; h1=h-1; + mask = calloc((w1)*(h1),1); // mask - one mask pixel is square of 2x2 pixels on an picture + if(!mask){g_err(_("Can't allocate memory"));return;} + // prepare isoscale + f.FilterType = STEP; f.w = nLevl; f.h = type; + if(!fillIsoScale(&f, &isoscale, ima->stat.min, ima->stat.max - ima->stat.min)){ + g_err(_("Can't allocate memory"));goto endOF;} + // Go through all levels + contours = calloc(nLevl, sizeof(Contour)); // all-picture contours array + if(!contours){g_err(_("Can't allocate memory"));goto endOF;} + for(level = 0; level < nLevl; ++level){ + /* + * 1. level segmentation & build mask + * Mask by square (if zero point in left upper corner) + * _______ + * | a | b | + * |---+---| + * | c | d | + * ------- + * bits order: 0000abcd + */ + int y; + float lvl = isoscale[level]; // current isoline's intensity level + #pragma omp parallel for + for(y = 0; y < h1; y++){ + int x; + unsigned char *out = &mask[y*w1]; + float *in = &image[y*w]; + for(x = 0; x < w1; x++, in++, out++) + *out = (unsigned char) + ((in[0]>lvl)<<3) | + ((in[1]>lvl)<<2) | + ((in[w]>lvl)<<1) | + (in[w+1]>lvl); + } + // go through all mask's points + for(y = 0; y < h1; y++){ + int x; + for(x = 0; x < w1; x++) + if(!process_it_(x, y, level, image, mask)) goto endOF; + } + } +endOF: + _FREE(mask); + if(contours){ + for(y = 0; y < nLevl; y++){ + g_print("Level: %d", y); + if(contours[y]) g_print(", %d contours\n", contours[y]->N); + else g_print("\n"); + } + // fill contour structure + ima->contours = contours; contours = NULL; + ima->Ncontours = nLevl; + if(isoscale){ima->cLevels = isoscale; isoscale = NULL;} + } + DBG("time: %g", dtime()-t0); + window->context->visualMode |= SHOW_ISOLINES; + gen_texture(ima, window, TRUE); + force_redraw(window->drawingArea); +} diff --git a/src/filelist.c b/src/filelist.c new file mode 100644 index 0000000..eb64438 --- /dev/null +++ b/src/filelist.c @@ -0,0 +1,168 @@ +// filelist.c - functions to work with list of FITS files in current directory +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. + +#include "filelist.h" +#include "fits.h" +/* + * receiving the file name of list, type can be one of the following: + * CURRENT - current file + * FIRST - first file + * LAST - last file + * PREVIOUS - previous file + * NEXT - next file + */ +gchar *get_filename(guchar type, Window *window){ + DBG("length of list = %d", window->files.list_length); + if(window->files.list_length < 1) return NULL; + if(!window->files.list_current){ + DBG("No current file. WTF?"); + return NULL; + } + //~ g_list_foreach(window->files.files_list, (GFunc)prnt, NULL); + switch(type){ + case FIRST: + if(window->files.list_current == window->files.files_list){ + DBG("1st or only file"); + return NULL; // try to open current opened file or there's no files + } + window->files.list_current = window->files.files_list; + break; + case LAST: + if(window->files.list_length < 2 || + window->files.list_current == window->files.list_end){ + DBG("last or only file"); + return NULL; + } + window->files.list_current = window->files.list_end; + break; + case PREVIOUS: + if(!window->files.list_current->prev){ + DBG("1st file"); + return NULL; + } + window->files.list_current = window->files.list_current->prev; + break; + case NEXT: + if(!window->files.list_current->next){ + DBG("last file"); + return NULL; + } + window->files.list_current = window->files.list_current->next; + break; + default: // current + break; + } + if(!window->files.list_current || !window->files.list_current->data){ + DBG("no current file? WTF?"); + return NULL; + } + DBG("change filename: %s\n\n", (gchar*)window->files.list_current->data); + return (gchar*)window->files.list_current->data; +} + +// get filename suffix +gboolean get_ext(gchar *filename){ + gchar *basename, *ext; + basename = strrchr(filename, '/'); + if(!basename) basename = filename; + else basename++; + ext = strrchr(basename, '.'); + if(!ext) return FALSE; + else ext++; + //DBG("basename = %s; ext = %s", basename, ext); + if(strcasecmp(ext, "fts") == 0 || strcasecmp(ext, "fit") == 0 || + strcasecmp(ext, "fits") == 0) return TRUE; + return FALSE; +} + +// File test for loadable +gboolean is_loadable(gchar *filename){ + if(!Global->add_all){ // if we don't try to open any file + if(!get_ext(filename)) // file suffix isn't fit/fits/fts + return FALSE; + } + return guess_fits(filename); +} + +gint add_file_to_list(gchar *filename, Window *window){ + if(is_loadable(filename)){ + DBG("File %s is FITS file\n", filename); + window->files.files_list = g_list_prepend(window->files.files_list, (gpointer)filename); + window->files.list_length++; + if(window->files.list_length == 1) + window->files.list_end = window->files.files_list; + return 1; + } + return 0; +} + +void free_filelist(Window *window){ + if(window->files.files_list){ + g_list_foreach(window->files.files_list, (GFunc)free, NULL); + g_list_free(window->files.files_list); + window->files.list_length = 0; + window->files.files_list = NULL; + } +} + +/* + * get a FITS files list in current directory + * filename - full filename (with path) + * returns a files number in list + */ +gint fill_filelist(gchar *filename, Window *window){ + gint nb_inserted = 0; + GDir *dir; + GError *err = NULL; + const gchar *dir_entry; + gchar *dirname = g_path_get_dirname(filename); + DBG("Open directory %s", dirname); + dir = g_dir_open(dirname, 0, &err); + if(dir == NULL){ + g_err(err->message); + g_error_free(err); + return 0; + } + free_filelist(window); // clear list if it was already created + while((dir_entry = g_dir_read_name(dir))){ + gchar *full_path = g_build_filename(dirname, dir_entry, NULL); + if(!g_file_test(full_path, G_FILE_TEST_IS_DIR)){ + nb_inserted += add_file_to_list(full_path, window); + } + } + g_dir_close(dir); + g_free(dirname); + window->files.files_list = g_list_sort(window->files.files_list, (GCompareFunc)strcmp); + DBG("\nSort %d items\n", nb_inserted); + window->files.list_end = g_list_last(window->files.files_list); + window->files.list_current = g_list_find_custom(window->files.files_list, filename,(GCompareFunc)strcmp); +//if(!window->files.list_current) window->files.list_current = g_list_first(window->files.files_list); + //~ place_first = g_list_find_custom(window->files.files_list, filename, strcmp); + //~ if(place_first && place_first->prev){ + //~ place_first->prev->next = place_first->next; + //~ if(place_first->next) + //~ place_first->next->prev = place_first->prev; + //~ place_first->prev = NULL; + //~ place_first->next = window->files.files_list; + //~ window->files.files_list->prev = place_first; + //~ window->files.files_list = place_first; + //~ } + return nb_inserted; +} + diff --git a/src/fits.c b/src/fits.c new file mode 100644 index 0000000..ab75c51 --- /dev/null +++ b/src/fits.c @@ -0,0 +1,396 @@ +// fits.c - main functions to work with FITS files +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "fits.h" +#include "gtk.h" +#include "fitsheaders.h" + +/* + * Macros for error processing when working with cfitsio functions + */ +#define TRYFITS(f, ...) \ +do{ gboolean status = FALSE; \ + f(__VA_ARGS__, &status); \ + if(status){ \ + fits_report_error(stderr, status); \ + return FALSE;} \ +}while(0) +#define FITSFUN(f, ...) \ +do{ gboolean status = FALSE; \ + int ret = f(__VA_ARGS__, &status); \ + if(ret || status) \ + fits_report_error(stderr, status); \ +}while(0) +#define WRITEKEY(...) \ +do{ gboolean status = FALSE; \ + fits_write_key(__VA_ARGS__, &status); \ + if(status) fits_report_error(stderr, status);\ +}while(0) + +/* + * Check whether a number is a complex + * If yes - returns true, no - false + * If re and/or im!=NULL, in them stored values + * of real and imaginary components + */ +gboolean cmplx_conv(gchar *value, double *re, double *im){ + double sign = 1.; + double r,i; + gchar *ptr1, *ptr2, *eptr; + ptr1 = g_strstr_len(value, -1, "("); + if(ptr1){ // form is (re, im) or (re im) + ptr1++; + ptr2 = g_strstr_len(ptr1, -1, ")"); + if(ptr2) *ptr2 = 0; + ptr2 = g_strstr_len(ptr1, -1, ","); + if(!ptr2) + ptr2 = g_strstr_len(ptr1, -1, " "); + } + else{ // form is re + iim or re + jim + ptr1 = value; + ptr2 = g_strstr_len(ptr1, -1, "i"); + if(!ptr2) + ptr2 = g_strstr_len(ptr1, -1, "j"); + } + if(ptr2){ + *ptr2 = 0; + ptr2++; + } + else + return FALSE; + if(g_strrstr_len(ptr1, 3, "-")) sign=-1.; + r = strtod(ptr1, &eptr); + if(eptr == ptr1) return FALSE; + i = sign*strtod(ptr2, &eptr); + if(eptr == ptr2) return FALSE; + if(re) *re = r; + if(im) *im = i; + return TRUE; +} +/* + * try to guess key type: + * TLONGLONG, TDOUBLE, TDBLCOMPLEX or TSTRING + */ +int guess_type(gchar *keyval, gchar* keytype){ + char *eptr, *val; + int dtype = TSTRING; + val = strdup(keyval); + if(strchr(val, '.')){ + errno = 0; + strtod(val, &eptr); + if(eptr != val && errno != ERANGE) + dtype = TDOUBLE; + } + if(dtype == TSTRING){ + errno = 0; + strtoll(val, &eptr, 0); + if(eptr != val && errno != ERANGE) dtype = TLONGLONG; + } + if(dtype == TSTRING && cmplx_conv(val, NULL, NULL)) + dtype = TDBLCOMPLEX; + if(keytype) switch(dtype){ + case TDOUBLE: + sprintf(keytype, "TDOUBLE"); + break; + case TLONGLONG: + sprintf(keytype, "TLONGLONG"); + break; + case TDBLCOMPLEX: + sprintf(keytype, "TDBLCOMPLEX"); + break; + default: + sprintf(keytype, "TSTRING"); + } + //~ DBG("dtype: %d, keytype: %s", dtype, (keytype) ? keytype : ""); + free(val); + return dtype; +} +/* + * Save image-structure to a FITS file + * (together whith keys) + */ +gboolean writefits(char *filename, IMAGE *image){ + FNAME(); + long naxes[2] = {image->width, image->height}; + int w = image->width, h = image->height; + ssize_t sz = w * h; + gchar *keytype, *keyname, *keyval, *keycomment; int keyfitstype; + GtkTreeIter iter; + fitsfile *fp; + gchar *card; + #ifdef EBUG + int n = 0; + #endif + TRYFITS(fits_create_file, &fp, filename); + TRYFITS(fits_create_img, fp, image->dtype, 2, naxes); + if(image->store){ + GtkTreePath *path = gtk_tree_path_new_first(); + GtkTreeModel *tree_model = GTK_TREE_MODEL(image->store); + if(gtk_tree_model_get_iter(tree_model, &iter, path)){ // there's keys + DBG("filename: %s", filename); + do{ + gtk_tree_model_get(tree_model, &iter, + L_TYPENM, &keytype, + L_KEY, &keyname, + L_VAL, &keyval, + L_COMM, &keycomment, + L_TYPE, &keyfitstype, + -1); + if(keyfitstype == TSTRING) // make sure, that string is in quotes + if(*keyval != '\''){ + gchar *str = g_strdup_printf("'%s'", keyval); + g_free(keyval); + keyval = str; + } + card = g_strdup_printf("%-8s= %s / %s", + keyname, keyval, keycomment); + g_free(keytype); g_free(keyname); g_free(keyval); + g_free(keycomment); + DBG("key#%d: %s", ++n, card); + FITSFUN(fits_write_record, fp, card); + g_free(card); + }while(gtk_tree_model_iter_next(tree_model, &iter)); + } + gtk_tree_path_free(path); + } + GLfloat min, wd; + min = image->stat.min; + wd = image->stat.max - min; + DBG("min = %f, wd = %f", min, wd); +/* int i,j; + GLfloat *newdata = malloc(sz*sizeof(GLfloat)); + GLfloat *ptro = newdata, *ptri = image->data; + for(i=0; idata); + TRYFITS(fits_close_file, fp); + return TRUE; +} + +gboolean try_open_file(gchar *filename, IMAGE *ima){ + if(!readfits(filename, ima)){ + g_err(_("Can't read fits file")); + ima->data = NULL; + return FALSE; + } + return TRUE; +} +/* +char *del_spaces(char *str){ + char *beg, *end; + beg = str; + end = str + strlen(str); + while(*beg == ' ' && beg < end) beg++; + if(beg == end) return beg; + end--; + if(beg == end) return beg; + while(*end == ' ' && end > beg) + *end-- = 0; + return beg; +} +gboolean strip_card(char *card, char *keyname, char *keyval, char *keycomment){ + char *ptr, *ptr1, *end; + char *c = strdup(card); + end = c + strlen(c); + ptr = strchr(c, '='); + if(!ptr) return FALSE; + *ptr = 0; ptr++; + ptr1 = del_spaces(c); + strncpy(keyname, ptr1, FLEN_KEYWORD-1); + ptr1 = strchr(ptr, '\''); + if(ptr1){ + ptr1++; + ptr = strchr(ptr1, '\''); + if(!ptr){ + ptr = strchr(ptr1, '/'); + if(!ptr){ + free(c); + return FALSE; + } + *ptr = 0; ptr++; + }else{ + *ptr = 0; ptr = strchr(ptr+1, '/'); + if(!ptr){ + free(c); + return FALSE; + } + *ptr = 0; ptr++; + } + }else{ + ptr1 = ptr; + ptr = strchr(ptr1, '/'); + if(!ptr){ + free(c); + return FALSE; + } + *ptr = 0; ptr++; + ptr1 = del_spaces(ptr1); + } + if(ptr >= end) return FALSE; + ptr1 = del_spaces(ptr1); + strncpy(keyval, ptr1, FLEN_VALUE); + ptr1 = del_spaces(ptr); + strncpy(keycomment, ptr1, FLEN_COMMENT); + free(c); + return TRUE; +}*/ + +/* + * Read FITS file filename to a structure image + */ +gboolean readfits(gchar *filename, IMAGE *image){ + FNAME(); + gboolean ret = TRUE; + fitsfile *fp; + GLfloat nullval = 0., imBits, bZero = 0., bScale = 1.; + free(image->data); + image->data = NULL; + int i, j, hdunum, keynum, morekeys, dtype, stat; + int naxis, w, h; + double val_f = -1e6; // VAL_F value + long naxes[4]; + ssize_t sz; + //char card[FLEN_CARD]; + char keyname[FLEN_KEYWORD], keyval[FLEN_VALUE]; + char keycomment[FLEN_COMMENT], cdtype[32]; + TRYFITS(fits_open_file, &fp, filename, READWRITE); +/* TRYFITS(fits_get_hdrspace, fp, &keynum, &morekeys); + if(morekeys == -1){ + return FALSE; + } + g_print("Read header of %s, %d keys\n", filename, keynum);*/ + FITSFUN(fits_get_num_hdus, fp, &hdunum); + //~ g_print(_("\nFile includes %d HDUs\n"), hdunum); + if(hdunum < 1){ + g_err(_("Can't read HDU")); + TRYFITS(fits_close_file, fp); + return FALSE; + } + FITSFUN(fits_get_hdu_type, fp, &dtype); + //~ if(dtype) g_print(_("Current HDU type: %d\n"), dtype); + init_keylist(image); + FITSFUN(fits_get_hdrpos ,fp, &keynum, &morekeys); + + for (j = 1; j <= keynum; j++){ + /*FITSFUN(fits_read_record, fp, j, card); + if(!strip_card(card, keyname, keyval, keycomment)){ + g_print("Corrupted card: %s!\n", card); + continue; + }*/ + FITSFUN(fits_read_keyn, fp, j, keyname, keyval, keycomment); + if(strcmp(keyname, "SIMPLE") == 0 || strcmp(keyname, "EXTEND") == 0) // key "file does conform ..." + continue; + else if(strcmp(keyname, "COMMENT") == 0) // comment of obligatory key in FITS head + continue; + else if(strstr(keyname, "NAXIS") == keyname || strcmp(keyname, "BITPIX") == 0) // NAXIS, NAXISxxx, BITPIX + continue; + else if(strcmp(keyname, "BZERO") == 0){ + bZero = atof(keyval); + //continue; + } + else if(strcmp(keyname, "BSCALE") == 0){ + bScale = atof(keyval); + if(bScale < SCALE_MIN) bScale = 1.; + //continue; + } + else if(strcmp(keyname, "VAL_F") == 0){ + val_f = atof(keyval); + } + dtype = guess_type(keyval, cdtype); + //DBG("key#%d; name=%s, dtype=%d (%s), value=%s, comment=%s\n", + // j, keyname, dtype, cdtype, keyval, keycomment); + add_key(image, cdtype, keyname, keyval, keycomment, dtype); + } + if(hdunum > 1){ + for(i = 2; !(fits_movabs_hdu(fp, i, &dtype, &stat)); i++){ + FITSFUN(fits_get_hdrpos ,fp, &keynum, &morekeys); + for (j = 1; j <= keynum; j++){ + FITSFUN(fits_read_keyn, fp, j, keyname, keyval, keycomment); + dtype = guess_type(keyval, cdtype); + add_key(image, cdtype, keyname, keyval, keycomment, dtype); + } + } + if(stat != END_OF_FILE){ + fits_report_error(stderr, (stat)); + ret = FALSE; + } + } + + TRYFITS(fits_get_img_param, fp, 4, &dtype, &naxis, naxes); + if(dtype > 0) imBits = (GLfloat)(1LL << dtype); // compute the max amplitude + else imBits = -1.; // image has floating point type + DBG("Image type: %d (%f bits)", dtype, imBits); + image->maxVal = imBits; + image->dtype = dtype; + if(naxis != 2){ + g_err(_("Not an image? (dimensions != 2)")); + return FALSE;} + + image->val_f = val_f; + image->bZero = bZero; + image->bScale = bScale; + image->width = w = naxes[0]; + image->height = h = naxes[1]; + sz = (ssize_t)(w) * (ssize_t)(h); + image->data = malloc(sz * sizeof(GLfloat)); + + TRYFITS(fits_read_img, fp, TFLOAT, 1, sz, &nullval, image->data, &stat); + GLfloat *ptr = image->data; + GLfloat min, max, wd; + min = 1e6; max = 0.; + for(i=0; i max) max = tmp; + else if(tmp < min) min = tmp; + } + wd = max - min; + DBG("min = %f, wd = %f", min, wd); + image->stat.max = max; + image->stat.min = min; + // histogram isn't initialized yet + image->stat.histogram.data = NULL; + image->stat.histogram.size = 0; + /* + ptr = image->data; + for(i=0; i +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "fitsheaders.h" +#include "fits.h" + +GtkListStore *combo_ = NULL; +GtkTreeSelection *sel; +GtkTreeView* Tree; +GtkWidget *del_button; +GtkWidget *window; +// Data types to write FITS keys into menu +gchar* key_types[] = { + "TSTRING", "TLOGICAL", "TBYTE", "TSHORT", "TUSHORT", "TINT", "TUINT", + "TLONG", "TULONG", "TLONGLONG", "TFLOAT", "TDOUBLE", "TCOMPLEX", + "TDBLCOMPLEX" +}; +// Values itself +int type_vals[] = { + TSTRING, + TLOGICAL, + TBYTE, + TSHORT, + TUSHORT, + TINT, + TUINT, + TLONG, + TULONG, + TLONGLONG, + TFLOAT, + TDOUBLE, + TCOMPLEX, + TDBLCOMPLEX +}; + +int VALS_N; // Number of items + +gchar *colnames[] = {N_("Type"), N_("Name"), N_("Value"), N_("Comment")}; +#define VISIBLE_COLS 4 // The amount of visible columns + +int find_list_type(char *type){ + int i; + for(i = 0; i < VALS_N; i++) + if(strcmp(key_types[i], type) == 0) break; + if(i == VALS_N) // Error: there's no such value in list + i = 0; + return i; +} + +gboolean ll_conv(gchar **value, int type){ + long long ll_val, oll_val; + gboolean ret = TRUE; + ll_val = oll_val = strtoll(*value, NULL, 0); + //g_free(*value); + switch(type){ + case TBYTE: + if(ll_val > CHAR_MAX || ll_val < CHAR_MIN) ll_val = 0L; + break; + case TSHORT: + if(ll_val > SHRT_MAX || ll_val < SHRT_MIN) ll_val = 0L; + break; + case TINT: + if(ll_val > INT_MAX || ll_val < INT_MIN) ll_val = 0L; + break; + case TLONG: + if(ll_val > LONG_MAX || ll_val < LONG_MIN) ll_val = 0L; + break; + case TLONGLONG: + if(ll_val > LLONG_MAX || ll_val < LLONG_MIN) ll_val = 0L; + break; + case TUSHORT: + if(ll_val > USHRT_MAX || ll_val < 0L) ll_val = 0L; + break; + case TUINT: + if(ll_val > UINT_MAX || ll_val < 0L) ll_val = 0L; + break; + case TULONG: + if((unsigned long)ll_val > ULONG_MAX || ll_val < 0L) ll_val = 0L; + break; + default: + ll_val = 0L; + break; + } + DBG("long long: %lld", ll_val); + if(ll_val != oll_val){ + g_err(_("Error in value range!")); + ret = FALSE; + } + g_free(*value); + *value = g_strdup_printf("%lld", ll_val); + return ret; +} + +gboolean mystrtod(gchar **val){ + double d_val; + gboolean ret = TRUE; + char *eptr = NULL, *ptr = *val; + errno = 0; + d_val = strtod(ptr, &eptr); + DBG("double: %.16e (was: %s)", d_val, *val); + if((eptr && *eptr) || errno == ERANGE){ + g_free(*val); + DBG("try convert %g into val", d_val); + *val = g_strdup_printf("%.16e", d_val); + g_err(_("Invalid double number!")); + ret = FALSE; + } + DBG("val: %s", *val); + return ret; +} + +gboolean check_complex_number(gchar **val){ + double re = 0., im = 0.; + gboolean ret = TRUE; + if(!cmplx_conv(*val, &re, &im)){ + gchar *msg = g_strdup_printf("%s\n%s", + _("Format error: complex number must be in format"), + "RE + iIM, RE + jIM, RE iIM, RE jIM, (RE, IM) or (RE IM)"); + g_err(msg); + g_free(msg); + ret = FALSE; + } + DBG("complex: (%g, %g)", re, im); + g_free(*val); + *val = g_strdup_printf("(%g, %g)", re, im); + return ret; +} + +gboolean check_val(gchar **newval, int type){ + gboolean b_val, ret = TRUE; + switch(type){ + case TBYTE: + case TSHORT: + case TINT: + case TLONG: + case TLONGLONG: + case TUSHORT: + case TUINT: + case TULONG: + // convert *char -> long long vith checking value + ret = ll_conv(newval, type); + break; + case TFLOAT: + case TDOUBLE: + // convert *char -> double + ret = mystrtod(newval); + break; + case TCOMPLEX: + case TDBLCOMPLEX: + // check whether a number is complex + ret = check_complex_number(newval); + break; + case TLOGICAL: + // convert value into boolean + if(g_ascii_strncasecmp("true", *newval, 4)==0) b_val = TRUE; + else + b_val = atoi(*newval); + g_free(*newval); + *newval = g_strdup_printf("%s", b_val ? "TRUE" : "FALSE"); + DBG("boolean: %s", *newval); + break; + default: + DBG("string: %s", *newval); // all other values a strings + } + return ret; +} + +gboolean try_change_val(IMAGE *ima, gchar **newval){ + int type; + GtkTreeIter cur_items; + gboolean ret; + GtkTreeModel *model; + FNAME(); + if(!gtk_tree_selection_get_selected(sel,&model, &cur_items)) return FALSE; + gtk_tree_model_get(GTK_TREE_MODEL(ima->store), &cur_items, L_TYPE, &type, -1); + ret = check_val(newval, type); + gtk_list_store_set(ima->store, &cur_items, L_VAL, *newval, -1); + DBG("ret"); + return ret; +} +guint cur_column; +void edit_cell( GtkCellRendererText *cell, + gchar *path, + gchar *new_text, + IMAGE *ima){ + GtkTreeModel *model; + GtkTreeIter cur_items; + cur_column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(cell), "column_num")); + gboolean nxtcol = TRUE, editable = TRUE; + if(!path) return; + DBG("path: %s, new: %s, colnum: %d", path, new_text, cur_column); + gchar *textptr; + if(!gtk_tree_selection_get_selected(sel,&model, &cur_items)) return; + switch(cur_column){ + case L_TYPENM: // key type was changed + gtk_list_store_set(ima->store, &cur_items, L_TYPE, + type_vals[find_list_type(new_text)], -1); + gtk_list_store_set(ima->store, &cur_items, cur_column, new_text, -1); + break; + case L_VAL: // key value was changed + textptr = g_strdup_printf("%s", new_text); + nxtcol = try_change_val(ima, &textptr); // try to change parameter according to its type + g_free(textptr); + break; + default: + gtk_list_store_set(ima->store, &cur_items, cur_column, new_text, -1); + break; + } + if(nxtcol){ + if(cur_column < L_COMM) // it's not a last column - select next + cur_column++; + else{ + cur_column = 0; + editable = FALSE; + } + } + DBG("set cursor col:%d, e=%d", cur_column, editable); + gtk_tree_view_set_cursor(Tree, + gtk_tree_model_get_path(GTK_TREE_MODEL(ima->store), &cur_items), + gtk_tree_view_get_column(Tree, cur_column), editable); + DBG("ret"); +} +/* +void cancel_edit(GtkCellRenderer *renderer){ + cur_column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(renderer), "column_num")); + gtk_tree_view_set_cursor(Tree, + gtk_tree_model_get_path(GTK_TREE_MODEL(store), &cur_items), + gtk_tree_view_get_column(Tree, cur_column), FALSE); +} +*/ +void add_key( IMAGE *ima, + gchar *keytype, gchar *keyname, gchar *keyval, + gchar *keycomment, int keyfitstype){ + GtkTreeIter iter; + //~ FNAME(); + gtk_list_store_append(ima->store, &iter); + gtk_list_store_set(ima->store, &iter, + L_TYPENM, keytype, + L_KEY, keyname, + L_VAL, keyval, + L_COMM, keycomment, + L_TYPE, keyfitstype, + -1);// the last element must be -1 +} + +void init_keylist(IMAGE *ima){ + /* Create a list model. + * We will have 4 columns: name of key type, key name, key value, comment, type + */ + if(ima->store) g_object_unref(G_OBJECT(ima->store)); // clear list, if it already exists + ima->store = gtk_list_store_new(L_MAX, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); +} + +GtkWidget *create_treeview(IMAGE *ima){ + int i, i_max = sizeof(key_types)/sizeof(char*);; + GtkTreeIter iter; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + if(combo_) + gtk_list_store_clear(combo_); + combo_ = gtk_list_store_new(1, G_TYPE_STRING); + for(i = 0; i < i_max; i++){ + gtk_list_store_append(combo_, &iter); + gtk_list_store_set(combo_, &iter, 0, key_types[i], -1); + } + // create list (treeview) according model store + Tree = (GtkTreeView*)gtk_tree_view_new_with_model(GTK_TREE_MODEL(ima->store)); + // Create columns in list + for(i = 0; i < VISIBLE_COLS; i++){ + if(i == 0){ + renderer = gtk_cell_renderer_combo_new(); + g_object_set(renderer, "text-column", 0, + "editable", TRUE, "has-entry", FALSE, "model", + GTK_TREE_MODEL(combo_), NULL); + } + else{ + renderer = gtk_cell_renderer_text_new (); + g_object_set(renderer, "editable", TRUE, NULL); + } + g_object_set_data(G_OBJECT(renderer), "column_num", GINT_TO_POINTER(i)); + column = gtk_tree_view_column_new_with_attributes(_(colnames[i]), + renderer, "text", i, NULL); + gtk_tree_view_append_column(Tree, column); // insert column + gtk_tree_view_column_set_sort_column_id(column, i);// it may be sorted + g_signal_connect(renderer, "edited", G_CALLBACK(edit_cell), ima); + // g_signal_connect(renderer, "editing-canceled", G_CALLBACK(cancel_edit), NULL); + } +/* + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("hidden",renderer, NULL); + g_object_set(column, "visible", FALSE, NULL); + gtk_tree_view_append_column(Tree, column); +*/ + return GTK_WIDGET(Tree); +} + +static void list_changed(GtkTreeSelection *sel){ + GtkTreeModel *model; + GtkTreeIter cur_items; + if(gtk_tree_selection_get_selected(sel,&model, &cur_items)) + gtk_widget_set_sensitive(del_button, TRUE); + else + gtk_widget_set_sensitive(del_button, FALSE); + /* + GtkTreeModel *model; + FNAME(); + gboolean showdel = FALSE; + if(!GTK_IS_TREE_SELECTION(sel)){ + showdel = TRUE; + if(GTK_IS_TREE_SELECTION(obj)) DBG("!!!"); + cur_column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(sel), "column_num")); + DBG("curcol: %d", cur_column); + } + else if(gtk_tree_selection_get_selected(sel,&model, &cur_items)){ + gint column = L_VAL; + gchar *value; + gtk_tree_model_get(model, &cur_items, column, &value, -1); + DBG("selected: %s", value); + gtk_tree_view_set_cursor(Tree, + gtk_tree_model_get_path(model, &cur_items), + gtk_tree_view_get_column(Tree, cur_column), FALSE); + g_free(value); + showdel = TRUE; + } + gtk_widget_set_sensitive(del_button, showdel); + DBG("ret");*/ +} + +void newline(IMAGE *ima){ + FNAME(); + GtkTreeIter cur_items; + gtk_list_store_append(ima->store, &cur_items); + gtk_list_store_set(ima->store, &cur_items, + L_TYPENM, key_types[0], + L_KEY, "NEW_KEY", + L_VAL, "", + L_COMM, "", + L_TYPE, type_vals[0], + -1); + // Set cursor onto a first field + gtk_tree_view_set_cursor(Tree, + gtk_tree_model_get_path(GTK_TREE_MODEL(ima->store), &cur_items), + gtk_tree_view_get_column(Tree, 0), TRUE); + DBG("ret"); +} + +gboolean del_line(IMAGE *ima){ + GtkTreeIter iter; + GtkTreeModel *model = GTK_TREE_MODEL(ima->store); + gboolean ret = FALSE; + FNAME(); + GtkTreeSelection *selection = gtk_tree_view_get_selection(Tree); + GtkTreePath *path; + if(gtk_tree_selection_get_selected(selection, NULL, &iter)){ + path = gtk_tree_model_get_path(model, &iter); + ret = gtk_list_store_remove(ima->store, &iter); + // check wherher &cur_items was last + if(gtk_list_store_iter_is_valid(ima->store, &iter)){ + gtk_tree_path_free(path); + path = gtk_tree_model_get_path(model, &iter); + } + else gtk_tree_path_prev(path); // goto previous + if(path && gtk_tree_model_get_iter_first(model, &iter)) + gtk_tree_view_set_cursor(Tree, path, NULL, FALSE); + else DBG("No entries"); + gtk_tree_path_free(path); + } + DBG("ret"); + return ret; +} +/* +void show_menu(){ + GtkWidget *menu, *menuitem; + gchar *text; + int i; + menu = gtk_menu_new(); + for(i = 0; i < 10; i++){ + text = g_strdup_printf("%d", i); + menuitem = gtk_menu_item_new_with_label(text); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + gtk_widget_show(menuitem); + g_free(text); + } + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, 0); +} + +gboolean treeview_button_press_cb(GtkWidget *wi, GdkEventButton *ev, gpointer user_data) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreePath *path, *oldpath; + GtkTreeViewColumn *column; + GtkTreeIter iter; + gchar *colname; + + // if there's no path where the click occurred... + if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(wi), ev->x, ev->y, &path, &column, NULL, NULL)) + return FALSE; // ...return FALSE to allow the event to continue + colname = strdup(gtk_tree_view_column_get_title(column)); + // get the tree view's selection + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(wi)); + + switch(ev->button) + { + // LMB + case 1: + // check to see if this is already the selected path + if(gtk_tree_selection_get_selected(sel, &model, &iter)) + { + oldpath = gtk_tree_model_get_path(model, &iter); + if(gtk_tree_path_compare(oldpath, path) == 0) + { + gtk_tree_path_free(oldpath); + gtk_tree_path_free(path); + return FALSE; + } + gtk_tree_path_free(oldpath); + } + + // select the clicked path + gtk_tree_selection_select_path(sel, path); + return TRUE; + // RMB + case 3: + if(strncmp(colname, "No.", 3)){ + gtk_tree_path_free(path); + return TRUE; + } + show_menu(); +// gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, ev->time); + break; + } + + + // free our path + gtk_tree_path_free(path); + + return FALSE; // allow it to continue +} +*/ + +void close_dlg(){ + gtk_widget_destroy(window); +} + +static gboolean press_key(IMAGE *ima, GdkEventKey * event){ + gboolean ret = FALSE; + if(event->state & (GDK_CONTROL_MASK)){ + switch(event->keyval){ + // ctrl+n inserts a line + case 'n': case 'N': + newline(ima); + ret = TRUE; + break; + // ctrl+d deletes current line + case 'd': case 'D': + ret = del_line(ima); + break; + // ctrl+w closes the window + case 'w': case 'W': + close_dlg(); + } + } + return ret; +} + +void edit_headers(Window *parent){ + VALS_N = sizeof(key_types) / sizeof(gchar *); + GtkWidget *treeview, *vbox, *hbox, *button; + if(!parent->image || !parent->image->store){ + g_err(_("Open fits file first")); + return; + } + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "List"); + gtk_signal_connect(GTK_OBJECT(window),"delete_event",GTK_SIGNAL_FUNC(gtk_false),NULL); + gtk_signal_connect(GTK_OBJECT(window),"destroy",GTK_SIGNAL_FUNC(close_dlg), NULL); + treeview = create_treeview(parent->image); + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); + g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(list_changed), NULL); + //g_signal_connect(G_OBJECT(treeview), "cursor-changed", G_CALLBACK(list_changed), NULL); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_container_add (GTK_CONTAINER (window), vbox); + + GtkWidget *sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(sw), treeview); + gtk_window_set_default_size(GTK_WINDOW (window), 640, 480); + + //~ gtk_box_pack_start(GTK_BOX(vbox), treeview, TRUE, TRUE, 0); + + hbox = gtk_hbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label(_("New entry (ctrl+n)")); + g_signal_connect_swapped(G_OBJECT(button), "clicked", + G_CALLBACK(newline), parent->image); + gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0); + + del_button = gtk_button_new_with_label(_("Delete entry (ctrl+d)")); + g_signal_connect_swapped(G_OBJECT(del_button), "clicked", + G_CALLBACK(del_line), parent->image); + gtk_box_pack_end(GTK_BOX(hbox), del_button, FALSE, FALSE, 0); + gtk_widget_set_sensitive(del_button, FALSE); + + button = gtk_button_new_with_label(_("Close (ctrl+w)")); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(close_dlg), NULL); + gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect_swapped(G_OBJECT(window), "key-press-event", + G_CALLBACK(press_key), parent->image); + run_modal_window(GTK_WINDOW(window), parent); +} diff --git a/src/fitsview b/src/fitsview new file mode 100755 index 0000000..533c09b Binary files /dev/null and b/src/fitsview differ diff --git a/src/fitsview.c b/src/fitsview.c new file mode 100644 index 0000000..94617a6 --- /dev/null +++ b/src/fitsview.c @@ -0,0 +1,123 @@ +// fitsview.c - main project file +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "fitsview.h" +#include "fits.h" +#include "gtk.h" +#include "opengl.h" +#include "open_dialog.h" +#include "filelist.h" + +#define RED "\033[1;31;40m" +#define GREEN "\033[1;32;40m" +#define OLDCOLOR "\033[0;0;0m" + +int (*red)(const char *fmt, ...); +int (*green)(const char *fmt, ...); + +int r_pr_(const char *fmt, ...){ + va_list ar; int i; + printf(RED); + va_start(ar, fmt); + i = vprintf(fmt, ar); + va_end(ar); + printf(OLDCOLOR "\n"); + return i; +} + +int g_pr_(const char *fmt, ...){ + va_list ar; int i; + printf(GREEN); + va_start(ar, fmt); + i = vprintf(fmt, ar); + va_end(ar); + printf(OLDCOLOR "\n"); + return i; +} + + +Global_context *Global; + +// init structures with zeros +void *init_struct(int size){ + int8_t *ptr = calloc(1, size); + return (void*) ptr; +} +// copy structure +void *copy_struct(void *src, int size){ + int8_t *ptr = calloc(1, size); + memcpy(ptr, src, size); + return (void*)ptr; +} + +// current time (for random number generator initialisation & for debug purposes) +double dtime(){ + double t; + struct timeval tv; + gettimeofday(&tv, NULL); + t = tv.tv_sec + ((double)tv.tv_usec)/1e6; + return t; +} + +// if we'll save preferences global context should be read fom file +void init_contexts(){ + // limit spot size + Global->maxSpotH = 100; + Global->minSpotH = 5; + Global->maxSpotW = 100; + Global->minSpotW = 5; +} + +// here will be a function to process command line arguments + + +int main(int argc, char *argv[]){ + gchar *filename = NULL, *str; + if(isatty(STDOUT_FILENO)){ // make color output in tty + red = r_pr_; green = g_pr_; + }else{ // no colors in case of pipe + red = printf; green = printf; + } +#ifdef CUDA_FOUND + DBG("CUDA found"); +#else + DBG("CUDA not found, using CPU instead"); +#endif + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); + INIT(Global, Global_context); + init_contexts(); + init_main_window(&argc, &argv); + getGLinfo(mainWindow->drawingArea); + setlocale(LC_NUMERIC, "C"); // for right strto[df] working + // Show all for begin + mainWindow->context->visualMode = SHOW_ALL; + mainWindow->image->data = NULL; + if(argc > 1){ + str = basename(argv[1]); + saved_path = get_current_dir_name(); + filename = g_strdup_printf("%s/%s", saved_path, str); + fill_filelist(filename, mainWindow); + change_image(filename, mainWindow); + g_free(filename); + }else + gen_texture(mainWindow->image, mainWindow, FALSE); + gtk_main(); + return 0; +} diff --git a/src/fitsview.glade b/src/fitsview.glade new file mode 100644 index 0000000..7de5fb3 --- /dev/null +++ b/src/fitsview.glade @@ -0,0 +1,1358 @@ + + + + + + + + True + vertical + + + True + + + True + _File + True + + + True + + + gtk-open + True + True + True + + + + + + gtk-save-as + True + True + True + + + + + Open in _new window + True + True + False + + + + True + gtk-open + + + + + + + Open in _3D window + True + True + False + + + + True + gtk-open + + + + + + + True + + + + + _Quit + True + True + False + + + True + + + + + + + + + + + _Edit + True + + + + + True + _View + True + + + True + + + True + Show _histogram + True + + + + + + True + Show _headers + True + + + + + + True + + + + + True + 3D view of full image + True + + + + + + True + 3D view of subframe + True + + + + + + True + Select _spots + True + + + + + + True + Draw _tracks + True + + + + + + True + + + + + True + _Zoom frame + True + + + + + + True + _Restore image + True + + + + + + True + Zoom _in + True + + + + + + True + Zoom _out + True + + + + + + + + + + True + _Math + True + + + True + + + True + Find and enumerate spots + _Spots + True + + + True + + + True + Choose minimal & maximal spot size + Size _tresholds + True + + + + + + True + Identify _spots + True + + + + + + True + So_rt hartmann spots + True + + + + + + True + Sa_ve spots + True + + + + + + + + + + True + Identify _circles + True + + + + + + True + _Hough transform + True + + + + + + True + _Filter + True + + + + + + + + + + True + _Help + True + + + True + + + gtk-about + True + True + True + + + + + + + + + + False + 0 + + + + + + + + True + 3 + 3 + + + 400 + 400 + True + + + 1 + 3 + 2 + + + + + True + 10 + 5 + 10 + + + 1 + 3 + 2 + 3 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + vertical + 10 + 5 + 10 + + + 2 + GTK_FILL + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + + + + 2 + + + + + True + 1 + + + True + True + False + False + + True + none + + + 0 + + + + + True + True + False + False + + True + none + + + False + 1 + + + + + True + True + False + False + + True + none + + + False + 2 + + + + + True + True + False + False + + True + none + + + False + 3 + + + + + False + 3 + + + + + + + + + True + vertical + + + True + + + True + _File + True + + + True + + + True + + + + + gtk-close + True + True + True + + + + + + + + + + + _Edit + True + + + + + True + _View + True + + + True + + + True + Y axis scale + True + + + True + + + True + Linear + True + True + True + + + + + + True + Log + True + True + graphLinMenuItem + + + + + + + + + + + + + + True + _Math + True + + + True + + + True + Approximate by _gaussian + True + + + + + + + + + + False + 0 + + + + + + + + True + 3 + 3 + + + 400 + 400 + True + + + 1 + 3 + 2 + + + + + True + 10 + 5 + 10 + + + 1 + 3 + 2 + 3 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + vertical + 10 + 5 + 10 + + + 2 + GTK_FILL + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + + + + 2 + + + + + True + + + True + True + False + False + + True + none + + + 0 + + + + + True + True + False + 8 + False + + 8 + True + none + + + False + 1 + + + + + True + True + False + 40 + False + + 28 + True + none + + + False + False + end + 2 + + + + + False + 3 + + + + + + + + + True + vertical + + + True + + + True + _File + True + + + True + + + gtk-open + True + True + True + + + + + + True + + + + + gtk-close + True + True + True + + + + + + + + + + _Edit + True + + + + + True + _View + True + + + True + + + True + _Move + True + + + True + + + True + Rotate X CW + True + + + + + + True + Rotate X CCW + True + + + + + + True + Rotate Z CCW + True + + + + + + True + Rotate Z CW + True + + + + + + True + Move right + True + + + + + + True + Move left + True + + + + + + True + Move down + True + + + + + + True + Move up + True + + + + + + True + Move backward + True + + + + + + True + Move forward + True + + + + + + + + + + True + + + + + True + Mouse and arrow keys navigation + _Game mode + True + + + + + + True + _Restore image + True + + + + + + + + + + True + _Math + True + + + + + False + 0 + + + + + + + + 400 + 400 + True + + + 2 + + + + + True + 1 + + + True + True + False + False + + True + none + + + 0 + + + + + True + True + False + False + + True + none + + + False + 1 + + + + + True + True + False + False + + True + none + + + False + 2 + + + + + True + True + False + False + + True + none + + + False + 3 + + + + + False + 3 + + + + + + + Spots' size treshold + False + True + center-on-parent + True + + + True + 7 + 7 + 7 + 7 + + + True + vertical + 7 + + + True + 7 + True + + + True + 0 + out + + + True + 12 + + + True + vertical + 4 + True + + + True + 5 + + + True + Min + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 0 + + + + + True + 5 + + + True + Max + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 1 + + + + + + + + + True + <b>Width</b> + True + + + label_item + + + + + 0 + + + + + True + 0 + in + + + True + 12 + + + True + vertical + 5 + True + + + True + 5 + + + True + Min + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 0 + + + + + True + 5 + + + True + Max + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 1 + + + + + + + + + True + <b>Height</b> + True + + + label_item + + + + + 1 + + + + + 0 + + + + + True + 10 + True + + + gtk-cancel + True + True + True + True + + + False + 0 + + + + + gtk-ok + True + True + True + True + + + False + 1 + + + + + False + False + 1 + + + + + + + + + 5 + normal + False + + + True + vertical + 2 + + + True + 7 + 7 + 7 + + + True + 7 + + + True + 0 + none + + + True + 12 + + + True + True + + + + + + + + True + <b>Focus value, mm</b> + True + + + label_item + + + + + 0 + + + + + True + 0 + none + + + True + 12 + + + True + vertical + 5 + True + + + Prefocal + True + True + False + True + True + + + 0 + + + + + Postfocal + True + True + False + True + FocalPreRadioBtn + + + 1 + + + + + + + + + True + <b>Image type</b> + True + + + label_item + + + + + 1 + + + + + + + 1 + + + + + True + center + + + + + + gtk-ok + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + diff --git a/src/fitsview.ui b/src/fitsview.ui new file mode 100644 index 0000000..cf9493e --- /dev/null +++ b/src/fitsview.ui @@ -0,0 +1,1281 @@ + + + + + + + + mainFileMenuItem + _File + + + + + gtk-open + mainOpenMenuItem + + + + + + gtk-save-as + mainSaveAsMenuItem + + + + + gtk-open + mainOpenInNewMenuItem + Open in _new window + + + + + + gtk-open + mainOpenIn3DMenuItem + Open in _3D window + + + + + + mainQuitMenuItem + _Quit + + + + + mainEditMenuItem + _Edit + + + + + mainViewMenuItem + _View + + + + + mainHistMenuItem + Show _histogram + + + + + + mainHeadersMenuItem + Show _headers + + + + + + main3DviewFullMenuItem + 3D view of full image + + + + + + main3DviewFrameMenuItem + 3D view of subframe + + + + + + mainSpotsidentMenuItem + Select _spots + + + + + + mainTrackMenuItem + Draw _tracks + + + + + + mainZoomframeMenuItem + _Zoom frame + + + + + + mainZoomrestoreMenuItem + _Restore image + + + + + + mainZoomnearMenuItem + Zoom _in + + + + + + mainZoomfarMenuItem + Zoom _out + + + + + + mainMathMenuItem + _Math + + + + + mainSpotsMenuItem + Find and enumerate spots + _Spots + + + + + mainSpotsParamMenuItem + Choose minimal & maximal spot size + Size _tresholds + + + + + + mainFindSpotsMenuItem + Identify _spots + + + + + + mainHartmannMenuItem + So_rt hartmann spots + + + + + + mainSaveSpotsMenuItem + Sa_ve spots + + + + + + mainCirclesMenuItem + Identify _circles + + + + + + mainHoughMenuItem + _Hough transform + + + + + + mainFilterMenuItem + _Filter + + + + + + mainHelpMenuItem + _Help + + + + + gtk-about + mainAboutMenuItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + graphFileMenuItem + _File + + + + + gtk-close + graphCloseMenuItem + + + + + + + graphEditMenuItem + _Edit + + + + + graphViewMenuItem + _View + + + + + graphScaleMenuItem + Y axis scale + + + + + True + graphLinMenuItem + Linear + + + + + + graphLinMenuItem + graphLogMenuItem + Log + + + + + + graphMathMenuItem + _Math + + + + + graphGaussMenuItem + Approximate by _gaussian + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + oglFileMenuItem + _File + + + + + gtk-open + oglOpenMenuItem + + + + + + gtk-close + oglCloseMenuItem + + + + + + oglEditMenuItem + _Edit + + + + + oglViewMenuItem + _View + + + + + oglMoveMenuItem + _Move + + + + + oglXAplusMenuItem + Rotate X CW + + + + + + oglXAminusMenuItem + Rotate X CCW + + + + + + oglZAplusMenuItem + Rotate Z CCW + + + + + + oglZAminusMenuItem + Rotate Z CW + + + + + + oglXplusMenuItem + Move right + + + + + + oglXminusMenuItem + Move left + + + + + + oglYplusMenuItem + Move down + + + + + + oglYminusMenuItem + Move up + + + + + + oglZplusMenuItem + Move backward + + + + + + oglZminusMenuItem + Move forward + + + + + + oglGameModeMenuItem + Mouse and arrow keys navigation + _Game mode + + + + + + oglZoomrestoreMenuItem + _Restore image + + + + + + oglMathMenuItem + _Math + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + vertical + + + True + + + False + 0 + + + + + + + + True + 3 + 3 + + + 400 + 400 + True + + + 1 + 3 + 2 + + + + + True + 10 + 5 + 10 + + + 1 + 3 + 2 + 3 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + vertical + 10 + 5 + 10 + + + 2 + GTK_FILL + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + + + + 2 + + + + + True + 1 + + + True + True + False + False + + True + none + + + 0 + + + + + True + True + False + False + + True + none + + + False + 1 + + + + + True + True + False + False + + True + none + + + False + 2 + + + + + True + True + False + False + + True + none + + + False + 3 + + + + + False + 3 + + + + + + + + + True + vertical + + + True + + + False + 0 + + + + + + + + True + 3 + 3 + + + 400 + 400 + True + + + 1 + 3 + 2 + + + + + True + 10 + 5 + 10 + + + 1 + 3 + 2 + 3 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + vertical + 10 + 5 + 10 + + + 2 + GTK_FILL + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + + + + 2 + + + + + True + + + True + True + False + False + + True + none + + + 0 + + + + + True + True + False + 8 + False + + 8 + True + none + + + False + 1 + + + + + True + True + False + 40 + False + + 28 + True + none + + + False + False + end + 2 + + + + + False + 3 + + + + + + + + + True + vertical + + + True + + + False + 0 + + + + + + + + 400 + 400 + True + + + 2 + + + + + True + 1 + + + True + True + False + False + + True + none + + + 0 + + + + + True + True + False + False + + True + none + + + False + 1 + + + + + True + True + False + False + + True + none + + + False + 2 + + + + + True + True + False + False + + True + none + + + False + 3 + + + + + False + 3 + + + + + + + Spots' size treshold + False + True + center-on-parent + True + + + True + 7 + 7 + 7 + 7 + + + True + vertical + 7 + + + True + 7 + True + + + True + 0 + out + + + True + 12 + + + True + vertical + 4 + True + + + True + 5 + + + True + Min + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 0 + + + + + True + 5 + + + True + Max + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 1 + + + + + + + + + True + <b>Width</b> + True + + + + + 0 + + + + + True + 0 + in + + + True + 12 + + + True + vertical + 5 + True + + + True + 5 + + + True + Min + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 0 + + + + + True + 5 + + + True + Max + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + False + False + 1 + + + + + + + + + True + <b>Height</b> + True + + + + + 1 + + + + + 0 + + + + + True + 10 + True + + + gtk-cancel + True + True + True + True + + + False + 0 + + + + + gtk-ok + True + True + True + True + + + False + 1 + + + + + False + False + 1 + + + + + + + + + 5 + normal + False + + + True + vertical + 2 + + + True + 7 + 7 + 7 + + + True + 7 + + + True + 0 + none + + + True + 12 + + + True + True + + + + + + + + True + <b>Focus value, mm</b> + True + + + + + 0 + + + + + True + 0 + none + + + True + 12 + + + True + vertical + 5 + True + + + Prefocal + True + True + False + True + True + + + 0 + + + + + Postfocal + True + True + False + True + FocalPreRadioBtn + + + 1 + + + + + + + + + True + <b>Image type</b> + True + + + + + 1 + + + + + + + 1 + + + + + True + center + + + + + + gtk-ok + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + diff --git a/src/gauss.c b/src/gauss.c new file mode 100644 index 0000000..64cdcf7 --- /dev/null +++ b/src/gauss.c @@ -0,0 +1,378 @@ +// gauss.c - funtions for gaussian approximation +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "gtk.h" +#include "gauss.h" +#ifdef GSL_FOUND // cmake found GSL library +#include +#include +#include +#include +#include + + +#undef DBG +#undef EBUG +#define DBG(...) + + +/* + * Returns value of gaussian value in point x + * x0, C, A, sigma - are parameters (middle, vertical shift, amplitude and std) + */ +double gaussian_pt(double C, double A, double sigma, double x0, double x){ + double s2 = sigma*sigma; + double ss = A/sqrt(2*M_PI*s2); + double E = x-x0; + return C + ss*exp(-E*E/2/s2); +} + +/* + * Fills the ordinates of structure Points by Gaussian + * (Abscissa does not move, so that this structure can be used + * for multiple rendering of the same area but with different + * parameters) + */ +void gaussian_v(double C, double A, double sigma, double x0, Points *pts){ + int i, n = pts->n; + Point *pt = pts->data; + double s2 = sigma*sigma * 2; + double ss = A/sqrt(2*M_PI*s2); + double E; + for(i = 0; i < n; i++, pt++){ + E = pt->x - x0; + pt->y = C + ss*exp(-E*E/s2); + //DBG("pt %d (%g, %g)", i, pt->x, pt->y); + } +} + +int gauss_f(const gsl_vector *parms, void *data, gsl_vector *f){ + size_t n = ((struct data *)data)->n; + double *x = ((struct data *)data)->x; + double *y = ((struct data *)data)->y; + double *dy = ((struct data *)data)->dy; + double x0 = gsl_vector_get(parms, 3); + double sigma = gsl_vector_get(parms, 2); + double A = gsl_vector_get(parms, 1) * 0.65; // without 0.65 A would be wrong (WTF?) + double C = gsl_vector_get(parms, 0); + size_t i; + double mul = A/fabs(sigma)/sqrt(2.*M_PI); + for (i = 0; i < n; i++){ + /* Model Yi = C + A/sqrt(2*pi*sigma^2) * exp(-(x-b)^2/2/sigma^2) */ + double E = (x[i]-x0)/sigma; + double Yi = C + mul * exp(-E * E/2.); + gsl_vector_set(f, i, (Yi - y[i])/dy[i]); + } + return GSL_SUCCESS; +} + +int gauss_df(const gsl_vector *parms, void *data, gsl_matrix *J){ + size_t n = ((struct data *)data)->n; + double *x = ((struct data *)data)->x; + double *dy = ((struct data *) data)->dy; + double A = gsl_vector_get(parms, 1); + double sigma = gsl_vector_get(parms, 2); + double x0 = gsl_vector_get(parms, 3); + double S2PI = 1. / sqrt(2.*M_PI); + size_t i; + for(i = 0; i < n; i++){ + /* Jacobian matrix J(i,j) = dfi / dxj, */ + /* and the xj are the parameters (C, A,sigma,x0) */ + double s = dy[i]; + double xbs = (x[i]-x0)/sigma; + double y = exp(-xbs*xbs/2.) *S2PI / fabs(sigma) / s; + double Ays = A*y/sigma; + // df/dx0 = f * (x-x0)/sigma^2 + gsl_matrix_set(J, i, 3, Ays*xbs); + // df/dsigma = -f(1-(x-x0)^2/sigma^2)/sigma + gsl_matrix_set(J, i, 2, Ays*(xbs*xbs-1.)); + gsl_matrix_set(J, i, 1, y); // df/dA + gsl_matrix_set(J, i, 0, 1. / s); // df/dC + } + return GSL_SUCCESS; +} + +int gauss_fdf(const gsl_vector *parms, void *data, gsl_vector *f, gsl_matrix *J){ + gauss_f(parms, data, f); + gauss_df(parms, data, J); + return GSL_SUCCESS; +} + +// pseudorandom numbers generator initialisation +void urandom_ini(){ + double tt = dtime() * 1e6; + double mx = (double)LONG_MAX; + srand48((long)(tt - mx * floor(tt/mx))); +} + + +#define FIT(i) gsl_vector_get(s->x, i) +#define ERR(i) sqrt(gsl_matrix_get(covar,i,i)) +#define FN(i) gsl_vector_get(s->f, i) +/* + * Gaussian parameters calculation y=A/sqrt(2*pi*sigma^2) exp(-(x-x_0)^2/2/sigma^2), + * which approximates the points set pts + * Parameters A_, sigma_, x0_ may be NULL (if you don't need any of them) + */ +void gauss_fit(Points *pts, double *C_, double *A_, double *sigma_, double *x0_){ + // VVVV lower parameters may be formed as a structure to change as function argument + double + epsabs = 1e-8,// absolute error + epsrel = 1e-5,// relative error + chi_max = 0.01;// max chi value for iterations criteria + int max_iter = 300; // limit iterations number of gsl_multifit_fdfsolver + size_t N_MIN = 10; // minimum points for approximation + double x_init[4]; + // AAAA upper parameters may be formed as a structure to change as function argument +/* x_init, the best approximations: + * x0 - not far from real (the nearest is the better) + * sigma - not far from real (the nearest is the better) + * A - not large ~10 (it has a weak effect) + */ + const gsl_multifit_fdfsolver_type *T; + gsl_multifit_fdfsolver *s; + int status; + #ifdef EBUG + int appNo = 0; + #endif + int iter; + size_t i, j, n = pts->n, oldn; + const size_t p = 4; + gsl_matrix *covar = gsl_matrix_alloc (p, p); + #ifdef EBUG + double t0; + #endif + double *x, *y, *dy, chi, C, A, sigma, x0; + if(n < 1) return; + x = malloc(n * sizeof(double)); + y = malloc(n * sizeof(double)); + dy = malloc(n * sizeof(double)); + struct data d = {n, x, y, dy}; + gsl_multifit_function_fdf f; + gsl_vector_view xx = gsl_vector_view_array(x_init, p); + const gsl_rng_type *type; + gsl_rng *r; + + gsl_rng_env_setup(); + type = gsl_rng_default; + r = gsl_rng_alloc (type); + f.f = &gauss_f; + f.df = &gauss_df; + f.fdf = &gauss_fdf; + f.n = n; + f.p = p; + f.params = &d; + // fill data structure. Don't forget Okkam's razor!!! + { + Point *pt = pts->data; + double *px = x, *py = y, *pdy = dy, sum = 0.; + for(i = 0; i < n; i++, pt++){ + *pdy++ = 1.; // I have no idea what is it, so init by 1 + *px++ = pt->x; + *py++ = pt->y; + sum += pt->y; + //DBG("point %d: (%g, %g)", i, pt->x, pt->y); + } + // fill x_init: x0, sigma, C, A (it can be a funtion parameter) + x_init[3] = (*(--px) + *x) / 2.; + x_init[2] = fabs((*x - *px) / 4.); + x_init[0] = sum/(double)n; + x_init[1] = sum; + DBG("\nInitial parameters: x0=%.1f, sigma=%.1f, A=%.1f, C=%.1f", + x_init[3], x_init[2], x_init[1], x_init[0]); + } + T = gsl_multifit_fdfsolver_lmder; // or also gsl_multifit_fdfsolver_lmsder + s = gsl_multifit_fdfsolver_alloc(T, n, p); + #ifdef EBUG + t0 = dtime(); + #endif + do{ + double dof, tres, c; + DBG("\n************ Approximation %d ******************\n", appNo++); + iter = 0; + gsl_multifit_fdfsolver_set(s, &f, &xx.vector); + do{ + iter++; + status = gsl_multifit_fdfsolver_iterate(s); + if(status) + break; + status = gsl_multifit_test_delta(s->dx, s->x, epsabs, epsrel); + }while(status == GSL_CONTINUE && iter < max_iter); + DBG("time=%g\n", dtime()-t0); + gsl_multifit_covar(s->J, 0.0, covar); + chi = gsl_blas_dnrm2(s->f); + dof = n - p; + tres = chi; + c = chi / sqrt(dof); // GSL_MAX_DBL(1., chi / sqrt(dof)); + C = FIT(0), A = FIT(1), sigma = FIT(2), x0 = FIT(3); + DBG("Number of iteratons = %d\n", iter); + DBG("chi = %g, chi/dof = %g\n", chi, chi / sqrt(dof)); + DBG("C = %.5f +/- %.5f\n", C, c*ERR(0)); + DBG("A = %.5f +/- %.5f\n", A, c*ERR(1)); + DBG("sigma = %.5f +/- %.5f\n", sigma, c*ERR(2)); + DBG("x0 = %.5f +/- %.5f\n", x0, c*ERR(3)); + j = 0; + oldn = n; + if(c < chi_max) break; + // throw out bad (by chi) data + for(i = 0; i < n; i++){ + if(fabs(FN(i)) < tres){ + if(i != j){ + x[j] = x[i]; + y[j] = y[i]; + dy[j] = dy[i]; + } + j++; continue; + } + } + if(j != n){ + DBG("Chi tresholding %g, %zd points of %zd\n", tres, j, n); + n = j; + d.n = n; + } + }while(chi > chi_max && n != oldn && n > N_MIN); + if(C_) *C_ = C; + if(A_) *A_ = A; + if(sigma_) *sigma_ = sigma; + if(x0_) *x0_ = x0; + //printf ("status = %s\n", gsl_strerror (status)); + gsl_multifit_fdfsolver_free(s); + gsl_matrix_free(covar); + gsl_rng_free(r); + free(x); free(y); free(dy); +} +/* +int circle_f(const gsl_vector *parms, void *data, gsl_vector *f){ + size_t n = ((struct data *)data)->n; + double *x = ((struct data *)data)->x; + double *y = ((struct data *)data)->y; + //double *dy = ((struct data *)data)->dy; + double X = gsl_vector_get(parms, 0); + double Y = gsl_vector_get(parms, 1); + double R = gsl_vector_get(parms, 2); + double sign, Yi, DX; + size_t i; + for (i = 0; i < n; i++){ + sign = (y[i] > Y) ? 1. : -1.; + DX = x[i] - X; + // Model Yi = Yc +- sqrt[R^2-(x-Xc)^2] + Yi = Y + sign * sqrt(R*R - DX*DX); + gsl_vector_set(f, i, (Yi - y[i])); + } + return GSL_SUCCESS; +} + +int circle_df(const gsl_vector *parms, void *data, gsl_matrix *J){ + size_t n = ((struct data *)data)->n; + double *x = ((struct data *)data)->x; + double *y = ((struct data *)data)->y; + //double *dy = ((struct data *)data)->dy; + double X = gsl_vector_get(parms, 0); + double Y = gsl_vector_get(parms, 1); + double R = gsl_vector_get(parms, 2); + double sign, Yi, DX; + size_t i; + for(i = 0; i < n; i++){ + // Jacobian matrix J(i,j) = dfi / dxj, + // and the xj are the parameters (Xc, Yc, R) + DX = x[i] - X; + sign = (y[i] > Y) ? 1. : -1.; + Yi = sign / sqrt(R*R - DX*DX); + gsl_matrix_set(J, i, 2, R*Yi); // df/dR + gsl_matrix_set(J, i, 1, 1.); // df/dYc + gsl_matrix_set(J, i, 0, DX * Yi); // df/dXc + } + return GSL_SUCCESS; +} + +int circle_fdf(const gsl_vector *parms, void *data, gsl_vector *f, gsl_matrix *J){ + circle_f(parms, data, f); + circle_df(parms, data, J); + return GSL_SUCCESS; +} + +void circle_fit(struct data *d, double *Xcenter, + double *Ycenter, double *Radius){ + double + epsabs = 1e-8, + epsrel = 1e-5; + int max_iter = 30; + double x_init[3], c, chi; + const gsl_multifit_fdfsolver_type *T; + gsl_multifit_fdfsolver *s; + int status; + int iter = 0; + size_t n = d->n; + const size_t p = 3; + FNAME(); + gsl_matrix *covar = gsl_matrix_alloc (p, p); + gsl_multifit_function_fdf f; + gsl_vector_view xx = gsl_vector_view_array(x_init, p); + const gsl_rng_type *type; + gsl_rng *r; + gsl_rng_env_setup(); + type = gsl_rng_default; + r = gsl_rng_alloc(type); + f.f = &circle_f; + f.df = &circle_df; + f.fdf = &circle_fdf; + f.n = n; + f.p = p; + f.params = d; + T = gsl_multifit_fdfsolver_lmder; + s = gsl_multifit_fdfsolver_alloc(T, n, p); + gsl_multifit_fdfsolver_set(s, &f, &xx.vector); + x_init[0] = *Xcenter; + x_init[1] = *Ycenter; + x_init[2] = *Radius; + do{ + iter++; + status = gsl_multifit_fdfsolver_iterate(s); + if(status) break; + DBG("iter %d", iter); + status = gsl_multifit_test_delta(s->dx, s->x, epsabs, epsrel); + }while(status == GSL_CONTINUE && iter < max_iter); + DBG("iter end"); + gsl_multifit_covar(s->J, 0.0, covar); + chi = gsl_blas_dnrm2(s->f); + c = chi / sqrt((double)(n - p)); + *Xcenter = FIT(0); + *Ycenter = FIT(1); + *Radius = FIT(2); + DBG("Number of iteratons = %d\n", iter); + DBG("chi = %g, chi/dof = %g\n", chi, c); + DBG("Xc = %.5f +/- %.5f\n", *Xcenter, c*ERR(0)); + DBG("Yc = %.5f +/- %.5f\n", *Ycenter, c*ERR(1)); + DBG("R = %.5f +/- %.5f\n", *Radius, c*ERR(2)); + gsl_multifit_fdfsolver_free(s); + gsl_matrix_free(covar); + gsl_rng_free(r); +} +*/ + +#else // GSL not found + +//double gaussian_pt(double C, double A, double sigma, double x0, double x){ +// GSLERR; return A+C+sigma+x+x0;} +void gaussian_v(double C __attribute__((unused)), double A __attribute__((unused)), + double sigma __attribute__((unused)), double x0 __attribute__((unused)), + Points *pts __attribute__((unused))){GSLERR;} +void gauss_fit(Points *pts __attribute__((unused)), double *C_ __attribute__((unused)), + double *A_ __attribute__((unused)), double *sigma_ __attribute__((unused)), + double *x0_ __attribute__((unused))){ GSLERR;} +#endif // GSL_FOUND diff --git a/src/gtk.c b/src/gtk.c new file mode 100644 index 0000000..c860139 --- /dev/null +++ b/src/gtk.c @@ -0,0 +1,1204 @@ +// gtk.c - main funtions to work with windows +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "gtk.h" +#include "tracking.h" +#include "opengl.h" +#include "ui.h" +#include "imtools.h" +#include "contours.h" +#include "fits.h" +#include "open_dialog.h" +#include "fitsheaders.h" +#include "terrain.h" +#include "filelist.h" +#include + +Window *mainWindow; + +double X0, Y0, lX0, lY0; // started coordinates of click, layer coordinates at click +// the longer key is pressed, the larger value of key_acceleration (3D moving): +double key_acceleration = 1.; +// multipliers for zoom in/out +const double ZOOM_NEAR = 1.1, ZOOM_FAR = 1./1.1; + +void run_modal_window(GtkWindow *w, Window *parent){ + gtk_window_set_transient_for(w, GTK_WINDOW(parent->window)); + gtk_window_set_modal(w, TRUE); + gtk_window_set_position(w, GTK_WIN_POS_MOUSE); + gtk_widget_show_all(GTK_WIDGET(w)); +} + +gint run_modal_dialog(GtkDialog *dialog, Window *parent){ + run_modal_window(GTK_WINDOW(dialog), parent); + return gtk_dialog_run(GTK_DIALOG(dialog)); +} + +/* + * Error messages output + * text - message text + */ +void g_err(gchar *text){ + GtkMessageDialog *dialog; + g_printerr(_("Error: %s\n"), text); + dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", text)); + run_modal_dialog(GTK_DIALOG(dialog), mainWindow); + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + +void show_about_dialog(){ + GtkAboutDialog *about = GTK_ABOUT_DIALOG(gtk_about_dialog_new()); + const gchar *author[] = { "Edward V. Emelianov , ", NULL }; + gtk_about_dialog_set_program_name(about, "FITS view & Hartman data reduction"); + gtk_about_dialog_set_version(about, PACKAGE_VERSION); + gtk_about_dialog_set_copyright(about, "Copyright 2012 Edward V. Emelianov"); + gtk_about_dialog_set_website(about, "http://eddyem.narod.ru"); + gtk_about_dialog_set_authors(about, author); + run_modal_dialog(GTK_DIALOG(about), mainWindow); + gtk_widget_destroy(GTK_WIDGET(about)); +} + +/* + * exit + */ +void chk_quit(){ + gtk_main_quit(); + // uncomment next, when autosaving of settings will be released + /*GtkMessageDialog *dialog; + dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + _("You sure, you want exit?"))); + gint ans = run_modal_dialog(GTK_DIALOG(dialog), mainWindow); + gtk_widget_destroy(GTK_WIDGET(dialog)); + if(ans == GTK_RESPONSE_YES){ + gtk_main_quit(); + }*/ +} + +void change_window_filename(gchar *filename, Window *window){ + gchar *title, *bsnm; + if(!filename) return; + g_free(window->image->filename); + bsnm = g_path_get_basename(filename); + if(window == mainWindow) + title = g_strdup_printf("%s (main) - %s", PRGNAME, bsnm); + else + title = g_strdup_printf("%s - %s", PRGNAME, bsnm); + gtk_window_set_title(GTK_WINDOW(window->window), title); + window->image->filename = bsnm; + g_free(title); +} + +void change_image(gchar *filename, Window *window){ + if(!filename) return; + if(try_open_file(filename, window->image)){ + change_window_filename(filename, window); + }else + change_window_filename("", window); + window->context->current_image = window->image; + gen_texture(window->image, window, FALSE); + window->context->transformMask = TRANS_NONE; +} + +// "save as" filename choosing dialog +gchar *get_save_filename(Window *window){ + FNAME(); + GtkFileChooser *dialog; + gchar *filename = NULL, *retfilename = NULL; + gint response; + dialog = GTK_FILE_CHOOSER( + gtk_file_chooser_dialog_new("Save File", NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL)); + if(saved_path) + gtk_file_chooser_set_current_folder(dialog, saved_path); + gtk_file_chooser_set_current_name(dialog, "new_fits_file.fit"); + gtk_file_chooser_set_do_overwrite_confirmation(dialog, TRUE); + response = run_modal_dialog(GTK_DIALOG(dialog), window); + if(response == GTK_RESPONSE_ACCEPT){ + filename = gtk_file_chooser_get_filename(dialog); + } + gtk_widget_destroy(GTK_WIDGET(dialog)); + if(filename){ + if(!get_ext(filename)) // There wasn't .fit suffix + retfilename = g_strdup_printf("!%s.fit", filename); // to rewrite file, if needed + else + retfilename = g_strdup_printf("!%s", filename); + g_free(filename); + } + return retfilename; +} + +// get choosed filename to open and fills filelist of current directory +gchar *get_open_filename(Window *window){ + FNAME(); + GtkFileChooser *dialog; + gchar *filename = NULL; + gint response; + dialog = open_fits_dialog(); + response = run_modal_dialog(GTK_DIALOG(dialog), window); + g_free(saved_path); + saved_path = gtk_file_chooser_get_current_folder(dialog); + if(response == GTK_RESPONSE_ACCEPT){ + filename = gtk_file_chooser_get_filename(dialog); + DBG("open file %s", filename); + gint nfiles = fill_filelist(filename, window); + DBG("found %d files", nfiles); + g_free(filename); filename = NULL; + if(nfiles > 0 && window->files.list_current) + filename = window->files.list_current->data; + else + g_err(_("Can't read fits file")); + } + gtk_widget_destroy(GTK_WIDGET(dialog)); + return filename; +} + +void save_as(Window *window){ + gchar *filename = NULL; + if((filename = get_save_filename(window))){ + change_window_filename(filename, window); + if(!writefits(filename, window->image)) + g_err(_("Can't save file")); + g_free(filename); + } +} +void open_file(Window *window){ + gchar *filename; + if((filename = get_open_filename(window))) + change_image(filename, window); +} +void open_in_new(Window *window){ + gchar *filename; + Window *newwin = init_window(window, OPENGL_WINDOW); + if(!(filename = get_open_filename(newwin))) + destroy_window(newwin); + else + change_image(filename, newwin); +} +void open_in_3D(Window *window){ + Window *terra = init_window(window, GL3D_WINDOW); + terrain_3D(terra, 0.,0.,0.,0., TRUE); +} + +void close_subwindow(Window *w){ + gtk_widget_hide(w->window); +} + +// Refreshing State fields of status bar +void refresh_state(Window *window){ + //FNAME(); + Context *c = window->context; + gchar buf[32]; + gchar draw[] = {'T', 'S', 'Q'}; + gchar track[] = {'A', 'V', 'H', 'D'}; + gchar graph[] = {'G', 'H'}; + if(window->id != GRAPH_WINDOW){ + g_sprintf(buf, "dX=%.1f, dY=%.1f", window->move.x, window->move.y); + set_status_text(StatusAdd, buf, window); + g_sprintf(buf, "%c %c %s (%.1f%%) Z=%.1f", + draw[c->drawingMode], track[c->trackingMode], + (c->transformMask ? "tr" : ""), + 100./window->Daspect*window->Zoom, window->Zoom); + set_status_text(StatusState, buf, window); + }else{ + g_sprintf(buf, "%c %s", + graph[c->graphMode], (isYlog(window) ? "log" : "lin")); + set_status_text(StatusState, buf, window); + } +} + +void set_status_text(guint barName, gchar *text, Window *window){ + if(!window || barName >= window->statusBlocks) return; + gtk_entry_set_text(window->SBars[barName], text); +} + +/* + * This three short functions need to have ability to change scale + * of axe Y from menu + */ +gboolean redraw_D(Window *window){ + Gconfigure(window); + gdk_window_invalidate_rect(window->drawingArea->window, NULL, FALSE); + refresh_state(window); + return TRUE; +} +gboolean log_scale(Window *window){ // press 'e' in graph window + window->context->graphYaxis |= + (window->context->graphMode == GR_GRAPH) ? Y_LOGGRAPH : Y_LOGHIST; + redraw_D(window); + return TRUE; +} +gboolean lin_scale(Window *window){ // press 'l' in graph window + window->context->graphYaxis &= (window->context->graphMode == GR_GRAPH) ? + ~Y_LOGGRAPH : ~Y_LOGHIST; + redraw_D(window); + return TRUE; +} + +// Show histogram in chart window +void show_histogram(Window *window){ + IMAGE *ima; + if(!window->context->current_image || !window->context->current_image->data){ + window->context->current_image = window->image; + if(!window->image || !window->image->data) return; + } + ima = window->context->current_image; + if(!ima->stat.histogram.data) + if(!make_histogram(ima)) + return; + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(window->graphWindow->LinLogMenu[isYlog(window)]), TRUE); + gtk_widget_show_all(window->graphWindow->window); + do_histogram(window); +} + +void correct_zoom_by_move(Window *window, double Zoom_factor, double *x, double *y){ + double xx, yy, Xc, Yc, ZZ; + if(!x) xx = (double)window->drawingArea->allocation.width / 2.; + else xx = *x; + if(!y) yy = (double)window->drawingArea->allocation.height / 2.; + else yy = *y; + //DBG("x=%g, y=%g",xx,yy); + conv_mouse_to_image_coords(xx, yy, &Xc, &Yc, window); + Xc -= window->texture->w / 2.; Yc -= window->texture->h / 2.; + //DBG("Xc=%g, Yc=%g, ZF=%g",Xc, Yc, Zoom_factor); + // It's amazing (if U don't know what float is), but Zoom*(Zoom_factor - 1) isn't the same + ZZ = window->Zoom*Zoom_factor - window->Zoom; + window->move.x -= Xc * ZZ; + window->move.y -= Yc * ZZ; + //DBG("moveX=%g, moveY=%g\n", window->move.x, window->move.y); + window->Zoom *= Zoom_factor; +} + +void zoom_near(Window *window){ // run for '+' key + correct_zoom_by_move(window, ZOOM_NEAR, NULL, NULL); + force_redraw(window->drawingArea); +} +void zoom_far(Window *window){ // run for '-' key + correct_zoom_by_move(window, ZOOM_FAR, NULL, NULL); + force_redraw(window->drawingArea); +} + +void zoom_selection(Window *window){ // function called after frame selection + double x0,y0, x,y, zX, zY, ZF; + get_sel_region(&x0, &y0, &x, &y); + zX = (double)window->drawingArea->allocation.width / abs(x-x0); + zY = (double)window->drawingArea->allocation.height / abs(y-y0); + ZF = (zX < zY) ? zX : zY; + x = (x + x0) / 2.; + y = (y + y0) / 2.; + correct_zoom_by_move(window, ZF, &x, &y); + force_redraw(window->drawingArea); +} + +void show_3D_terrain(Window *window){ + double imX0, imY0, imX, imY; + double x0,y0, x,y; + get_sel_region(&imX0, &imY0, &imX, &imY); + conv_mouse_to_image_coords(imX0, imY0, &x0, &y0, window); + conv_mouse_to_image_coords(imX, imY, &x, &y, window); + Window *terra = init_window(window, GL3D_WINDOW); + terrain_3D(terra, x0,y0, x,y, FALSE); +} + +void full_3D_terrain(Window *window){ + Window *terra = init_window(window, GL3D_WINDOW); + terrain_3D(terra, 0,0, window->image->width,window->image->height, FALSE); +} + +gboolean is_tracking = FALSE; // TRUE is cutting mode +gboolean zoom_frame = FALSE; // TRUE is zoom frame mode +gboolean select_3D = FALSE; // TRUE is 3D frame selection mode +/* + * Processing of mouse button press event + * 1. Main window + */ +gboolean press_main_mbtn(Window *window, GdkEventButton *event){ + Context *c = window->context; + switch(event->button){ + case 1: // Left button? Start cutting + // Redraw window to delete old garbage + if(!window->image->data) break; + switch(c->drawingMode){ + case DRAW_TRACK: + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(window->graphWindow->LinLogMenu[isYlog(window)]), TRUE); + gtk_widget_show_all(window->graphWindow->window); + c->graphMode = GR_GRAPH; + if(event->state & GDK_CONTROL_MASK) + c->trackingMode = TRACK_DIAG; + else if(event->state & GDK_SHIFT_MASK) + c->trackingMode = TRACK_VERT; + else if(event->state & GDK_MOD1_MASK) + c->trackingMode = TRACK_HORIZ; + else + c->trackingMode = TRACK_ANY; + case DRAW_QUAD: // There's no break here and it's not an error! + is_tracking = TRUE; + do_tracking(event->x, event->y, TRUE, window); + break; + case DRAW_SELECTION: + if(window->image->imagetype == HOUGH){ + double R, phi; + get_houg_line(window, event->x, event->y, &phi, &R); + gchar *text = g_strdup_printf("%s: R=%.0f, phi=%.1f", + _("Selected line"), R, phi); + set_status_text(StatusText, text, window); + g_free(text); + } + else + pickRegion(event->x, event->y); + break; + } + gdk_window_invalidate_rect(window->drawingArea->window, + &window->drawingArea->allocation, FALSE); + break; + case 2: // Middle button moves an picture + X0 = event->x; Y0 = event->y; + lX0 = window->move.x; lY0 = window->move.y; + break; + case 3: + break; + } + refresh_state(window); + return FALSE; +} +// 2. Auxiliary window +gboolean press_sub_mbtn(Window *window, GdkEventButton * event){ + switch(event->button){ + case 1: + is_tracking = graph_mouse_btn((int)event->x, window); + break; + case 2: + break; + case 3: + break; + } + refresh_state(window); + return FALSE; +} + +/* + * Reaction for button release + * 1. Main window + */ +gboolean release_main_mbtn(Window *window, GdkEventButton * event){ + switch(event->button){ + case 1: + // there was something selected by frame + if(is_tracking && window->context->drawingMode == DRAW_QUAD){ + if(zoom_frame){ // It was a zoom selection + zoom_selection(window); + }else if(select_3D){ + show_3D_terrain(window); + } + } + break; + case 2: + break; + case 3: + break; + } + return FALSE; +} + +// 2. Auxiliary window +gboolean release_sub_mbtn(Window *window, GdkEventButton * event){ + switch(event->button){ + case 1: + switch(window->context->graphMode){ + case GR_GRAPH: + break; + case GR_HISTOGRAM: + gen_tres_texture(window->parent); + break; + } + break; + } + return FALSE; +} + +/* + * Functions of menus processing + */ +void show_hist(Window *window){ // 'h' in main window + window->context->graphMode = GR_HISTOGRAM; + show_histogram(window); +} +void spots_coords(Window *window){ // 'c' + get_spots_coords(window, window->image_transformed); +} + +void set_spots_parameters(Window *window){ // 'alt+s' + GtkBuilder *builder; + GError *err = NULL; + GtkWidget *dialog, *btn; + void getval(GtkSpinButton *spin_button, int *val){ + *val = gtk_spin_button_get_value_as_int(spin_button); + DBG("val = %d", *val); + } + void chk_tres_vals(GtkWidget *wnd){ + GtkMessageDialog *dialog; + gchar *text; + gboolean W, H; + H = (Global->maxSpotH > Global->minSpotH); + W = (Global->maxSpotW > Global->minSpotW); + if(H && W){ // OK + gtk_widget_destroy(wnd); + return; + } + if(!(H || W)) + text = _("Both max values are less than min"); + else{ + if(!H) + text = _("Max height value less than min"); + else + text = _("Max width value less than min"); + } + dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + text)); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(wnd)); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(GTK_WIDGET(dialog)); + } + void conn_sig(gchar *object, int *val){ + GtkAdjustment *adj; + GtkWidget *spin = GTK_WIDGET(gtk_builder_get_object(builder, object)); + g_signal_connect(spin, "value-changed", G_CALLBACK(getval), val); + adj = (GtkAdjustment*) gtk_adjustment_new(*val, 1.0, 4000.0, 1.0, 0.0, 0.0); + gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(spin), adj); + } + builder = gtk_builder_new(); + if(!gtk_builder_add_from_string(builder, ui, -1, &err)){ + g_warning("%d, %s\n", err->code, err->message); + exit(-1); + } + dialog = GTK_WIDGET(gtk_builder_get_object(builder, "TresWindow")); + conn_sig("MinWidth", &Global->minSpotW); + conn_sig("MaxWidth", &Global->maxSpotW); + conn_sig("MinHeight", &Global->minSpotH); + conn_sig("MaxHeight", &Global->maxSpotH); + btn = GTK_WIDGET(gtk_builder_get_object(builder, "TresCancel")); + g_signal_connect_swapped(btn, "clicked", + G_CALLBACK(gtk_widget_destroy), dialog); + btn = GTK_WIDGET(gtk_builder_get_object(builder, "TresOK")); + g_signal_connect_swapped(btn, "clicked", + G_CALLBACK(chk_tres_vals), dialog); + run_modal_window(GTK_WINDOW(dialog), window); +} + +void circle_params(Window *window){ // 'x' + get_circles_params(window, window->image_transformed); +} +void spot_ident(Window *window){ // 's' + window->context->visualMode |= SHOW_POTSELECT; + window->context->drawingMode = DRAW_SELECTION; + force_redraw(window->drawingArea); +} +void save_spots(Window *window){ + if(!window->spots){ + g_err(_("No recognized spots")); + return; + } + GtkFileChooser *dialog; + gchar *filename = NULL, *tmp = NULL; + gint response; + dialog = GTK_FILE_CHOOSER( + gtk_file_chooser_dialog_new("Save File", NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL)); + if(saved_path) + gtk_file_chooser_set_current_folder(dialog, saved_path); + filename = g_strdup_printf("%s", window->image->filename); + DBG("saved path: %s; filename: %s", saved_path, filename); + if( (tmp = strrchr(filename, '.')) ) *tmp = 0; + tmp = g_strdup_printf("%s.spotlist", filename); + gtk_file_chooser_set_current_name(dialog, tmp); + g_free(tmp); + g_free(filename); filename = NULL; + gtk_file_chooser_set_do_overwrite_confirmation(dialog, TRUE); + response = run_modal_dialog(GTK_DIALOG(dialog), window); + if(response == GTK_RESPONSE_ACCEPT){ + filename = gtk_file_chooser_get_filename(dialog); + } + gtk_widget_destroy(GTK_WIDGET(dialog)); + if(!filename) return; + spots_save(window->spots, filename); + g_print("%s saved", filename); + tmp = g_path_get_basename(filename); + g_free(filename); + filename = g_strdup_printf("%s saved", tmp); + set_status_text(StatusText, filename, window); + g_free(filename); g_free(tmp); +} +void draw_tracks(Window *window){ // 't' + window->context->drawingMode = DRAW_TRACK; + window->context->visualMode &= ~SHOW_POTSELECT; +} +void zoom_restore(Window *window){ // '0' + window->Zoom = 1.; + window->move.x = 0.; + window->move.y = 0.; + window->Zangle = 0.; + window->Xangle = 0.; + force_redraw(window->drawingArea); +} +void zooom_qframe(Window *window){ // 'z' + window->context->drawingMode = DRAW_QUAD; + zoom_frame = TRUE; +} +void select_terrain(Window *window){ // '3' + window->context->drawingMode = DRAW_QUAD; + select_3D = TRUE; +} +void filter(Window *window){ // 'f' +/* Filter *f; + INIT(f, Filter);*/ +/* f->FilterType = MEDIAN; + f->sx=f->sy=9.; + f->w = f->h = 3; + filter_image(window, f);*/ +/* f->FilterType = SIMPLEGRAD; + filter_image(window, f); +*/ + find_contours(window, 16, LOG); +/* f->FilterType = STEP; + f->w = 16; + f->h = LOG; + filter_image(window, f);*/ + /*f->FilterType = LAPGAUSS; + f->w = f->h = 256; + f->sx = f->sy = 5.f; + filter_image(window, f);*/ +} + +/* + * Processing of a key pressing events + * 1. Main window + */ +gboolean press_main_key(Window *window, GdkEventKey * event){ + //FNAME(); + gboolean ret = TRUE; + if(event->keyval == GDK_Escape){ + if(zoom_frame || select_3D){ + window->context->drawingMode = DRAW_TRACK; + zoom_frame = FALSE; // ESC cansels zoom + select_3D = FALSE; + } + return TRUE; + } + if(event->state & (GDK_CONTROL_MASK)){ // with ctrl + switch(event->keyval){ + default: ret = FALSE; + } + } + else{ + switch(event->keyval){ + case GDK_Page_Up: case GDK_Up: case GDK_Left: + if(window->files.list_length > 1) + change_image(get_filename(PREVIOUS, window), window); + break; + case GDK_Page_Down: case GDK_Down: case GDK_Right: + if(window->files.list_length > 1) + change_image(get_filename(NEXT, window), window); + break; + case GDK_Home: + if(window->files.list_length > 1) + change_image(get_filename(FIRST, window), window); + break; + case GDK_End: + if(window->files.list_length > 1) + change_image(get_filename(LAST, window), window); + break; + default: ret = FALSE; + } + } + //refresh_state(window); + return ret; +} +// 2. Auxiliary window +/* +gboolean press_sub_key(Window *window, GdkEventKey * event){ + //FNAME(); + gboolean ret = TRUE; + switch(event->keyval){ + default: ret = FALSE; + } + return ret; +} +*/ + +/* +gboolean release_key(GtkWidget * none, GdkEventKey * event){ + //DBG("Release key %d\n", event->keyval); + return FALSE; +}*/ + +// Scroll +gboolean main_scroll(Window *window, GdkEventScroll *event){ + gboolean ret = FALSE; + if(event->state & (GDK_CONTROL_MASK)){ + switch(event->direction){ + case GDK_SCROLL_UP: + zoom_far(window); + ret = TRUE; + break; + case GDK_SCROLL_DOWN: + zoom_near(window); + ret = TRUE; + break; + default: break; + } + } + return ret; +} + +/* + * Mouse move events + * 1. Main window + */ +gboolean main_motion(Window *window, GdkEventMotion * event){ + double x,y; + gchar buf[40]; + conv_mouse_to_image_coords(event->x, event->y, &x, &y, window); + g_snprintf(buf, 39, "(%.1f, %.1f)", x, y); + set_status_text(StatusCoords, buf, window); + if(event->state & GDK_BUTTON1_MASK){ + switch(window->context->drawingMode){ + case DRAW_TRACK: // In tracking mode show track + case DRAW_QUAD: // the same in frame selection mode + if(is_tracking) do_tracking(event->x, event->y, FALSE, window); + break; + } + } + else if(event->state & GDK_BUTTON2_MASK){ + window->move.x = lX0 + (event->x - X0) * window->Daspect; + window->move.y = lY0 + (Y0 - event->y) * window->Daspect; + force_redraw(window->drawingArea); + } + else if(event->state & GDK_BUTTON3_MASK){ + } + return TRUE; +} + +// 2. Auxiliary window +gboolean sub_motion(Window *window, GdkEventMotion * event){ + double x = event->x / xScale; + double y = gYmax - event->y/yScale; + double xx, yy; + gchar buf[40]; + get_image_crds(x, &xx, &yy); + if(isYlog(window)) y = exp(y); + g_snprintf(buf, 39, "(%.3f, %.3f) I(%.1f, %.1f)", x, y, xx, yy); + set_status_text(StatusCoords, buf, window); + if(event->state & GDK_BUTTON1_MASK){ + if(is_tracking) graph_mouse_btn((int)event->x, window); + } + return TRUE; +} + +// 3. OpenGL window +void open3D(Window *window){ + terrain_3D(window, 0.,0.,0.,0., TRUE); // Show open dialog & load new picture +} +void rotXp(Window *window){ + window->Xangle += key_acceleration; + if(window->Xangle > 360.) window->Xangle -= 360.; + force_redraw(window->drawingArea); +} +void rotXm(Window *window){ + window->Xangle -= key_acceleration; + if(window->Xangle < 0.) window->Xangle += 360.; + force_redraw(window->drawingArea); +} +void rotZp(Window *window){ + window->Zangle += key_acceleration; + if(window->Zangle > 360.) window->Zangle -= 360.; + force_redraw(window->drawingArea); +} +void rotZm(Window *window){ + window->Zangle -= key_acceleration; + if(window->Zangle < 0.) window->Zangle += 360.; + force_redraw(window->drawingArea); +} +void moveXp(Window *window){ + window->move.x += key_acceleration; + force_redraw(window->drawingArea); +} +void moveXm(Window *window){ + window->move.x -= key_acceleration; + force_redraw(window->drawingArea); +} +void moveYp(Window *window){ + window->move.y += key_acceleration; + force_redraw(window->drawingArea); +} +void moveYm(Window *window){ + window->move.y -= key_acceleration; + force_redraw(window->drawingArea); +} +void game_move(Window *window, double where){ + double lx, ly, lz, za = window->Zangle, xa = window->Xangle; + lx = cos(za)*sin(xa); + ly = -sin(za); + lz = -cos(za)*cos(xa); + double delta = where * key_acceleration; + window->move.x += delta*lx; + window->move.y += delta*ly; + window->Zoom += delta*lz/window->image->height; +} +void moveZp(Window *window){ + if(window->context->drawingMode == GAME_MODE) + game_move(window, 1.); + else + window->Zoom *= 1.1; + force_redraw(window->drawingArea); +} +void moveZm(Window *window){ + if(window->context->drawingMode == GAME_MODE) + game_move(window, -1.); + else + window->Zoom /= 1.1; + force_redraw(window->drawingArea); +} +void game_mode(Window *window){ + if(window->context->drawingMode == GAME_MODE){ + window->context->drawingMode = DRAW_TRACK; + gdk_pointer_ungrab(GDK_CURRENT_TIME); + gtk_window_unfullscreen(GTK_WINDOW(window->window)); + gdk_window_set_cursor(GDK_WINDOW(window->drawingArea->window), NULL); + } + else{ + window->context->drawingMode = GAME_MODE; + gdk_pointer_grab(window->drawingArea->window, TRUE, 0, + window->drawingArea->window, NULL, + GDK_CURRENT_TIME); + gtk_window_fullscreen(GTK_WINDOW(window->window)); + GdkCursor *cursor = gdk_cursor_new(GDK_BLANK_CURSOR); + gdk_window_set_cursor(GDK_WINDOW(window->drawingArea->window), cursor); + } + zoom_restore(window); +} +gfloat oldXbtn, oldYbtn; +gint oldXscreen, oldYscreen; +gboolean CursorWasMoved = TRUE; +gboolean press_ogl_mbtn(Window *window, GdkEventButton *event){ + if(window->context->drawingMode == GAME_MODE && + event->button == 1){ + GdkDisplay *disp = gdk_display_get_default(); + GdkScreen* screen = gdk_display_get_default_screen(disp); + oldXscreen = gdk_screen_get_width(screen) / 2; + oldYscreen = gdk_screen_get_height(screen) / 2; + gdk_display_warp_pointer(disp, screen, + oldXscreen, oldYscreen); + CursorWasMoved = TRUE; + } + return FALSE; +} +gboolean release_ogl_mbtn(Window *window __attribute__((unused)), + GdkEventButton *event __attribute__((unused))){ + return FALSE; +} +/* + * Move mouse in 3D mode + * !!! In game mode angles measures in radians, not degrees !!! + */ +gboolean ogl_motion(Window *window, GdkEventMotion * event){ + if(window->context->drawingMode == GAME_MODE + && event->state & GDK_BUTTON1_MASK){ + if(CursorWasMoved){ + CursorWasMoved = FALSE; + oldXbtn = event->x; + oldYbtn = event->y; + return FALSE; + } + window->Xangle += (event->x - oldXbtn)/1000.; + window->Zangle += (event->y - oldYbtn)/1000.; + GdkDisplay *disp = gdk_display_get_default(); + gdk_display_warp_pointer(disp, gdk_display_get_default_screen(disp), + oldXscreen, oldYscreen); + CursorWasMoved = TRUE; + force_redraw(window->drawingArea); + } + return FALSE; +} +gboolean reset_accel(){ + key_acceleration = 1.; + return FALSE; +} +gboolean press_ogl_key(Window *window, GdkEventKey * event){ + static gint tag = 0; + if(tag) gtk_timeout_remove(tag); + tag = gtk_timeout_add(100, reset_accel, NULL); + key_acceleration += 1.; + switch(event->keyval){ + case GDK_Left: + moveXp(window); // Move left - picture moves to the right + break; + case GDK_Right: + moveXm(window); + break; + case GDK_Up: + moveZp(window); + break; + case GDK_Down: + moveZm(window); + break; + default: + return FALSE; + } + return TRUE; +} +gboolean release_ogl_key(Window *window __attribute__((unused)), + GdkEventKey *event __attribute__((unused))){ + return FALSE; +} +gboolean ogl_scroll(Window *window __attribute__((unused)), + GdkEventScroll *event __attribute__((unused))){ + return FALSE; +} + +/* + * Redefine rulers limits, x0 & xm are the limits of horizontal ruler + * y0 & ym - vertical + * hrule, vrule - appropriate rulers + */ +void set_rules(double x0, double y0, double xm, double ym, + GtkWidget *hrule, GtkWidget *vrule){ + double yy = (y0 < 0.) ? 0 : y0; + gtk_ruler_set_range(GTK_RULER (hrule), x0, xm, 0., xm); + gtk_ruler_set_range(GTK_RULER (vrule), y0, ym, yy, ym); +} +void set_Drulers(double x0, double y0, double xm, double ym, Window *window){ + set_rules(-x0, ym, xm, -y0, window->hRule, window->vRule); +} +void set_Grulers(double y0, double xm, double ym, Window *window){ + //DBG("gXm=%g, gY0=%g, gYm=%g\n", xm, y0, ym); + set_rules(0., ym, xm, y0, window->hRule, window->vRule); +} + +void destroy_window(Window* window){ + FNAME(); + if(!window) return; + if(window->id == GRAPH_WINDOW) + window->parent->graphWindow = NULL; + else{ + destroy_window(window->graphWindow); + _FREE(window->context); + } + if(window->id == GL3D_WINDOW) + freeGLmemory(window); + if(window->image){ + if(window->id == GL3D_WINDOW){ + window->image->cLevels = NULL; + } + destroy_image(window->image); + } + if(window->image_transformed) + destroy_image(window->image_transformed); + _FREE(window->texture); + spots_free(&window->spots); + free_filelist(window); + gtk_widget_destroy(window->window); + free(window); +} + +/* + * Signal handler structure definition + * array Sighandler[] should end by NULL element + */ +typedef struct{ + gchar *elementName; // name of element + gchar *sigName; // signal name + GCallback sigHandler; // function - handler, a kind of void f(Window* w) +} Sighandler; +// Signals for graph window +Sighandler graphSigHandler[] = { + {"graphCloseMenuItem", "activate", G_CALLBACK(close_subwindow)}, + {"graphGaussMenuItem", "activate", G_CALLBACK(fit_gaussian)}, + {"graphLinMenuItem", "activate", G_CALLBACK(lin_scale)}, + {"graphLogMenuItem", "activate", G_CALLBACK(log_scale)}, + {"graphArea", "expose-event", G_CALLBACK(Gexpose)}, + {"graphArea", "configure-event", G_CALLBACK(Gconfigure)}, + {NULL, "button-press-event", G_CALLBACK(press_sub_mbtn)}, + {NULL, "button-release-event", G_CALLBACK(release_sub_mbtn)}, + {NULL, "motion-notify-event", G_CALLBACK(sub_motion)}, +// {NULL, "key-press-event", G_CALLBACK(press_sub_key)}, + {NULL, NULL, NULL} +}; +// Signals for window with picture +Sighandler mainSigHandler[] = { + {"mainOpenMenuItem", "activate", G_CALLBACK(open_file)}, + {"mainSaveAsMenuItem", "activate", G_CALLBACK(save_as)}, + {"mainOpenInNewMenuItem", "activate", G_CALLBACK(open_in_new)}, + {"mainOpenIn3DMenuItem", "activate", G_CALLBACK(open_in_3D)}, + {"mainHistMenuItem", "activate", G_CALLBACK(show_hist)}, + {"mainHeadersMenuItem", "activate", G_CALLBACK(edit_headers)}, + {"mainSpotsidentMenuItem", "activate", G_CALLBACK(spot_ident)}, + {"mainSaveSpotsMenuItem", "activate", G_CALLBACK(save_spots)}, + {"mainTrackMenuItem", "activate", G_CALLBACK(draw_tracks)}, + {"main3DviewFrameMenuItem", "activate", G_CALLBACK(select_terrain)}, + {"main3DviewFullMenuItem", "activate", G_CALLBACK(full_3D_terrain)}, + {"mainZoomframeMenuItem", "activate", G_CALLBACK(zooom_qframe)}, + {"mainZoomrestoreMenuItem", "activate", G_CALLBACK(zoom_restore)}, + {"mainZoomnearMenuItem", "activate", G_CALLBACK(zoom_near)}, + {"mainZoomfarMenuItem", "activate", G_CALLBACK(zoom_far)}, + {"mainSpotsParamMenuItem", "activate", G_CALLBACK(set_spots_parameters)}, + {"mainFindSpotsMenuItem", "activate", G_CALLBACK(spots_coords)}, + {"mainHartmannMenuItem", "activate", G_CALLBACK(hartmann_spots)}, + {"mainCirclesMenuItem", "activate", G_CALLBACK(circle_params)}, + {"mainHoughMenuItem", "activate", G_CALLBACK(hough_lines)}, + {"mainFilterMenuItem", "activate", G_CALLBACK(filter)}, +// {"", "activate", G_CALLBACK()}, + {NULL, "button-press-event", G_CALLBACK(press_main_mbtn)}, + {NULL, "button-release-event", G_CALLBACK(release_main_mbtn)}, + {NULL, "motion-notify-event", G_CALLBACK(main_motion)}, + {NULL, "key-press-event", G_CALLBACK(press_main_key)}, + {NULL, "scroll-event", G_CALLBACK(main_scroll)}, + {NULL, NULL, NULL} +}; +// Signals for 3D window +Sighandler oglSigHandler[] = { + {"oglOpenMenuItem", "activate", G_CALLBACK(open3D)}, + {"oglCloseMenuItem", "activate", G_CALLBACK(destroy_window)}, + {"oglZoomrestoreMenuItem", "activate", G_CALLBACK(zoom_restore)}, + {"oglXAplusMenuItem", "activate", G_CALLBACK(rotXp)}, + {"oglXAminusMenuItem", "activate", G_CALLBACK(rotXm)}, + {"oglZAplusMenuItem", "activate", G_CALLBACK(rotZp)}, + {"oglZAminusMenuItem", "activate", G_CALLBACK(rotZm)}, + {"oglXplusMenuItem", "activate", G_CALLBACK(moveXp)}, + {"oglXminusMenuItem", "activate", G_CALLBACK(moveXm)}, + {"oglYplusMenuItem", "activate", G_CALLBACK(moveYp)}, + {"oglYminusMenuItem", "activate", G_CALLBACK(moveYm)}, + {"oglZplusMenuItem", "activate", G_CALLBACK(moveZp)}, + {"oglZminusMenuItem", "activate", G_CALLBACK(moveZm)}, + {"oglGameModeMenuItem", "toggled", G_CALLBACK(game_mode)}, + {NULL, "button-press-event", G_CALLBACK(press_ogl_mbtn)}, + {NULL, "button-release-event", G_CALLBACK(release_ogl_mbtn)}, + {NULL, "motion-notify-event", G_CALLBACK(ogl_motion)}, + {NULL, "key-press-event", G_CALLBACK(press_ogl_key)}, + {NULL, "key-release-event", G_CALLBACK(release_ogl_key)}, + {NULL, "scroll-event", G_CALLBACK(ogl_scroll)}, + {NULL, NULL, NULL} +}; + +inline GtkWidget *initA(gchar *prefix, GtkBuilder *builder){ + gchar text[32]; + g_sprintf(text, "%sArea", prefix); + GtkWidget *A = GTK_WIDGET(gtk_builder_get_object(builder, text)); + gtk_widget_set_events(A, GDK_EXPOSURE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK| + GDK_BUTTON_RELEASE_MASK); + return A; +} +inline GtkWidget *initR(GtkWidget *area, gchar *name, GtkBuilder *builder){ + GtkWidget *R = GTK_WIDGET(gtk_builder_get_object(builder, name)); + g_signal_connect_swapped(area, "motion-notify-event", + G_CALLBACK(EVENT_METHOD(R, motion_notify_event)), R); + return R; +} + +/* + * Window initialisation + * parent - parent window + * handlers - array of signal handlers + * prefix - window name prefix (main, graph, ...) + * nBlocks - amount of blocks in status bar + */ +Window *init_window(Window *parent, int winId){ + FNAME(); + guint i; + GtkBuilder *builder; + Window *window; + Sighandler *handlers = NULL; + gchar *prefix = NULL, *title = NULL; + guint nBlocks = 0; + GError *err = NULL; + gchar text[32]; + builder = gtk_builder_new(); + if(!gtk_builder_add_from_string(builder, ui, -1, &err)){ + g_warning("%d, %s\n", err->code, err->message); + exit(-1); + } + INIT(window, Window); + window->id = winId; + if(parent){ + window->parent = parent; + if(winId == GRAPH_WINDOW) // if window is graph context equal to parent's + window->context = parent->context; + else{ + window->context = COPY(parent->context, Context); + window->context->visualMode = SHOW_ONLY_IMAGE; + } + }else + INIT(window->context, Context); + switch(winId){ + case MAIN_WINDOW: + case OPENGL_WINDOW: + prefix = "main"; + handlers = mainSigHandler; + nBlocks = 4; + break; + case GRAPH_WINDOW: + prefix = "graph"; + handlers = graphSigHandler; + nBlocks = 3; + break; + case GL3D_WINDOW: + prefix = "ogl"; + handlers = oglSigHandler; + break; + } + g_sprintf(text, "%sWindow", prefix); + window->window = GTK_WIDGET(gtk_builder_get_object(builder, text)); + if(winId == MAIN_WINDOW || winId == OPENGL_WINDOW){ + GtkAction *quit = GTK_ACTION(gtk_builder_get_object(builder, "mainQuitMenuItem")); + gtk_action_disconnect_accelerator(quit); + if(winId == MAIN_WINDOW){ + g_signal_connect(quit, "activate", chk_quit, NULL); + gtk_action_set_icon_name(quit, "application-exit"); + gtk_action_set_accel_path(quit, "
/quit"); + gtk_action_set_label(quit, _("Quit")); + }else{ + gtk_action_set_accel_path(quit, "
/close"); + gtk_action_set_label(quit, _("Close")); + g_signal_connect_swapped(quit, "activate", + G_CALLBACK(destroy_window), window); + gtk_action_set_icon_name (quit, "window-close"); + } + gtk_action_connect_accelerator(quit); + } + window->drawingArea = initA(prefix, builder); + if(winId != GL3D_WINDOW){ + g_sprintf(text, "%sHruler", prefix); + window->hRule = initR(window->drawingArea, text, builder); + g_sprintf(text, "%sVruler", prefix); + window->vRule = initR(window->drawingArea, text, builder); + } + for(i = 0; i < nBlocks; i++){ + g_sprintf(text, "%sStatusBar%d", prefix, i); + window->SBars[i] = GTK_ENTRY(gtk_builder_get_object(builder, text)); + #ifdef EBUG + set_status_text(i, text, window); + #endif + } + window->statusBlocks = nBlocks; + if(winId != GRAPH_WINDOW){ + INIT(window->image, IMAGE); + INIT(window->image_transformed, IMAGE); + initGl(window->drawingArea); + }else{ + window->LinLogMenu[0] = GTK_RADIO_ACTION(gtk_builder_get_object(builder, "graphLinMenuItem")); + window->LinLogMenu[1] = GTK_RADIO_ACTION(gtk_builder_get_object(builder, "graphLogMenuItem")); + gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(window->LinLogMenu[0]), TRUE); + } + while(handlers->sigHandler){ + void *element; + if(handlers->elementName) + element = (void*)gtk_builder_get_object(builder, handlers->elementName); + else + element = (void*)window->window; + g_signal_connect_swapped(element, handlers->sigName, + handlers->sigHandler, window); + handlers++; + } + gtk_builder_connect_signals(builder, NULL); + g_object_unref(G_OBJECT(builder)); + if(winId != GRAPH_WINDOW){ + if(winId == MAIN_WINDOW || winId == OPENGL_WINDOW) + window->graphWindow = init_window(window, GRAPH_WINDOW); + gtk_widget_show_all(window->window); + } + window->Daspect = 1.; window->Zoom = 1.; + if(winId != OPENGL_WINDOW) + title = g_strdup_printf("%s (%s)", PRGNAME, prefix); + else + title = g_strdup_printf("%s", PRGNAME); + gtk_window_set_title(GTK_WINDOW(window->window), title); + g_free(title); + return window; +} + +void init_main_window(int *argc, char ***argv){ + gtk_init(argc, argv); + gtk_gl_init(argc, argv); + gtk_accel_map_add_entry("
/close", GDK_w, GDK_CONTROL_MASK); + gtk_accel_map_add_entry("
/quit", GDK_q, GDK_CONTROL_MASK); + mainWindow = init_window(NULL, MAIN_WINDOW); +} + +/* + * *prefocal = TRUE if image is prefocal, FALSE otherwisw + * (Ask user, this function calls only if there's a problems to find VAL_F in FITS keys) + */ +void get_prefocal(Window *window, gboolean *prefocal){ + double foc; + GtkWidget *dialog, *btn, *focus, *radio; + void ch_val_f(GtkEntry *e){ + char *eptr = NULL; + errno = 0; + foc = strtod(gtk_entry_get_text(e), &eptr); + if((eptr && *eptr) || errno == ERANGE){ + gchar *txt = g_strdup_printf("%g", foc); + gtk_entry_set_text(e, txt); + g_free(txt); + } + DBG("F: %g", foc); + } + void ch_vals(GtkWidget *w){ + *prefocal = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)); + DBG("prefocal: %d\n", *prefocal); + window->image->val_f = foc; + gtk_widget_destroy(w); + } + GtkBuilder *builder; + GError *err = NULL; + gchar *text; + if(!window || !window->image) return; + if(window->image->val_f > 0.) text = g_strdup_printf("%.3f", window->image->val_f); + else text = g_strdup_printf("0.000"); + builder = gtk_builder_new(); + if(!gtk_builder_add_from_string(builder, ui, -1, &err)){ + g_warning("%d, %s\n", err->code, err->message); + exit(-1); + } + dialog = GTK_WIDGET(gtk_builder_get_object(builder, "FocalWindow")); + radio = GTK_WIDGET(gtk_builder_get_object(builder, "FocalPostRadioBtn")); + focus = GTK_WIDGET(gtk_builder_get_object(builder, "FocFocusEntry")); + g_signal_connect(focus, "changed", G_CALLBACK(ch_val_f), NULL); + gtk_entry_set_text(GTK_ENTRY(focus), text); + btn = GTK_WIDGET(gtk_builder_get_object(builder, "FocalOK")); + g_signal_connect_swapped(btn, "clicked", + G_CALLBACK(ch_vals), dialog); + run_modal_dialog(GTK_DIALOG(dialog), window); + g_free(text); +} diff --git a/src/imtools.c b/src/imtools.c new file mode 100644 index 0000000..436595b --- /dev/null +++ b/src/imtools.c @@ -0,0 +1,638 @@ +// imtools.c - different image transforms +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "fitsheaders.h" +#include "imtools.h" +#include "contours.h" +#include "opengl.h" +#include "gauss.h" +#include "CUtools.h" + +// Histogram size, 256 by default +const int HIST_SIZE = 256; +const int MIN_SPOT_NO = 200; // minimum amount of spots on a hartmanogram +const int MAX_SPOT_NO = 258; // max amount -//- + +void destroy_image(IMAGE *image){ + if(!image) return; + _FREE(image->data); + g_free(image->filename); + if(image->store) // clear parameters from FITS header + gtk_list_store_clear(image->store); + _FREE(image->cLevels); // isolines' intensity + free_contours(image); // isolines itself + _FREE(image); +} + +#ifdef LEPTONICA_FOUND +/* + * Convert an image ima (after tresholding) + * into a pixmap for pixConnComp from leptonica + */ +PIX *conv_image2pix(IMAGE *ima){ + PIX *pixs; + l_uint32 byte, *pptr; + GLfloat *iptr, *idata; + int w, h, wpl, i, j, k, N; + w = ima->width; + h = ima->height; + pixs = pixCreate(w, h, 1); + wpl = pixGetWpl(pixs); + pptr = pixGetData(pixs); + idata = ima->data; + for(i = h - 1; i > -1; i--){ // lines cycle + N = 0; + iptr = &idata[i * w]; + for(j = 0; j < wpl; j++){ // cycle by 32 columns + byte = 0; + for(k = 0; k < 32; k++){ // cycle by a 32-bit word bits + byte <<= 1; + // if string still doesn't end & intensity higher 0.1% + if(N++ < w && *iptr++ > 0.001) + byte |= 1; // set lesser bit + } + *pptr++ = byte; + } + } + return pixs; +} + +gboolean check_box(Window *window, BOX *box, int *X0, int *Y0, int *X, int *Y){ + if(!window->image->data) return FALSE; + int WD = window->image->width, HT = window->image->height - 1; + if(!(box && X0 && Y0 && X && Y)) return FALSE; + *X0 = box->x; + *Y0 = box->y; + *X = *X0 + box->w; + *Y = *Y0 + box->h; // coordinates of a box corners + if(box->w < 5 || box->h < 5) return FALSE; + if(*X0 < 0 || *X0 > WD || *X < 0 || *X > WD || + *Y0 < 0 || *Y0 > HT || *Y < 0 || *Y > HT) + return FALSE; + return TRUE; +} + + //Calculation of min & max intensity by picture to show it +void compute_minmax(IMAGE *ima){ + float min, max, *dst = ima->data; + int i, j, w=ima->width, h=ima->height; + max = min = *dst; + for(i=0; i max) max = tmp; + else if(tmp < min) min = tmp; + } + ima->stat.max = max; + ima->stat.min = min; + //ima->width = w; ima->height = h; + DBG("stat: max=%.2f, min=%.2f", max, min); +} + +/* + * Calculation of the center of box for image ima + * by inscribing a Gaussian to a vector obtained + * by addition of box columns (for X) and rows (for Y) + */ +gboolean get_gaussian_center(Window *window, BOX *box, Coordinates *crds){ + int WD = window->image->width, HT = window->image->height - 1; + int x, y; + GLfloat *data; + Points row, col; + Point *rptr, *cptr; + double x0, sigma; + int X0, Y0, X, Y; + #ifndef GSL_FOUND + return FALSE; + #endif + if(!check_box(window, box, &X0, &Y0, &X, &Y)) return FALSE; + if(!crds) return FALSE; + row.data = calloc(box->w, sizeof(Point)); + row.n = box->w; + col.data = calloc(box->h, sizeof(Point)); + col.n = box->h; + cptr = col.data; + for(y = Y0; y < Y; y++, cptr++){ + data = &window->image->data[(HT - y) * WD + X0]; + cptr->x = (double)y; + rptr = row.data; + for(x = X0; x < X; x++, data++, rptr++){ + double d = (double) *data; + rptr->y += d; + cptr->y += d; + } + } + rptr = row.data; + for(x = X0; x < X; x++, rptr++) rptr->x = (double)x; + // we inscribe Gaussian and change the the coordinates and half-widths of the spot + gauss_fit(&row, NULL, NULL, &sigma, &x0); + crds->x = x0; crds->rx = sigma; + gauss_fit(&col, NULL, NULL, &sigma, &x0); + crds->y = x0; crds->ry = sigma; + _FREE(row.data); _FREE(col.data); + return TRUE; +} + +// count of bits amount in word i +// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel +// http://en.wikipedia.org/wiki/Hamming_weight +int count_bits(l_uint32 i){ + i = i - ((i >> 1) & 0x55555555); + i = (i & 0x33333333) + ((i >> 2) & 0x33333333); + return ((i + ((i >> 4) & 0xF0F0F0F)) * 0x1010101) >> 24; +} + +gboolean get_circle_center(Window *window, BOX *box, PIX *pix, Coordinates *crds){ + int WD = window->image->width, HT = window->image->height - 1; + int N = 0, X0, Y0, X, Y, n; + l_uint32 *pixdata, *pixptr; + l_int32 w, h, d, wpl, sz, i, x, y; + double Xcenter, Ycenter, Radius, xx, yy, Intens, X2, Y2; + GLfloat *data; + if(!check_box(window, box, &X0, &Y0, &X, &Y)) return FALSE; + if(!(pix && crds)) return FALSE; + pixGetDimensions(pix, &w, &h, &d); + if(d != 1) return FALSE; + wpl = pixGetWpl(pix); + pixdata = pixGetData(pix); + pixptr = pixdata; + sz = wpl * h; + for(i = 0; i < sz; i++) N += count_bits(*pixptr++); + if(N < 200000) return FALSE; + n = 0; + Xcenter = (double)(X0 + X) / 2.; + Ycenter = (double)(Y0 + Y) / 2.; + Radius = (double)(w + h) / 4.; + DBG("x0=%g, y0=%g, r0=%g, N=%d", Xcenter, Ycenter, Radius, N); + Xcenter = Ycenter = X2 = Y2 = Intens = 0.; + AxisX = 0.; AxisY = (double)HT; + for(y = 0; y < h; y++){ + yy = (double)(HT - y - Y0); + data = &window->image->data[(HT - y - Y0) * WD + X0]; + l_uint32 *line = pixdata + y * wpl; + for(x = 0; x < w; x++, data++){ + if(GET_DATA_BIT(line, x)){ + double d = *data; + xx= (X0 + x); + n++; + Intens += d; + Xcenter += xx * d; + Ycenter += yy * d; + X2 += xx*xx*d; + Y2 += yy*yy*d; + } + } + } + xx = Xcenter / Intens; + yy = Ycenter / Intens; + crds->x = xx; + crds->y = yy; + crds->rx= sqrt(X2 / Intens - xx*xx); + crds->ry= sqrt(Y2 / Intens - yy*yy); + DBG("xx: %g, yy: %g, rx:%g, ry: %g", xx, yy, crds->rx, crds->ry); + return TRUE; +} + +// Detection of spots' center, yet lousy through gaussians +void get_spots_coords(Window *window, IMAGE *ima){ + BOXA *boxa; + BOX *box; + PIX *pixs; + Spot spot; + Spots *spots; + Coordinates scrds; + int n, i, k; + float xc, yc; + if(!( /*(window->context->transformMask & TRANS_TRES) &&*/ window->image_transformed->data)){ + g_err(_("Define treshold limits first")); + window->context->graphMode = GR_HISTOGRAM; + show_histogram(window); + return; + } + pixs = conv_image2pix(ima); + boxa = pixConnComp(pixs, NULL, 8); + n = boxaGetCount(boxa); + red("Number of boxes: %d", n); + spots_free(&window->spots); + if(!(window->spots = spots_alloc(n))) return; + spots = window->spots; + #ifndef GSL_FOUND + g_err(_("No GSL library found, don't calculate centroids")); + #endif + for(i = 0, k = 0; i < n; i++){ + if((box = boxaGetBox(boxa, i, L_CLONE)) == NULL){ + g_err(_("Box not found")); + continue; + } + if( box->w < Global->minSpotW || + box->h < Global->minSpotH || + box->w > Global->maxSpotW || + box->h > Global->maxSpotH){ + boxDestroy(&box); + continue; + } + if(!get_gaussian_center(window, box, &scrds)){ + scrds.rx = ((double)box->w-1.)/2.; + scrds.ry = ((double)box->h-1.)/2.; + xc = box->x + scrds.rx; + yc = box->y + scrds.ry; + scrds.x = xc; scrds.y = yc; + } + spot.c = scrds; spot.id = k++; + spot.box.x = box->x; + spot.box.y = box->y; + spot.box.w = box->w; + spot.box.h = box->h; + spots_add(spots, &spot, BY_COPY); + boxDestroy(&box); + } + red("Number of spots: %d", k); + boxaDestroy(&boxa); + gdk_window_invalidate_rect(window->drawingArea->window, + &window->drawingArea->allocation, FALSE); + DBG("return"); +} + +void hartmann_spots(Window *window){ // 'r' + Spots *spots = window->spots; + if(!spots){ + g_err(_("Find spots first")); + return; + } + int n = spots->n; + if(n > MAX_SPOT_NO){ + g_err(_("Too many points")); + return; + } + if(n < MIN_SPOT_NO){ + g_err(_("Not enough points")); + return; + } + sort_spots(window); + window->context->transformMask |= TRANS_HARTMANN; // points are sorted + gdk_window_invalidate_rect(window->drawingArea->window, + &window->drawingArea->allocation, FALSE); +} + +void get_circles_params(Window *window, IMAGE *ima){ + BOXA *boxa; + PIXA *pixa; + BOX *box; + PIX *pixs, *pix; + Spot spot; + Coordinates scrds; + Spots *spots; + int n, i; + if(!( (window->context->transformMask & TRANS_TRES) && window->image_transformed->data)){ + g_err(_("Define treshold limits first")); + window->context->graphMode = GR_HISTOGRAM; + show_histogram(window); + return; + } + pixs = conv_image2pix(ima); + + boxa = pixConnComp(pixs, &pixa, 8); + n = boxaGetCount(boxa); + + red("Number of boxes: %d", n); + spots_free(&window->spots); + if(!(window->spots = spots_alloc(n))) return; + spots = window->spots; + for(i = 0; i < n; i++){ + if((box = boxaGetBox(boxa, i, L_CLONE)) == NULL){ + g_err(_("Box not found")); + continue; + } + if((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL){ + g_err(_("Pix not found")); + continue; + } + if(get_circle_center(window, box, pix, &scrds)){ + spot.c = scrds; spot.id = i; + spot.box.x = box->x; + spot.box.y = box->y; + spot.box.w = box->w; + spot.box.h = box->h; + spots_add(spots, &spot, BY_COPY); + } + boxDestroy(&box); + } + + boxaDestroy(&boxa); + pixaDestroy(&pixa); + + gdk_window_invalidate_rect(window->drawingArea->window, + &window->drawingArea->allocation, FALSE); + DBG("return"); +} +#else // LEPTONICA_FOUND not defined +void get_spots_coords(Window *window __attribute__((unused)), + IMAGE *ima __attribute__((unused))){ + LEPTERR; +} +gboolean get_gaussian_center(Window *window __attribute__((unused)), + BOX *box __attribute__((unused)), Coordinates *crds __attribute__((unused))){ + LEPTERR; + return FALSE; +} +PIX *conv_image2pix(IMAGE *ima __attribute__((unused))){ + LEPTERR; + return NULL; +} +void get_circles_params(Window *window __attribute__((unused)), + IMAGE *ima __attribute__((unused))){ + LEPTERR; +} +#endif // LEPTONICA_FOUND + + +/* + * Build the histogram of picture ima + * The default is 256 shades of gray + */ +gboolean make_histogram(IMAGE *ima){ + FNAME(); + ssize_t i, sz; + int j, pt, *hdata, max, min; // hmax + GLfloat *ptr, hsize, datamin = ima->stat.min, datawd = ima->stat.max - datamin; + #ifdef EBUG + double t0 = dtime(); + #endif + if(!ima->data){ + g_err(_("Image is empty")); + return FALSE; + } + _FREE(ima->stat.histogram.data); + // to change the size of the histogram replace HIST_SIZE by a variable value + ima->stat.histogram.data = calloc(HIST_SIZE+1, sizeof(int)); + if(!ima->stat.histogram.data){ + g_err(_("Can't allocate memory for histogram")); + return FALSE; + } + hdata = ima->stat.histogram.data; + ima->stat.histogram.size = HIST_SIZE; + hsize = (GLfloat) HIST_SIZE / datawd; + ima->stat.histogram.scale = 1. / hsize; + //hmax = HIST_SIZE - 1; + ptr = ima->data; + sz = (ssize_t)(ima->width) * (ssize_t)(ima->height); + DBG("datamin=%g, hsize=%g, sz=%zd, scale=%g", datamin, hsize, sz, ima->stat.histogram.scale); + #pragma omp parallel for private(i, pt) shared(hdata) + for(i = 0; i < sz; i++){ + pt = (int)((ptr[i] - datamin) * hsize); + #pragma omp atomic + hdata[pt]++; + } +/* for(i = 0; i < sz; i++, ptr++){ + pt = (int)(*ptr * hsize); + if(pt < 0) pt = 0; + else if(pt > hmax) pt = hmax; + hdata[pt]++; + }*/ + max = min = hdata[0]; + for(j = 1; j < HIST_SIZE; j++){ + pt = hdata[j]; + if(max < pt) max = pt; + else if(min > pt) min = pt; + } + ima->stat.histogram.max = max; + ima->stat.histogram.min = min; + ima->stat.histogram.bot_tres = -1.; + ima->stat.histogram.top_tres = -1.; + DBG("max: %d, min: %d; time: %g", max, min, dtime() - t0); + return TRUE; +} + +/* + * Fill the structure image_transformed by adjusted by the threshold values + * Bottom - the lower bound, top - upper bound (in units of image intensity) + * Brightness less than bottom, replaced by zero + * Brightness larger than top, replaced by a unit + * The rest - are calculated linearly (equalization histogram) + */ +void treshold_image(Window *window, GLfloat bottom, GLfloat top){ + FNAME(); + ssize_t i, sz; + IMAGE *ima = window->image; + IMAGE *i_new = window->image_transformed; + GLfloat *p_old, *p_new, pt, wd; + GLfloat scale = ima->stat.histogram.scale * (GLfloat)ima->stat.histogram.size; + #ifdef EBUG + double t0 = dtime(); + #endif + if(!(p_old = ima->data)){ + g_err(_("Image is empty")); + return; + } + _FREE(i_new->data); + if((top - bottom) < 0.01) top = bottom + 0.01; + sz = (ssize_t)(ima->width) * (ssize_t)(ima->height); + i_new->data = malloc(sz * sizeof(GLfloat)); + i_new->width = ima->width; i_new->height = ima->height; + i_new->bZero = 0.; i_new->bScale = 1.; + i_new->maxVal = ima->maxVal; + i_new->stat.max = 1.;//top * ima->stat.max; + i_new->stat.min = 0.; + p_new = i_new->data; + DBG("top=%g, bottom=%g, scale=%g", top, bottom, ima->stat.histogram.scale); + top = top * scale+ima->stat.min; + bottom = bottom * scale+ima->stat.min; + DBG("top=%g, bottom=%g", top, bottom); + wd = top - bottom; + for(i = 0; i < sz; i++, p_new++, p_old++){ + pt = *p_old; + if(pt < bottom) pt = 0.; + else if(pt > top) pt = 1.; + else pt = (pt - bottom) / wd; + *p_new = pt; + } + DBG("time: %g", dtime() - t0); + window->context->transformMask |= TRANS_TRES; + window->context->current_image = window->image_transformed; + gen_texture(window->image_transformed, window, TRUE); +} + +const int SINCOSSIZE = 540; // size of the array of sines & cosines + // [-90 , +180) degrees + +void hough_lines(Window *window){ // key 'i' + FNAME(); + int w, h, Rmax, sz; + double hd, wd; + IMAGE *ima = window->image; + Window *outwin = init_window(window, OPENGL_WINDOW); + IMAGE *i_new = outwin->image; + if(!ima->data){ + g_err(_("Image is empty")); + return; + } + _FREE(i_new->data); + w = ima->width; h = ima->height; + wd = (double)w; hd = (double)h; + Rmax = (int)(0.5 + sqrt(wd*wd + hd*hd)); + sz = SINCOSSIZE * Rmax; + DBG("sz: %d", sz); + i_new->data = calloc(sz, sizeof(GLfloat)); + if(!i_new->data){ + g_err(_("Can't allocate memory for Hough transform")); + return; + } + i_new->width = Rmax; i_new->height = SINCOSSIZE; + i_new->bZero = 0.; i_new->bScale = 1.; + i_new->maxVal = 1.; + i_new->dtype = FLOAT_IMG; + gchar *str = malloc(256); + snprintf(str, 255, "Hough transform of file %s", ima->filename); + init_keylist(i_new); + add_key(i_new, "TSTRING", "OBJECT", "Hough", str, TSTRING); + _FREE(str); +#ifdef EBUG + double t0 = dtime(); +#endif + if(!fill_hough_lines(ima->data, ima->stat.min, ima->stat.max, w, h, Rmax, SINCOSSIZE, i_new->data)){ + g_err(_("Error in Hough transform module")); + destroy_window(outwin); + return; + } + DBG("Hough transform for lines, time: %gs", dtime() - t0); + //Context->transformMask |= TRANS_TRES; + //DBG("HOUGH: maxR=%d, size=%d, max: %g at R=%d, theta=%g", + //i_new->width, i_new->height, max, R%Rmax, ((double)(R/Rmax))/2.-90.); + outwin->context->current_image = outwin->image; + compute_minmax(outwin->image); + gen_texture(outwin->image, outwin, FALSE); + force_redraw(outwin->drawingArea); + outwin->image->imagetype = HOUGH; + CH_WIN_TITLE(outwin, "%s (Hough) %s", PRGNAME, window->image->filename); +} + +/* + * respect to the coordinates (x, y) of a click in the window of a Hough transform + * we obtain the parameters of the line (R, phi) and display in the parent window + * the corresponding line + */ +void get_houg_line( Window *window, + double x, double y, // mouse click coordinates + double *phi_, double *R_ // Output: parameters of line + ){ + double R, phi, ang_sep, sinphi, cosphi; + double x1=0.,y1=0., x2=0.,y2=0., yl,yr, xu,xd, X1, X2, Y1, Y2; + if(!window || !window->parent) return; + double W = window->parent->image->width-1., H = window->parent->image->height-1.; + int rdy = 0; + IMAGE *image = window->image; + conv_mouse_to_image_coords(x, y, &R, &phi, window); + ang_sep = 270. / (double)image->height / 180. * M_PI; + phi = phi*ang_sep - M_PI/2.; + sinphi = sin(phi); cosphi = cos(phi); + phi *= 180./M_PI; // convert rad => deg + DBG("line: phi = %g, R = %g", phi, R); + *phi_ = phi; *R_ = R; + GdkDrawable *d = window->parent->drawingArea->window; + static GdkGC *gc = NULL; + if(!gc){ + gc = gdk_gc_new(d); + gdk_gc_set_foreground(gc, &(window->parent->drawingArea->style->white)); + gdk_gc_set_function(gc, GDK_XOR); + } + yl = R/sinphi; yr = (R-W*cosphi)/sinphi; // left & right bounds of a picture + xd = R/cosphi; xu = (R-H*sinphi)/cosphi; // down & up bounds + DBG("yl=%g, yr=%g, xd=%g, xu=%g",yl,yr,xd,xu); + if(yl < H && yl > -0.5){ + DBG("left selected"); + y1 = yl; x1 = 0.; rdy = 1; + } + if(yr < H && yr > -0.5){ + DBG("right selected"); + if(rdy == 0){ + y1 = yr; x1 = W; rdy = 1; + }else{ + y2 = yr; x2 = W; rdy = 2; goto lines_selected; + } + } + if(xd < W && xd > -0.5){ + DBG("down selected"); + if(rdy == 0){ + x1 = xd; y1 = 0.; rdy = 1; + }else{ + x2 = xd; y2 = 0.; rdy = 2; goto lines_selected; + } + } + if(xu < W && xu > -0.5){ + DBG("up selected"); + if(rdy == 0){ + x1 = xu; y1 = H; rdy = 1; + }else{ + x2 = xu; y2 = H; rdy = 2; + } + } +lines_selected: + if(rdy != 2) DBG("WTF? not two points"); + else{ + DBG("line in image CS: (%g, %g) - (%g, %g)", x1,y1,x2,y2); + conv_image_to_mouse_coords(x1, y1, &X1, &Y1, window->parent); + conv_image_to_mouse_coords(x2, y2, &X2, &Y2, window->parent); + gdk_draw_line(d, gc, X1,Y1, X2,Y2); + DBG("line in window CS: (%g, %g) - (%g, %g)", X1,Y1,X2,Y2); + } +} + +// Filtering of an picture window->image by filter f +void filter_image( Window *window, + Filter *f + ){ + float *src, *dst; + int w,h; + gboolean res = FALSE; + // If picture already transformed + if(window->context->current_image == window->image_transformed){ + src = window->image_transformed->data; + w = window->image_transformed->width; + h = window->image_transformed->height; + }else{ + src = window->image->data; + w = window->image->width; + h = window->image->height; + if(window->image_transformed) _FREE(window->image_transformed->data); +// window->image_transformed = COPY(window->image, IMAGE); +// window->image_transformed->data = NULL; + } + switch(f->FilterType){ + case MEDIAN: + res = MedFilter(src, &dst, f, w, h); + break; + case SIMPLEGRAD: + res = GradFilterSimple(src, &dst, f, w, h); + break; + case STEP: // "posterisation" + res = StepFilter(src, &dst, f, w, h, window->image->stat.min, window->image->stat.max, NULL); + break; + default: // differential filters + res = DiffFilter(src, &dst, f, w, h); + } + if(!res){ g_err(_("Error in image filter module")); return; } + window->context->transformMask |= TRANS_FILTER; + _FREE(src); + window->image->data = dst; + window->context->current_image = window->image; + compute_minmax(window->image); + //gen_texture(window->image_transformed, window, TRUE); + gen_texture(window->image, window, TRUE); + force_redraw(window->drawingArea); +} + diff --git a/src/include/CUtools.h b/src/include/CUtools.h new file mode 100644 index 0000000..f894f1e --- /dev/null +++ b/src/include/CUtools.h @@ -0,0 +1,51 @@ +// CUtools.h - common definitions and functions for CUDA.c and NOCUDA.c +#ifndef _CUTOOLS_H_ +#define _CUTOOLS_H_ +#ifdef _CUDA_CU_ // file was included from CUDA.cu + #define EXTERN extern "C" +#else + #define EXTERN +#endif +#ifndef _GNU_SOURCE + #define _GNU_SOURCE +#endif +#include +#include +#include + +typedef struct{ + unsigned char FilterType; // filter type + int w; // filter width + int h; // height + double sx; // x half-width + double sy; // y half-width (sx, sy - for Gaussian-type filters) +} Filter; +// FilterType +enum{ + LAPGAUSS // laplasian of gaussian + ,GAUSS // gaussian + ,SOBELH // Sobel horizontal + ,SOBELV // -//- vertical + ,SIMPLEGRAD // simple gradient (by Sobel) + ,PREWITTH // Prewitt (horizontal) - simple derivative + ,PREWITTV // -//- (vertical) + ,MEDIAN // median + ,STEP // "posterisation" +}; +// f->h for STEP filter +enum{ + UNIFORM + ,LOG + ,EXP + ,SQRT + ,POW +}; + +// export section +EXTERN int fill_hough_lines(float *ima, float min, float max, int imW, int imH, int Rmax, int angles, float *hough); +EXTERN int DiffFilter(float *ima, float **result, Filter *f, int sizex, int sizey); +EXTERN int MedFilter(float *ima, float **result, Filter *f, int sizex, int sizey); +EXTERN int fillIsoScale(Filter *f, float **scale, float min, float wd); +EXTERN int StepFilter(float *ima, float **result, Filter *f, int sizex, int sizey, float min, float max, float **scale); +EXTERN int GradFilterSimple(float *ima, float **result, Filter *f, int sizex, int sizey); +#endif // _CUTOOLS_H_ diff --git a/src/include/contours.h b/src/include/contours.h new file mode 100644 index 0000000..2543b21 --- /dev/null +++ b/src/include/contours.h @@ -0,0 +1,9 @@ +#ifndef __CONTOURS_H__ +#define __CONTOURS_H__ +#include "fitsview.h" +#include "gtk.h" +#include "spots.h" +void find_contours(Window *window, int n, int type); +void free_contours(IMAGE *image); +int copy_contours(IMAGE *in, IMAGE *out, float dX, float dY); +#endif // __CONTOURS_H__ diff --git a/src/include/filelist.h b/src/include/filelist.h new file mode 100644 index 0000000..e985f4d --- /dev/null +++ b/src/include/filelist.h @@ -0,0 +1,19 @@ +#ifndef __FILELIST_H__ +#define __FILELIST_H__ + +#include "fitsview.h" +#include "gtk.h" + +enum{ + CURRENT, + FIRST, + LAST, + PREVIOUS, + NEXT +}; + +void free_filelist(Window *window); +gint fill_filelist(gchar *filename, Window *window); +gchar *get_filename(guchar type, Window *window); +gboolean get_ext(gchar *filename); +#endif // __FILELIST_H__ diff --git a/src/include/fits.h b/src/include/fits.h new file mode 100644 index 0000000..106f9f1 --- /dev/null +++ b/src/include/fits.h @@ -0,0 +1,11 @@ +#ifndef _FITS_H_ +#define _FITS_H_ +#include "fitsview.h" + +#define SCALE_MIN (0.01) // scale smaller than this is wrong +gboolean cmplx_conv(gchar *value, double *re, double *im); +gboolean readfits(gchar *filename, IMAGE *data); +gboolean writefits(gchar *filename, IMAGE *data); +gboolean try_open_file(gchar *filename, IMAGE *data); +gboolean guess_fits(gchar *filename); +#endif // _FITS_H_ diff --git a/src/include/fitsheaders.h b/src/include/fitsheaders.h new file mode 100644 index 0000000..8871a33 --- /dev/null +++ b/src/include/fitsheaders.h @@ -0,0 +1,20 @@ +#ifndef _FITSHEADERS_H_ +#define _FITSHEADERS_H_ +#include "fitsview.h" +#include "gtk.h" + +enum{ // key parameters field + L_TYPENM, // type name + L_KEY, // key name + L_VAL, // key value + L_COMM, // comment + L_TYPE, // key type (invisible column, we need you?) + L_MAX // amount of parameters +}; + +void edit_headers(Window *parent); +void add_key( IMAGE *ima, + gchar *keytype, gchar *keyname, gchar *keyval, + gchar *keycomment, int keyfitstype); +void init_keylist(IMAGE *ima); +#endif diff --git a/src/include/fitsview.h b/src/include/fitsview.h new file mode 100644 index 0000000..066de17 --- /dev/null +++ b/src/include/fitsview.h @@ -0,0 +1,233 @@ +#ifndef _FITSVIEW_H_ +#define _FITSVIEW_H_ + +#ifndef _GNU_SOURCE + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for locale +#include // for dtime +#include // for basename +#include +#include +#include +#include +#include // for color text in tty + + +#include "CUtools.h" + +#ifndef GETTEXT_PACKAGE +#define GETTEXT_PACKAGE "fitsview" +#endif +#define PRGNAME GETTEXT_PACKAGE +#ifndef LOCALEDIR +#define LOCALEDIR "/home/eddy/locale" +#endif + +#define _(String) gettext(String) +#define gettext_noop(String) String +#define N_(String) gettext_noop(String) + +#ifndef THREAD_NUMBER + #define THREAD_NUMBER 2 +#endif +#ifndef OMP_NUM_THREADS + #define OMP_NUM_THREADS THREAD_NUMBER +#endif + +extern const int HIST_SIZE; +void *copy_struct(void *src, int size); +void *init_struct(int size); +#define INIT(var, type) var = (type*)init_struct(sizeof(type)) +#define COPY(src, type) (type*)copy_struct((void*)src, sizeof(type)) +#define _FREE(ptr) do{free(ptr); ptr = NULL;}while(0) + +/* + * If the distance from the current position of the mouse to a certain point + * less than this value, it is believed that this kind of chosen point + */ +#define PT_TRESHOLD 5 + +// debug mode, -DEBUG +#ifdef EBUG + #define FNAME() fprintf(stderr, "\n%s (%s, line %d)\n", __func__, __FILE__, __LINE__) + #define DBG(...) do{fprintf(stderr, "%s (%s, line %d): ", __func__, __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n");} while(0) +#else + #define FNAME() do{}while(0) + #define DBG(...) do{}while(0) +#endif //EBUG + +extern int (*red)(const char *fmt, ...); +extern int (*green)(const char *fmt, ...); + +// contour poing +typedef struct _cPoint{ + float x; // coordinates of the contour point in the CS picture + float y; + struct _cPoint *prev;// pointers to the previous and next points of the contour + struct _cPoint *next; +} cPoint; +// contour structure +typedef struct _Contour{ + int N; // amount of points + cPoint *first; // the first point of the contour (list) + cPoint *last; // last -//- +// struct _Contour *prev; // pointers to the previous and following contour of the group + struct _Contour *next; + unsigned char closed;// == TRUE, if contour is closed +} Contour; +typedef struct{ + int L; // level (at the lower boundary of 0, 1, etc) + int N; // number of units in the structure + Contour *first; // pointer to the first contour in the list + Contour *last; // pointer to the last contour in the list +}cList; + +// image histogram +typedef struct{ + int size; // it is possible that the size of the histogram can be changed + int max; // maximum and minimum values of the histogram (the amplitude) + int min; + double bot_tres; // upper and lower thresholds for equalization (normalized to 1) + double top_tres; + GLfloat scale; // the scale - the range of intensities per unit of histogram + int *data; +} ImageHistogram; +// image statistics +struct ImageStat{ + GLfloat max; + GLfloat min; + ImageHistogram histogram; + /*GLfloat median; + GLfloat mean; + GLfloat std;*/ +}; + +// image itself +typedef struct{ + int width; // width + int height; // height + float bZero; // conditional zero point + float bScale; // scale + float maxVal; // the limiting value of the intensity (1<image or window->image_transformed) +} Context; + +typedef struct{ + unsigned char previewMode; // preview settings + gboolean add_all; // try to add all the files (or only .fts / .fit [s]) + int minSpotW; // the limit sizes of points + int maxSpotW; + int minSpotH; + int maxSpotH; +} Global_context; + +extern Global_context *Global; + +/* + * description fields of the structure of context + * the first (zero) should go the default values + */ +// drawingMode: +enum{ + DRAW_TRACK // tracking mode + ,DRAW_SELECTION // and so on + ,DRAW_QUAD + ,GAME_MODE +}; +// trackingMode: +enum{ + TRACK_ANY + ,TRACK_VERT + ,TRACK_HORIZ + ,TRACK_DIAG +}; +// graphYaxis, may be masking: LSB - the graph, Sr. - histogram +enum{ + Y_LINEAR = 0 + ,Y_LOGBOTH = 3 + ,Y_LOGHIST = 2 + ,Y_LOGGRAPH= 1 +}; +// graphMode - what is shown on graph (histogram or track) +enum{ + GR_GRAPH + ,GR_HISTOGRAM +}; +// transformMask - bit components +enum{ + TRANS_NONE = 0 + ,TRANS_TRES = 1 + ,TRANS_ROT = 2 + ,TRANS_MOVE = 4 + ,TRANS_SCALE = 8 + ,TRANS_FILTER = 16 + ,TRANS_HARTMANN = 32 +}; +// visualMode - bit components +enum{ + SHOW_ONLY_IMAGE = 0 + ,SHOW_POTBOXES = 1 + ,SHOW_POTSELECT = 2 + ,SHOW_ISOLINES = 4 + ,SHOW_ALL = 255 +}; +// previewMode - bit components, masks +enum{ + PREVIEW_DEFAULT // (512x512, sqrt) + ,PREVIEW_128 + ,PREVIEW_256 + ,PREVIEW_512 + ,PREVIEW_768 + ,PREVIEW_1024 + ,PREVIEW_SIZEMASK = 7 + ,PREVIEW_LINEAR = 8 + ,PREVIEW_LOG = 16 + ,PREVIEW_SQRT = 24 + ,PREVIEW_COLFNMASK = 24 +}; + +double dtime(); + +#endif // _FITSVIEW_H_ diff --git a/src/include/gauss.h b/src/include/gauss.h new file mode 100644 index 0000000..5e71924 --- /dev/null +++ b/src/include/gauss.h @@ -0,0 +1,20 @@ +#ifndef _GAUSS_H_ +#define _GAUSS_H_ +#include "fitsview.h" +#include "tracking.h" +#ifndef GSL_FOUND + #define GSLERR g_err(_("Install GSL library and remake sources for this functions")) +#endif +struct data { + size_t n; + double *x; + double *y; + double *dy; +}; + +double gaussian_pt(double C, double A, double sigma, double x0, double x); +void gaussian_v(double C, double A, double sigma, double x0, Points *pts); +void gauss_fit(Points *pts, double *C_, double *A_, double *sigma_, double *x0_); +/*void circle_fit(struct data *data, double *Xcenter, + double *Ycenter, double *Radius);*/ +#endif // _GAUSS_H_ diff --git a/src/include/gtk.h b/src/include/gtk.h new file mode 100644 index 0000000..40b162d --- /dev/null +++ b/src/include/gtk.h @@ -0,0 +1,92 @@ +#ifndef _GTK_H_ +#define _GTK_H_ +#include "fitsview.h" +#define EVENT_METHOD(i, x) GTK_WIDGET_GET_CLASS(i)->x + +#define CH_WIN_TITLE(wndw, ...) do{ \ + gchar *title = g_strdup_printf(__VA_ARGS__); \ + gtk_window_set_title(GTK_WINDOW(wndw->window), title);\ + g_free(title); \ + }while(0) + +// Maximum number of cells lines the status window +#define SBAR_MAX 4 +// Status bar fields +enum{ + StatusText + ,StatusState + ,StatusCoords + ,StatusAdd +}; + +typedef struct{ + GLuint tex; // texture itself + GLuint w; // its size + GLuint h; +} TEXTURE; + +typedef struct{ + double x; + double y; +} XY; + +// List of files in the current directory for this window +typedef struct{ + gint list_length; + GList *files_list; + GList *list_current; + GList *list_end; +} FITSlist; + +struct Spots; +// window description structure +struct Window{ + int id; // identificator: MAIN_WINDOW, GRAPH_WINDOW, OPENGL_WINDOW + GtkWidget *window; // pointer to a window + Context *context; // context of a window + struct Window *parent; // parent window (NULL for the main) + struct Window *graphWindow; // window's graph (while there's no - NULL) + GtkWidget *drawingArea; // drawing area (openGL) + GtkWidget *hRule; // rulers + GtkWidget *vRule; + GtkEntry *SBars[SBAR_MAX]; // an array of pointers to the cells of the status bar + guint statusBlocks; // status bar blocks amount + IMAGE *image; // image for a window + IMAGE *image_transformed; // and its transformation + struct Spots *spots; // spots array for a window + TEXTURE *texture; // texture or VBO buffers + GtkRadioAction *LinLogMenu[2]; // scale lin/log + double Zoom; // zoom scale + double Daspect; // scale in image region (in window) + XY move; // image motions (in window) + XY mouse; // .x, .y - the beginning of the window coordinates in SC of a picture + GLfloat Xangle; // angles rotation relative to the X and Z axes + GLfloat Zangle; + FITSlist files; // files in current directory for this window +}; +typedef struct Window Window; +// window id +enum{ + MAIN_WINDOW, + GRAPH_WINDOW, + OPENGL_WINDOW, + GL3D_WINDOW +}; + +extern Window *mainWindow; + +void change_image(gchar *filename, Window *window); +void init_main_window(int *argc, char ***argv); +Window *init_window(Window *parent, int winId); +void destroy_window(Window* window); +void run_modal_window(GtkWindow *w, Window *parent); +gint run_modal_dialog(GtkDialog *dialog, Window *parent); +void g_err(gchar *text); +void set_Drulers(double x0, double y0, double xm, double ym, Window *window); +void set_Grulers(double y0, double xm, double ym, Window *window); +void show_histogram(Window *window); +void set_status_text(guint barName, gchar *text, Window *window); +void refresh_state(Window *window); +gchar *get_open_filename(Window *window); +void get_prefocal(Window *window, gboolean *prefocal); +#endif // _GTK_H_ diff --git a/src/include/imtools.h b/src/include/imtools.h new file mode 100644 index 0000000..98c5d73 --- /dev/null +++ b/src/include/imtools.h @@ -0,0 +1,19 @@ +#ifndef _IMTOOLS_H_ +#define _IMTOOLS_H_ +#include "fitsview.h" +#include "gtk.h" +#include "spots.h" + +void destroy_image(IMAGE *image); +void free_contours(IMAGE *image); +gboolean make_histogram(IMAGE *ima); +void treshold_image(Window *window, GLfloat bottom, GLfloat top); +void get_spots_coords(Window *window, IMAGE *ima); +void hartmann_spots(Window *window); +gboolean get_gaussian_center(Window *window, BOX *box, Coordinates *crds); +PIX *conv_image2pix(IMAGE *ima); +void get_circles_params(Window *window, IMAGE *ima); +void hough_lines(Window *window); +void get_houg_line(Window *window, double x, double y, double *phi_, double *R_); +void filter_image(Window *window, Filter *f); +#endif // _IMTOOLS_H_ diff --git a/src/include/open_dialog.h b/src/include/open_dialog.h new file mode 100644 index 0000000..b3a1893 --- /dev/null +++ b/src/include/open_dialog.h @@ -0,0 +1,8 @@ +#ifndef _OPEN_DIALOG_H_ +#define _OPEN_DIALOG_H_ +#include "gtk.h" + +extern gchar *saved_path; +GtkFileChooser *open_fits_dialog(); +void gray2rgb(float gray, guchar *rgb); +#endif diff --git a/src/include/opengl.h b/src/include/opengl.h new file mode 100644 index 0000000..6a9b814 --- /dev/null +++ b/src/include/opengl.h @@ -0,0 +1,21 @@ +#ifndef _OPENGL_H_ +#define _OPENGL_H_ +#define GL_GLEXT_PROTOTYPES +#include "fitsview.h" +#include +#include +#include +#include "gtk.h" + +extern gboolean useVBO; + +void initGl(GtkWidget *drawingArea); +void gen_texture(IMAGE *image, Window *window, gboolean redraw); +void force_redraw(GtkWidget *Area); +void pickRegion(double x, double y); +void conv_mouse_to_image_coords(double x, double y, double *X, double *Y, Window *window); +void conv_image_to_mouse_coords(double x, double y, double *X, double *Y, Window *window); +void init3D(Window *window); +void getGLinfo(GtkWidget *Area); +void freeGLmemory(Window *window); +#endif // _OPENGL_H_ diff --git a/src/include/spots.h b/src/include/spots.h new file mode 100644 index 0000000..faad810 --- /dev/null +++ b/src/include/spots.h @@ -0,0 +1,61 @@ +#ifndef _SPOTS_H_ +#define _SPOTS_H_ +#include "fitsview.h" + +#ifndef LEPTONICA_FOUND + #define LEPTERR g_err(_("Install Leptonica library and remake sources for this functions")) + typedef struct{double x; double y; double w; double h;} BOX; + typedef void* PIX; +#else + #include // from leptonica +#endif // LEPTONICA_FOUND + +/* + * Coordinates of the center of area and its sizes was originally initiated + * based on the box-structures, then adjusted to a coordinate system relative + * to the optical axis, further refined by a Gaussian + * (x and y - as the central parameters of Gaussians, + * rx and ry - as the standard deviation of a Gaussian + */ +typedef struct{ + double x; + double y; // center coordinates + double rx; + double ry; // half-sizes +} Coordinates; + +// spot parameters +typedef struct{ + BOX box; // bounding box + Coordinates c; // dimensions and coordinates in CS relative to the optical axis, without rotation + double r; // module of the radius vector in the CS of the center of spots + double phi; // angular coordinate (from OX counter-clockwise) + double xC; // coordinates of spots in the CS of an optical axis, taking into account the rotation + double yC; + int id; // number, id etc. +} Spot; + +// spots array +struct Spots{ + int size; // array size + int n; // amount of spots in array + int memflag;// BY_COPY or BY_PTR - method of spots allocation + Spot **spot;// spots themselves +}; + +typedef struct Spots Spots; + +Spots* spots_alloc(int n); +Spot* spots_add(Spots *spots, Spot *spot, int copy); +enum { // definition for the function spots_add + BY_COPY, // copy spot + BY_PTR // only insert pointer to a spot +}; +void spots_free(Spots **spots); +struct Window; +void sort_spots(struct Window *window); +void spots_save(Spots *spots, gchar *filename); + +extern double AxisX, AxisY; + +#endif // _SPOTS_H_ diff --git a/src/include/terrain.h b/src/include/terrain.h new file mode 100644 index 0000000..ab2453a --- /dev/null +++ b/src/include/terrain.h @@ -0,0 +1,9 @@ +#ifndef __TERRAIN_H__ +#define __TERRAIN_H__ + +#include "fitsview.h" +#include "gtk.h" + +void terrain_3D(Window *window, double x0, double y0, double x, double y, gboolean from_file); + +#endif // __TERRAIN_H__ diff --git a/src/include/tracking.h b/src/include/tracking.h new file mode 100644 index 0000000..ff2a2ee --- /dev/null +++ b/src/include/tracking.h @@ -0,0 +1,31 @@ +#ifndef _TRACKING_H_ +#define _TRACKING_H_ + +#include "fitsview.h" +#include "gtk.h" + +typedef struct{ + double x; + double y; +} Point; + +typedef struct{ + int n; + Point *data; +} Points; + +extern double gYmax, gXmax; +extern double xScale, yScale; + +void do_tracking(double x, double y, gboolean start, Window *window); +void do_histogram(Window *window); +gboolean graph_mouse_btn(int x, Window *window); +gboolean isYlog(Window *window); + +void gen_tres_texture(Window *window); +gboolean Gexpose(Window *window); +gboolean Gconfigure(Window *window); +gboolean fit_gaussian(Window *window); +void get_image_crds(double x, double *xx, double *yy); +void get_sel_region(double *x1, double *y1, double *x2, double *y2); +#endif // _TRACKING_H_ diff --git a/src/locale/ru/LC_MESSAGES/fitsview.mo b/src/locale/ru/LC_MESSAGES/fitsview.mo new file mode 100644 index 0000000..2c92fc9 Binary files /dev/null and b/src/locale/ru/LC_MESSAGES/fitsview.mo differ diff --git a/src/locale/ru/messages.po b/src/locale/ru/messages.po new file mode 100644 index 0000000..a327ac4 --- /dev/null +++ b/src/locale/ru/messages.po @@ -0,0 +1,596 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-12-10 17:22+0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: gtk.c:58 +#, c-format +msgid "Error: %s\n" +msgstr "" + +#: gtk.c:174 fits.c:191 +msgid "Can't read fits file" +msgstr "" + +#: gtk.c:185 +msgid "Can't save file" +msgstr "" + +#: gtk.c:367 +msgid "Selected line" +msgstr "" + +#: gtk.c:473 +msgid "Both max values are less than min" +msgstr "" + +#: gtk.c:476 +msgid "Max height value less than min" +msgstr "" + +#: gtk.c:478 +msgid "Max width value less than min" +msgstr "" + +#: gtk.c:525 +msgid "No recognized spots" +msgstr "" + +#: gtk.c:1090 +msgid "Quit" +msgstr "" + +#: gtk.c:1093 +msgid "Close" +msgstr "" + +#: fitsheaders.c:53 +msgid "Type" +msgstr "" + +#: fitsheaders.c:53 +msgid "Name" +msgstr "" + +#: fitsheaders.c:53 +msgid "Value" +msgstr "" + +#: fitsheaders.c:53 +msgid "Comment" +msgstr "" + +#: fitsheaders.c:101 +msgid "Error in value range!" +msgstr "" + +#: fitsheaders.c:120 +msgid "Invalid double number!" +msgstr "" + +#: fitsheaders.c:132 +msgid "Format error: complex number must be in format" +msgstr "" + +#: fitsheaders.c:486 +msgid "Open fits file first" +msgstr "" + +#: fitsheaders.c:513 +msgid "New entry (ctrl+n)" +msgstr "" + +#: fitsheaders.c:518 +msgid "Delete entry (ctrl+d)" +msgstr "" + +#: fitsheaders.c:524 +msgid "Close (ctrl+w)" +msgstr "" + +#: tracking.c:57 tracking.c:62 tracking.c:350 +msgid "Can't allocate memory for track points" +msgstr "" + +#: tracking.c:483 +#, c-format +msgid "Selected region from %.1f to %.1f, %d datapoints" +msgstr "" + +#: tracking.c:518 +msgid "No parent window or image" +msgstr "" + +#: tracking.c:526 +msgid "No data points" +msgstr "" + +#: tracking.c:531 +msgid "Bad amount of data points" +msgstr "" + +#: tracking.c:536 +msgid "Invalid number of first point" +msgstr "" + +#: tracking.c:543 contours.c:394 contours.c:400 contours.c:449 contours.c:453 +#: contours.c:456 terrain.c:264 terrain.c:270 +msgid "Can't allocate memory" +msgstr "" + +#: tracking.c:559 +#, c-format +msgid "Fit gaussian, x0=%.2f I(%.2f, %.2f), s=%.2f, A=%.1f, C=%.3f" +msgstr "" + +#: spots.c:63 +msgid "Too many markers" +msgstr "" + +#: spots.c:64 +msgid "Not enough markers" +msgstr "" + +#: spots.c:65 +msgid "Bad markers angle" +msgstr "" + +#: spots.c:66 +msgid "Too many rings" +msgstr "" + +#: spots.c:83 +msgid "Can't allocate memory for new spots" +msgstr "" + +#: spots.c:88 +msgid "Can't allocate memory for pointers in spots array" +msgstr "" + +#: spots.c:122 +msgid "Spots array is full, can't place new spot\n" +msgstr "" + +#: spots.c:126 +msgid "Zero pointer to spot\n" +msgstr "" + +#: spots.c:132 +msgid "Can't allocate memory for new spot" +msgstr "" + +#: spots.c:137 +msgid "Can't copy spot to new one" +msgstr "" + +#: spots.c:499 +msgid "Can't open file" +msgstr "" + +#: spots.c:511 +msgid "Can't write to file" +msgstr "" + +#: imtools.c:222 imtools.c:302 +msgid "Define treshold limits first" +msgstr "" + +#: imtools.c:235 +msgid "No GSL library found, don't calculate centroids" +msgstr "" + +#: imtools.c:239 imtools.c:318 +msgid "Box not found" +msgstr "" + +#: imtools.c:274 +msgid "Find spots first" +msgstr "" + +#: imtools.c:279 +msgid "Too many points" +msgstr "" + +#: imtools.c:283 +msgid "Not enough points" +msgstr "" + +#: imtools.c:322 +msgid "Pix not found" +msgstr "" + +#: imtools.c:377 imtools.c:439 imtools.c:481 +msgid "Image is empty" +msgstr "" + +#: imtools.c:384 +msgid "Can't allocate memory for histogram" +msgstr "" + +#: imtools.c:492 +msgid "Can't allocate memory for Hough transform" +msgstr "" + +#: imtools.c:508 +msgid "Error in Hough transform module" +msgstr "" + +#: imtools.c:628 +msgid "Error in image filter module" +msgstr "" + +#: NOCUDA.c:809 +msgid "No memory left" +msgstr "" + +#: fits.c:284 +msgid "Can't read HDU" +msgstr "" + +#: fits.c:345 +msgid "Not an image? (dimensions != 2)" +msgstr "" + +#: opengl.c:34 +msgid "OpenGL not supported" +msgstr "" + +#: opengl.c:55 +msgid "" +"\n" +"OpenGL info:\n" +msgstr "" + +#: opengl.c:60 +msgid "Color bits" +msgstr "" + +#: opengl.c:65 +msgid "Depth bits" +msgstr "" + +#: opengl.c:66 +msgid "Stencil bits" +msgstr "" + +#: opengl.c:66 +msgid "Max amount of lights" +msgstr "" + +#: opengl.c:69 +msgid "Max texture size" +msgstr "" + +#: opengl.c:70 +msgid "Max clip planes" +msgstr "" + +#: opengl.c:75 +msgid "Max stack depths" +msgstr "" + +#: opengl.c:76 +msgid "modelview" +msgstr "" + +#: opengl.c:76 +msgid "projection" +msgstr "" + +#: opengl.c:76 +msgid "attrib" +msgstr "" + +#: opengl.c:77 +msgid "texture" +msgstr "" + +#: opengl.c:80 +msgid "VBO is supported\n" +msgstr "" + +#: opengl.c:288 +msgid "Empty region" +msgstr "" + +#: opengl.c:295 +msgid "Selected spot[s]:\n" +msgstr "" + +#: opengl.c:297 +msgid "Selected spot: " +msgstr "" + +#: opengl.c:299 +msgid "Selected spots: " +msgstr "" + +#: opengl.c:483 terrain.c:321 +msgid "Can't allocate memory for texture" +msgstr "" + +#: terrain.c:227 +msgid "Selected area too small" +msgstr "" + +#: terrain.c:233 +msgid "Error occured when tried to add contours" +msgstr "" + +#: terrain.c:256 +msgid "No image in parent window" +msgstr "" + +#: terrain.c:332 +msgid "Can't generate VBO / GL list" +msgstr "" + +#: open_dialog.c:79 +msgid "linear" +msgstr "" + +#: open_dialog.c:79 +msgid "log" +msgstr "" + +#: open_dialog.c:79 +msgid "square root" +msgstr "" + +#: open_dialog.c:96 open_dialog.c:354 +msgid "Preview size" +msgstr "" + +#: open_dialog.c:105 +msgid "Colormap function" +msgstr "" + +#: open_dialog.c:142 +msgid "Select fits file to open" +msgstr "" + +#: open_dialog.c:155 +msgid "FITS files" +msgstr "" + +#: open_dialog.c:164 +msgid "All files" +msgstr "" + +#: open_dialog.c:188 +msgid "Preview settings" +msgstr "" + +#: open_dialog.c:353 +msgid "Image size" +msgstr "" + +#: open_dialog.c:355 +msgid "Preview scale" +msgstr "" + +#: fitsview.glade:16 fitsview.glade:448 fitsview.glade:679 +msgid "_File" +msgstr "" + +#: fitsview.glade:40 +msgid "Open in _new window" +msgstr "" + +#: fitsview.glade:55 +msgid "Open in _3D window" +msgstr "" + +#: fitsview.glade:92 fitsview.glade:474 fitsview.glade:713 +msgid "_Edit" +msgstr "" + +#: fitsview.glade:99 fitsview.glade:481 fitsview.glade:720 +msgid "_View" +msgstr "" + +#: fitsview.glade:107 +msgid "Show _histogram" +msgstr "" + +#: fitsview.glade:115 +msgid "Show _headers" +msgstr "" + +#: fitsview.glade:128 +msgid "3D view of full image" +msgstr "" + +#: fitsview.glade:136 +msgid "3D view of subframe" +msgstr "" + +#: fitsview.glade:144 +msgid "Select _spots" +msgstr "" + +#: fitsview.glade:152 +msgid "Draw _tracks" +msgstr "" + +#: fitsview.glade:165 +msgid "_Zoom frame" +msgstr "" + +#: fitsview.glade:173 fitsview.glade:834 +msgid "_Restore image" +msgstr "" + +#: fitsview.glade:181 +msgid "Zoom _in" +msgstr "" + +#: fitsview.glade:189 +msgid "Zoom _out" +msgstr "" + +#: fitsview.glade:201 fitsview.glade:525 fitsview.glade:846 +msgid "_Math" +msgstr "" + +#: fitsview.glade:209 +msgid "Find and enumerate spots" +msgstr "" + +#: fitsview.glade:210 +msgid "_Spots" +msgstr "" + +#: fitsview.glade:218 +msgid "Choose minimal & maximal spot size" +msgstr "" + +#: fitsview.glade:219 +msgid "Size _tresholds" +msgstr "" + +#: fitsview.glade:227 +msgid "Identify _spots" +msgstr "" + +#: fitsview.glade:235 +msgid "So_rt hartmann spots" +msgstr "" + +#: fitsview.glade:243 +msgid "Sa_ve spots" +msgstr "" + +#: fitsview.glade:255 +msgid "Identify _circles" +msgstr "" + +#: fitsview.glade:263 +msgid "_Hough transform" +msgstr "" + +#: fitsview.glade:271 +msgid "_Filter" +msgstr "" + +#: fitsview.glade:283 +msgid "_Help" +msgstr "" + +#: fitsview.glade:489 +msgid "Y axis scale" +msgstr "" + +#: fitsview.glade:497 +msgid "Linear" +msgstr "" + +#: fitsview.glade:507 +msgid "Log" +msgstr "" + +#: fitsview.glade:533 +msgid "Approximate by _gaussian" +msgstr "" + +#: fitsview.glade:728 +msgid "_Move" +msgstr "" + +#: fitsview.glade:736 +msgid "Rotate X CW" +msgstr "" + +#: fitsview.glade:744 +msgid "Rotate X CCW" +msgstr "" + +#: fitsview.glade:752 +msgid "Rotate Z CCW" +msgstr "" + +#: fitsview.glade:760 +msgid "Rotate Z CW" +msgstr "" + +#: fitsview.glade:768 +msgid "Move right" +msgstr "" + +#: fitsview.glade:776 +msgid "Move left" +msgstr "" + +#: fitsview.glade:784 +msgid "Move down" +msgstr "" + +#: fitsview.glade:792 +msgid "Move up" +msgstr "" + +#: fitsview.glade:800 +msgid "Move backward" +msgstr "" + +#: fitsview.glade:808 +msgid "Move forward" +msgstr "" + +#: fitsview.glade:825 +msgid "Mouse and arrow keys navigation" +msgstr "" + +#: fitsview.glade:826 +msgid "_Game mode" +msgstr "" + +#: fitsview.glade:942 +msgid "Spots' size treshold" +msgstr "" + +#: fitsview.glade:986 fitsview.glade:1084 +msgid "Min" +msgstr "" + +#: fitsview.glade:1018 fitsview.glade:1116 +msgid "Max" +msgstr "" + +#: fitsview.glade:1050 +msgid "Width" +msgstr "" + +#: fitsview.glade:1148 +msgid "Height" +msgstr "" + +#: fitsview.glade:1248 +msgid "Focus value, mm" +msgstr "" + +#: fitsview.glade:1277 +msgid "Prefocal" +msgstr "" + +#: fitsview.glade:1290 +msgid "Postfocal" +msgstr "" + +#: fitsview.glade:1308 +msgid "Image type" +msgstr "" diff --git a/src/locale/ru/ru.po b/src/locale/ru/ru.po new file mode 100644 index 0000000..54bbbec --- /dev/null +++ b/src/locale/ru/ru.po @@ -0,0 +1,590 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "Project-Id-Version: PACKAGE VERSION\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2011-07-15 11:01+0400\n" + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" + "Last-Translator: FULL NAME \n" + "Language-Team: LANGUAGE \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=koi8-r\n" + "Content-Transfer-Encoding: 8bit\n" + +#: opengl.c:53 +#, fuzzy +msgid "\n" + "OpenGL info:\n" +msgstr "Информация об OpenGL:\n" + +#: fitsview.glade:128 +msgid "3D view of full image" +msgstr "Просмотр всего изображения в 3D" + +#: fitsview.glade:136 +msgid "3D view of subframe" +msgstr "Просмотр части изображения в 3D" + +#: fitsview.glade:1240 +msgid "Focus value, mm" +msgstr "Фокус, мм" + +#: fitsview.glade:1140 +msgid "Height" +msgstr "Высота" + +#: fitsview.glade:1300 +msgid "Image type" +msgstr "Тип изображения" + +#: fitsview.glade:1042 +msgid "Width" +msgstr "Ширина" + +#: opengl.c:76 +msgid "All extensions" +msgstr "Все расширения" + +#: open_dialog.c:164 +msgid "All files" +msgstr "Все файлы" + +#: fitsview.glade:525 +msgid "Approximate by _gaussian" +msgstr "Аппроксимация _гауссианой" + +#. мало либо много точек +#: tracking.c:523 +msgid "Bad amount of data points" +msgstr "Неверное количество точек с данными" + +#: spots.c:68 +msgid "Bad markers angle" +msgstr "Неверный угол между маркерами" + +#: gtk.c:461 +msgid "Both max values are less than min" +msgstr "Обе максимальные величины меньше минимальной" + +#: imtools.c:217 imtools.c:292 +msgid "Box not found" +msgstr "Область не обнаружена" + +#: tracking.c:535 terrain.c:207 terrain.c:213 +msgid "Can't allocate memory" +msgstr "Не могу выделить память" + +#: imtools.c:445 +msgid "Can't allocate memory for Hough transform" +msgstr "Не могу выделить память для образа Хафа" + +#: imtools.c:356 +msgid "Can't allocate memory for histogram" +msgstr "Не могу выделить память для гистограммы" + +#: spots.c:138 +msgid "Can't allocate memory for new spot" +msgstr "Не могу выделить память для новой точки" + +#: spots.c:89 +msgid "Can't allocate memory for new spots" +msgstr "Не могу выделить память для новых точек" + +#: spots.c:94 +msgid "Can't allocate memory for pointers in spots array" +msgstr "Не могу выделить память для указателей в массиве точек" + +#: opengl.c:425 terrain.c:258 +msgid "Can't allocate memory for texture" +msgstr "Не могу выделить память для текстуры" + +#: tracking.c:57 tracking.c:62 tracking.c:351 +msgid "Can't allocate memory for track points" +msgstr "Не могу выделить память для точек трека" + +#: spots.c:143 +msgid "Can't copy spot to new one" +msgstr "Не могу скопировать точку" + +#: terrain.c:269 +msgid "Can't generate VBO / GL list" +msgstr "Не могу сгенерировать VBO / GL списки" + +#: spots.c:507 +#, fuzzy +msgid "Can't open file" +msgstr "Не могу сохранить файл" + +#: fits.c:287 +msgid "Can't read HDU" +msgstr "Не могу прочесть HDU" + +#: fits.c:192 +msgid "Can't read fits file" +msgstr "Не могу прочесть fits файл" + +#: gtk.c:183 +msgid "Can't save file" +msgstr "Не могу сохранить файл" + +#: spots.c:519 +#, fuzzy +msgid "Can't write to file" +msgstr "Не могу прочесть fits файл" + +#: fitsview.glade:218 +msgid "Choose minimal & maximal spot size" +msgstr "Выберите предельные размеры пятен" + +#: gtk.c:1084 +msgid "Close" +msgstr "Закрыть" + +#: fitsheaders.c:524 +msgid "Close (ctrl+w)" +msgstr "Закрыть (ctrl+w)" + +#: opengl.c:58 +msgid "Color bits" +msgstr "Биты цвета" + +#: open_dialog.c:105 +msgid "Colormap function" +msgstr "Цветовая функция" + +#: fitsheaders.c:54 +msgid "Comment" +msgstr "Комментарий" + +#. (window->context->transformMask & TRANS_TRES) && +#: imtools.c:200 imtools.c:276 +msgid "Define treshold limits first" +msgstr "Определите пороги на гистограмме" + +#: fitsheaders.c:518 +msgid "Delete entry (ctrl+d)" +msgstr "Удалить запись (ctrl+d)" + +#: opengl.c:63 +msgid "Depth bits" +msgstr "Биты глубины" + +#: fitsview.glade:152 +msgid "Draw _tracks" +msgstr "Построение т_реков" + +#: opengl.c:232 +msgid "Empty region" +msgstr "Пустая область" + +#: imtools.c:462 +msgid "Error in Hough transform module" +msgstr "Ошибка в модуле преобразования Хафа" + +#: fitsheaders.c:102 +msgid "Error in value range!" +msgstr "Ошибка в диапазоне значений!" + +#: gtk.c:58 +#, c-format +msgid "Error: %s\n" +msgstr "Ошибка: %s\n" + +#: open_dialog.c:155 +msgid "FITS files" +msgstr "FITS файлы" + +#: fitsview.glade:209 +msgid "Find and enumerate spots" +msgstr "Распознавание пятен" + +#: tracking.c:551 +#, c-format +msgid "Fit gaussian, x0=%.2f I(%.2f, %.2f), s=%.2f, A=%.1f, C=%.3f" +msgstr "Аппроксимация гауссианой, x0=%.2f I(%.2f, %.2f), s=%.2f, A=%.1f, C=" + "%.3f" + +#: fitsheaders.c:133 +msgid "Format error: complex number must be in format" +msgstr "Ошибка формата: комплексное число должно иметь формат" + +#: fitsview.glade:255 +msgid "Identify _circles" +msgstr "Идентификация _окружностей" + +#: fitsview.glade:227 +msgid "Identify _spots" +msgstr "Идентификация _пятен" + +#: imtools.c:348 imtools.c:398 imtools.c:434 +msgid "Image is empty" +msgstr "Изображение не загружено" + +#: open_dialog.c:326 +msgid "Image size" +msgstr "Размер изображения" + +#: fitsheaders.c:121 +msgid "Invalid double number!" +msgstr "Неверное число двойной точности!" + +#. номер первой точки +#. не туда попали? +#: tracking.c:528 +msgid "Invalid number of first point" +msgstr "Неверный номер первой точки" + +#: fitsview.glade:489 +msgid "Linear" +msgstr "Линейный" + +#: fitsview.glade:499 +msgid "Log" +msgstr "Логарифмический" + +#: fitsview.glade:1010 fitsview.glade:1108 +msgid "Max" +msgstr "Максимум" + +#: opengl.c:64 +msgid "Max amount of lights" +msgstr "Максимальное количество источников освещения" + +#: opengl.c:68 +msgid "Max clip planes" +msgstr "Максимальное количество плоскостей отсечения" + +#: gtk.c:464 +msgid "Max height value less than min" +msgstr "Максимальная высота пятна меньше минимальной" + +#: opengl.c:73 +msgid "Max stack depths" +msgstr "Максимальная глубина стека" + +#: opengl.c:67 +msgid "Max texture size" +msgstr "Максимальный размер текстуры" + +#: gtk.c:466 +msgid "Max width value less than min" +msgstr "Максимальная ширина меньше минимальной" + +#: fitsview.glade:978 fitsview.glade:1076 +msgid "Min" +msgstr "Минимум" + +#: fitsview.glade:817 +msgid "Mouse and arrow keys navigation" +msgstr "Навигация мышью и клавиатурой" + +#: fitsview.glade:792 +msgid "Move backward" +msgstr "Назад" + +#: fitsview.glade:776 +msgid "Move down" +msgstr "Вниз" + +#: fitsview.glade:800 +msgid "Move forward" +msgstr "Вперед" + +#: fitsview.glade:768 +msgid "Move left" +msgstr "Влево" + +#: fitsview.glade:760 +msgid "Move right" +msgstr "Вправо" + +#: fitsview.glade:784 +msgid "Move up" +msgstr "Вверх" + +#: fitsheaders.c:54 +msgid "Name" +msgstr "Название" + +#: fitsheaders.c:513 +msgid "New entry (ctrl+n)" +msgstr "Новая запись (ctrl+n)" + +#: imtools.c:213 +msgid "No GSL library found, don't calculate centroids" +msgstr "Не обнаружена библиотека GSL, не вычисляю центроиды" + +#. точек нет? (О_о, а такое вообще может быть?) +#: tracking.c:518 +msgid "No data points" +msgstr "Отсутствуют точки с данными" + +#: terrain.c:199 +msgid "No image in parent window" +msgstr "В родительское окно не загружено изображение" + +#: tracking.c:510 +msgid "No parent window or image" +msgstr "Отсутствует родительское окно или изображение в нем" + +#. "Отсутствуют распознанные пятна" +#: gtk.c:514 +msgid "No recognized spots" +msgstr "Отсутствуют распознанные пятна" + +#: fits.c:356 +msgid "Not an image? (dimensions != 2)" +msgstr "FITS-файл не является изображением? (размерность не равна двум)" + +#: spots.c:67 +msgid "Not enough markers" +msgstr "Недостаточное количество маркеров" + +#: imtools.c:257 +msgid "Not enough points" +msgstr "Недостаточно точек" + +#: fitsheaders.c:486 +msgid "Open fits file first" +msgstr "Вначале откройте fits-файл" + +#: fitsview.glade:55 +msgid "Open in _3D window" +msgstr "Открыть в окне _3D" + +#: fitsview.glade:40 +msgid "Open in _new window" +msgstr "Открыть в _новом окне" + +#: opengl.c:32 +msgid "OpenGL not supported" +msgstr "OpenGL не поддерживается вашей системой" + +#: imtools.c:296 +msgid "Pix not found" +msgstr "Маска не обнаружена" + +#: fitsview.glade:1282 +msgid "Postfocal" +msgstr "Зафокальное" + +#: fitsview.glade:1269 +msgid "Prefocal" +msgstr "Предфокальное" + +#: open_dialog.c:328 +msgid "Preview scale" +msgstr "Масштаб предпросмотра" + +#: open_dialog.c:188 +msgid "Preview settings" +msgstr "Настройки предпросмотра" + +#: open_dialog.c:96 open_dialog.c:327 +msgid "Preview size" +msgstr "Размер предпросмотра" + +#: gtk.c:1081 +msgid "Quit" +msgstr "_Выход" + +#: fitsview.glade:736 +msgid "Rotate X CCW" +msgstr "Вращать против ЧС вокруг оси X" + +#: fitsview.glade:728 +msgid "Rotate X CW" +msgstr "Врощать по ЧС вокруг оси X" + +#: fitsview.glade:744 +msgid "Rotate Z CCW" +msgstr "Вращать против ЧС вокруг оси Z" + +#: fitsview.glade:752 +msgid "Rotate Z CW" +msgstr "Врощать по ЧС вокруг оси Z" + +#: fitsview.glade:243 +msgid "Sa_ve spots" +msgstr "Со_хранить точки" + +#: fitsview.glade:144 +msgid "Select _spots" +msgstr "Выбор _точек" + +#: open_dialog.c:142 +msgid "Select fits file to open" +msgstr "Выберите, какой fits-файл открыть" + +#: terrain.c:177 +msgid "Selected area too small" +msgstr "Выделенная область слишком мала" + +#: gtk.c:358 +msgid "Selected line" +msgstr "Выбрана линия" + +#: tracking.c:475 +#, c-format +msgid "Selected region from %.1f to %.1f, %d datapoints" +msgstr "Выделена область от %.1f до %.1f, %d точек" + +#: opengl.c:241 +msgid "Selected spot: " +msgstr "Выбрано пятно: " + +#: opengl.c:239 +msgid "Selected spot[s]:\n" +msgstr "Выбраны пятна:\n" + +#: opengl.c:243 +msgid "Selected spots: " +msgstr "Выбраны пятна: " + +#: fitsview.glade:115 +msgid "Show _headers" +msgstr "Отобразить _заголовки" + +#: fitsview.glade:107 +msgid "Show _histogram" +msgstr "Отобразить _гистограмму" + +#: fitsview.glade:219 +msgid "Size _tresholds" +msgstr "Ограничение размеров" + +#: fitsview.glade:235 +msgid "So_rt hartmann spots" +msgstr "Сортировать пятна гартманограммы" + +#: spots.c:128 +msgid "Spots array is full, can't place new spot\n" +msgstr "Массив точек полон, не могу добавить новую точку\n" + +#: fitsview.glade:934 +msgid "Spots' size treshold" +msgstr "Ограничение размеров пятен" + +#: opengl.c:64 +msgid "Stencil bits" +msgstr "Биты трафарета" + +#: spots.c:66 +msgid "Too many markers" +msgstr "Слишком много маркеров" + +#: imtools.c:253 +msgid "Too many points" +msgstr "Слишком много точек" + +#: spots.c:69 +msgid "Too many rings" +msgstr "Слишком много колец" + +#. количество записей +#: fitsheaders.c:54 +msgid "Type" +msgstr "Тип" + +#: opengl.c:78 +msgid "VBO is supported\n" +msgstr "Поддерживаются буферы объектов\n" + +#: fitsheaders.c:54 +msgid "Value" +msgstr "Значение" + +#: fitsview.glade:481 +msgid "Y axis scale" +msgstr "Масштаб оси Y" + +#: spots.c:132 +msgid "Zero pointer to spot\n" +msgstr "Нулевой указатель на точку\n" + +#: fitsview.glade:181 +msgid "Zoom _in" +msgstr "У_величить" + +#: fitsview.glade:189 +msgid "Zoom _out" +msgstr "У_меньшить" + +#: fitsview.glade:92 fitsview.glade:466 fitsview.glade:705 +msgid "_Edit" +msgstr "_Изменить" + +#: fitsview.glade:16 fitsview.glade:440 fitsview.glade:671 +msgid "_File" +msgstr "_Файл" + +#: fitsview.glade:818 +msgid "_Game mode" +msgstr "" + +#: fitsview.glade:275 +msgid "_Help" +msgstr "_Помощь" + +#: fitsview.glade:263 +msgid "_Hough transform" +msgstr "Преобразование _Хафа" + +#: fitsview.glade:201 fitsview.glade:517 fitsview.glade:838 +msgid "_Math" +msgstr "_Математика" + +#: fitsview.glade:720 +msgid "_Move" +msgstr "_Переместить" + +#: fitsview.glade:173 fitsview.glade:826 +msgid "_Restore image" +msgstr "_Восстановить масштаб" + +#: fitsview.glade:210 +msgid "_Spots" +msgstr "_Пятна" + +#: fitsview.glade:99 fitsview.glade:473 fitsview.glade:712 +msgid "_View" +msgstr "_Вид" + +#: fitsview.glade:165 +msgid "_Zoom frame" +msgstr "_Увеличение рамкой" + +#: opengl.c:74 +msgid "attrib" +msgstr "атрибутовый" + +#: open_dialog.c:79 +msgid "linear" +msgstr "линейная" + +#: open_dialog.c:79 +msgid "log" +msgstr "логарифмическая" + +#: opengl.c:74 +msgid "modelview" +msgstr "модельно-видовый" + +#: opengl.c:74 +msgid "projection" +msgstr "проекций" + +#: open_dialog.c:79 +msgid "square root" +msgstr "квадратный корень" + +#: opengl.c:75 +msgid "texture" +msgstr "текстур" + +#~ msgid "You sure, you want exit?" +#~ msgstr "Вы уверены, что хотите выйти?" diff --git a/src/locale/ru/update b/src/locale/ru/update new file mode 100755 index 0000000..968e913 --- /dev/null +++ b/src/locale/ru/update @@ -0,0 +1,2 @@ +#!/bin/sh +msgmerge -Uis ru.po messages.po diff --git a/src/open_dialog.c b/src/open_dialog.c new file mode 100644 index 0000000..1a1b129 --- /dev/null +++ b/src/open_dialog.c @@ -0,0 +1,433 @@ +// open_dialog.c - open file dialog with preview +// Copyright: +// Guillaume Chazarain (gliv project) +// 2011 Edward V. Emelianoff (modification for fits open) +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "fitsview.h" +#include "open_dialog.h" + +gchar *saved_path = NULL; + +typedef struct{ + GtkLabel *label; + GtkWidget *image; +} fits_preview; + +GdkPixbuf *getfits(char *filename, gchar **descr); + +void update_preview( GtkFileChooser *file_chooser, + fits_preview *FP){ + GtkWidget *preview = FP->image; + GtkLabel *ilabel = FP->label; + char *filename; + gchar *descr = NULL; + GdkPixbuf *pixbuf = NULL; + filename = gtk_file_chooser_get_preview_filename(file_chooser); + if (filename == NULL) return; + pixbuf = getfits(filename, &descr); + if(pixbuf){ + gint w = gdk_pixbuf_get_width(pixbuf); + w = w - w/4; + if(w < 200) w = 200; + gtk_widget_set_size_request(GTK_WIDGET(ilabel), w, -1); + gtk_label_set_text(ilabel, descr); + } + g_free(filename); + gtk_image_set_from_pixbuf(GTK_IMAGE(preview), pixbuf); + if(pixbuf) g_object_unref(pixbuf); + gtk_file_chooser_set_preview_widget_active(file_chooser, (pixbuf != NULL)); +} + +void cb_changed(GtkComboBox *combo, GtkObject *chooser){ + GtkTreeIter iter; + GtkTreeModel *model; + int val; + gtk_combo_box_get_active_iter(combo, &iter); + model = gtk_combo_box_get_model(combo); + gtk_tree_model_get(model, &iter, 1, &val, -1); + // clear needed config bits + if(val & PREVIEW_SIZEMASK) Global->previewMode &= ~PREVIEW_SIZEMASK; + else if(val & PREVIEW_COLFNMASK) Global->previewMode &= ~PREVIEW_COLFNMASK; + Global->previewMode |= val; // set new + gtk_signal_emit_by_name(chooser, "update-preview"); // redraw +} + +// arguments of funtion which creates menu +enum{ + COMBO_SIZE, + COMBO_COLORFN +}; +GtkWidget *create_combo(unsigned char type, GtkObject *chooser){ + #define SIZES 5 + #define COLORFNS 3 + gchar *sizes[] = {"128 x 128", "256 x 256", "512 x 512", + "768 x 768", "1024 x 1024"}; + gchar *colorfns[] = {N_("linear"), N_("log"), N_("square root")}; + gchar **ptr; + GtkWidget *label = NULL, *vbox = NULL; + gchar *label_text = NULL; + int curpos, i, n; + int startpos = 0; // bit position of appropriate numbers + GtkListStore *store; + GtkWidget *combo; + GtkCellRenderer *cell; + GtkTreeIter iter; + if(type == COMBO_SIZE){ + ptr = sizes; n = SIZES; + if((curpos = Global->previewMode & PREVIEW_SIZEMASK)) + curpos--; + else + curpos = PREVIEW_512 - 1; + startpos = 0; + label_text = g_strdup_printf(_("Preview size")); + } + else if(type == COMBO_COLORFN){ + ptr = colorfns; n = COLORFNS; + if((curpos = (Global->previewMode & PREVIEW_COLFNMASK) >> 3)) + curpos--; + else + curpos = (PREVIEW_SQRT>>3) - 1; + startpos = 3; + label_text = g_strdup_printf(_("Colormap function")); + } + else return NULL; + vbox = gtk_vbox_new(FALSE, 3); + label = gtk_label_new(label_text); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); + for(i = 0; i < n; i++){ + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, _(ptr[i]), 1, (i+1)<3) r=255; + } + *rgb++ = r; + *rgb++ = g; + *rgb++ = b; +}*/ +void gray2rgb(float gray, guchar *rgb){ + int i = (int)(gray * 4.); + float x = gray - (float)i * .25; + guchar r = 0, g = 0, b = 0; + switch(i){ + case 0: + g = (guchar)(255. * x); + b = 255; + break; + case 1: + g = 255; + b = (guchar)(255. * (1. - x)); + break; + case 2: + r = (guchar)(255. * x); + g = 255; + break; + case 3: + r = 255; + g = (guchar)(255. * (1. - x)); + break; + default: + r = 255; + } + *rgb++ = r; + *rgb++ = g; + *rgb++ = b; +} + +GdkPixbufDestroyNotify free_preview_data(guchar *pixels, gpointer data){ + free(pixels); + free(data); + return FALSE; +} + +// get preview from FITS file filename +GdkPixbuf *getfits(char *filename, gchar **descr){ + gboolean status; + fitsfile *fp; + double (*colorfun)(double); + double linfun(double arg){ return arg; } // bung for PREVIEW_LINEAR + double logfun(double arg){ return log(1.+arg); } // for PREVIEW_LOG + gchar *description = NULL; // some keywords from FITS header + #define TRYFITS(f, ...) \ + do{ status = FALSE; \ + f(__VA_ARGS__, &status); \ + if(status){ \ + free(ima_data); free(pixbuf_data); \ + free(description); free(pix); \ + fits_close_file(fp, &status); \ + return NULL;} \ + }while(0) + void add_keyw(char *keyw){ + char keyval[FLEN_VALUE], *ptr; + if(VALUE_UNDEFINED == fits_read_key(fp, + TSTRING, keyw, keyval, NULL, &status)) return; + if(status) return; + ptr = g_strdup_printf("%s;\t%s=%s", description, keyw, keyval); + free(description); + description = ptr; + } + int MAX_SIZE = 512; // max preview size + unsigned char cntxt; + GdkPixbuf *pixbuf = NULL; + float nullval = 0.; + int i, j, k, l, N, M, stat; + int naxis, w, h, pixScale, Ws, Hs, dtype; + int sz; + cntxt = Global->previewMode & PREVIEW_SIZEMASK; // get preview size + if(cntxt) + switch(cntxt){ + case PREVIEW_128: MAX_SIZE = 128; + break; + case PREVIEW_256: MAX_SIZE = 256; + break; + case PREVIEW_512: MAX_SIZE = 512; + break; + case PREVIEW_768: MAX_SIZE = 768; + break; + case PREVIEW_1024:MAX_SIZE = 1024; + break; + } + // array for preview picture line + float *pix = malloc(MAX_SIZE * sizeof(float)); + long naxes[4]; + float *ima_data = NULL, *ptr, byte, n, m, max, min, wd, avr; + guchar *pptr, *pixbuf_data = NULL; + DBG("Try to open file %s\n", filename); + TRYFITS(fits_open_file, &fp, filename, READONLY); + TRYFITS(fits_get_img_param, fp, 4, &dtype, &naxis, naxes); + if(naxis != 2) return NULL; + w = naxes[0]; + h = naxes[1]; + sz = w * h; + ima_data = malloc(sz * sizeof(float)); + pixbuf_data = malloc(3 * MAX_SIZE * MAX_SIZE * sizeof(guchar)); + + TRYFITS(fits_read_img, fp, TFLOAT, 1, sz, &nullval, ima_data, &stat); + ptr = ima_data; + min = max = *ptr; avr = 0.; + // get statistics: + for(i=0; i max) max = tmp; + else if(tmp < min) min = tmp; + avr += tmp; + } + avr /= (float)sz; + wd = max - min; + i = (int)ceil((float)w / MAX_SIZE); + j = (int)ceil((float)h / MAX_SIZE); + DBG("i=%d, j=%d, ms=%d",i,j,MAX_SIZE); + pixScale = (i > j) ? i : j; // picture scale factor + Ws = w / pixScale; // picture width in pixScale blocks + Hs = h / pixScale; // -//- height pixScale + DBG("w=%d, h=%d, Ws=%d, Hs=%d, pixScale=%d",w,h,Ws,Hs,pixScale); + // prepare a comment to a prewiew: + description = g_strdup_printf( + "%s=%dx%d\n%s=%dx%d\n%s=%.1f%%;\tMax=%g,\tMin=%g,\tAvr=%g", + _("Image size"), w, h, + _("Preview size"), Ws, Hs, + _("Preview scale"), 100./(double)pixScale, + max, min, avr + ); + add_keyw("BITPIX"); + add_keyw("IMAGETYP"); + add_keyw("OBJECT"); + add_keyw("EXPTIME"); add_keyw("EXP"); + add_keyw("AUTHOR"); + add_keyw("DATE"); + M = 0; // line number + for(i = 0; i < Hs; i++){ // cycle through a blocks by lines + //pptr = &pixbuf_data[i * Ws * 3]; + for(j = 0; j < MAX_SIZE; j++) pix[j] = 0; + m = 0.; // amount of strings read in block + for(l = 0; l < pixScale; l++, m++){ // cycle through a block lines + ptr = &ima_data[M * w]; + N = 0; // number of column + for(j = 0; j < Ws; j++){ // cycle through a blocks by columns + n = 0.; // amount of columns read in block + byte = 0.; // average intensity in block + for(k = 0; k < pixScale; k++, n++){ // cycle through block pixels + if(N++ < w) // row didn't end + byte += *ptr++; // sum[(pix-min)/wd]/n = [sum(pix)/n-min]/wd + else break; + } + pix[j] += byte / n;//(byte / n - min)/wd; + } + if(++M >= h) break; + } + // fill unused picture pixels + ptr = &ima_data[i*Ws]; + for(l = 0; l < Ws; l++) + *ptr++ = pix[l] / m; + } + ptr = ima_data; + sz = Ws * Hs; + max = min = *ptr; + avr = 0; + for(i=0; i < sz; i++, ptr++){ + float tmp = *ptr; + if(tmp > max) max = tmp; + else if(tmp < min) min = tmp; + avr += tmp; + } + avr /= (float)sz; + wd = max - min; + avr = (avr - min) / wd; // normal average by preview + avr = -log(avr); // scale factor + if(avr > 1.) wd /= avr; + ptr = ima_data; + colorfun = sqrt; + if((Global->previewMode & PREVIEW_COLFNMASK) == PREVIEW_LINEAR) + colorfun = linfun; + else if((Global->previewMode & PREVIEW_COLFNMASK) == PREVIEW_LOG) + colorfun = logfun; + for(i = Hs - 1; i > -1; i--){// fill pixbuf mirroring image by vertical + pptr = &pixbuf_data[Ws * i * 3]; + for(j = 0; j < Ws; j++){ + gray2rgb(colorfun((*ptr++ - min) / wd), pptr); + pptr += 3; + } + } + fits_close_file(fp, &status); + pixbuf = gdk_pixbuf_new_from_data( + pixbuf_data, // guchar* data + GDK_COLORSPACE_RGB, // only this supported + FALSE, // no alpha + 8, // number of bits in byte (WTF? who is this idiot?) + Ws, Hs, // size + Ws * 3, // line length in bytes + (GdkPixbufDestroyNotify)free_preview_data, // function (*GdkPixbufDestroyNotify) (guchar *pixels, gpointer data); + (gpointer)description // pointer data + ); + free(ima_data); + free(pix); + *descr = description; + DBG("OK, preview is ready, previewMode = %d", Global->previewMode); + return pixbuf; +} diff --git a/src/opengl.c b/src/opengl.c new file mode 100644 index 0000000..d5f1899 --- /dev/null +++ b/src/opengl.c @@ -0,0 +1,731 @@ +// opengl.c - functions to work with openGL +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "opengl.h" +#include "spots.h" +#include "imtools.h" +#include "contours.h" +#include "open_dialog.h" + +#define BUFSIZE 512 // select buffer sizw +GLdouble ObjX = 0., ObjY = 0.; // mouse click coordinates (in window CS) +gboolean forcesel = FALSE; +gboolean useVBO = FALSE; // use VBO or lists + +// these functions are absent in headers +void glBlendEquation(GLenum); +void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); + +#define BADOGL() do{g_err(_("OpenGL not supported")); \ + g_assert_not_reached();}while(0) + +gboolean queryExtension(char *extName){ + char *p = (char *)glGetString(GL_EXTENSIONS); + char *end = p + strlen(p); + while(p < end){ + size_t n = strcspn(p, " "); + if((strlen(extName)==n) && (strncmp(extName,p,n)==0)) + return TRUE; + p += (n + 1); + } + return FALSE; +} + +void getGLinfo(GtkWidget *Area){ + GLint a,b,c,d; + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + g_print(_("\nOpenGL info:\n")); + glGetIntegerv(GL_RED_BITS, &a); + glGetIntegerv(GL_GREEN_BITS, &b); + glGetIntegerv(GL_BLUE_BITS, &c); + glGetIntegerv(GL_ALPHA_BITS, &d); + g_print("%s (R, G, B, alpha) = (%d, %d, %d, %d)\n", _("Color bits"), + a, b, c, d); + glGetIntegerv(GL_DEPTH_BITS, &a); + glGetIntegerv(GL_STENCIL_BITS, &b); + glGetIntegerv(GL_MAX_LIGHTS, &c); + g_print("%s = %d, %s = %d, %s = %d\n", _("Depth bits"), a, + _("Stencil bits"), b, _("Max amount of lights"), c); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &a); + glGetIntegerv(GL_MAX_CLIP_PLANES, &b); + g_print("%s = %d, %s = %d\n", _("Max texture size"), a, + _("Max clip planes"), b); + glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &a); + glGetIntegerv(GL_MAX_PROJECTION_STACK_DEPTH, &b); + glGetIntegerv(GL_MAX_ATTRIB_STACK_DEPTH, &c); + glGetIntegerv(GL_MAX_TEXTURE_STACK_DEPTH, &d); + g_print("%s: %s = %d, %s = %d, %s = %d, %s = %d\n", _("Max stack depths"), + _("modelview"), a, _("projection"), b, _("attrib"), c, + _("texture"), d); + //g_print("%s: %s\n", _("All extensions"), (char *)glGetString(GL_EXTENSIONS)); + useVBO = queryExtension("GL_ARB_vertex_buffer_object"); + if(useVBO) g_print(_("VBO is supported\n")); + gdk_gl_drawable_gl_end(glDrawable); +} + +void freeGLmemory(Window *window){ + if(!window->texture) return; + GLuint *tex = &window->texture->tex; + if(!*tex) return; + if(useVBO) + glDeleteBuffersARB(1, tex); + else + glDeleteLists(*tex, 1); +} + +/* + * Coordinates transformation from CS of drawingArea into CS of picture + * x,y - pointer coordinates + * X,Y - coordinates of appropriate point at picture + */ +void conv_mouse_to_image_coords(double x, double y, + double *X, double *Y, + Window *window){ + double a = window->Daspect / window->Zoom; + *X = x * a - window->mouse.x; + *Y = window->mouse.y - y * a; +} + +void conv_image_to_mouse_coords(double X, double Y, + double *x, double *y, + Window *window){ + double a = window->Zoom / window->Daspect; + *x = (X + window->mouse.x) * a; + *y = (window->mouse.y - Y) * a; +} + +gboolean configure(GtkWidget *Area, GdkEventExpose *event, Window *window){ + //FNAME(); + double a, A, W, H, w, h; + double Zoom = window->Zoom; + if(event) + if(event->count) return FALSE; + TEXTURE *texture = (TEXTURE*)window->texture; + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + gdk_gl_drawable_wait_gdk(glDrawable); + // set position + double W0, H0, x0,y0, xm,ym; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + W = W0 = Area->allocation.width; + H = H0 = Area->allocation.height; + A = W / H; + glViewport(0, 0, W, H); + if(texture){ + // compute the right dimensions for rectangle with picture + w = texture->w; h = texture->h; + a = w / h; w /= 2.; h /= 2.; + if(A > a){ + W = h * A; H = h; + //window->mouse.rx = (W0 - H0*a)/2.; window->mouse.ry = 0.; + window->Daspect = h / H0 * 2.; + } + else{ + H = w / A; W = w; + //window->mouse.ry = (H0 - W0/a)/2.; window->mouse.rx = 0.; + window->Daspect = w / W0 * 2.; + } + // recalculate limits for the rulers + x0 = W/Zoom - w + window->move.x / Zoom; + window->mouse.x = x0; + y0 = H/Zoom - h + window->move.y / Zoom; + xm = 2. * W / Zoom - x0; + ym = 2. * H / Zoom - y0; + window->mouse.y = ym; + //DBG("W=%g, w=%g, Zoom=%g, x0=%g, xm=%g, Da=%g", W, w, Zoom, x0, xm, Daspect); + set_Drulers(x0, y0, xm, ym, window); + glOrtho(-W,W, -H,H, -1., 1.); + } + glMatrixMode(GL_MODELVIEW); + glBlendEquation(GL_FUNC_ADD); + glEnable(GL_BLEND); + gdk_gl_drawable_gl_end(glDrawable); + refresh_state(window); + return FALSE; +} + +/* + * Ellipse with center at (x,y) and radii (rx,ry) + * plots as polyline with stride of 10 degr + */ +void draw_ellipce(GLfloat x, GLfloat y, GLfloat rx, GLfloat ry){ + GLfloat pi2 = M_PI * 2., step = pi2 / 36., angle; + glBegin(GL_LINE_LOOP); + for(angle = 0.; angle < pi2; angle += step) + glVertex2f(x+rx*cos(angle), y+ry*sin(angle)); + glEnd(); +} + +/* + * Graphical indication of recognized spots + * window - the window with picture + * w, h - half-width & half-height of picture (texture) + * CX0, CY0 - zero point coordinates in picture CS (in center) + * box == TRUE - draw region, == FALSE - set selection region + */ +void draw_squares(Window *window, double w, double h, double CX0, double CY0, gboolean box){ + int i, n; + double x0, y0, x1, y1; + BOX *bbox; Spot **spot; Coordinates *crds; + Spots *spots = window->spots; + if(!spots) return; + n = spots->n; + spot = spots->spot; + glPushMatrix(); + for(i = 0; i < n; i++){ + bbox = &spot[i]->box; + crds = &spot[i]->c; + x0 = bbox->x - w; y0 = h - bbox->y; + x1 = x0 + bbox->w; y1 = y0 - bbox->h; + if(box){ + glColor3f(0., 1., 0.); // green is box borders + glBegin(GL_LINE_LOOP); + glVertex2f(x0, y0); glVertex2f(x1, y0); + glVertex2f(x1, y1); glVertex2f(x0, y1); + glEnd(); + if(window->context->transformMask & TRANS_HARTMANN){ + x0 = crds->x + CX0; + y0 = crds->y + CY0; + glColor3f(1., 0., 0.); // red is spot half-widths + draw_ellipce(x0, y0, crds->rx, crds->ry); + //glColor3f(0., 1., 0.); + } + }else{ // box == FALSE - set frames for selection + glLoadName(i); + glRectf(x0, y0, x1, y1); + } + } + if(box){ // draw cross - CS zero + glColor3f(1., 0., 1.); + glBegin(GL_LINES); + glVertex2f(CX0-15., CY0-15.); glVertex2f(CX0+15., CY0+15.); + glVertex2f(CX0-15., CY0+15.); glVertex2f(CX0+15., CY0-15.); + glEnd(); + } + glPopMatrix(); +} + +/* + * graphical indication of isolines + * window - the window + * w, h - half-width & half-height of picture (texture) + * CX0, CY0 - zero point coordinates in picture CS (in center) + * _2D == TRUE for 2D images + */ +void draw_isolines(Window *window, double w, double h, gboolean _2D){ + //FNAME(); + IMAGE *ima = window->image; + if(!ima) return; + Contour *c; + cPoint *p; + cList **cl = window->image->contours; + int i, n = window->image->Ncontours; + if(!cl || n < 1) return; + float m = ima->stat.min, wd = ima->stat.max - m; + float *lvls = ima->cLevels, curLevel; + guchar colr[3] = {255,0,0}; // isolines' colors + glPushMatrix(); + for(i = 0; i < n; i++, cl++){ // cycle by isolines + if(!*cl) continue; // given level doesn't have isolines + c = (*cl)->first; // first contour for given level + if(!c || (*cl)->N < 1) continue; + gray2rgb((lvls[(*cl)->L]-m)/wd, colr); // convert level into color + glColor3ubv(colr); + if(_2D) curLevel = 0.; + else curLevel = lvls[(*cl)->L] * ((GLfloat)w + (GLfloat)h) / 2./ wd; + //DBG("curLevel=%g, gray=%g, color=(%d,%d,%d)", curLevel, (lvls[(*cl)->L]-m)/wd, colr[0],colr[1],colr[2]); + while(c){ // cycle by contours + p = c->first; + if(!p || c->N < 1){ c = c->next; continue;} + if(c->closed) + glBegin(GL_LINE_LOOP); + else + glBegin(GL_LINE_STRIP); + while(p){ // cycle by contour points + glVertex3f(p->x-w, p->y-h, curLevel); + p = p->next; + } + glEnd(); + c = c->next; + } + } + glPopMatrix(); +} + +void processHits(Window *window, GLint hits, GLuint buffer[]){ + unsigned int i, j; + GLuint names, *ptr; + Spot *spot; + //Coordinates crds; + gchar buf[256]; + int l = 255; + Spots *spots = window->spots; + if(!spots) return; + printf ("hits = %d\n", hits); + ptr = (GLuint *) buffer; + if(hits < 1){ + set_status_text(StatusText, _("Empty region"), window); + return; + } + for (i = 0; i < (unsigned int)hits; i++){ + names = *ptr; + DBG("number of names for this hit = %d\n", names); + ptr += 3; + g_print(_("Selected spot[s]:\n")); + if(names == 1) + g_sprintf(buf, _("Selected spot: ")); + else + g_sprintf(buf, _("Selected spots: ")); + l -= strlen(buf); + for (j = 0; j < names; j++){ + if(*ptr < (GLuint)spots->n){ + spot = spots->spot[*ptr]; + g_print("id: %d, xC=%.2f, yC=%.2f, r=%.2f, phi=%.2f, ", + spot->id, spot->xC, spot->yC, spot->r, spot->phi); + g_print("cn: (%.1f, %.1f), dr:(%.1f, %.1f)\n", + spot->c.x, spot->c.y, spot->c.rx, spot->c.ry); + g_print("box X: [%d, %d], box Y: [%d, %d], cn: [%d, %d]\n", + spot->box.x, spot->box.x+spot->box.w, + spot->box.y, spot->box.y+spot->box.h, + spot->box.x+spot->box.w/2, spot->box.y+spot->box.h/2); + /* get_gaussian_center(&spot->box, &crds); + g_print("\tCenter: (%.1f, %.1f), wd: (%.1f, %.1f)", + crds.x, (double)image->height-crds.y-1., crds.rx, crds.ry); + */ + l -= g_snprintf(&buf[strlen(buf)], l, "%d ", spot->id); + } + ptr++; + } + g_print("\n"); + set_status_text(StatusText, buf, window); + } +} + +gboolean expose(GtkWidget *Area, GdkEventExpose *event, Window *window){ + TEXTURE *texture = (TEXTURE*)window->texture; + double w, h; + GLfloat tred[4] = {1.,0.,0.,1.}; + GLfloat twhite[4] = {1.,1.,1.,1.}; + if(event) + if(event->count) return FALSE; + //FNAME(); + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + glClearColor(.1, .1, .1, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glColor3fv(tred); + if(texture){ + w = texture->w / 2.; h = texture->h / 2.; + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(window->move.x, window->move.y, 0.); + glScalef(window->Zoom, window->Zoom, 1.); + //DBG("moveXY=%g, %g; Zoom=%g, texture: id=%d, w=%d", + // window->move.x, window->move.y, window->Zoom, texture->tex, texture->w); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture->tex); + //DBG("texture: %d", texture->tex); + //DBG("w=%g, h=%g\n", w, h); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, twhite); + glDisable(GL_BLEND); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); glVertex2f(-w, -h ); + glTexCoord2f(1.0f, 0.0f); glVertex2f( w, -h ); + glTexCoord2f(1.0f, 1.0f); glVertex2f( w, h ); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-w, h ); + glEnd(); + glDisable(GL_TEXTURE_2D); + /*glBegin(GL_LINE_LOOP); + glTexCoord2f(0.0f, 0.0f); glVertex2f(-w, -h ); + glTexCoord2f(1.0f, 0.0f); glVertex2f( w, -h ); + glTexCoord2f(1.0f, 1.0f); glVertex2f( w, h ); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-w, h ); + glEnd();*/ + glEnable(GL_BLEND); + + // draw addition information + if(window->context->visualMode & + (SHOW_POTBOXES|SHOW_POTSELECT|SHOW_ISOLINES)){ + GLuint selectBuf[BUFSIZE]; + GLint hits; + double CX0 = AxisX - w, CY0 = h - AxisY; + gboolean showiso = window->context->visualMode & SHOW_ISOLINES; + gboolean showbox = window->context->visualMode & SHOW_POTBOXES; + gboolean showsel = window->context->visualMode & SHOW_POTSELECT; + GLint viewport[4]; + if(showsel && forcesel){ // get coordinates from selection buffer + glPushMatrix(); + glGetIntegerv(GL_VIEWPORT, viewport); + glSelectBuffer(BUFSIZE, selectBuf); + glRenderMode(GL_SELECT); + glInitNames(); + glPushName(0); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + /* create pixel picking region near cursor location */ + DBG("x=%g, y=%g", ObjX, ObjY); + gluPickMatrix(ObjX, (viewport[3] - ObjY), 5.0, 5.0, viewport); + //gluPickMatrix(ObjX, ObjY, 10.0, 10.0, viewport); + double W = Area->allocation.width; + double H = Area->allocation.height; + double A = W / H; + if(A > w / h){ + W = h * A; H = h;} + else{ + H = w / A; W = w;} + glOrtho(-W,W, -H,H, -1., 1.); + glMatrixMode(GL_MODELVIEW); + if(showbox) draw_squares(window, w, h, CX0, CY0, FALSE); + if(showiso) draw_isolines(window, w, h, TRUE); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + hits = glRenderMode(GL_RENDER); + processHits(window, hits, selectBuf); + forcesel = FALSE; + glPopMatrix(); + } + glMatrixMode(GL_MODELVIEW); + if(showbox) draw_squares(window, w, h, CX0, CY0, TRUE); + if(showiso) draw_isolines(window, w, h, TRUE); + } +/* + // layers mix ===> + glLoadIdentity(); + //glTranslatef(moveX,moveY,0); + glScalef(window->Zoom, window->Zoom, 1.); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, tred); + glBlendFunc(GL_ONE, GL_ONE); + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); glVertex2f(-w, -h ); + glTexCoord2f(1.0f, 0.0f); glVertex2f( w, -h ); + glTexCoord2f(1.0f, 1.0f); glVertex2f( w, h ); + glTexCoord2f(0.0f, 1.0f); glVertex2f(-w, h ); + glEnd(); + glDisable(GL_TEXTURE_2D); + // <==== +*/ + } + else DBG("No texture"); + gdk_gl_drawable_swap_buffers(glDrawable); + gdk_gl_drawable_wait_gl(glDrawable); + gdk_gl_drawable_gl_end(glDrawable); + return FALSE; +} + +/* +gboolean idle(gpointer userData){ + GtkWidget *Area = GTK_WIDGET(userData); + gdk_window_invalidate_rect(Area->window, &Area->allocation, FALSE); + return TRUE; +}*/ + +void initGl(GtkWidget *Area){ + FNAME(); + GdkGLConfig *glConfig; + GdkGLConfigMode mode = GDK_GL_MODE_RGB | + GDK_GL_MODE_DEPTH | + GDK_GL_MODE_ALPHA | + GDK_GL_MODE_DOUBLE; + if(!gdk_gl_query_extension()) + BADOGL(); + glConfig = gdk_gl_config_new_by_mode(mode); + if(!glConfig) + glConfig = gdk_gl_config_new_by_mode(mode & ~GDK_GL_MODE_ALPHA); + if(!glConfig) + BADOGL(); + if(!gtk_widget_set_gl_capability(Area, glConfig, NULL, TRUE, + GDK_GL_RGBA_TYPE)) + BADOGL(); +} + +/* + * generation of textures to show in Area widget of image + * if redraw == TRUE texture substitutes previous otherwise generate the new one + */ +void gen_texture(IMAGE *image, Window *window, gboolean redraw){ + //~ FNAME(); + TEXTURE *texture = window->texture; + GtkWidget *Area = window->drawingArea; + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + glEnable(GL_TEXTURE_2D); + if(!texture){ + INIT(window->texture, TEXTURE); + texture = window->texture; + if(!texture){ + g_err(_("Can't allocate memory for texture")); + return; + } + //~ g_print(_("Generating texture\n")); + glGenTextures(1, &texture->tex); + //~ DBG("%s, tex=%d\n", gluErrorString(glGetError()), texture->tex); + } + //~ DBG("Image size: w=%d, h=%d", image->width, image->height); + glBindTexture(GL_TEXTURE_2D, texture->tex); + if(image && image->data){ + int i,j, k, w = image->width, h = image->height; + GLfloat *tex = NULL; + GLfloat min = image->stat.min, wd = image->stat.max - min; + GLfloat *ptri = image->data, *ptro; + DBG("min = %g, wd = %g", min, wd); + if(image->stat.max > 1. || min < 0.){ + tex = malloc(w * h * sizeof(GLfloat)); + ptro = tex; + #ifdef EBUG + double t0 = dtime(); + #endif + #pragma omp parallel for + for(i=0; idata; + if(redraw) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, + GL_LUMINANCE, GL_FLOAT, ptro); // s/image->data/tex/ + else + glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY, w, h, + 0, GL_LUMINANCE, GL_FLOAT, ptro); + //DBG("%s\n", gluErrorString(glGetError())); + //DBG("W: %d, H:%d, im[100]=%f\n", image->width, image->height, image->data[100]); + texture->w = image->width; texture->h = image->height; + free(tex); + } + else{ + DBG("Empty texture"); + glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY, 10., 10., + 0, GL_INTENSITY, GL_FLOAT, NULL); + } + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); + glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); + //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); + //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); + glDisable(GL_TEXTURE_2D); + gdk_gl_drawable_gl_end(glDrawable); +/* + * Events: + * expose - redraw part of an image + * configure - redraw window when size/position changed + * if handler returns TRUE signal processing stops + * otherwise it can be process by other handlers + */ + g_signal_connect(Area, "expose-event", + G_CALLBACK(expose), window); + g_signal_connect(Area, "configure-event", + G_CALLBACK(configure), window); + //~ DBG("end"); + force_redraw(Area); +} + +void force_redraw(GtkWidget *Area){ + gtk_widget_queue_resize(Area); + gtk_widget_queue_draw(Area); +} + +void pickRegion(double x, double y){ + FNAME(); + forcesel = TRUE; + ObjX = x; + ObjY = y; +} + +gboolean configure3D(GtkWidget *Area, GdkEventExpose *event, Window *window){ + FNAME(); + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + double w, h; + GLfloat ratio; +if(!window)return FALSE; + //~ GLfloat H = (GLfloat)window->texture->h; + if(event) + if(event->count) return FALSE; + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + w = Area->allocation.width; + h = Area->allocation.height; + ratio = w / h; + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45., ratio, 0.1, 1e10); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gdk_gl_drawable_gl_end(glDrawable); + return FALSE; +} + +gboolean expose3D(GtkWidget *Area, GdkEventExpose *event, Window *window){ + FNAME(); + TEXTURE *texture = window->texture; + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + GLfloat H = (GLfloat)window->image->height; + if(event) + if(event->count) return FALSE; + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + GLfloat spotDir[] = {0.,0.,-1.,1.}; + GLfloat pos[] = {0.,0.,0.,1.}; + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + glLightfv(GL_LIGHT1,GL_POSITION,pos); + glLightfv(GL_LIGHT1,GL_SPOT_DIRECTION, spotDir); + if(window->context->drawingMode == GAME_MODE){ // mouse navigation + GLfloat lx, ly, lz, za = window->Zangle, xa = window->Xangle; + GLfloat x = window->move.x, y = window->move.y, z = H * window->Zoom; + lx = cosf(za)*sinf(xa); + ly = -sinf(za); + lz = -cosf(za)*cosf(xa); + gluLookAt(x, y, z, + x+lx, y+ly, z+lz, + 0., 1., 0.); + //~ GLfloat w = Area->allocation.width, h = Area->allocation.height; + //GLfloat z = H * window->Zoom; + //~ GLfloat yangle = 180. / M_PI * atan2f(window->Xangle - w/2., z); + //~ GLfloat xangle = 180. / M_PI * atan2f(window->Zangle - h/2., z); + //~ glRotatef(yangle, 0.,1.,0.); + //~ glRotatef(xangle, 1.,0.,0.); + //~ glTranslatef(window->move.x, 0., -z); + //glRotatef(window->Xangle, 0.,1.,0.); + //~ glRotatef(window->Zangle, 1.,0.,0.); + //glRotatef(window->Zangle, cosf(window->Xangle*M_PI/180.),0.,sinf(window->Xangle*M_PI/180.)); + //glTranslatef(window->move.x, 0., -z); + }else{ // key navigation + glTranslatef(window->move.x, window->move.y, -H * window->Zoom); + glRotatef(window->Xangle, 1.,0.,0.); + glRotatef(window->Zangle, 0.,0.,1.); + } + glPushMatrix(); + glEnable(GL_LIGHTING); + if(useVBO){ + GLint y, w = window->image->width, h = window->image->height; + GLint pts = w*2; + size_t sz = w * h * 3 * sizeof(GLfloat); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, texture->tex); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, texture->w); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_INDEX_ARRAY); + glNormalPointer(GL_FLOAT, 0, (GLfloat*)sz); + glIndexPointer(GL_UNSIGNED_INT, 0, 0 ); + glVertexPointer(3, GL_FLOAT, 0, 0); + h--; + sz = 0; w = pts * sizeof(GLfloat); + for(y = 0; y < h; y++, sz += w) + glDrawElements(GL_TRIANGLE_STRIP, pts, GL_UNSIGNED_INT, (GLvoid*)(sz)); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_INDEX_ARRAY); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + }else + glCallList(texture->tex); + glPushMatrix(); + glDisable(GL_LIGHTING); + glLineWidth(3.0); + draw_isolines(window, ((double)window->image->width)/2.+.5, + ((double)window->image->height)/2.+.5, FALSE); + /*glColor3f(0.,0.,1.); + glBegin(GL_QUADS); + glVertex3f(-100.f, -100.f, -12.f ); + glVertex3f(-100.f, 100.f, -12.f ); + glVertex3f( 100.f, 100.f, -12.f ); + glVertex3f( 100.f, -100.f, -12.f ); + glEnd(); + glBegin(GL_LINES); + glVertex3fv(pos); + glColor3f(1.f,0.f,0.f); + glVertex3fv(spotDir); + glEnd();*/ + glPopMatrix(); + gdk_gl_drawable_swap_buffers(glDrawable); + gdk_gl_drawable_wait_gl(glDrawable); + gdk_gl_drawable_gl_end(glDrawable); + return FALSE; +} + +void init3D(Window *window){ + FNAME(); + GtkWidget *Area = window->drawingArea; + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + GLfloat lAmbient[] = {0.3,0.3,0.3,1.0}; + GLfloat lDiffuse[] = {1.0,1.0,1.0,1.0}; + GLfloat lSpecular[]= {1.0,1.0,1.0,1.0}; + GLfloat l2Ambient[] = {0.0,0.0,0.1,1.0}; + GLfloat l2Diffuse[] = {0.0,0.0,0.3,1.0}; + GLfloat l2Specular[]= {0.0,0.0,0.5,1.0}; + GLfloat mShininess[] = {110.0}; + GLfloat mSpecular[] = {0.8,0.8,1.0,1.0}; + GLfloat mDiffuse[] = {0.8,0.8,1.0,1.0}; + GLfloat mAmbient[] = {0.2,0.2,0.2,1.0}; + if (!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + glEnable(GL_RESCALE_NORMAL); + glEnable(GL_DEPTH_TEST); + //~ glEnable(GL_CULL_FACE); + //~ glCullFace(GL_BACK); + GLfloat spotDir[] = {0.,0.,-1.,1.}; + GLfloat pos[] = {0.,0.,0.,1.}; + glLightfv(GL_LIGHT1,GL_POSITION,pos); + glLightfv(GL_LIGHT1,GL_SPOT_DIRECTION, spotDir); + glLightf(GL_LIGHT1,GL_SPOT_CUTOFF, 90.); + glLightf(GL_LIGHT1,GL_SPOT_EXPONENT, 20.); + glLightfv(GL_LIGHT0,GL_AMBIENT,l2Ambient); + glLightfv(GL_LIGHT0,GL_DIFFUSE,l2Diffuse); + glLightfv(GL_LIGHT0,GL_SPECULAR,l2Specular); + glLightfv(GL_LIGHT1,GL_AMBIENT,lAmbient); + glLightfv(GL_LIGHT1,GL_DIFFUSE,lDiffuse); + glLightfv(GL_LIGHT1,GL_SPECULAR,lSpecular); + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE); + glMaterialfv(GL_FRONT, GL_SPECULAR, mSpecular); + glMaterialfv(GL_FRONT, GL_SHININESS,mShininess); + glMaterialfv(GL_FRONT, GL_AMBIENT, mAmbient); + glMaterialfv(GL_FRONT, GL_DIFFUSE, mDiffuse); + //~ glMaterialfv(GL_BACK, GL_SPECULAR, mSpecular); + //~ glMaterialfv(GL_BACK, GL_SHININESS,mShininess); + //~ glMaterialfv(GL_BACK, GL_AMBIENT, mGray); + //~ glMaterialfv(GL_BACK, GL_DIFFUSE, mWhite); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + gdk_gl_drawable_gl_end(glDrawable); + g_signal_connect(Area, "expose-event", + G_CALLBACK(expose3D), window); + g_signal_connect(Area, "configure-event", + G_CALLBACK(configure3D), window); + force_redraw(Area); +} diff --git a/src/scripts/genh b/src/scripts/genh new file mode 100755 index 0000000..c0395bf --- /dev/null +++ b/src/scripts/genh @@ -0,0 +1,3 @@ +#!/bin/sh +echo "const gchar *ui = `sed -r 's/^[[:space:]]*//;s/\"/\\\\\"/g;s/(^.*$$)/\"&\"/' $1` ;" > ui.h +echo "Wrote ui.h" diff --git a/src/spots.c b/src/spots.c new file mode 100644 index 0000000..387311a --- /dev/null +++ b/src/spots.c @@ -0,0 +1,534 @@ +// spots.c - functions to work with spots (sorting, enumerating & so on) +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. + +/* + * Since our image is mirrored, to establish the direction of the axis + * of symmetry it is necessary to know the position angle of the camera, etc. + * It is easier to work in a "mirror" coordinates. Similarly, the coordinates + * of the mask should also be given the "mirror". + */ +#include "gtk.h" +#include "spots.h" +#include "gauss.h" + +const double M_2PI = 2. * M_PI; +// about a quarter of 11.25 degrees - the threshold for the identification of radial points and frames +const double PHI_STEP = 0.05; +// angle between the markers (for identification), 56.25 degrees +const double DPHI_MARKERS = 0.98; +const double aStep = M_PI / 16.; // 11.25 degrees, the angle between adjacent rays +const int N_ANGLES = 16; // number of lines on hartmanogramm (half of number of rays) +const int N_RINGS = 8; // number of rings on hartmanogramm + +// angle of the Y-axis of the matrix relative to the Y-axis mask +double SlopeAngle; +// angular position of the marker (near the center) for a zero slope, -101.25 degrees +const double Slope0 = -1.767146; + +/* + * these constants are the position of the axis of rotation of the table. + * In the coordinate system of the image (ie, the Y axis goes from top to bottom) + * means that it coincides with the optical axis. Naturally, in the work + * program these coordinates should be calculated + */ +double AxisX = 1523., AxisY = 1533.; + + +// declaration of the flags of errors leading to the exit from auto mode +enum{ + TOO_MANY_MARKERS // discovered more than two markers + ,NOT_ENOUGH_MARKERS // not found the two markers + ,BAD_MARKERS_ANGLE // angle between the markers over DPHI_MARKERS +- PHI_STEP + ,TOO_MANY_RINGS // found more rings than it should be +}; + +// messages appropriate to flags +char *errmsg[] = { + N_("Too many markers") + ,N_("Not enough markers") + ,N_("Bad markers angle") + ,N_("Too many rings") +}; + +#ifdef LEPTONICA_FOUND +/* + * Violated any restrictions on the automatic mode + * In theory the function should exit the batch mode with the mapping errors + */ +void bad_circumstances(int errcode){ + red("\nerror, need to stop\n"); + g_err(_(errmsg[errcode])); +} + +// memory allocation for n spots array +Spots* spots_alloc(int n){ + Spots *ptr = malloc(sizeof(Spots)); + if(!ptr){ + g_err(_("Can't allocate memory for new spots")); + return NULL; + } + ptr->spot = malloc(n * sizeof(Spot*)); + if(!ptr->spot){ + g_err(_("Can't allocate memory for pointers in spots array")); + free(ptr); + return NULL; + } + ptr->size = n; + ptr->n = 0; + // originally put the flag BY_COPY, which is removed by the first copy BY_PTR, + // order to do not try to free one area several times + ptr->memflag = BY_COPY; + return ptr; +} + +void spots_free(Spots **spots){ + FNAME(); + int i, n; + if(!(*spots)) return; + n = (*spots)->n; + Spot **spot = (*spots)->spot; + if((*spots)->memflag != BY_PTR) // check whether spot-regions were allocated + for(i = 0; i < n; i++, spot++) + free(*spot); + free((*spots)->spot); + free(*spots); + *spots = NULL; +} + +/* + * Add point spot into array spots by copy. + * If array is full return NULL + * + */ +Spot* spots_add(Spots *spots, Spot *spot, int copy){ + Spot *ptr = NULL; + if(spots->size <= spots->n){ + g_err(_("Spots array is full, can't place new spot\n")); + return NULL; // there's no enough space + } + if(!spot){ + g_err(_("Zero pointer to spot\n")); + return NULL; + } + if(copy == BY_COPY){ + ptr = malloc(sizeof(Spot)); + if(!ptr){ + g_err(_("Can't allocate memory for new spot")); + return NULL; + } + if(!memcpy(ptr, spot, sizeof(Spot))){ + free(ptr); + g_err(_("Can't copy spot to new one")); + return NULL; + } + } + else if(copy == BY_PTR){ + spots->memflag = BY_PTR; // change flag to not do free() + ptr = spot; + } + spots->spot[spots->n] = ptr; // jam into array + spots->n++; // increment points number + return ptr; +} + +/* + * alignment of the coordinate system spot.c to CS relative to the optical axis, + * approximate calculation of the angular and radial coordinates of each spot in + * the coordinates with respect to the center of gravity of spots (for further identification) + */ +void spots_center(Spots *spots){ + int i, n; + double X = 0., Y = 0., N, x0, y0; + double *xx, *yy, *px, *py; + Spot **spot; + n = spots->n; N = (double) n; + if(n == 0) return; + xx = malloc(n * sizeof(double)); px = xx; + yy = malloc(n * sizeof(double)); py = yy; + spot = spots->spot; + for(i = 0; i < n; i++, px++, py++, spot++){ + //x0 = (*spot)->c.x; y0 = (*spot)->c.y; + x0 = (*spot)->c.x - AxisX; + //y0 = (*spot)->c.y - AxisY; + y0 = AxisY - (*spot)->c.y; // flip into right coordinates + (*spot)->c.x = x0; (*spot)->c.y = y0; + *px = x0; + *py = y0; + X += *px; Y += *py; + } + X /= N; Y /= N; + green("\nspots center: %g, %g", X, Y); + px = xx; py = yy; + spot = spots->spot; + for(i = 0; i < n; i++, px++, py++, spot++){ + x0 = *px - X; y0 = *py - Y; + (*spot)->phi = atan2(y0, x0); + (*spot)->r = sqrt(x0*x0 + y0*y0); + } + free(xx); free(yy); +} + +int cmp_spots_r(const void *spot1, const void *spot2){ + return ((*(Spot**)spot1)->r > (*(Spot**)spot2)->r); +} +void sort_spots_by_r(Spots *spots){ + qsort(spots->spot, spots->n, sizeof(Spot*), cmp_spots_r); +} + +int cmp_spots_phi(const void *spot1, const void *spot2){ + return ((*(Spot**)spot1)->phi > (*(Spot**)spot2)->phi); +} +void sort_spots_by_phi(Spots *spots){ + qsort(spots->spot, spots->n, sizeof(Spot*), cmp_spots_phi); +} + +int cmp_spots_id(const void *spot1, const void *spot2){ + return ((*(Spot**)spot1)->id > (*(Spot**)spot2)->id); +} +void sort_spots_by_id(Spots *spots){ + qsort(spots->spot, spots->n, sizeof(Spot*), cmp_spots_id); +} + + +/* + * Sorting the points in R and phi we find the markers. + * After sorting, id of markers is set to + 100 * . + * The markers are numbered as 100 * + +*/ +void sort_spots(Window *window){ + FNAME(); + Spots *spots = window->spots; + gboolean Prefocal = TRUE; + int i, n; + int step = 0, // a step - goto next group + num = 0, // number of ray or ring + mZero = 0; // id of zero ray + Spot **spot = spots->spot; + Spots *markers = spots_alloc(2); // allocate space for markers + double *angles_pre, *angles_post; // angle coordinate of each ray in "normal" orientation + const int N_RAY_MAX = 2 * N_ANGLES - 1; // max ray number + const int N_RAYS = 2 * N_ANGLES; + const int N_ANGLE_MAX = N_ANGLES - 1; // max line number + double r_aver = 0.; // mid radius of spots + n = spots->n; + // fill the array angles_0 (for obtaining further angle of inclination of hartmanogramm) + angles_pre = calloc(N_ANGLES, sizeof(double)); + angles_post = calloc(N_ANGLES, sizeof(double)); + { + angles_post[0] = M_PI_2 - aStep/2.; + angles_pre[0] = M_PI_2 + aStep/2.; + for(i = 1; i < N_ANGLES; i++){ + angles_pre[i] = angles_pre[i-1] + aStep; + angles_post[i] = angles_post[i-1] - aStep; + green("angles[%d]: pre=%g, post=%g", + i, angles_pre[i]*180/M_PI, angles_post[i]*180/M_PI); + } + } + // find the center of gravity of points on hartmanogramm and determine + // for each point the polar coordinates in the system of the center of gravity + spots_center(spots); + // sort points by phi to find markers and number by rays + { + sort_spots_by_phi(spots); + double phi0 = (*spot)->phi, phi, dphi; + green("\nSort by phi\n"); + for(i = 0; i < n; i++, spot++){ + phi = (*spot)->phi; + dphi = fabs(phi-phi0); + phi0 = phi; + // If we two consecutive jump occurs, the previous point has to be a marker. + // If we we find two more markers, generate an error + if(dphi > PHI_STEP){ + num++; + if(!step){ + step = 1; + }else{ + num--; + red("FOUND MARKER"); + (*(spot-1))->id = 99 - (*(spot-1))->id; + DBG("spot[%d]: r=%g, phi=%g\n", (*(spot-1))->id, + (*(spot-1))->r, (*(spot-1))->phi); + if(!spots_add(markers, *(spot-1), BY_PTR)){ + bad_circumstances(TOO_MANY_MARKERS); + goto ret; + } + } + } + else step = 0; + (*spot)->id = num; + //DBG("spot[%d]: r=%g, phi=%g, dphi=%g\n", (*spot)->id, (*spot)->r, + // phi, dphi); + } + } + // check markers + { + if(markers->n != 2){ + if(markers->n == 0){ + bad_circumstances(NOT_ENOUGH_MARKERS); + goto ret; // no marker found + } + spot = spots->spot; + for(i = 0; i < n; i++, spot++) + if((*spot)->id < N_RAYS) r_aver += (*spot)->r; + r_aver /= (double)n; + green("R_aver = %.2f, R=%.2f", r_aver, markers->spot[0]->r); + if(window->image->val_f < 0.){ + // obtain the value of Prefocal from human as VAL_F is unknown + get_prefocal(window, &Prefocal); + }else Prefocal = (window->image->val_f < 100.) ? TRUE : FALSE; + int m_id = markers->spot[0]->id; + if(!Prefocal){ + if(markers->spot[0]->r < r_aver){ // nearest, zero marker + mZero = 99 - m_id; + }else{ // far, fifth marker + mZero = 104 - m_id; // 5 rays clockwise + } + }else{ + if(markers->spot[0]->r < r_aver){ + mZero = 98 - m_id; + }else{ + mZero = 93 - m_id; + } + } + if(markers->spot[0]->r < r_aver) + markers->spot[0]->id = 299; // finally change markers' numbers + else + markers->spot[0]->id = 794; + if(mZero > N_RAY_MAX) mZero -= N_RAYS; + else if(mZero < 0) mZero += N_RAYS; + }else{ + sort_spots_by_r(markers); + spot = markers->spot; + for(i = 0; i < 2; i++) + green("marker #%d: r=%g, phi=%g, tmp_id=%d, x=%g, y=%g", i, + spot[i]->r, spot[i]->phi, spot[i]->id, spot[i]->c.x, spot[i]->c.y); + double dphi = spot[0]->phi - spot[1]->phi; + DBG("Angle between markers: %g degr\n", dphi*180./M_PI); + if(fabs(dphi - DPHI_MARKERS) > PHI_STEP){ + bad_circumstances(BAD_MARKERS_ANGLE); + goto ret; + } + if(dphi > 0.){ + Prefocal = FALSE; + mZero = 99 - spot[0]->id; // number of ray to be zero + }else{ + mZero = 98 - spot[0]->id; // number of ray to be zero + if(mZero > N_RAY_MAX) mZero -= N_RAYS; + } + spot[0]->id = 299; + spot[1]->id = 794; + } + } + // renumbers points in a clockwise direction, starting from the scheme + // EEERRRRRRROOOOOOORRRRR!!!! + // смены направления отсчетов нет: и на пред- и на зафокальном одинаково! + // The numbering starts from zero! + spot = spots->spot; + green("\nmZero = %d\n", mZero); + if(!Prefocal){ // postfocal image + red("\nPostfocal image\n"); + for(i = 0; i < n; i++, spot++){ + if((*spot)->id > N_RAY_MAX) continue; // don't convert markers' numbers + int sid = mZero - (*spot)->id; // change the numbering and its direction + if(sid < 0) sid += N_RAYS; + (*spot)->id = sid; + } + }else{ // prefocal image + red("\nPrefocal image\n"); + for(i = 0; i < n; i++, spot++){ + if((*spot)->id > N_RAY_MAX) continue; + int sid = (*spot)->id - mZero; // only change the numbering + if(sid < 0) sid += N_RAYS; + (*spot)->id = sid; + } + } + /* by the method of least squares we are counting angle of inclination + * y=a+bx, b=tg(slope+alpha_i0) + * b = [\sum x_i \sum y_i - n\sum (x_i y_i)] / + * [ (\sum x_i)^2 - n\sum (x_i)^2 ] + * + * Let S1 = \sum x_i, S2 = \sum y_i, S3 = \sum(x_i)^2, + * S4 = \sum(x_i y_i), then + * b = (S1*S2 - n*S4) / (S1*S1 - n*S3) + * + * Set up arrays four elements Sj of 16 (for each line by an item) + * In the loop over all spots (except markers) fill the array. + * Then we are counting the coefficients b and fill the matrix of slope angles + */ + { + double *S1, *S2, *S3, *S4, *N, *angles; + double x, y, *angles_0; + int idx; + angles_0 = (Prefocal) ? angles_pre : angles_post; + S1 = calloc(N_ANGLES, sizeof(double)); + S2 = calloc(N_ANGLES, sizeof(double)); + S3 = calloc(N_ANGLES, sizeof(double)); + S4 = calloc(N_ANGLES, sizeof(double)); + N = calloc(N_ANGLES, sizeof(double)); + angles = calloc(N_ANGLES, sizeof(double)); + SlopeAngle = 0.; + spot = spots->spot; + for(i = 0; i < n; i++, spot++){ + idx = (*spot)->id; + if(idx > N_RAY_MAX) continue; + if(idx > N_ANGLE_MAX) idx -= N_ANGLES; // convert the ray number to the number of line + x = (*spot)->c.x; y = (*spot)->c.y; + S1[idx] += x; // \sum x_i + S2[idx] += y; // \sum y_i + S3[idx] += x*x; // \sum (x_i)^2 + S4[idx] += x*y; // \sum (x_i y_i) + N[idx] += 1.; + } + for(i = 0; i < N_ANGLES; i++){ + double ang; + // calculate an shift for i-th line + angles[i] = atan2((S1[i]*S2[i] - N[i]*S4[i]) , + (S1[i]*S1[i] - N[i]*S3[i])); + DBG("Angle: %g", angles[i]*180./M_PI); + // check whether there's no jumps near +-pi + if(i){ + ang = angles[i] - angles[i-1]; + DBG("Ang: %g", ang*180./M_PI); + if(!Prefocal){ + if(ang > M_PI) angles[i] -= M_2PI; + else if(ang > 0.) angles[i] -= M_PI; + else if(ang < -M_2PI) angles[i] += M_2PI; + else if(ang < -M_PI) angles[i] += M_PI; + }else{ + if(ang < -M_PI) angles[i] += M_2PI; + else if(ang < 0.) angles[i] += M_PI; + else if(ang > M_2PI) angles[i] -= M_2PI; + else if(ang > M_PI) angles[i] -= M_PI; + } + } + ang = angles[i] - angles_0[i]; + if(ang > M_2PI) ang -= M_2PI; + else if(ang < 0.) ang += M_2PI; + SlopeAngle += ang; + DBG("angle[%d] = %g; ang=%g; a0=%g\n", i, angles[i]*180./M_PI, + ang*180./M_PI, angles_0[i]*180./M_PI); + } + SlopeAngle /= (double)N_ANGLES; // average + red("Slope Angle: %g degr", SlopeAngle * 180. / M_PI); + free(S1); free(S2); free(S3); free(S4); free(N); free(angles); + } + // sort spots by r to enumerate by rings + { + sort_spots_by_r(spots); + spot = spots->spot; + double r0 = (*spot)->r, r; + // rtres == 1/7 of average distance between rings + double rtres = (spot[n-1]->r - r0) / ((double)N_RINGS - 1.) / 7.; + num = 0; + green("\nSort by r\n"); + for(i = 0; i < n; i++, spot++){ + if((*spot)->id > N_RAY_MAX) continue; // we have already counts markers + r = (*spot)->r; + if(fabs(r-r0) > rtres){ + num+=100; + } + (*spot)->id += num; + // DBG("spot[%d]: r=%g, phi=%g, dr=%g\n", (*spot)->id, r, + // (*spot)->phi, r - r0); + r0 = r; + } + if((num/100 + 1) > N_RINGS) + bad_circumstances(TOO_MANY_RINGS); + } + /* + * At this stage it would be nice to display the picture boxes, + * numbered points, detected angle and center. + */ + + /* + * Recalculate the the coordinates of centers points' for the translation + * to the coordinate system relative to the optical axis + * (rotation + reflection from the vertical axis). + * (\beta == SlopeAngle) + * x^0_i = -x_i\cos\beta - y_i\sin\beta; + * y^0_i = -x_i\sin\beta + y_i\cos\beta + */ + { + double cosSA = cos(SlopeAngle); + double sinSA = sin(SlopeAngle); + double x,y; + spot = spots->spot; + if(!Prefocal){ + for(i = 0; i < n; i++, spot++){ + x = (*spot)->c.x; y = (*spot)->c.y; + (*spot)->xC = -x*cosSA - y*sinSA; + (*spot)->yC = -x*sinSA + y*cosSA; + } + }else{ + for(i = 0; i < n; i++, spot++){ + x = (*spot)->c.x; y = (*spot)->c.y; + (*spot)->xC = x*cosSA + y*sinSA; + (*spot)->yC = -x*sinSA + y*cosSA; + } + } + } +ret: + free(angles_pre); free(angles_post); +} + +void spots_save(Spots *spots, gchar *filename){ + Spot **spot, *s; + FILE *f; + if(! spots || ! spots->spot || ! filename) return; + int i, n = spots->n; + sort_spots_by_id(spots); + spot = spots->spot; + f = fopen(filename, "w"); + if(!f){ + g_err(_("Can't open file")); + return; + } + fprintf(f, "\t***** Bounding boxes *****\t** Coords w/o rot. comp. **\tCoords with rot. comp.\n"); + fprintf(f, "id\tx0\ty0\tw\th\txc\tyc\tw2\th2\txc\tyc\n"); + for(i = 0; i < n; i++, spot++){ + s = *spot; + if(fprintf(f, "%03d\t%d\t%d\t%d\t%d\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\n", + s->id, + s->box.x, s->box.y, s->box.w, s->box.h, + s->c.x, s->c.y, s->c.rx, s->c.ry, + s->xC, s->yC) <= 0){ + g_err(_("Can't write to file")); + break; + } + } + fclose(f); +} + +#else // LEPTONICA_FOUND not set +Spots* spots_alloc(int n __attribute__((unused))){ + LEPTERR; + return NULL; +} +Spot* spots_add(Spots *spots __attribute__((unused)), + Spot *spot __attribute__((unused)), int copy __attribute__((unused))){ + LEPTERR; + return NULL; +} +void spots_free(Spots **spots __attribute__((unused))){ + LEPTERR; +} +void sort_spots(Spots *spots __attribute__((unused))){ + LEPTERR; +} +#endif // LEPTONICA_FOUND diff --git a/src/terrain.c b/src/terrain.c new file mode 100644 index 0000000..0615c09 --- /dev/null +++ b/src/terrain.c @@ -0,0 +1,423 @@ +// terrain.c - functions for 3D view +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "opengl.h" +#include "terrain.h" +#include "contours.h" +#include "fits.h" +#include "imtools.h" // for destroy_image + + +#define MINSZ 5 // minimal size of a selection region + +/* + * Calculate the normals for the point (x, y) of the image. + * First, fill arrays of normals for the the lower right and upper + * left triangles with one vertex at a given point. + * Then for each point the total normal is calculated + * + * Principle + * *---B---*---* (rd is the lower right triangle, ul is upper left) + * | / | / | / | Normal at the point A at the map of triangles (as shown) + * E---A---C---* is composed of normal Aul + Bdr + Cul + Adr + Dul + Edr + * | / | / | / | The normals for individual triangles (X - intensity at a point): + * *---D---*---* (normal = vertical vector x horizontal vector) + * Not normalized Aul = (E-A, A-B, 1) + * Adr = (A-C, D-A, 1) + */ + +/* + * Computing of the normals UL & DR + * REWRITE FOR CUDA + */ +GLfloat *mkTrNormals(IMAGE *image){ + FNAME(); + int W = image->width, H = image->height, x, y, i; + /* + * Normal for the the lower right and upper left triangles coming out of a given point + * Component 0 - UL, 1 - DR + * To simplify further calculations, supplemented by a frame in one node (zeros) + */ + GLfloat *tr_norms = calloc((W+2)*(H+2), 6*sizeof(GLfloat)); + int Wstride = (W + 2) * 6; + int ww = W - 1, hh = H - 1; + GLfloat *heights = image->data; + GLfloat *ptr; + #ifdef EBUG + double t0 = dtime(); + #endif + inline void soft_normalize(GLfloat *v){ + GLfloat l,x,y; + x = v[0]; + y = v[1]; + l = sqrtf((x*x) + (y*y) + 1.f); + v[0] = x / l; + v[1] = y / l; + v[2] = 1.f/ l; + } + inline void calc_UL(GLfloat *height, GLfloat *ptr){ + GLfloat A = *height; + if(A){}; + ptr[0] = height[-1] - A; // E - A + ptr[1] = height[-W] - A; // B - A + ptr[2] = 1.f; + soft_normalize(ptr); + } + inline void calc_DR(GLfloat *height, GLfloat *ptr){ + GLfloat A = *height; + if(A){}; + ptr += 3; // move to DR + ptr[0] = A - height[1]; // A - C + ptr[1] = A - height[W]; // A - D + ptr[2] = 1.f; + soft_normalize(ptr); + } + inline void calc_two_norms(GLfloat *height, GLfloat *ptr){ + calc_UL(height, ptr); + calc_DR(height, ptr); + } + ptr = &tr_norms[Wstride + 1]; + #pragma omp parallel for + for(x = 0; x < ww; x++){ + calc_DR(heights+x, ptr + 6*x); // upper border without right point, only DR + } + i = 0; + #pragma omp parallel for private(y, ptr, x, i) + for(y = 1; y < hh; y++){ + ptr = &tr_norms[Wstride*(y+1)+1]; + i = y * W; + calc_DR(heights+(i++), ptr); // left border - only DR + ptr += 6; + for(x = 1; x < ww; x++, ptr += 6){ + calc_two_norms(heights+(i++), ptr); + } + calc_UL(heights+i, ptr); // right border - only UL + } + ptr = &tr_norms[Wstride * H + 1]; + heights = &image->data[hh * W]; + #pragma omp parallel for + for(x = 1; x < W; x++) + calc_UL(heights, ptr+6*x); // down border - onlyUL + /* + ptr = &tr_norms[Wstride + 1]; + for(x = 0; x < ww; x++, ptr += 6, heights++) + calc_DR(heights, ptr); + heights++; + for(y = 1; y < hh; y++){ + ptr = &tr_norms[Wstride*(y+1)+1]; + calc_DR(heights++, ptr); + ptr += 6; + for(x = 1; x < ww; x++, ptr += 6, heights++){ + calc_two_norms(heights, ptr); + } + calc_UL(heights++, ptr); + } + heights++; + ptr = &tr_norms[Wstride * H + 2]; + for(x = 1; x < W; x++, heights++, ptr += 6) + calc_UL(heights, ptr); + */ + DBG("time: %g", dtime() - t0); + return tr_norms; +} + +void computeNormals(Window *window){ + FNAME(); + int x, y; + IMAGE *verts = window->image; + IMAGE *norms = window->image_transformed; + int W = verts->width, H = verts->height; + int Wstride = (W + 2) * 6; + #ifdef EBUG + double t0 = dtime(); + #endif + GLfloat *tr_norms = mkTrNormals(verts); + GLfloat *ptr; + GLfloat *normal = norms->data; + inline void addVectors(GLfloat *norm, GLfloat *dbl){ + GLfloat *Aul, *Adr, *Bdr, *Cul, *Dul, *Edr; + Aul = dbl; Adr = dbl + 3; + Bdr = dbl - Wstride + 3; + Cul = dbl + 6; + Dul = dbl + Wstride; + Edr = dbl - 3; + *norm++ = (*Aul++) + (*Bdr++) + (*Cul++) + (*Adr++) + (*Dul++) + (*Edr++); + *norm++ = (*Aul++) + (*Bdr++) + (*Cul++) + (*Adr++) + (*Dul++) + (*Edr++); + *norm = (*Aul) + (*Bdr) + (*Cul) + (*Adr) + (*Dul) + (*Edr); + } + inline void normalize(GLfloat *v){ + GLfloat l,x,y,z; + x = v[0]; + y = v[1]; + z = v[2]; + l = sqrtf((x*x) + (y*y) + (z*z)); + v[0] = x / l; + v[1] = y / l; + v[2] = z / l; + } + /* + int WW = W*3; + #pragma omp parallel for private(y, ptr, normal) + for(y = 0; y < H; y++){ + ptr = &tr_norms[Wstride*(y+1)+1]; + normal = &norms->data[y*WW]; + for(x = 0; x < W; x++, normal+=3, ptr+=6){ + addVectors(normal, ptr); + normalize(normal); + } + }*/ + for(y = 0; y < H; y++){ + ptr = &tr_norms[Wstride*(y+1)+1]; + for(x = 0; x < W; x++, normal+=3, ptr+=6){ + addVectors(normal, ptr); + normalize(normal); + } + } + free(tr_norms); + DBG("time: %g", dtime() - t0); +} + +/* + * A simple 3D-display selected in window->parent field + * Image corners coordinates (x0, y0), (x, y) + * Coordinates in CS IMAGES, ANGLES are outermost, not known + * window - a window with the 3D image + * X0, y0, x, y - the image area of the parent window, from which information will be taken + * from_file - TRUE to load the file into the same window (then the coordinates do not care) + * FALSE for download from the [sub] the image of the parent window + */ +gboolean loadTerrain(Window *window, + double x0, double y0, double x, double y, + gboolean from_file){ + FNAME(); + int i, j, imW; + IMAGE *verts, *norms; // vertexes & normals (pointers to w->im, w->im_tr) + IMAGE *image = NULL; // pointer to the parent image or a new image + int X0,Y0, X1,Y1; // here will be stored the coordinates of the upper left and lower right corners + int W, H; // Here are the dimensions of the selected area + GLfloat *in, *out; // Pointers to the points in the image + GLfloat scale; // scale (1/8 of the perimeter of selected region) + #ifdef EBUG + double t0 = dtime(); + #endif + if(!from_file){ + if(x0 < x){ X0 = (int)(x0+0.5); X1 = (int)(x+0.5);} + else{ X0 = (int)(x+0.5); X1 = (int)(x0+0.5);} + if(y0 < y){ Y0 = (int)(y0+0.5); Y1 = (int)(y+0.5);} + else{ Y0 = (int)(y+0.5); Y1 = (int)(y0+0.5);} + W = X1 - X0; H = Y1 - Y0; + DBG("subimage size: %dx%d", W, H); + DBG("subimage @ (%d,%d)-(%d,%d)", X0, Y0, X1, Y1); + if(W < MINSZ || H < MINSZ){ + g_err(_("Selected area too small")); + return FALSE; + } + image = window->parent->image; + if(!copy_contours(image, window->image, + (float)-X0, (float)-Y0)) + g_err(_("Error occured when tried to add contours")); + window->image->cLevels = image->cLevels; + + window->image->stat.min = image->stat.min; + window->image->stat.max = image->stat.max; + }else{ // load to the same window from file + gchar *filename; + INIT(image, IMAGE); + if( !(filename = get_open_filename(window)) || + !readfits(filename, image)){ + g_free(filename); + destroy_image(image); + return FALSE; + } + g_free(filename); + X1 = W = image->width; + Y1 = H = image->height; + X0 = Y0 = 0; + } + // check if there are shoals with the filling of the structure of points and normals + free(window->image->data); window->image->data = NULL; + free(window->image_transformed->data); window->image_transformed->data = NULL; + if(!image || !image->data){ + g_err(_("No image in parent window")); + goto abnormal_exit; + } + imW = image->width; + verts = window->image; + norms = window->image_transformed; + verts->data = calloc(W*H, sizeof(GLfloat)); + if(!verts->data){ + g_err(_("Can't allocate memory")); + goto abnormal_exit; + } + // normals for each point + norms->data = calloc(W*H*3, sizeof(GLfloat)); + if(!norms->data){ + g_err(_("Can't allocate memory")); + free(verts->data); + verts->data = NULL; + goto abnormal_exit; + } + verts->width = W; verts->height = H; + norms->width = W; norms->height = H; + scale = ((GLfloat)W + (GLfloat)H) / 4.f / (image->stat.max-image->stat.min); + // Copy the selected piece of image with scaling + #pragma omp parallel for + for(j = Y0; j < Y1; j++){ + //int l = (j - Y0)*W; + in = &image->data[j * imW + X0]; + out = &verts->data[(j - Y0)*W]; + for(i = 0; i < W; i++) + (*out++) = (*in++) * scale; + } + if(from_file) + destroy_image(image); + DBG("time: %g", dtime() - t0); + return TRUE; +abnormal_exit: + if(from_file) + destroy_image(image); + return FALSE; +} + +gboolean createList(Window *window){ + FNAME(); + TEXTURE *texture = window->texture; // number of list stores in texture->tex + GLfloat *Verts = window->image->data; + GLfloat *Norms = window->image_transformed->data; + int W = window->image->width, H = window->image->height; + int x, y, stride = 3 * W; + GLfloat dX = -(GLfloat)W / 2.f, dY = -(GLfloat)H / 2.f; + GtkWidget *Area = window->drawingArea; + if(!Area){ + g_err("???"); + return FALSE; + } + GdkGLContext *glContext = gtk_widget_get_gl_context(Area); + GdkGLDrawable *glDrawable = gtk_widget_get_gl_drawable(Area); + if(!gdk_gl_drawable_gl_begin(glDrawable, glContext)) + g_assert_not_reached(); + #ifdef EBUG + double t0 = dtime(); + #endif + if(!texture){ + INIT(window->texture, TEXTURE); + texture = window->texture; + if(!texture){ + g_err(_("Can't allocate memory for texture")); + return FALSE; + } + if(useVBO){ + glGenBuffersARB(1, &texture->tex); + glGenBuffersARB(1, &texture->w); + }else{ + texture->tex = glGenLists(1); + texture->w = W; + } + if(texture->tex == 0){ + g_err(_("Can't generate VBO / GL list")); + return FALSE; + } + } + texture->h = H; + if(useVBO){ + GLint sz = W*H*3*sizeof(GLfloat), szi = W*(H-1)*2*sizeof(GLuint); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, texture->tex); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, texture->w); + glBufferDataARB(GL_ARRAY_BUFFER_ARB, sz*2, + 0, GL_STATIC_DRAW_ARB); + GLfloat *vertexes = malloc(sz); + GLuint *indexes = malloc(szi); + GLfloat *ptr = vertexes; + #pragma omp parallel for + for(y = 0; y < H; y++){ + ptr = &vertexes[y*W*3]; + Verts = &window->image->data[y*W]; + for(x = 0; x < W; x++, Verts++){ + *ptr++ = dX + x; + *ptr++ = dY + y; + *ptr++ = *Verts; + } + } + H--; + #pragma omp parallel for + for(y = 0; y < H; y++){ + GLuint *iptr = &indexes[y*W*2]; + GLuint idx = y*W; + for(int x = 0; x < W; x++, idx++){ + *iptr++ = idx + W; + *iptr++ = idx; + } + } + glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, szi, 0, GL_STATIC_DRAW_ARB); + glBufferSubDataARB(GL_ELEMENT_ARRAY_BUFFER, 0, szi, indexes); + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, sz, vertexes); + glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, sz, + sz, window->image_transformed->data); + free(vertexes); + free(indexes); + free(window->image->data); + window->image->data = NULL; + free(window->image_transformed->data); + window->image_transformed->data = NULL; + //~ free(window->image_transformed); window->image_transformed = NULL; + + }else{ + glNewList(texture->tex, GL_COMPILE); + H--; + for(y = 0 ; y < H; y++){ + glBegin(GL_TRIANGLE_STRIP); + for(x = 0; x < W; x++, Verts++, Norms+=3){ + glNormal3fv(Norms); + glVertex3f(dX + x, dY + y, *Verts); + glNormal3fv(&Norms[stride]); + glVertex3f(dX + x, dY + (y + 1), Verts[W]); + } + glEnd(); + } + glEndList(); + } + gdk_gl_drawable_gl_end(glDrawable); + DBG("time: %g", dtime() - t0); + return TRUE; +} + +/* + * External function for the preparation of [[section] [parental]] picture + * to download into 3D-box + * window - 3D-window + * x0, y0, x, y - the corners of the selected area from picture of the parent window + * from_file - TRUE to load from the called file selection window + * FALSE for download from the parent window + * If from_file == TRUE, the coordinates of the corners of subimage are ignored + */ +void terrain_3D(Window *window, + double x0, double y0, double x, double y, + gboolean from_file){ + FNAME(); + if(!window) return; + if(!loadTerrain(window, x0, y0, x, y, from_file)){ + if(!from_file) destroy_window(window); + return; + } + computeNormals(window); + if(!createList(window)){ + destroy_window(window); + return; + } + init3D(window); // initialisation of openGL window +} diff --git a/src/tracking.c b/src/tracking.c new file mode 100644 index 0000000..5103f3f --- /dev/null +++ b/src/tracking.c @@ -0,0 +1,565 @@ +// tracking.c - funcions for cutting tracking +// +// Copyright 2011 Edward V. Emelianoff +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +#include "tracking.h" +#include "imtools.h" +#include "gauss.h" +#include "opengl.h" + +const double LHWD = 5.; // length of the cross-mark bar +double x_1, y_1, x_2, y_2; // coordinates the selection frame or line +//double GW, GH; // height and width of the graph + +double gY0, gYm, gXm; // range of values of Y, the maximum value of X +double gYmax; // limits to indicate the coordinates in the status bar +double xScale, yScale; // scale by axes +double bot_X = -1., top_X = -1.; // values (in units of X) of the lower and upper bounds on the abscissa +gint g_botX, g_topX; // the same, but in the coordinates of the window + +/* the data for translation of the x coordinate from Pdata into coordinates x, y of image + * X = x0 + x*cx, Y= y0 + x*cy (cx - the cosine of the angle of the cutting, cy - sine) */ +struct{ + double x0; // starting poing + double y0; + double cx; // cosine of the angle of inclination + double cy; // sine -//- +} Pdata2image; +Points *Pdata = NULL; // array of data points +GdkPoint *gPoints = NULL; // points in window coordinates +Points selection = {0, NULL}; // the same for the selected region +GdkPoint *selPoints = NULL; + +gint Npts; // number of data points + +Point *alloc_Pdata(int N){ + if(Pdata){ + free(Pdata->data); + Pdata->data = NULL; + } + else + Pdata = malloc(sizeof(Pdata)); + if(!Pdata){ + g_err(_("Can't allocate memory for track points")); + return NULL; + } + Pdata->data = malloc(N*sizeof(Point)); + if(!Pdata->data){ + g_err(_("Can't allocate memory for track points")); + return NULL; + } + return Pdata->data; +} +// returns the coordinates xx and yy on the image (in CS of the window!) +// for the point with coordinate x in the graph +void get_image_crds(double x, double *xx, double *yy){ + *xx = Pdata2image.x0 + x * Pdata2image.cx; + *yy = Pdata2image.y0 + x * Pdata2image.cy; +} + +// reset the sample +void clear_selection(){ + free(selection.data); + selection.data = NULL; + selection.n = 0; +} +/* + * filling an array Pdata based on the values of coordinates initial (X0, Y0) + * and final (X, Y) points + */ +void get_data(double X0, double X, double Y0, double Y, Window *window){ + int n = 0, N; // new array size + IMAGE *image = window->image; + int x0, y0, yi, WD = image->width, HT = image->height; + double x, y, A, xstep, ystep; + Point *ptr; + gboolean Vert = FALSE; + double tgXY, cosXY; + inline void minmax(double amp){ + if(gY0 > amp) gY0 = amp; + else if(gYm < amp) gYm = amp; + } + inline double dist(double dx, double dy){ + return sqrtf(dx*dx + dy*dy); + } + gXm = dist(X-X0, Y-Y0); + N = (int)(gXm + 1.); + if(N < 2) return; + if(!image->data) + return; + if( X0 < 0. || X0 > WD || X < 0. || X > WD || + Y0 < 0. || Y0 > HT || Y < 0. || Y > HT) return; + if(!(ptr = alloc_Pdata(N))) return; + Pdata2image.x0 = X0; + Pdata2image.y0 = Y0; //HT - 1. - Y0; + Pdata2image.cx = (X-X0)/gXm; + Pdata2image.cy = (Y-Y0)/gXm; + //DBG("N=%d, ", N); + x0 = (int)(X0 + 0.5); + y0 = (int)(Y0 + 0.5); + if(fabs(X-X0) < fabs(Y-Y0)){ // closer to the vertical + Vert = TRUE; + tgXY = (X-X0) / (Y-Y0); + cosXY = fabs(cos(atan(tgXY))); + } + else{ + tgXY = (Y-Y0) / (X-X0); + cosXY = fabs(cos(atan(tgXY))); + } + yi = x0 + WD * y0; + gY0 = gYm = image->data[yi];// * wd + min; + if(Vert){ + ystep = (Y > Y0) ? cosXY : -cosXY; + for(y = Y0; ; y+=ystep, n++, ptr++){ + if(n > N) break; + if(ystep > 0.){ + if(y > Y) break; + } + else{ + if(y < Y) break; + } + ptr->x = (double)n; + x = X0 + tgXY * (y - Y0); + //yi = (int)(x+0.5) + WD * (HT - (int)(y+0.5)); + yi = (int)(x+0.5) + WD * ((int)(y+0.5)); + A = image->data[yi];// * wd + min; + ptr->y = A; + minmax(A); + } + } + else{ + xstep = (X > X0)? cosXY : -cosXY; + for(x = X0; ; x+=xstep, n++, ptr++){ + if(n > N) break; + if(xstep > 0.){ + if(x > X) break; + } + else{ + if(x < X) break; + } + y = Y0 + tgXY * (x-X0); + ptr->x = (double)n; + //yi = (int)(x+0.5) + WD * (HT - (int)(y+0.5)); + yi = (int)(x+0.5) + WD * ((int)(y+0.5)); + A = image->data[yi];// * wd + min; + ptr->y = A; + minmax(A); + } + } + //DBG("Ymin=%g, Ymax=%g\n", gY0, gYm); + Pdata->n = n - 1; +} + +void draw_cross(GdkDrawable *d, GdkGC *gc, double x, double y){ + gdk_draw_line(d, gc, x-LHWD,y, x+LHWD, y); + gdk_draw_line(d, gc, x,y-LHWD, x, y+LHWD); +} +// draw rectangle (gdk_draw_rectangle don't match because it require x,y > x0,y0 +void draw_rect(GdkDrawable *d, GdkGC *gc, double x0, double y0, double x, double y){ + gdk_draw_line(d, gc, x0,y0, x0,y); + gdk_draw_line(d, gc, x0,y0, x,y0); + gdk_draw_line(d, gc, x,y, x, y0); + gdk_draw_line(d, gc, x,y, x0, y); +} + +// get coordinates of selected region +void get_sel_region(double *x0, double *y0, double *x, double *y){ + *x0 = x_1; *y0 = y_1; *x = x_2; *y = y_2; +} +void do_tracking(double xx, double yy, gboolean start, Window *window){ + static double xold, yold, X0, Y0; + double X, Y; + double imW = (double)window->texture->w; + double imH = (double)window->texture->h; + Context *c = window->context; + gboolean conv = FALSE; + x_2 = xx; y_2 = yy; + conv_mouse_to_image_coords(x_2, y_2, &X, &Y, window); + //FNAME(); + if(!window->image->data) return; + if(X > imW){ X = imW; conv = TRUE;} + else if(X < 0){ X = 0; conv = TRUE;} + if(Y > imH){ Y = imH; conv = TRUE;} + else if(Y < 0){ Y = 0; conv = TRUE;} + if(conv) conv_image_to_mouse_coords(X, Y, &x_2, &y_2, window); + //DBG("X=%g, Y=%g, x=%g, y=%g", X, Y, x_2, y_2); + if(start){ + clear_selection(); + X0 = X; Y0 = Y; + x_1 = xold = x_2; + y_1 = yold = y_2; + return; + } + if(c->trackingMode == TRACK_VERT){ + X = X0; x_2 = x_1; + } + else if(c->trackingMode == TRACK_HORIZ){ + Y = Y0; y_2 = y_1; + } + else if(c->trackingMode == TRACK_DIAG){ + double s, adx, ady; + gboolean changed = FALSE; + adx = fabs(X-X0); ady = fabs(Y-Y0); + void corrY(){ + s = (Y > Y0) ? 1. : -1.; + Y = Y0 + s * adx; + y_2 = y_1 + s * fabs(x_2-x_1); + if(Y > imH || Y < 0.){ + if(Y > imH) Y = imH; + else Y = 0.; + conv_image_to_mouse_coords(X, Y, &x_2, &y_2, window); + changed = TRUE; + } + /* + if(Y > imH){ Y = imH; y = Y/Daspect + Dy; changed = TRUE;} + else if(Y < 0){ Y = 0; y = Dy; changed = TRUE;}*/ + ady = fabs(Y-Y0); + //DBG("Y=%g, Y0=%g", Y, Y0); + } + void corrX(){ + s = (X > X0) ? 1. : -1; + X = X0 + s * ady; + x_2 = x_1 + s * fabs(y_2-y_1); + if(X > imW || X < 0.){ + if(X > imW) X = imW; + else X = 0.; + conv_image_to_mouse_coords(X, Y, &x_2, &y_2, window); + changed = TRUE; + } + adx = fabs(X-X0); + //DBG("X=%g, X0=%g", X, X0); + } + if(adx > ady){ + corrY(); + if(changed) corrX(); + } + else{ + corrX(); + if(changed) corrY(); + } + } + + GdkDrawable *d = window->drawingArea->window; + static GdkGC *gc = NULL; + if(!gc){ + gc = gdk_gc_new(d); + gdk_gc_set_foreground(gc, &(window->drawingArea->style->white)); + gdk_gc_set_function(gc, GDK_XOR); + } + if(c->drawingMode == DRAW_QUAD){// draw rectangle + draw_rect(d, gc, x_1,y_1, xold,yold); + draw_rect(d, gc, x_1,y_1, x_2,y_2); + } + else{ // draw lint + // first erase old one + gdk_draw_line(d, gc, x_1,y_1, xold,yold); + draw_cross(d, gc, xold, yold); + // then draw new one + gdk_draw_line(d, gc, x_1,y_1, x_2,y_2); + draw_cross(d, gc, x_2, y_2); + //DBG("Xima=%g, Yima=%g (X0=%g, Y0=%g)", X, Y, X0, Y0); + get_data(X0, X, Y0, Y, window); + bot_X = 0.; top_X = gXm; + //DBG("X=%g, X0=%g, Y=%g, Y0=%g", X, X0, Y, Y0); + gchar str[80]; + g_snprintf(str, 79, "length=%.1f, angle=%.1fdegr", + sqrt((X-X0)*(X-X0)+(Y-Y0)*(Y-Y0)), + atan2(Y-Y0,X-X0)/M_PI*180.); + set_status_text(StatusText, str, window->parent); + set_status_text(StatusText, "", window); + Gconfigure(window->graphWindow); + gdk_window_invalidate_rect(window->graphWindow->drawingArea->window, + NULL, TRUE); + } + xold = x_2; yold = y_2; +} + +gboolean Gexpose(Window *window){//, GdkEventExpose *event, gpointer userData){ + cairo_t *cr = NULL; + int i; + GtkWidget *Area = window->drawingArea; + int H = (int)(Area->allocation.height + 0.5); + GdkPoint *gptr = &gPoints[1]; + if(!cr){ + cr = gdk_cairo_create(Area->window); + cairo_set_line_width (cr, 1.); + } + cairo_set_source_rgb(cr, 0., 0., 0.); + if(!Pdata || !gPoints) + return FALSE; + if(!Pdata->data || Pdata->n < 2) + return FALSE; + cairo_move_to(cr, gPoints->x, gPoints->y); + for(i = 1; i < Npts; i++, gptr++) + cairo_line_to(cr, gptr->x, gptr->y); + cairo_stroke(cr); + cairo_set_source_rgb(cr, 0., 1., 0.); + cairo_move_to(cr, g_botX, 0); + cairo_line_to(cr, g_botX, H); + cairo_stroke(cr); + cairo_set_source_rgb(cr, 1., 0., 0.); + cairo_move_to(cr, g_topX, 0); + cairo_line_to(cr, g_topX, H); + cairo_stroke(cr); + if(selPoints && selection.n){ + int n = selection.n; + cairo_set_source_rgb(cr, 0., 0., 1.); + cairo_move_to(cr, selPoints->x, selPoints->y); + gptr = &selPoints[1]; + //DBG("n=%d", n); + for(i = 1; i < n; i++, gptr++) + cairo_line_to(cr, gptr->x, gptr->y); + cairo_stroke(cr); + } + cairo_destroy(cr); + return FALSE; +} + +// calculation of the chart points in window coordinates +gboolean Gconfigure(Window *window){//, GdkEventConfigure *event, gpointer none){ + int i, n; + Point *ptr; + //FNAME(); + double W, H, ym, y0; + GdkPoint *gptr; + gboolean log_scale = isYlog(window); + if(!Pdata) + return FALSE; + if(!Pdata->data || Pdata->n < 2) + return FALSE; + GtkWidget *Area = window->drawingArea; + free(gPoints); + gPoints = NULL; + Npts = Pdata->n; + gPoints = malloc((Npts + 1)* sizeof(GdkPoint)); + if(!gPoints){ + g_err(_("Can't allocate memory for track points")); + return FALSE; + } + ptr = Pdata->data; + if(!ptr) return FALSE; + gptr = gPoints; + W = Area->allocation.width; + H = Area->allocation.height; + ym = gYm; y0 = gY0; + if(log_scale){ + if(y0 < 0.) y0 = -log(-y0 + 1.); + else y0 = log(y0 + 1.); + if(ym < 0.) ym = -log(-ym + 1.); + else ym = log(ym + 1.); + } + xScale = W / gXm; + yScale = H / (ym - y0); + gYmax = ym; + for(i = 0; i < Npts; i++, ptr++, gptr++){ + gptr->x = (int)(ptr->x * xScale + 0.5); + if(log_scale){ + double yy = ptr->y; + if(yy < 0.) + yy = -log(-yy + 1.); + else + yy = log(yy + 1.); + gptr->y = (int)(H - (yy - y0) * yScale + 0.5); + } + else + gptr->y = (int)(H - (ptr->y - y0) * yScale + 0.5); + } + g_botX = (int)(bot_X * xScale + 0.5); + g_topX = (int)(top_X * xScale + 0.5); + // Long live code monkeys of all time! + if(selection.data) do{ + n = selection.n; + free(selPoints); + selPoints = malloc((n+1)* sizeof(GdkPoint)); + if(!selPoints) break; + gptr = selPoints; + ptr = selection.data; + for(i = 0; i < n; i++, ptr++, gptr++){ + gptr->x = (int)(ptr->x * xScale + 0.5); + if(log_scale){ + double yy = ptr->y; + if(yy < 0.) + yy = -log(-yy + 1.); + else + yy = log(yy + 1.); + gptr->y = (int)(H - (yy - y0) * yScale + 0.5); + } + else + gptr->y = (int)(H - (ptr->y - y0) * yScale + 0.5); +// DBG("g: (%g, %g), w: (%d, %d)", ptr->x, ptr->y, gptr->x, gptr->y); + } + }while(0); + /* + if(ym > 1000.) + set_Grulers(y0/1000, gXm, ym/1000); + else*/ + set_Grulers(y0, gXm, ym, window); + return FALSE; +} + +// display mode of the histogram in the field of graph +void do_histogram(Window *window){ + FNAME(); + IMAGE *image = window->context->current_image; + if(!image || !image->data) image = window->image; + if(!image || !image->data) return; + int i, hsize = image->stat.histogram.size; + int *hptr = image->stat.histogram.data; + Point *ptr; + double histsiz = (double)image->stat.histogram.size; + double scale = image->stat.histogram.scale * (double)image->stat.histogram.size; + //double imin = image->stat.min; + if(!(ptr = alloc_Pdata(hsize))) return; + gXm = 1.;//scale+imin; + scale /= histsiz; + gYm = (double)(image->stat.histogram.max); + gY0 = (double)(image->stat.histogram.min); + for(i = 0; i < hsize; i++, ptr++, hptr++){ + ptr->x = (double) i / histsiz; + //ptr->x = (double) i * scale + imin; + ptr->y = (double) *hptr; + } + Pdata->n = hsize; + DBG("bot: %g, top: %g", image->stat.histogram.bot_tres, image->stat.histogram.top_tres); + if(image->stat.histogram.bot_tres > 0.) + bot_X = image->stat.histogram.bot_tres; + else + bot_X = 0.; + if(image->stat.histogram.top_tres > 0.) + top_X = image->stat.histogram.top_tres; + else + top_X = gXm; + Gconfigure(window->graphWindow); + gdk_window_invalidate_rect(window->graphWindow->drawingArea->window, NULL, TRUE); +} + +/* + * will try to move boundaries when clicking near graph borders + * when clicked near a curve - something else can be done ... + * Window - graphics window + * Return value: TRUE, if will need to handle mouse motion events further + */ +gboolean graph_mouse_btn(int x, Window *window){ + gboolean redraw = FALSE; + gboolean ret = FALSE; + double W, xScale, tmp; + GtkWidget *graphArea = window->drawingArea; + int dist = (g_topX - g_botX) / 2; // half of a distance between borders + if(dist < PT_TRESHOLD) dist = PT_TRESHOLD; + W = graphArea->allocation.width; + xScale = W / gXm; + if(abs(x - g_botX) < dist || x < g_botX){ // select down border + tmp = (double) x / xScale; + if(tmp < top_X) bot_X = tmp; + if(bot_X < 0.) bot_X = 0.; + ret = TRUE; + redraw = TRUE; + } + else if(abs(x - g_topX) < dist || x > g_topX){ // select up border + tmp = (double) x / xScale; + if(tmp > bot_X) top_X = tmp; + if(top_X > gXm) top_X = gXm; + ret = TRUE; + redraw = TRUE; + } + if(redraw){ + gchar msg[80]; + //DBG("move: botX=%g, topX=%g", bot_X, top_X); + g_snprintf(msg, 79, + _("Selected region from %.1f to %.1f, %d datapoints"), + bot_X, top_X, (int)(((double)Npts)/gXm*(top_X-bot_X)+0.5)); + set_status_text(StatusText, msg, window); + Gconfigure(window); + gdk_window_invalidate_rect(graphArea->window, NULL, TRUE); + } + return ret; +} + +void gen_tres_texture(Window *window){ + window->image->stat.histogram.bot_tres = bot_X; + window->image->stat.histogram.top_tres = top_X; + treshold_image(window, bot_X / gXm, top_X / gXm); +} + +gboolean isYlog(Window *window){ + gboolean log_scale = FALSE; + if(window->context->graphMode == GR_GRAPH){ + if(window->context->graphYaxis & Y_LOGGRAPH) + log_scale = TRUE; + } else if(window->context->graphYaxis & Y_LOGHIST) + log_scale = TRUE; + return log_scale; +} + +gboolean fit_gaussian(Window *window){ + //FNAME(); + Point *pt, *pt0; + double C, A, s, x0; + double xx, yy; + #ifndef GSL_FOUND + GSLERR; + return FALSE; + #endif + if(!window->parent || !window->parent->texture){ + g_err(_("No parent window or image")); + return FALSE; + } + double imW = (double)window->parent->texture->w; + double imH = (double)window->parent->texture->h; + gchar msg[160]; + GtkWidget *graphArea = window->drawingArea; + if(!Pdata){ // no points? (O_o, but such a thing be?) + g_err(_("No data points")); + return FALSE; + } + int n = (int)(((double)Npts)/gXm*(top_X-bot_X) + 0.5); + if(n < 10 || n > (int)(sqrt(imW * imW + imH * imH))){ // few or many points + g_err(_("Bad amount of data points")); + return FALSE; + } + int n0 = (int)(((double)Npts)/gXm*bot_X); // the number of a first point + if(n0 < 0 || (n0 + n) > Npts){ // do not get there? + g_err(_("Invalid number of first point")); + return FALSE; + } + clear_selection(); + selection.n = n; + selection.data = malloc(n * sizeof(Point)); + if(!selection.data){ + g_err(_("Can't allocate memory")); + return FALSE; + } + pt = selection.data; + pt0 = &Pdata->data[n0]; + DBG("pt0: (%g, %g)", pt0->x, pt0->y); + // Copy the desired sample into a separate array + for(; n--; pt++, pt0++){ + pt->x = pt0->x; + pt->y = pt0->y; + } + gauss_fit(&selection, &C, &A, &s, &x0); + gaussian_v(C, A, s, x0, &selection); + get_image_crds(x0, &xx, &yy); + //DBG("move: botX=%g, topX=%g", bot_X, top_X); + g_snprintf(msg, 159, + _("Fit gaussian, x0=%.2f I(%.2f, %.2f), s=%.2f, A=%.1f, C=%.3f"), + x0, xx, yy, s, A, C); + set_status_text(StatusText, msg, window); + Gconfigure(window); + gdk_window_invalidate_rect(graphArea->window, NULL, TRUE); + return TRUE; +} diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..202090f --- /dev/null +++ b/src/ui.h @@ -0,0 +1,1281 @@ +const gchar *ui = "" +"" +"" +"" +"" +"" +"" +"mainFileMenuItem" +"_File" +"" +"" +"" +"" +"gtk-open" +"mainOpenMenuItem" +"" +"" +"" +"" +"" +"gtk-save-as" +"mainSaveAsMenuItem" +"" +"" +"" +"" +"gtk-open" +"mainOpenInNewMenuItem" +"Open in _new window" +"" +"" +"" +"" +"" +"gtk-open" +"mainOpenIn3DMenuItem" +"Open in _3D window" +"" +"" +"" +"" +"" +"mainQuitMenuItem" +"_Quit" +"" +"" +"" +"" +"mainEditMenuItem" +"_Edit" +"" +"" +"" +"" +"mainViewMenuItem" +"_View" +"" +"" +"" +"" +"mainHistMenuItem" +"Show _histogram" +"" +"" +"" +"" +"" +"mainHeadersMenuItem" +"Show _headers" +"" +"" +"" +"" +"" +"main3DviewFullMenuItem" +"3D view of full image" +"" +"" +"" +"" +"" +"main3DviewFrameMenuItem" +"3D view of subframe" +"" +"" +"" +"" +"" +"mainSpotsidentMenuItem" +"Select _spots" +"" +"" +"" +"" +"" +"mainTrackMenuItem" +"Draw _tracks" +"" +"" +"" +"" +"" +"mainZoomframeMenuItem" +"_Zoom frame" +"" +"" +"" +"" +"" +"mainZoomrestoreMenuItem" +"_Restore image" +"" +"" +"" +"" +"" +"mainZoomnearMenuItem" +"Zoom _in" +"" +"" +"" +"" +"" +"mainZoomfarMenuItem" +"Zoom _out" +"" +"" +"" +"" +"" +"mainMathMenuItem" +"_Math" +"" +"" +"" +"" +"mainSpotsMenuItem" +"Find and enumerate spots" +"_Spots" +"" +"" +"" +"" +"mainSpotsParamMenuItem" +"Choose minimal & maximal spot size" +"Size _tresholds" +"" +"" +"" +"" +"" +"mainFindSpotsMenuItem" +"Identify _spots" +"" +"" +"" +"" +"" +"mainHartmannMenuItem" +"So_rt hartmann spots" +"" +"" +"" +"" +"" +"mainSaveSpotsMenuItem" +"Sa_ve spots" +"" +"" +"" +"" +"" +"mainCirclesMenuItem" +"Identify _circles" +"" +"" +"" +"" +"" +"mainHoughMenuItem" +"_Hough transform" +"" +"" +"" +"" +"" +"mainFilterMenuItem" +"_Filter" +"" +"" +"" +"" +"" +"mainHelpMenuItem" +"_Help" +"" +"" +"" +"" +"gtk-about" +"mainAboutMenuItem" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"graphFileMenuItem" +"_File" +"" +"" +"" +"" +"gtk-close" +"graphCloseMenuItem" +"" +"" +"" +"" +"" +"" +"graphEditMenuItem" +"_Edit" +"" +"" +"" +"" +"graphViewMenuItem" +"_View" +"" +"" +"" +"" +"graphScaleMenuItem" +"Y axis scale" +"" +"" +"" +"" +"True" +"graphLinMenuItem" +"Linear" +"" +"" +"" +"" +"" +"graphLinMenuItem" +"graphLogMenuItem" +"Log" +"" +"" +"" +"" +"" +"graphMathMenuItem" +"_Math" +"" +"" +"" +"" +"graphGaussMenuItem" +"Approximate by _gaussian" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"oglFileMenuItem" +"_File" +"" +"" +"" +"" +"gtk-open" +"oglOpenMenuItem" +"" +"" +"" +"" +"" +"gtk-close" +"oglCloseMenuItem" +"" +"" +"" +"" +"" +"oglEditMenuItem" +"_Edit" +"" +"" +"" +"" +"oglViewMenuItem" +"_View" +"" +"" +"" +"" +"oglMoveMenuItem" +"_Move" +"" +"" +"" +"" +"oglXAplusMenuItem" +"Rotate X CW" +"" +"" +"" +"" +"" +"oglXAminusMenuItem" +"Rotate X CCW" +"" +"" +"" +"" +"" +"oglZAplusMenuItem" +"Rotate Z CCW" +"" +"" +"" +"" +"" +"oglZAminusMenuItem" +"Rotate Z CW" +"" +"" +"" +"" +"" +"oglXplusMenuItem" +"Move right" +"" +"" +"" +"" +"" +"oglXminusMenuItem" +"Move left" +"" +"" +"" +"" +"" +"oglYplusMenuItem" +"Move down" +"" +"" +"" +"" +"" +"oglYminusMenuItem" +"Move up" +"" +"" +"" +"" +"" +"oglZplusMenuItem" +"Move backward" +"" +"" +"" +"" +"" +"oglZminusMenuItem" +"Move forward" +"" +"" +"" +"" +"" +"oglGameModeMenuItem" +"Mouse and arrow keys navigation" +"_Game mode" +"" +"" +"" +"" +"" +"oglZoomrestoreMenuItem" +"_Restore image" +"" +"" +"" +"" +"" +"oglMathMenuItem" +"_Math" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"True" +"vertical" +"" +"" +"True" +"" +"" +"False" +"0" +"" +"" +"" +"" +"" +"" +"" +"True" +"3" +"3" +"" +"" +"400" +"400" +"True" +"" +"" +"1" +"3" +"2" +"" +"" +"" +"" +"True" +"10" +"5" +"10" +"" +"" +"1" +"3" +"2" +"3" +"GTK_EXPAND | GTK_SHRINK | GTK_FILL" +"GTK_FILL" +"" +"" +"" +"" +"True" +"vertical" +"10" +"5" +"10" +"" +"" +"2" +"GTK_FILL" +"GTK_EXPAND | GTK_SHRINK | GTK_FILL" +"" +"" +"" +"" +"" +"" +"" +"2" +"" +"" +"" +"" +"True" +"1" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"0" +"" +"" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"False" +"1" +"" +"" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"False" +"2" +"" +"" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"False" +"3" +"" +"" +"" +"" +"False" +"3" +"" +"" +"" +"" +"" +"" +"" +"" +"True" +"vertical" +"" +"" +"True" +"" +"" +"False" +"0" +"" +"" +"" +"" +"" +"" +"" +"True" +"3" +"3" +"" +"" +"400" +"400" +"True" +"" +"" +"1" +"3" +"2" +"" +"" +"" +"" +"True" +"10" +"5" +"10" +"" +"" +"1" +"3" +"2" +"3" +"GTK_EXPAND | GTK_SHRINK | GTK_FILL" +"GTK_FILL" +"" +"" +"" +"" +"True" +"vertical" +"10" +"5" +"10" +"" +"" +"2" +"GTK_FILL" +"GTK_EXPAND | GTK_SHRINK | GTK_FILL" +"" +"" +"" +"" +"" +"" +"" +"2" +"" +"" +"" +"" +"True" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"0" +"" +"" +"" +"" +"True" +"True" +"False" +"8" +"False" +"" +"8" +"True" +"none" +"" +"" +"False" +"1" +"" +"" +"" +"" +"True" +"True" +"False" +"40" +"False" +"" +"28" +"True" +"none" +"" +"" +"False" +"False" +"end" +"2" +"" +"" +"" +"" +"False" +"3" +"" +"" +"" +"" +"" +"" +"" +"" +"True" +"vertical" +"" +"" +"True" +"" +"" +"False" +"0" +"" +"" +"" +"" +"" +"" +"" +"400" +"400" +"True" +"" +"" +"2" +"" +"" +"" +"" +"True" +"1" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"0" +"" +"" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"False" +"1" +"" +"" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"False" +"2" +"" +"" +"" +"" +"True" +"True" +"False" +"False" +"" +"True" +"none" +"" +"" +"False" +"3" +"" +"" +"" +"" +"False" +"3" +"" +"" +"" +"" +"" +"" +"Spots' size treshold" +"False" +"True" +"center-on-parent" +"True" +"" +"" +"True" +"7" +"7" +"7" +"7" +"" +"" +"True" +"vertical" +"7" +"" +"" +"True" +"7" +"True" +"" +"" +"True" +"0" +"out" +"" +"" +"True" +"12" +"" +"" +"True" +"vertical" +"4" +"True" +"" +"" +"True" +"5" +"" +"" +"True" +"Min" +"" +"" +"False" +"False" +"0" +"" +"" +"" +"" +"True" +"True" +"" +"" +"" +"1" +"" +"" +"" +"" +"False" +"False" +"0" +"" +"" +"" +"" +"True" +"5" +"" +"" +"True" +"Max" +"" +"" +"False" +"False" +"0" +"" +"" +"" +"" +"True" +"True" +"" +"" +"" +"1" +"" +"" +"" +"" +"False" +"False" +"1" +"" +"" +"" +"" +"" +"" +"" +"" +"True" +"<b>Width</b>" +"True" +"" +"" +"" +"" +"0" +"" +"" +"" +"" +"True" +"0" +"in" +"" +"" +"True" +"12" +"" +"" +"True" +"vertical" +"5" +"True" +"" +"" +"True" +"5" +"" +"" +"True" +"Min" +"" +"" +"False" +"False" +"0" +"" +"" +"" +"" +"True" +"True" +"" +"" +"" +"1" +"" +"" +"" +"" +"False" +"False" +"0" +"" +"" +"" +"" +"True" +"5" +"" +"" +"True" +"Max" +"" +"" +"False" +"False" +"0" +"" +"" +"" +"" +"True" +"True" +"" +"" +"" +"1" +"" +"" +"" +"" +"False" +"False" +"1" +"" +"" +"" +"" +"" +"" +"" +"" +"True" +"<b>Height</b>" +"True" +"" +"" +"" +"" +"1" +"" +"" +"" +"" +"0" +"" +"" +"" +"" +"True" +"10" +"True" +"" +"" +"gtk-cancel" +"True" +"True" +"True" +"True" +"" +"" +"False" +"0" +"" +"" +"" +"" +"gtk-ok" +"True" +"True" +"True" +"True" +"" +"" +"False" +"1" +"" +"" +"" +"" +"False" +"False" +"1" +"" +"" +"" +"" +"" +"" +"" +"" +"5" +"normal" +"False" +"" +"" +"True" +"vertical" +"2" +"" +"" +"True" +"7" +"7" +"7" +"" +"" +"True" +"7" +"" +"" +"True" +"0" +"none" +"" +"" +"True" +"12" +"" +"" +"True" +"True" +"" +"" +"" +"" +"" +"" +"" +"True" +"<b>Focus value, mm</b>" +"True" +"" +"" +"" +"" +"0" +"" +"" +"" +"" +"True" +"0" +"none" +"" +"" +"True" +"12" +"" +"" +"True" +"vertical" +"5" +"True" +"" +"" +"Prefocal" +"True" +"True" +"False" +"True" +"True" +"" +"" +"0" +"" +"" +"" +"" +"Postfocal" +"True" +"True" +"False" +"True" +"FocalPreRadioBtn" +"" +"" +"1" +"" +"" +"" +"" +"" +"" +"" +"" +"True" +"<b>Image type</b>" +"True" +"" +"" +"" +"" +"1" +"" +"" +"" +"" +"" +"" +"1" +"" +"" +"" +"" +"True" +"center" +"" +"" +"" +"" +"" +"gtk-ok" +"True" +"True" +"True" +"True" +"" +"" +"False" +"False" +"1" +"" +"" +"" +"" +"False" +"end" +"0" +"" +"" +"" +"" +"" +"" ;