""" Парсер для извлечения FITS-метаданных из JP2 файлов """ import xml.etree.ElementTree as ET from typing import Dict, Optional import struct class MetadataParser: """Парсер FITS-метаданных из JP2 файлов""" @staticmethod def extract_metadata(filepath: str) -> Optional[Dict[str, str]]: """ Извлекает FITS-метаданные из JP2 файла Args: filepath: Путь к JP2 файлу Returns: Словарь с метаданными или None """ try: # Пытаемся прочитать JP2 файл как бинарный и найти XML with open(filepath, 'rb') as f: data = f.read() # Ищем XML данные в файле (JP2 может содержать XML в специальных боксах) # Простой способ: ищем теги XML xml_start = data.find(b'', xml_start + 100) # Примерный поиск # Находим закрывающий тег if xml_end != -1: # Расширяем поиск до полного XML depth = 1 pos = xml_start while depth > 0 and pos < len(data): pos += 1 if pos >= len(data): break if data[pos:pos+2] == b' Dict[str, str]: """ Парсит XML с FITS-заголовком Args: xml_content: XML строка Returns: Словарь с параметрами FITS """ metadata = {} try: root = ET.fromstring(xml_content) # Ищем секцию fits или FITS fits_section = root.find('.//fits') or root.find('.//FITS') if fits_section is not None: for child in fits_section: # Извлекаем ключ и значение key = child.tag value = child.text if child.text else "" # Убираем namespace если есть if '}' in key: key = key.split('}')[-1] metadata[key.upper()] = value.strip() # Если не нашли fits секцию, ищем другие возможные места if not metadata: for child in root.iter(): if child.tag.endswith('keyword'): key = child.get('name', '') value = child.text if child.text else '' if key: metadata[key.upper()] = value.strip() # Форматируем некоторые ключи для удобства чтения metadata = MetadataParser._format_metadata(metadata) except Exception as e: print(f"Ошибка парсинга XML: {e}") return metadata @staticmethod def _format_metadata(metadata: Dict[str, str]) -> Dict[str, str]: """ Форматирует метаданные для удобного отображения Args: metadata: Сырые метаданные Returns: Отформатированные метаданные """ formatted = {} # Переименовываем некоторые ключи для понятности key_mapping = { 'TELESCOP': 'Телескоп', 'INSTRUME': 'Инструмент', 'DETECTOR': 'Детектор', 'WAVELNTH': 'Длина волны', 'WAVEUNIT': 'Единица длины волны', 'DATE-OBS': 'Дата наблюдения', 'DATE-BEG': 'Начало экспозиции', 'DATE-END': 'Конец экспозиции', 'EXPTIME': 'Время экспозиции (сек)', 'CRPIX1': 'Центр X (пикс)', 'CRPIX2': 'Центр Y (пикс)', 'CDELT1': 'Шаг пикселя X (arcsec)', 'CDELT2': 'Шаг пикселя Y (arcsec)', 'CROTA2': 'Угол поворота (град)', 'NAXIS1': 'Ширина (пикс)', 'NAXIS2': 'Высота (пикс)', 'BITPIX': 'Бит на пиксель', 'BZERO': 'Смещение данных', 'BSCALE': 'Масштаб данных', 'IMG_TYPE': 'Тип изображения', 'QUALITY': 'Качество', 'LEVEL': 'Уровень обработки', 'STATUS': 'Статус', 'OBSRVTRY': 'Обсерватория' } for key, value in metadata.items(): # Используем переименованный ключ или оригинал display_key = key_mapping.get(key, key) formatted[display_key] = value return formatted @staticmethod def extract_from_jp2_box(filepath: str) -> Optional[Dict]: """ Альтернативный метод: извлечение метаданных через чтение JP2 боксов Не требует glymur, читает файл напрямую """ try: with open(filepath, 'rb') as f: data = f.read() # JP2 signature if data[0:4] != b'\x00\x00\x00\x0c': return None # Ищем XML бокс (box type 'xml ') offset = 0 while offset < len(data) - 8: box_len = struct.unpack('>I', data[offset:offset+4])[0] box_type = data[offset+4:offset+8] if box_type == b'xml ' or box_type == b'XML ': # Нашли XML бокс xml_data = data[offset+8:offset+box_len] try: xml_str = xml_data.decode('utf-8', errors='ignore') return MetadataParser._parse_fits_xml(xml_str) except: pass if box_len == 0: break offset += box_len return None except Exception as e: print(f"Ошибка чтения JP2 боксов: {e}") return None