Добавление строки в таблицу СООТВЕТСТВИЯ с помощью astropy

#astropy #fits

Вопрос:

У меня есть проблема, которая должна быть тривиальной, но, похоже, была сильно усложнена из-за колонковой природы FITS BinTableHDU.

Сценарий, который я пишу, должен быть тривиальным: выполнить итерацию по файлу FITS и записать подмножество строк в файл FITS одинакового формата, сократив количество строк с c700 кб/3,6 ГБ примерно до 350 строк. Я обработал входной файл, и у меня есть каждая строка, которую я хочу сохранить, в массиве записей python FITS:

     outarray = []

    self.indata=Table.read(self.infile, hdu=1)
    
    for r in self._indata:

        RecPassesFilter = FilterProc(r, self)

        #
        # Add to output array only if passes all filters...  
        #   
        if RecPassesFilter:
            outarray.append(r)
 

Теперь я создал пустой BintableHDU с точно такими же столбцами и форматами, и я хочу добавить отфильтрованные данные:

[…много пропущенного кода позже…}

             mycols =  []
            for inputcol in self._coldefs:
                mycols.append(fits.Column(name=inputcol.name, format=inputcol.format))

           # Next line should produce an empty BinTableHDU in the identical format to the output data

            SaveData = fits.BinTableHDU.from_columns(mycols)

            for s in self._outdata:
                SaveData.data.append(s)
 

Теперь эта последняя строка не только завершается ошибкой, но и каждый ее вариант (SaveData.append() или .add_row() или что-то еще) также завершается ошибкой «нет такого метода». По-видимому, существует исключительное отсутствие документации о том, как выполнить тривиальную задачу добавления записи. Очевидно, я что-то упускаю, но два дня спустя я все еще рисую пробел.

Может ли кто-нибудь указать мне правильное направление здесь?

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

1. Модуль более низкого уровня astropy.io.fits на самом деле не предназначен для общих манипуляций с таблицами. Поскольку вы уже используете Table для чтения файл, вы также можете создать пустой Table с теми же столбцами для вывода на использование Table.add_row , как описано в этих документах: docs.astropy.org/en/stable/table/…

2. Спасибо за ответ. Мне нужно убедиться, что новый файл FITS, который я создаю, имеет формат, идентичный исходному. Я не мог понять, как вы можете сделать это, используя только астропию. Таблица, поскольку, по-видимому, нет механизма для указания формата столбца [ПОДХОДИТ]. Есть ли способ сделать это?

3. Один из способов, хотя я не знаю, самый ли он лучший, — это прочитать таблицу из файла FITS, например: t1 = Table.read('path/to/table.fits') затем создайте новую пустую таблицу с типом dtype из первого: t2 = Table(dtype=t1.dtype) . Затем начните использовать t2.add_row . Это правда, что это дает менее очевидный контроль над некоторыми деталями более низкого уровня, такими как другие T<AAAA> ключевые слова заголовка, но я думаю, что есть способы добавить и их.

4. Вы также можете использовать комбинацию Table кода и кода FITS более низкого уровня. Если вы можете предоставить более подробную информацию о том, каким именно должен быть результат, я мог бы что-нибудь придумать

Ответ №1:

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

             with fits.open(self._infile) as HDUSet:
                tableHDU=HDUSet[1]
                self._coldefs = tableHDU.columns

            
            FITScols =  []
            for inputcol in self._coldefs:
                NewColData = []
                for r in self._outdata:
                    NewColData.append(r[inputcol.name])
                FITScols.append(fits.Column(name=inputcol.name, format=inputcol.format, array=NewColData))

            SaveData = fits.BinTableHDU.from_columns(FITScols)

            SaveData.writeto(fname)
 

Это решает мою проблему для подмножества из 350 строк. Я еще не осмелился попробовать это для подмножества строк в 250 тысяч, которое мне нужно для следующей части моего проекта!

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

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

2. Это связано с тем, что добавление данных по строкам в формат таблицы, ориентированный на столбцы, является болезненной задачей. Вместо того, чтобы хранить строку за строкой, вы должны хранить столбец за столбцом. Это эквивалентно добавлению столбца в таблицу в базе данных SQL, когда каждая строка имеет разное значение для этого столбца, и вы не можете изменить определение исходной таблицы для загрузки!

3. Я согласен, я просто думал, что это была твоя первоначальная цель.

Ответ №2:

Я только что вспомнил, что для этого BinTableHDU.from_columns нужен nrows аргумент. Если вы передадите это вместе со столбцами существующей таблицы HDU, она скопирует структуру столбцов, но инициализирует последующие строки пустыми данными:

 >>> hdul = fits.open('astropy/io/fits/tests/data/table.fits')
>>> table = hdul[1]
>>> table.columns
ColDefs(
    name = 'target'; format = '20A'
    name = 'V_mag'; format = 'E'
)
>>> table.data
FITS_rec([('NGC1001', 11.1), ('NGC1002', 12.3), ('NGC1003', 15.2)],
         dtype=(numpy.record, [('target', 'S20'), ('V_mag', '>f4')]))
>>> new_table = fits.BinTableHDU.from_columns(table.columns, nrows=5)
>>> new_table.columns
ColDefs(
    name = 'target'; format = '20A'
    name = 'V_mag'; format = 'E'
)
>>> new_table.data
FITS_rec([('NGC1001', 11.1), ('NGC1002', 12.3), ('NGC1003', 15.2),
          ('',  0. ), ('',  0. )],
         dtype=(numpy.record, [('target', 'S20'), ('V_mag', '<f4')]))
 

Как вы можете видеть, это по-прежнему копирует данные из исходных столбцов. Я думаю, что изначально идея заключалась в добавлении новых строк в существующую таблицу. Однако вы также можете инициализировать совершенно пустую новую таблицу, передав fill=True :

 >>> new_table_zeroed = fits.BinTableHDU.from_columns(table.columns, nrows=5, fill=True)
>>> new_table_zeroed.data
FITS_rec([('', 0.), ('', 0.), ('', 0.), ('', 0.), ('', 0.)],
         dtype=(numpy.record, [('target', 'S20'), ('V_mag', '<f4')]))