from pathlib import Path
from typing import Any, cast
from filecache import FCPath, FileCache
from nav.config import Config
from nav.support.misc import safe_lstrip_zero
from .dataset import ImageFile
from .dataset_pds3 import DataSetPDS3
[docs]
class DataSetPDS3VoyagerISS(DataSetPDS3):
"""Implements dataset access for PDS3 Voyager ISS (Imaging Science Subsystem) data.
This class provides specialized functionality for accessing and parsing Voyager
ISS image data stored in PDS3 format.
"""
_MIN_5xxx_VOL1 = 5101
_MAX_5xxx_VOL1 = 5120
_MIN_5xxx_VOL2 = 5201
_MAX_5xxx_VOL2 = 5214
_MIN_6xxx_VOL1 = 6101
_MAX_6xxx_VOL1 = 6121
_MIN_6xxx_VOL2 = 6201
_MAX_6xxx_VOL2 = 6215
_MIN_7xxx_VOL2 = 7201
_MAX_7xxx_VOL2 = 7207
_MIN_8xxx_VOL2 = 8201
_MAX_8xxx_VOL2 = 8210
_ALL_VOLUME_NAMES = tuple(
f'VGISS_{x:04d}'
for x in list(range(_MIN_5xxx_VOL1, _MAX_5xxx_VOL1 + 1))
+ list(range(_MIN_5xxx_VOL2, _MAX_5xxx_VOL2 + 1))
+ list(range(_MIN_6xxx_VOL1, _MAX_6xxx_VOL1 + 1))
+ list(range(_MIN_6xxx_VOL2, _MAX_6xxx_VOL2 + 1))
+ list(range(_MIN_7xxx_VOL2, _MAX_7xxx_VOL2 + 1))
+ list(range(_MIN_8xxx_VOL2, _MAX_8xxx_VOL2 + 1))
)
_INDEX_COLUMNS = ('FILE_SPECIFICATION_NAME',)
_VOLUMES_DIR_NAME = 'volumes'
# Methods inherited from DataSetPDS3
@staticmethod
def _get_label_filespec_from_index(row: dict[str, Any]) -> str:
"""Extracts the label file specification from a row from an index table.
Parameters:
row: Dictionary containing PDS3 index table row data.
Returns:
The file specification string from the row.
"""
filespec = cast(str, row['FILE_SPECIFICATION_NAME'])
if not filespec.endswith('.LBL'):
raise ValueError(f'Bad Primary File Spec "{filespec}" - expected ".LBL"')
return filespec
@staticmethod
def _get_image_filespec_from_label_filespec(label_filespec: str) -> str:
"""Extracts the image file specification from a label file specification.
Parameters:
label_filespec: The label file specification string to parse.
Returns:
The image file specification string.
"""
return label_filespec.replace('.LBL', '.IMG')
@staticmethod
def _get_img_name_from_label_filespec(filespec: str) -> str | None:
"""Extract the image name (with no extension) from a file specification.
Parameters:
filespec: The file specification string to parse.
Raises:
ValueError: If the file specification format is invalid.
"""
parts = filespec.split('/')
if len(parts) != 3:
raise ValueError(f'Bad Primary File Spec "{filespec}" - expected 3 directory levels')
if parts[0].upper() != 'DATA':
raise ValueError(f'Bad Primary File Spec "{filespec}" - expected "DATA"')
range_dir = parts[1]
img_name = parts[2]
if len(range_dir) != 8 or range_dir[0] != 'C':
raise ValueError(f'Bad Primary File Spec "{filespec}" - expected "Cddddddd"')
if not img_name.endswith('_GEOMED.LBL'):
return None
return img_name.rsplit('_GEOMED')[0]
@staticmethod
def _img_name_valid(img_name: str) -> bool:
"""True if an image name is valid for this instrument.
Parameters:
img_name: The name of the image.
Returns:
True if the image name is valid for this instrument, False otherwise.
"""
img_name = img_name.upper()
# Cddddddd
if len(img_name) != 8 or img_name[0] != 'C':
return False
try:
_ = int(safe_lstrip_zero(img_name[1:]))
except ValueError:
return False
return True
@staticmethod
def _extract_img_number(img_name: str) -> int:
"""Extract the image number from an image name.
Parameters:
img_name: The name of the image.
Returns:
The image number.
Raises:
ValueError: If the image name format is invalid.
"""
if not DataSetPDS3VoyagerISS._img_name_valid(img_name):
raise ValueError(f'Invalid image name "{img_name}"')
return int(safe_lstrip_zero(img_name[1:]))
@staticmethod
def _volset_and_volume(volume: str) -> str:
"""Get the volset and volume name.
Parameters:
volume: The volume name.
"""
return f'VGISS_{volume[6]}xxx/{volume}'
@staticmethod
def _volume_to_index(volume: str) -> str:
"""Get the index file name for a volume.
Parameters:
volume: The volume name.
"""
return f'VGISS_{volume[6]}xxx/{volume}/{volume}_index.lbl'
@staticmethod
def _results_path_stub(volume: str, filespec: str) -> str:
"""Get the results path stub for an image filespec.
Parameters:
volume: The volume name.
filespec: The filespec of the image.
"""
return str(Path(f'{volume}/{filespec}').with_suffix(''))
def __init__(
self,
pds3_holdings_root: str | Path | FCPath | None = None,
*,
index_filecache: FileCache | None = None,
pds3_holdings_filecache: FileCache | None = None,
config: Config | None = None,
) -> None:
"""Initializes a Voyager ISS dataset handler.
Parameters:
pds3_holdings_root: Path to PDS3 holdings directory. If None, uses PDS3_HOLDINGS_DIR
environment variable. May be a URL accepted by FCPath.
index_filecache: FileCache object to use for index files. If None, creates a new one.
pds3_holdings_filecache: FileCache object to use for PDS3 holdings files. If None,
creates a new one.
config: Configuration object to use. If None, uses DEFAULT_CONFIG.
"""
super().__init__(
pds3_holdings_root=pds3_holdings_root,
index_filecache=index_filecache,
pds3_holdings_filecache=pds3_holdings_filecache,
config=config,
)
[docs]
def pds4_bundle_template_dir(self) -> str:
"""Returns absolute path to template directory for PDS4 bundle generation."""
template_dir = None
vgiss_config = self.config.pds4.get('vgiss', {})
if 'template_dir' in vgiss_config:
template_dir = str(vgiss_config['template_dir'])
if template_dir is None:
template_dir = 'voyager_iss_1.0'
if Path(template_dir).is_absolute():
return template_dir
pds4_templates_dir = Path(__file__).resolve().parent.parent.parent / 'pds4' / 'templates'
return str(pds4_templates_dir / template_dir)
[docs]
def pds4_bundle_name(self) -> str:
"""Returns bundle name for PDS4 bundle generation."""
vgiss_config = self.config.pds4.get('vgiss', {})
if 'bundle_name' in vgiss_config:
return str(vgiss_config['bundle_name'])
return 'voyager_iss_backplanes_rsfrench2027'
[docs]
@staticmethod
def pds4_bundle_path_for_image(image_name: str) -> str:
"""Maps image name to bundle directory path."""
raise NotImplementedError
[docs]
def pds4_path_stub(self, image_file: ImageFile) -> str:
"""Returns PDS4 path stub for bundle directory structure."""
raise NotImplementedError
[docs]
def pds4_image_name_to_browse_lid(self, image_name: str) -> str:
"""Returns the browse LID for the given image name.
Parameters:
image_name: The image name to convert to a browse LID.
Returns:
The browse LID.
"""
raise NotImplementedError
[docs]
def pds4_image_name_to_browse_lidvid(self, image_name: str) -> str:
"""Returns the browse LIDVID for the given image name.
Parameters:
image_name: The image name to convert to a browse LIDVID.
Returns:
The browse LIDVID.
"""
raise NotImplementedError
[docs]
def pds4_image_name_to_data_lid(self, image_name: str) -> str:
"""Returns the data LID for the given image name.
Parameters:
image_name: The image name to convert to a data LID.
Returns:
The data LID.
"""
raise NotImplementedError
[docs]
def pds4_image_name_to_data_lidvid(self, image_name: str) -> str:
"""Returns the data LIDVID for the given image name.
Parameters:
image_name: The image name to convert to a data LIDVID.
Returns:
The data LIDVID.
"""
raise NotImplementedError
[docs]
def pds4_template_variables(
self,
*,
image_file: ImageFile,
nav_metadata: dict[str, Any],
backplane_metadata: dict[str, Any],
) -> dict[str, Any]:
"""Returns template variables for PDS4 label generation."""
raise NotImplementedError