чтение шестнадцатеричных данных из файла в fortran - PullRequest
0 голосов
/ 10 июля 2020

В Fortran я пытаюсь прочитать файл с данными в 8-битных (шестнадцатеричных) байтах, на Linux.

В 'hexedit' первая строка выглядит так, как должна, для Это tiff-файл.

49 49 2A 00  08 00 20 00  00 00 0B 02  00 00 00 00  II*... .........

Я объявляю двухбайтовую символьную переменную (character(len=2) :: tifhead(8)) и читаю так:

      open(1,file=filename,access='stream')
      read(1) tifhead,greyvalue

Я получаю первые два (49 49), которые распечатываются как II в форматированной записи (формат (2Z2), но не другие.

Как я могу получить все эти шестнадцатеричные значения? Я должен увидеть 49 49 2A 00 08 ........

Ответы [ 4 ]

1 голос
/ 11 июля 2020

Ваш оператор read просто прочитает 2 символа для tifhead(1), следующие 2 символа для tifhead(2), et c, , включая пробелы . Следовательно, вы получите tifhead(1)="49", tifhead(2)=" 4", tifhead(3)="9 " и так далее. Вы думаете, что прочитали первые 2 байта правильно только потому, что вы печатаете строки «49», «4», «9», ... одну за другой, поэтому на выходе они выглядят как «49 49». Компилятор не может узнать, что есть одно пустое пространство, разделяющее строки, и 2 пробела на каждые четыре данных.

Чтобы правильно прочитать ваши данные, вы должны использовать форматированный read ing, что подразумевает, что вы также должны объявить свой stream как «отформатированный» в заявлении open. В следующем примере показано, как это можно сделать:

program example
implicit none
character(len=2) :: tifhead(8), greyscale(8)
open(1, file="example.txt", access='stream', form='formatted')
read(1, "(4(a2,tr1),tr1,3(a2,tr1),a2)", advance='no') tifhead
read(1, "(tr2,4(a2,tr1),tr1,3(a2,tr1),a2)", advance='no') greyscale
close(1)
print "(a,7(a2,tr1),a2,a)", "  tifhead = (", tifhead, ")"
print "(a,7(a2,tr1),a2,a)", "greyscale = (", greyscale, ")"
end program example

Возможно, потребуется некоторое пояснение: a2,tr1 означает чтение строки из 2 символов, а затем однократное перемещение указателя чтения (при этом пропускается пробел между шестнадцатеричными числами. «числа» - фактически, они рассматриваются как просто строки). 4(a2,tr1) означает сделать это 4 раза. Читаются первые 4 байта плюс один пробел. Теперь есть еще один пробел перед следующими данными, которые нужно прочитать, поэтому мы добавляем tr1, чтобы пропустить его, и пока наш формат 4(a2,tr1),tr1; затем мы читаем еще 3 байта с 3(a2,tr1), затем последний байт только с a2 (не пропуская пробел после него). Таким образом, строка формата (4(a2,tr1),tr1,3(a2,tr1),a2), которая будет правильно читать первые 8 байтов, оставляя указатель чтения сразу после 8-го байта. Обратите внимание, что advance='no' необходимо, иначе Fortran предположит возврат каретки и пропустит остальные данные в той же записи (строке).

Теперь, чтобы прочитать следующие 8 байтов, мы используем тот же формат, за исключением того, что мы добавляем tr2 в начале, чтобы пропустить два пробела. Я добавил в программу форматированную печать, чтобы проверить правильность чтения данных. Запуск программы дает:

  tifhead = (49 49 2A 00 08 00 20 00)
greyscale = (00 00 0B 02 00 00 00 00)

, который проверяет, что данные были прочитаны правильно.

И последнее, но не менее важное: я бы рекомендовал избегать использования устаревшего Fortran в вашем коде и приведенном выше примере. Это означает, что используйте newunit, чтобы позволить программе найти первую свободную единицу вместо того, чтобы явно указывать номер единицы, иметь способ проверить, действительно ли существует файл, который вы пытаетесь открыть, или если вы достигли конца файла, избегайте безымянных аргументов, используйте атрибут dimension для объявления массивов и т.д. c. Ничего из этого не является строго необходимым, и поначалу это может показаться излишним многословием. Но в конечном итоге строгость (как поощряет современный Фортран) сэкономит вам много времени при отладке более крупных программ. Таким образом, приведенный выше пример можно (возможно, следует) записать следующим образом:

program example2
implicit none
integer :: unt, status
character(len=2), dimension(8) :: tifhead, greyscale
open(newunit=unt, file="example.txt", access='stream', form='formatted',&
     action='read', status='old', iostat=status)
if (status /= 0) then
  print "(a)","Error reading file."; stop
end if
! More sophisticated reading is probably needed to check for end of file.
read(unit=unt, fmt="(4(a2,tr1),tr1,3(a2,tr1),a2)", advance='no') tifhead
read(unit=unt, fmt="(tr2,4(a2,tr1),tr1,3(a2,tr1),a2)") greyscale
close(unit=unt)
print "(a,7(a2,tr1),a2,a)", "  tifhead = (", tifhead, ")"
print "(a,7(a2,tr1),a2,a)", "greyscale = (", greyscale, ")"
end program example2
0 голосов
/ 23 июля 2020

Вот код, который у меня работает. По большей части это комментарии. Приветствуются любые ваши замечания по поводу стиля фортран. Обратите внимание, что я был знаком с фортраном 77 в прошлом и узнал немного более современный фортран в процессе написания этого фрагмента кода

  program putiff

c This program is solely intended to read the data from the .tif files made by the CCD camera
c PIXIS 1024F at beamline 1-BM at the Advanced Photon Source, so that they can be manipulated
c in fortran. It is not a general .tif reader.
c A little bit extra work may make this a reader for baseline .tif files,: some of the 
c information below may help with such an implementation.
c  
c The PIXIS .tif file is written in hex with the little-endian convention. 
c The hex numbers have two 8-bit bytes. They are read with an integer(kind=2) declaration.
c When describing an unsigned integer these cover numbers from 0 to 65535 (or 2**16-1). 
c For the PIXIS files the first two bytes are the decimal number 18761. The TIFF6 specification 
c gives them as a hexadecimal number (0x4949 for a little-endian convention, 4D4D for the
c big-endian convention. The PIXIS files are little-endian. 
c
c The next two bytes should be 42 decimal, and 0x2A.
c
c The next 4 bytes give the byte offset for the first image file directory (IFD) that contains
c all the other information needed to understand how the .tif files are put together.
c This number should be read together as a 4 byte integer (kind=4). These (unsigned) integers
c go from 0 to 2**32-1, or 4294967295: this is the maximum file length for a .tif file. 
c For the PIXIS this number is 2097160, or 0x200008: in between are the image date for the
c PIXIS's 1024x1024 pixels, each with a two-byte gray range from 0 to 2**16-1 (or 65535 decimal).   
c Therefore the PIXIS image can be read without understanding the IFD. 
c
c The line right below the hex representation gives the byte order, for the 
c little-endian convention indicated by two first bytes. It's 4949  for little-endian,
c in both the first and in the second byte separately. The byte order is then least importan
c part first; with two bytes together, it is byte by byte. For big-endian it is 4D4D.
c
c One way to confirm all this information is to look at the files
c with a binary editor (linux has xxd) or a binary editor (linux has hexedit).
c For the PIXIS image .tif file, the first 8 bytes in hexedit are indeed:
c   49 49    2A 00    08 00    20 00
c For a little-endian file, the bytes are read from the least important to the 
c most important within the two-byte number, like this:
c   49 49    2A 00    08 00    20 00
c  (34 12)  (34 12)  (78 56    34 12)
c Here the byte order is indicated below the numbers. The second two-byte number is
c therefore 2+2*16+0*256+0*4096, or 42. Likewise, the last 4-byte number is 0x00200008.  
c
c (When the individual byte are read in binary (with 'xxd -b -l 100') this gives   
c for the hexadecimals    49       49       2A       00       08       00       20       00
c         binary          01001001 01001001 00101010 00000000 00001000 00000000 00100000 00000000 
c         in ASCII        I        I        *        .        .        .        .        .        )

c After the PIXIS data comes the so-called IFD (Image File Directory).
c These contain 209 bytes. They mean something, but what I do not know. I printed them
c out one by one at the end of the program. Perhaps they are better read in two-byte units
c (right now they are read as 'integer(kind=1); integer(kind=2) may be better). But, then
c there's an odd number so you have to read one separately.

c I want to know these only because I want to use the same .tif format to
c write the results of rctopo (the max, the COM, the FWHM, and the spread).
c I know what's in the first 8 bytes, and what the data are, so I can just
c copy the ifd at the end and count on getting a good .tif file back. 
c It's sort of stupid, but it should work.
      use iso_fortran_env
      implicit logical (A-Z)
  
      integer                         :: j,jmin,jmax
      integer                         :: k,kmin,kmax
      integer                         :: ifdlength
      data jmin,kmin/1,1,/
      parameter(jmax=1024,kmax=1024)
      parameter(ifdlength=209)

c 8-byte header that starts the PIXIS data file
      integer (kind=2)                :: tifh12,tifh34 ! each two (8-bit) bytes
      integer (kind=4)                :: tifh5678      ! 4 bytes


c open and read the file now that you have the correct file name in the sequence
      open(newunit=unt,file='tiff_file,access='stream',iostat=ios)
      if (ios /= 0) then ; call problem(ios,'read_in_samples'); end if   
      read (unt) tifh12,tifh34,tifh5678,greyread,ifd
      close (unt)

      stop
      end
0 голосов
/ 21 июля 2020

Я не был уверен, нужно ли мне массово изменять свои предыдущие ответы (поскольку я считаю, что они все еще служат цели), поэтому я решил просто добавить еще один ответ, надеюсь, последний. Прошу прощения за многословие.

Следующий модуль Fortran 90 предоставляет подпрограмму с именем tiff_reader_16bit, которая читает любой файл данных TIFF и возвращает его 16-битное содержимое в виде массива целых чисел:

module tiff_reader
implicit none
private
public :: tiff_reader_16bit
contains
subroutine tiff_reader_16bit(filename, tifdata, ndata)
character(len=*), intent(in) :: filename
integer, allocatable, intent(out) :: tifdata(:)
integer, intent(out) :: ndata
integer, parameter :: max_integers=10000000
integer :: unt, status, record_length, i, records, lsb, msb
character ch;
integer, dimension(max_integers) :: temp
ndata=0
inquire(iolength=record_length) ch
open(newunit=unt, file=filename, access='direct', form='unformatted',&
     action='read', status='old', iostat=status, recl=record_length)
if (status /= 0) then
  print "(3a)","Error reading file """,filename,""": File not found."; return
end if
records=1
do i=1,max_integers
  read(unit=unt, rec=records, iostat=status) ch; msb=ichar(ch)
  if (status /= 0) then; records=records-1; ndata=i-1; exit; end if
  read(unit=unt, rec=records+1, iostat=status) ch; lsb=ichar(ch)
  if (status /= 0) then; ndata=i; temp(ndata)=msb; exit; end if
  temp(i)=lsb+256*msb; records=records+2
end do
close(unit=unt)
if (ndata==0) then
  print "(a)","File partially read."; records=records-1; ndata=max_integers
end if
allocate(tifdata(ndata), stat=status); tifdata=temp(:ndata)
print "(2(i0,a),/)",records," records read, ",ndata," 16-bit integers returned."
end subroutine tiff_reader_16bit
end module tiff_reader

Подпрограмма получает имя файла TIFF и возвращает массив целых чисел вместе с общим числом прочитанных целых чисел. Внутренне подпрограмма использует массив фиксированного размера temp для временного хранения данных. Для экономии памяти подпрограмма возвращает выделяемый массив tifdata, который является частью temp, содержащий данные, которые были доступны только для чтения. Максимальное количество считываемых данных установлено в параметре max_integers до 10 миллионов, но может быть от go до huge(0), если необходимо и если позволяет память (в моей системе это около 2,14 миллиарда целых чисел); он может go даже больше, если вы используете «высшие» kind целых чисел. Теперь есть другие способы сделать это, избегая использования временного массива фиксированного размера, но обычно это происходит за счет дополнительного времени вычислений, и я бы не стал go таким образом. Могут быть реализованы и более сложные реализации, но это добавит сложности коду, и я не думаю, что это подходит здесь.

Поскольку вам нужны результаты в виде 16-битных данных, два последовательных байты из файла должны быть прочитаны, затем вы обрабатываете их как наиболее значимый байт первым, а затем менее значимый байт. Вот почему первый прочитанный байт в каждой итерации умножается на 256. Обратите внимание, что это НЕ всегда имеет место в двоичных файлах (но в TIFF). Некоторые двоичные файлы сначала идут с менее значимым байтом.

Подпрограмма длиннее, чем в предыдущих примерах, которые я опубликовал, но это потому, что я добавил проверку ошибок, которая действительно необходима. Вы всегда должны проверять, существует ли файл и был ли достигнут конец при его чтении. Особое внимание следует уделять изображениям TIFF с "осиротевшим" последним байтом (это действительно так для образца файла "FLAG_T24.TIF", который я нашел здесь - но не в случае образца изображения «MARBLES.TIF», найденный на той же веб-странице).

Пример программы драйвера, использующей указанный выше модуль, будет:

program tiff_reader_example
use tiff_reader
implicit none
integer :: n
integer, allocatable :: tifdata(:)
call tiff_reader_16bit("FLAG_T24.TIF", tifdata, n);
if (n > 0) then
  print "(a,7(z4.4,tr1),z4.4,a)", "First 8 integers read: (", tifdata(:8), ")"
  print "(a,7(z4.4,tr1),z4.4,a)", " Last 8 integers read: (", tifdata(n-7:), ")"
  deallocate(tifdata)
end if
end program tiff_reader_example

Запуск программы дает:

46371 records read, 23186 16-bit integers returned.

First 8 integers read: (4949 2A00 0800 0000 0E00 FE00 0400 0100)
 Last 8 integers read: (F800 F8F8 00F8 F800 F8F8 00F8 F800 00F8)

, что правильно. Обратите внимание, что в этом случае количество записей (= байтов, поскольку файл открывается как unformatted) не вдвое превышает количество возвращенных целых чисел. Это потому, что этот конкретный образец изображения имеет тот «осиротевший» последний байт, о котором я упоминал ранее. Также обратите внимание, что я использовал другой формат для печати 16-битных шестнадцатеричных чисел, включая при необходимости начальные нули.

Можно дать более подробные объяснения, но этот поток уже довольно длинный. Не стесняйтесь спрашивать в комментариях, если что-то непонятно.

EDIT : по умолчанию Intel Fortran обрабатывает записи прямого доступа как 4-байтовые слова, что мне не совсем подходит . Это необычное поведение можно исправить с помощью флага компилятора, но, чтобы избежать отсутствия переносимости в случае, если кто-то использует этот c компилятор без такого флага, я немного изменил модуль tiff_reader, чтобы исправить это.

0 голосов
/ 11 июля 2020

Предполагая, что ваши данные на самом деле хранятся в двоичном формате (на самом деле это, кажется, tiff файл данных изображения), мой первый ответ действителен только в том случае, если вы конвертируете данные в простой текст. Если вы предпочитаете читать двоичный файл напрямую, самый простой способ, который я могу придумать, - это открыть файл с помощью access='direct' и читать данные побайтно. Каждый байт читается как символ, затем он преобразуется в целое число, что, я думаю, более полезно, чем строка, которая должна представлять шестнадцатеричное число.

В качестве примера следующая программа будет читать заголовок ( первые 8 байтов) из файла данных tiff. В примере читаются данные из образца tiff-изображения, которое я нашел здесь , но он работает для любого двоичного файла.

program read_tiff_data
implicit none
integer :: unt, status, i
character :: ch
integer, dimension(8) :: tifhead

open(newunit=unt, file="flag_t24.tif", access='direct', form='unformatted',
     action='read', status='old', iostat=status, recl=1)
if (status /= 0) then
  print "(a)","Error reading file."; stop
end if
do i=1,8
  read(unit=unt, rec=i) ch; tifhead(i)=ichar(ch)
end do
close(unit=unt)

print "(a,7(i0,tr1),i0,a)", "tifhead = (", tifhead, ")"
end program read_tiff_data

Программа дает следующий результат:

tifhead = (73 73 42 0 8 0 0 0)

что правильно. Вы можете легко расширить программу, чтобы прочитать больше данных из файла.

Если вам все еще нужно шестнадцатеричное представление, просто замените i0 на z0 в операторе печати, чтобы он читал

print "(a,7(z0,tr1),z0,a)", "tifhead = (", tifhead, ")"

Результат будет напечатан в шестнадцатеричном формате, в данном случае:

tifhead = (49 49 2A 0 8 0 0 0)
...