четверг, 1 ноября 2012 г.

Чтение - запись Fits C++

В прошлый раз я рассказал, как подключить библиотеку для работы с файлами FITS к g++ в линукс. Сегодня мы займёмся непосредственно чтением картинки, небольшим её редактированием и записью в новую картинку. На данном этапе мы не будем учитывать сотни возможностей формата Fits(большое кол-во слоев изображения, дополнительные параметры , многомерные таблицы и т.д. ). В качестве преобразования изображения, мы возьмём инвертирование. Мы просто составим программу вот такого алгоритма




Отразим алгоритм в функции main:
int main(int argc, char *argv[]) {
 if(argc>=3){
  char * input = argv[1];
  char * output = argv[2];
  readFITSImage(input);
  dataChange();
  writeFITSImage(output);
  return 0; 
 }
 cout<<"Not enough parameters"<<endl;
 cout<<"Usage: \n\t FitsExamp inputfile outputfile"<<endl;
}

Теперь приступим к разбору каждого блока:

Чтение картинки.
В документации есть очень не плохой пример для чтения картинки и её атрибутов, но они, всё-же, уж очень изощренно и не оправдано пользуются библиотекой std. Это не особо нужно для данной-конкретной задачи. Поэтому, разобрав их пример, я взял только самое необходимое и составил свою функцию чтения.
void readFITSImage(char *path){
 // read FITS file from path to pFits and put image data to contents;
  FITS::setVerboseMode(true);
  pFits.reset(new FITS(path,Read,true));
  image = &pFits->pHDU();// get main image from file
  image->readAllKeys();//read Keys
  image->read(contents); // read DATA to valarray
}

Как видите, нет абсолютно ничего сложного. Да, я не спорю - я не рассматриваю многих тонкостей, что выявлены в стандартном примере. Для начала, вам достаточно знать - как читать файл fits и занести его в массив, а для этого не нужно быть гуру в std и умножать 2 числа через пень-колоду. Обратите внимание так-же на то, что я считываю только данные изображения - если в этом файле есть дополнительная информация, она считана не будет. В то-же время, я оставил лазейку - "умный" указатель pFits объявлен в глобальном контексте, а значит хранит файл целиком и после завершения функции чтения. Это дает возможность считать необходимую информацию как-нибудь потом.

Изменение картинки
За это у меня отвечает функция dataChange(). Как я уже говорил, я буду инвертировать картинку( то что было тёмным станет светлым и наоборот). Картинка у нас состоит из множества short значений( это было выявлено эмпирический методом научного тыка ). FITS нас не обязывает писать в short, в чём хочешь в том и пиши. Мне было странно, что не используется unsigned char  с его 1 байтом. Я привык к градуировке [0 - 255] и мне кажется - 2 байта на 1 чёрно-белый пиксель это расточительно.  Я ещё не понял всю глубину данного выбора. Но short так short- не принципиально...
Инверсия одного значения - это разница между максимально допустимым и текущим значением.

Где M - максимально допустимое значение A[i]

Максимально допустимое значение short - 2 байта или 32767. По хорошему - каждый раз определять его функцией sizeof(short), а учитывая то, что в программе я не привязывался к определённому типу данных и объявил макрос #define DATATYPE short, буду вычислять это значение 
sizeof(DATATYPE)

Функция преобразования в 2 строки,вот она:
void dataChange(){
 for(int i = 0;i<contents.size();i  ) //inverse 
  contents[i]= sizeof(DATATYPE)-contents[i]; 
}

Запись новой картинки
Последний этап нашего пути - запись измененных данных в новый файл fits.
Не смотря на ужасающие строки стандартных примеров, ничего сложного в этом нет.
После некоторого отсева даю вам необходимую выжимку:

void writeFITSImage(char* path){
 
 long naxis=2; 
 long naxes[2] = { image->axis(0), image->axis(1) };
 try{
  pFits.reset( new FITS(path , SHORT_IMG , naxis, naxes ) );
 }
 catch(FITS::CantCreate){
  cout<<"Output file exists"<<endl;
 }
 long& vectorLength = naxes[0];// more useful names 
 long& numberOfRows = naxes[1];// of variables 
 long n = vectorLength*numberOfRows;
 image = &pFits->pHDU();
 image->write(1,n,contents);
 cout<<"Write OK"<<endl;
}

В документации сказано, что этот конструктор FITS создает файл с данным именем, если его не было,  но если файл есть - бросится исключение, которое я ловлю блоком catch.
Код, как, мне кажется, понятен, да и прокомментирован. Но на всякий случай расскажу, что тут делается.

В массив naxes заносится размерность картинки, которая хранится в соответствующих полях  объекта image .Это нужно для конструктора.
Далее, мы просто вычисляем кол-во элементом путём умножения высоты матрицы на размерность 1 строки, и записываем это кол-во short-ов в объект из массива, который мы создали на этапе "Чтение картинки" и модифицировали на этапе "Изменение картинки" начиная с 1 short-а.

Вот весь исходный код программы.
#include <CCfits>
using namespace CCfits;
using namespace std;


#define DATATYPE short

valarray<DATATYPE>contents;  // array that contains file data
std::auto_ptr<FITS> pFits;// FITS File Pointer
PHDU* image; // sorce of image 

void readFITSImage(char *path){
 // read FITS file from path to pFits and put image data to contents;
  FITS::setVerboseMode(true);
  pFits.reset(new FITS(path,Read,true));
  image = &pFits->pHDU();// get main image from file
  image->readAllKeys();//read Keys
  image->read(contents); // read DATA to valarray
}
void dataChange(){
 for(int i = 0;i<contents.size();i  ) //inverse 
  contents[i]= sizeof(DATATYPE)-contents[i]; 
}
void writeFITSImage(char* path){
 
 long naxis=2; 
 long naxes[2] = { image->axis(0), image->axis(1) };
 try{
  pFits.reset( new FITS(path , SHORT_IMG , naxis, naxes ) );
 }
 catch(FITS::CantCreate){
  cout<<"Output file exists"<<endl;
 }
 long& vectorLength = naxes[0];// more useful names 
 long& numberOfRows = naxes[1];// of variables 
 long n = vectorLength*numberOfRows;
 image = &pFits->pHDU();
 image->write(1,n,contents);
 cout<<"Write OK"<<endl;
}
int main(int argc, char *argv[]) {
 if(argc>=3){
  char * input = argv[1];
  char * output = argv[2];
  readFITSImage(input);
  dataChange();
  writeFITSImage(output);
  return 0; 
 }
 cout<<"Not enough parameters"<<endl;
 cout<<"Usage: \n\t FitsExamp inputfile outputfile"<<endl;
}


Ниже приведен Makefile для этой программы. Он работает при условии, что установка CCfits прошла так, как написано в прошлой моей статье.
CXX           = g  
CFLAGS        =-Wall -O3
INCPATH       = -I/usr/local/include/CCfits -I/usr/local/lib/cfitsio/include -I. 
LIBS          = -L/usr/local/lib -lCCfits

SOURCES       = FitsExamp.cpp
OBJECTS       = FitsExamp.o
TARGET        = FitsExamp

RM            =rm -f
all:$(TARGET)
 $(RM) output.fts

$(TARGET): $(OBJECTS)
 $(CXX)  -o $(TARGET) $(OBJECTS) $(LIBS)
       
$(OBJECTS): $(SOURCES)
 $(CXX) -c $(INCPATH) -o $(OBJECTS) $(SOURCES) 
        
clean:
 $(RM) $(OBJECTS) $(TARGET)
        

Использование программы

Ну как-же без отчёта?

Выполняем:
 $make
 $./FitsExamp  input.fts outfut.fts

При этом должен существовать файл input.fts и не должно существовать файла outfut.fts .
Для просмотра крайне советую вот эту программу http://hea-www.harvard.edu/RD/ds9/site/Download.html . Распространяется в виде 1 исполняемого файла - батарейки в комплекте.  Удобно просматривать fits файлы, без нее с fits  справляется только gimp , а это неудобно и долго.

Ну и вот отчёт:

Было изображение
Стало изображение

Комментариев нет:

Отправить комментарий