diff --git a/docs/source/pipeline_canfar.md b/docs/source/pipeline_canfar.md index 7e8bb29c5..c548979bc 100644 --- a/docs/source/pipeline_canfar.md +++ b/docs/source/pipeline_canfar.md @@ -29,7 +29,15 @@ canfar auth switch default ### Set variables (optional) -Set the current patch in the shell as +> **Patch convention by catalogue version.** Runs up to `v1.6` are organised +> in sky patches `P1`–`P9` (7 patches for `v1.3`/`v1.4`, 8 for `v1.5`), each +> patch a run directory of its own with its `summary/` subdirectory. **The +> patch concept is removed in `v2.0`**: there is a single run root, and +> summary files (`summary/missing_job_32_all.txt`, `exp_numbers.txt`) live +> directly under it. Tools that take a catalogue version (e.g. +> `get_ccds_with_psf -V`) follow this convention automatically. + +For a `v1.x` run, set the current patch in the shell as ```bash patch=P[1-9] @@ -458,13 +466,12 @@ calibrate_comprehensive ```{note} -The matched-star-catalogue and coverage-mask diagnostics in the next two -sections have largely moved out of ShapePipe into -[`sp_validation`](https://github.com/CosmoStat/sp_validation) / `cosmo_val`. -Several helpers referenced below — `merge_psf_cat.py`, `download_headers`, -`extract_field_corners`, `build_coverage_map`, `plot_coverage_map` — are no -longer shipped in this repository; see `sp_validation` for their current -equivalents. (`get_ccds_with_psf` and `summary_run` are still part of ShapePipe.) +The matched-star-catalogue diagnostic (`merge_psf_cat.py`) has moved into +[`sp_validation`](https://github.com/CosmoStat/sp_validation) / `cosmo_val`; +see that repository for its current equivalent. The coverage-mask helpers +(`get_ccds_with_psf`, `download_headers`, `extract_field_corners`, +`build_coverage_map`, `plot_coverage_map`, and `summary_run`) are shipped by +ShapePipe and documented below. ``` ### Create matched star catalogue @@ -495,29 +502,57 @@ If the file `$patch/exp_numbers.txt` does not exist for a given patch, create it summary_run $patch 1 ``` -Now, create the list of CCDs that have PSF information with +Now, create the list of single-CCD footprints that have a valid PSF model. +Only these CCDs are stamped into the mask, so the accumulated map counts, per +sky pixel, the number of exposures with a valid PSF. The list is written to +`ccds_with_psf_v1.6.txt`: ```bash -get_ccds_with_psf -v -V v1.6 +get_ccds_with_psf -v -V v1.6 -o ccds_with_psf_v1.6.txt ``` -Next, download exposures headers +For a (patch-less) `v2.0` run, call this from the run root instead: ```bash -download_headers -i ccds_with_psfs_v1.6.txt -o headers_v1.6 -v +get_ccds_with_psf -v -V v2.0 -o ccds_with_psf_v2.0.txt ``` -From the headers, the CCD corner coordinates are extracted with +Next, download the exposure headers; indicate (with `-d`) a directory of +already downloaded headers, which are linked so duplicate downloads are +skipped: + ```bash -extract_field_corners -i headers_v1.6 -v +download_headers -i ccds_with_psf_v1.6.txt -o headers_v1.6 -d headers_v1.3 -v +``` + +From the headers, the per-CCD corner coordinates are extracted. Pass the +CCD list with `-l` so only CCDs with a valid PSF are written: + +```bash +extract_field_corners -i headers_v1.6 -l ccds_with_psf_v1.6.txt -o exp_ra_dec_v1.6.txt -v ``` Then, build the healsparse coverage mask file as ```bash -build_coverage_map +build_coverage_map -i exp_ra_dec_v1.6.txt -o coverage_v1.6.x.hsp -c 128 -n 131072 -v ``` -Use `plot_coverage_map` to create plots of the coverage mask. +The healsparse resolutions (128, 131072) match the bit masks. + + +Use `plot_coverage_map` to plot a region of the coverage mask. For example, +the SGC region with a colorbar clipped to the 1–5 exposure range: + +```bash +plot_coverage_map -i coverage_v1.6.x.hsp -o coverage_v1.6_SGC.png \ + -C -R -20 -r 45 -D 18 -d 40 -m 1 -M 5 -v +``` + +Here `-R`/`-r` and `-D`/`-d` set the RA and Dec plot limits, `-m`/`-M` the +colorbar range, and `-C` adds the colorbar. Building and plotting for a range +of versions (SGC and NGC regions) is automated by +`build_and_plot_coverage_maps.sh`. + ## Extra Utilities diff --git a/log_-i b/log_-i new file mode 100644 index 000000000..e90567d12 --- /dev/null +++ b/log_-i @@ -0,0 +1 @@ +-i /tmp/pytest-of-cdaley/pytest-8/test_build_map_pole_guard_skip0/corners.txt -o /tmp/pytest-of-cdaley/pytest-8/test_build_map_pole_guard_skip0/cov.hsp -c 32 -n 1024 diff --git a/pyproject.toml b/pyproject.toml index 8b259deec..5333aa91c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,8 @@ dependencies = [ "cs_util>=0.2.1", "galsim>=2.8", "h5py", + "healsparse", + "hpgeom", "joblib>=1.4", "matplotlib>=3.10", "mccd>=1.2.4", @@ -39,6 +41,7 @@ dependencies = [ "reproject>=0.19", "sf_tools>=2.0.4", "skaha>=1.7", + "skyproj", "sqlitedict>=2.0", "termcolor", "tqdm>=4.68.1", @@ -63,6 +66,10 @@ jupyter = [ lint = [ "ruff", ] +plot = [ + "skyproj", + "matplotlib>=3.10", +] release = [ "build", "twine", @@ -73,7 +80,7 @@ test = [ "pytest-cov>=5.0", ] fitsio = ["fitsio"] -dev = ["shapepipe[doc,jupyter,lint,release,test,fitsio]"] +dev = ["shapepipe[doc,jupyter,lint,plot,release,test,fitsio]"] [project.scripts] shapepipe_run = "shapepipe.shapepipe_run:main" @@ -81,6 +88,11 @@ summary_run = "shapepipe.summary_run:main" canfar_submit_job = "shapepipe.canfar_run:run_job" canfar_monitor = "shapepipe.canfar_run:run_log" canfar_monitor_log = "shapepipe.canfar_run:run_monitor_log" +get_ccds_with_psf = "shapepipe.get_ccds_run:run_ccd_psf_handler" +download_headers = "shapepipe.coverage_run:run_download_headers" +extract_field_corners = "shapepipe.coverage_run:run_extract_corners" +build_coverage_map = "shapepipe.coverage_run:run_build_coverage" +plot_coverage_map = "shapepipe.coverage_run:run_plot_coverage" [tool.uv] # shapepipe targets Linux only; skip Windows/macOS wheel resolution diff --git a/scripts/python/get_ccds_with_psf.py b/scripts/python/get_ccds_with_psf.py old mode 100644 new mode 100755 index 5f917ad02..e284e2640 --- a/scripts/python/get_ccds_with_psf.py +++ b/scripts/python/get_ccds_with_psf.py @@ -9,192 +9,50 @@ """ - import sys -import numpy as np - -from shapepipe.utilities import summary - - -def get_lines(fname): - """Get Lines. - - Return list of lines read from a text file. - - Parameters - ---------- - fname: str - input file name - Returns: - list - IDs +from shapepipe.utilities.ccd_psf_handler import CcdPsfHandler - """ - IDs = [] - with open(fname) as f: - lines = f.readlines() - for line in lines: - IDs.append(line.rstrip()) - - return IDs +def run_ccd_psf_handler(args=None): + """Run CCD PSF Handler. -def get_exp_shdu_missing(patches): - """Get Exp Shdu Missing. - - Returns set of missing CCDs (single-exposure single-HDU IDs) from a list of patches. + Create instance and run the CCD PSF handler. Parameters ---------- - patches: list - input patches + args : list, optional + command line arguments Returns ------- - set - missing CCD IDs + int + exit code """ - exp_shdu_missing_all = set() - - for patch in patches: - - path_exp_shdu_missing = f"{patch}/summary/missing_job_32_all.txt" - exp_shdu_missing = get_lines(path_exp_shdu_missing) - - print(f"Patch {patch}: Found {len(exp_shdu_missing)} missing ccds", end="; ") - - exp_shdu_missing_all.update(exp_shdu_missing) - - print(f"cumulative {len(exp_shdu_missing_all)} missing ccds") - - print() - - return exp_shdu_missing_all - - -def get_exp(patches): - """Get Exp. - - Return set of exposures from a list of patches. - - Parameters - ---------- - patches: list - input patches - - Returns - ------- - set - exposure IDs - - """ - exp_all = set() - - for patch in patches: - - path_exp = f"{patch}/exp_numbers.txt" - exp = get_lines(path_exp) - - print(f"Patch {patch}: Found {len(exp)} exposures", end="; ") - - exp_all.update(exp) - - print(f"cumulative {len(exp_all)} exposures") + # Create instance + obj = CcdPsfHandler() - print() + return obj.run(args=args) - return exp_all +def main(argv=None): + """Main. -def get_ccds_with_psf(patches, n_CCD=40): - """Get CCDs With PSF. - - Return set of CCDs from list of patches. + Main program. Parameters ---------- - patches: list - input patches + argv : list, optional + command line arguments Returns ------- - set - CCD IDs + int + exit code """ - # Get missing CCDs - print("=== get missing CCDs ===") - exp_shdu_missing_all = get_exp_shdu_missing(patches) - - # Get all exposures used in tiles - print("=== get exposures ===") - exp_all = get_exp(patches) - - # Turn exposures into exposure-single-HDU names (CCDs) - exp_shdu_all = summary.get_all_shdus(exp_all, n_CCD) - - print(f"Found {len(exp_shdu_all)} CCDs") - - return exp_shdu_all - - -def get_ccds_with_psf_method_2(patches, n_CCD=40): - - for patch in patches: - directory = f"{patch}/exp_runs" - - for entry in os.scandir(directory): - pass - -def save(IDs, path): - """Save. - - Save list of IDs to text file. - - Parameters - ---------- - IDs: set - input IDs - - path: str - output file name - - """ - with open(path, "w") as f_out: - for ID in IDs: - print(ID, file=f_out) - -def main(argv): - """Main. - - Main program. - - """ - version = "v1.5" - - if version == "v1.4": - n_patch = 7 - elif version == "v1.5": - n_patch = 8 - else: - raise ValueError(f"Invalid version {version}") - - patches = [f'P{x}' for x in np.arange(n_patch) + 1] - - print(f"=== get_ccds_with_psf for version {version}, patches {patches} ===") - - print("=== method 1: exp_list - missing === ") - exp_shdu_all = get_ccds_with_psf(patches) - - save(exp_shdu_all, f"ccds_with_psf_{version}.txt") - - #print("=== method 2: star cats === ") - #exp_shdu_all_method_2 = get_ccds_with_psf_method_2(patches) - #save(exp_shdu_all_method_2, f"ccds_with_psf_{version}_method_2.txt") - - return 0 + return run_ccd_psf_handler(args=argv) if __name__ == "__main__": diff --git a/scripts/sh/build_and_plot_coverage_maps.sh b/scripts/sh/build_and_plot_coverage_maps.sh new file mode 100755 index 000000000..b7ea9c8b2 --- /dev/null +++ b/scripts/sh/build_and_plot_coverage_maps.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +set -euo pipefail + +# Build and plot per-CCD coverage (nexp) maps for a range of catalogue +# versions. Each map counts, per sky pixel, the number of exposures with a +# valid PSF model. The per-version flow is: +# 0. get_ccds_with_psf -> ccds_with_psf_.txt (valid CCD IDs) +# 1. download_headers -> per-exposure header text files +# 2. extract_field_corners-> exp_ra_dec_.txt (per-CCD corners) +# 3. build_coverage_map -> coverage_.x.hsp +# 4. plot_coverage_map -> SGC / NGC plots +# Steps 0-2 are expected to have been run already on canfar (they need +# VOSpace access); this script rebuilds from the extracted per-CCD corners and +# plots. Uncomment the marked lines to run the full chain. + +# Configuration variables + +## Catalogue versions +VERSIONS=("v1.3" "v1.4" "v1.5" "v1.6") + +## Output directory of plots +OUTPUT_DIR="coverage_map_plots" + +# healsparse resolution. nside=131072 gives ~0.1" per pixel, chosen to +# match the UNIONS bit-mask resolution so coverage and mask align pixel- +# wise. CoverageMapBuilder defaults to nside=2048 for lighter-weight +# offline use; override here for the production build. +BUILD_NSIDE=131072 +BUILD_CHANNELS=128 + +# Common parameters +VERBOSE="-v" + +## Colorbar +PLOT_COLORBAR="-C" +PLOT_MIN=1 +PLOT_MAX=5 + +# Plot parameters +## SGC region +SGC_RA_MIN=-20 +SGC_RA_MAX=45 +SGC_DEC_MIN=18 +SGC_DEC_MAX=40 + +## NGC region +NGC_RA_MIN=110 +NGC_RA_MAX=270 +NGC_DEC_MIN=28 +NGC_DEC_MAX=90 + +# Create output directory if it doesn't exist +mkdir -p "${OUTPUT_DIR}" + +# Loop over versions +for VERSION in "${VERSIONS[@]}"; do + echo "Processing ${VERSION}..." + + # Define file paths + CCD_LIST="ccds_with_psf_${VERSION}.txt" + HEADER_DIR="headers_${VERSION}" + INPUT_CORNERS="exp_ra_dec_${VERSION}.txt" + COVERAGE_MAP="coverage_${VERSION}.x.hsp" + + # Step 0-2: build the per-CCD corner file (canfar / VOSpace only). + # Uncomment to run the full chain from scratch. + # get_ccds_with_psf -V "${VERSION}" -o "${CCD_LIST}" ${VERBOSE} + # download_headers -i "${CCD_LIST}" -o "${HEADER_DIR}" ${VERBOSE} + # extract_field_corners -i "${HEADER_DIR}" -l "${CCD_LIST}" \ + # -o "${INPUT_CORNERS}" ${VERBOSE} + + # Build coverage map + echo " Building coverage map from ${INPUT_CORNERS}..." + CMD="build_coverage_map -i ${INPUT_CORNERS} -o ${COVERAGE_MAP} -c ${BUILD_CHANNELS} -n ${BUILD_NSIDE} ${VERBOSE}" + echo "$CMD" + $CMD + + # Plot SGC region + echo " Plotting SGC region..." + CMD="plot_coverage_map -i ${COVERAGE_MAP} -o ${OUTPUT_DIR}/coverage_${VERSION}_SGC.png ${VERBOSE} ${PLOT_COLORBAR} -R ${SGC_RA_MIN} -r ${SGC_RA_MAX} -D ${SGC_DEC_MIN} -d ${SGC_DEC_MAX} -m ${PLOT_MIN} -M ${PLOT_MAX}" + echo "$CMD" + $CMD + + # Plot NGC region + echo " Plotting NGC region..." + CMD="plot_coverage_map -i ${COVERAGE_MAP} -o ${OUTPUT_DIR}/coverage_${VERSION}_NGC.png ${VERBOSE} ${PLOT_COLORBAR} -R ${NGC_RA_MIN} -r ${NGC_RA_MAX} -D ${NGC_DEC_MIN} -d ${NGC_DEC_MAX} -m ${PLOT_MIN} -M ${PLOT_MAX}" + echo "$CMD" + $CMD + + echo " Done with ${VERSION}" + echo +done + +echo "All versions processed successfully!" diff --git a/src/shapepipe/coverage_run.py b/src/shapepipe/coverage_run.py new file mode 100644 index 000000000..15aaa1a37 --- /dev/null +++ b/src/shapepipe/coverage_run.py @@ -0,0 +1,104 @@ +"""COVERAGE_RUN + +Call coverage processing classes. + +Author: Martin Kilbinger + +""" + +import sys + +from shapepipe.utilities.header_downloader import HeaderDownloader +from shapepipe.utilities.field_corners_extractor import FieldCornersExtractor +from shapepipe.utilities.coverage_map_builder import CoverageMapBuilder +from shapepipe.utilities.coverage_plotter import CoveragePlotter + + +def run_download_headers(args=None): + """Run Download Headers. + + Download FITS headers from VOSpace for exposures in a CCD list. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code + + """ + obj = HeaderDownloader() + return obj.run(args=args) + + +def run_extract_corners(args=None): + """Run Extract Corners. + + Extract per-CCD sky-footprint corner coordinates from FITS headers. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code + + """ + obj = FieldCornersExtractor() + return obj.run(args=args) + + +def run_build_coverage(args=None): + """Run Build Coverage. + + Build HealSparse coverage maps from per-CCD corner coordinates. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code + + """ + obj = CoverageMapBuilder() + return obj.run(args=args) + + +def run_plot_coverage(args=None): + """Run Plot Coverage. + + Plot HealSparse coverage map. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code + + """ + obj = CoveragePlotter() + return obj.run(args=args) + + +def main(argv=None): + """Main. + + Main program. + + """ + # Scripts to call coverage classes are created by pyproject.toml + return 0 diff --git a/src/shapepipe/get_ccds_run.py b/src/shapepipe/get_ccds_run.py new file mode 100644 index 000000000..fb18b0a74 --- /dev/null +++ b/src/shapepipe/get_ccds_run.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +"""GET_CCDS_WITH_PSF + +Obtain list of CCDs (single-exposure single-HDU files) for which valid PSF information +is available. This can serve to create a footprint coverage mask. + +Author: Martin Kilbinger + +""" + +import sys + +from shapepipe.utilities.ccd_psf_handler import CcdPsfHandler + + +def run_ccd_psf_handler(args=None): + """Run CCD PSF Handler. + + Create instance and run the CCD PSF handler. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code + + """ + # Create instance + + obj = CcdPsfHandler() + + return obj.run(args=args) + + +def main(argv=None): + """Main. + """ + # A scripts to call the ccd psf class is created by pyproject.toml + return 0 diff --git a/src/shapepipe/utilities/ccd_psf_handler.py b/src/shapepipe/utilities/ccd_psf_handler.py new file mode 100644 index 000000000..495125624 --- /dev/null +++ b/src/shapepipe/utilities/ccd_psf_handler.py @@ -0,0 +1,402 @@ +"""CCD_PSF_HANDLER + +Obtain list of CCDs (single-exposure single-HDU files) for which valid PSF +information is available. This can serve to create a footprint coverage mask. + +Author: Mike Hudson, Martin Kilbinger + +""" + +import glob +import os +import re +import sys + +import numpy as np + +from cs_util import args as cs_args +from cs_util import logging + +from shapepipe.utilities import summary + + +class CcdPsfHandler(object): + """CCD PSF Handler Class. + + Handles extraction of CCDs with valid PSF information from shapepipe + patches. + """ + + def __init__(self): + """Initialize the handler.""" + self.params_default() + + def params_default(self): + """Set default parameters and command line options.""" + + self._params = { + "version_cat": "v1.6", + "n_CCD": 40, + "n_patch": 0, + "output": None, + } + + self._short_options = { + "version_cat": "-V", + "n_CCD": "-n", + "n_patch": "-p", + "output": "-o", + } + + self._types = { + "n_CCD": "int", + "n_patch": "int", + } + + self._help_strings = { + "version_cat": "catalogue major version, allowed are v1.3, v1.4, v1.5, v1.6, v2.0; default is {}", + + "n_CCD": "number of CCDs per exposure; default is {}", + "n_patch": ( + "number of patches (P1..P); overrides the version" + + " default, 0 = use version default (v2.0 is patch-less:" + + " summary files are read from the run root); default is {}" + ), + "output": "output file path; default is ccds_with_psf_.txt", + } + + def set_params_from_command_line(self, args): + """Set Params From Command line. + + Only use when calling using python from command line. + Does not work from ipython or jupyter. + + Parameters + ---------- + args : list + command line arguments + + """ + # Read command line options + options = cs_args.parse_options( + self._params, + self._short_options, + self._types, + self._help_strings, + args=args, + ) + self._params = options + + # Save calling command + logging.log_command(args) + + def update_params(self): + """Update parameters. + + Set derived parameters based on input parameters. + """ + # Determine patch directories: explicit -p wins, else version default. + # v2.0 removes the patch concept: summary files live at the run root. + version = self._params["version_cat"] + if self._params["n_patch"]: + n_patch = self._params["n_patch"] + elif version in ("v1.3", "v1.4"): + n_patch = 7 + elif version == "v1.5": + n_patch = 8 + elif version == "v1.6": + n_patch = 9 + elif version == "v2.0": + n_patch = 0 + else: + raise ValueError(f"Invalid version {version}") + + self._params["n_patch"] = n_patch + self._params["patches"] = ( + [f"P{x}" for x in np.arange(n_patch) + 1] if n_patch else ["."] + ) + + # Set output file if not specified + if self._params["output"] is None: + self._params["output"] = f"ccds_with_psf_{version}.txt" + + def check_params(self): + """Check parameters for validity.""" + # Add any parameter validation logic here + pass + + def get_lines(self, fname): + """Get Lines. + + Return list of lines read from a text file. + + Parameters + ---------- + fname : str + input file name + + Returns + ------- + list + IDs + + """ + IDs = [] + with open(fname) as f: + lines = f.readlines() + for line in lines: + IDs.append(line.rstrip()) + + return IDs + + def get_exp_shdu_missing(self, patches): + """Get Exp Shdu Missing. + + Returns set of missing CCDs (single-exposure single-HDU IDs) from a list of patches. + + Parameters + ---------- + patches : list + input patches + + Returns + ------- + set + missing CCD IDs + + """ + exp_shdu_missing_all = set() + + for patch in patches: + + path_exp_shdu_missing = f"{patch}/summary/missing_job_32_all.txt" + exp_shdu_missing = self.get_lines(path_exp_shdu_missing) + + print( + f"Patch {patch}: Found {len(exp_shdu_missing)} missing ccds", + end="; ", + ) + + exp_shdu_missing_all.update(exp_shdu_missing) + + print(f"cumulative {len(exp_shdu_missing_all)} missing ccds") + + print() + + return exp_shdu_missing_all + + def get_exp(self, patches): + """Get Exp. + + Return set of exposures from a list of patches. + + Parameters + ---------- + patches : list + input patches + + Returns + ------- + set + exposure IDs + + """ + exp_all = set() + + for patch in patches: + + path_exp = f"{patch}/exp_numbers.txt" + exp = self.get_lines(path_exp) + + print(f"Patch {patch}: Found {len(exp)} exposures", end="; ") + + exp_all.update(exp) + + print(f"cumulative {len(exp_all)} exposures") + + print() + + return exp_all + + def get_ccds_with_psf(self, patches, n_CCD=40): + """Get CCDs With PSF. + + Return set of CCDs with valid PSF from a list of patches. A CCD has a + valid PSF if it appears among the exposures' single-HDU IDs but not in + the per-patch missing-CCD lists. + + Parameters + ---------- + patches : list + input patches + n_CCD : int + number of CCDs per exposure + + Returns + ------- + set + CCD IDs with valid PSF + + """ + # Get missing CCDs + print("=== get missing CCDs ===") + exp_shdu_missing_all = self.get_exp_shdu_missing(patches) + + # Get all exposures used in tiles + print("=== get exposures ===") + exp_all = self.get_exp(patches) + + # Turn exposures into exposure-single-HDU names (CCDs) + exp_shdu_all = set(summary.get_all_shdus(exp_all, n_CCD)) + + # Subtract the CCDs whose PSF model is missing + exp_shdu_with_psf = exp_shdu_all - exp_shdu_missing_all + + print( + f"Found {len(exp_shdu_all)} CCDs, " + f"{len(exp_shdu_missing_all)} missing, " + f"{len(exp_shdu_with_psf)} with valid PSF" + ) + + return exp_shdu_with_psf + + def get_ccds_with_psf_method_v1_3(self, patches, n_CCD=40): + """Get CCDs With PSF Method v1.3. + + Return set of CCDs with valid PSF by scanning run directories. + Finds star_selection files and checks corresponding stat files + for valid (non-nan) FWHM values. + + Parameters + ---------- + patches : list + input patches + n_CCD : int + number of CCDs per exposure (unused, kept for API consistency) + + Returns + ------- + set + CCD IDs with valid PSF + + """ + exp_shdu_all = set() + + for patch in patches: + # Find all star_selection files + mask_pattern = ( + f"{patch}/output/run_sp_exp_SxSePs*/" + f"setools_runner/output/mask/star_selection-*.fits" + ) + mask_files = glob.glob(mask_pattern) + + print( + f"Patch {patch}: Found {len(mask_files)} star_selection FITS" + + " files" + ) + + for mask_file in mask_files: + # Extract exp_shdu from filename + basename = os.path.basename(mask_file) + match = re.match(r"star_selection-(.+)\.fits", basename) + if not match: + print(f"Warning: Non-matching file name {basename}") + continue + exp_shdu = match.group(1) + + # Get corresponding stat file path + # Replace mask dir with stat/stat dir in the path + stat_file = mask_file.replace( + "/mask/star_selection-", + "/stat/star_stat-", + ).replace(".fits", ".txt") + + #print("MKDEBUG ", exp_shdu, stat_file) + + # Check if stat file exists and has valid FWHM + if not os.path.exists(stat_file): + print("MKDEBUG stat file ", stat_file, " does not exist") + continue + + # Read stat file and check for nan in "Mean star fwhm selected" + has_nan = False + with open(stat_file) as f: + for line in f: + if "Mean star fwhm selected" in line: + if "nan" in line.lower(): + has_nan = True + break + + if not has_nan: + #print("MKDEBUG append", exp_shdu) + exp_shdu_all.add(exp_shdu) + + print(f"After patch {patch}: {len(exp_shdu_all)} CCDs with valid PSF") + + print(f"Found {len(exp_shdu_all)} CCDs with valid PSF") + + return exp_shdu_all + + def save(self, IDs, path): + """Save. + + Save list of IDs to text file. + + Parameters + ---------- + IDs : set + input IDs + path : str + output file name + + """ + with open(path, "w") as f_out: + for ID in IDs: + print(ID, file=f_out) + + def run(self, args=None): + """Run. + + Main execution method. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code (0 for success) + + """ + if args is None: + args = sys.argv[1:] + + # Set parameters from command line + self.set_params_from_command_line(args) + self.update_params() + self.check_params() + + # Get parameters + patches = self._params["patches"] + version = self._params["version_cat"] + n_CCD = self._params["n_CCD"] + output = self._params["output"] + + print( + f"=== get_ccds_with_psf for version {version}, patches {patches} ===" + ) + + if self._params["version_cat"] == "v1.3": + print("=== method for 1.3: exp with PSF star sample === ") + exp_shdu_all = self.get_ccds_with_psf_method_v1_3(patches, n_CCD) + else: + print("=== method for >= 1.4: exp_list - missing === ") + exp_shdu_all = self.get_ccds_with_psf(patches, n_CCD) + + self.save(exp_shdu_all, output) + + print(f"Results saved to {output}") + + return 0 diff --git a/src/shapepipe/utilities/coverage_map_builder.py b/src/shapepipe/utilities/coverage_map_builder.py new file mode 100644 index 000000000..c7144ef5c --- /dev/null +++ b/src/shapepipe/utilities/coverage_map_builder.py @@ -0,0 +1,372 @@ +"""COVERAGE_MAP_BUILDER + +Build HealSparse coverage maps from per-CCD corner coordinates. + +Each input row is one CCD footprint. Since the CCDs of a single exposure do +not overlap, stamping value 1 per CCD polygon and accumulating makes the map +count, per sky pixel, the number of exposures with a valid PSF model covering +that pixel. + +Author: Mike Hudson, Martin Kilbinger + +""" + +import sys +from os.path import exists + +import numpy as np +import healsparse as hsp +import hpgeom as hpg + +from cs_util import args as cs_args +from cs_util import logging + +# Declination beyond which a polygon is considered too close to a pole for +# HealSparse's planar polygon fill to be reliable. CCD footprints are ~10 +# arcmin, so this is a guard against pathological headers, not a common path. +_DEC_POLE_LIMIT = 89.0 + + +def unwrap_ra(ra): + """Unwrap RA. + + Shift RA values onto a common branch when a polygon straddles the RA=0 + seam, so the corners describe the small footprint rather than its ~360° + complement. Values above 180 deg are shifted by -360 deg when the raw + spread exceeds 180 deg; otherwise the values are returned unchanged. + + Parameters + ---------- + ra : array_like + polygon RA corners, in degrees + + Returns + ------- + numpy.ndarray + RA corners on a common branch, in degrees + + """ + ra = np.asarray(ra, dtype=float) + if ra.max() - ra.min() > 180.0: + return np.where(ra > 180.0, ra - 360.0, ra) + return ra + + + +class CoverageMapBuilder(object): + """Coverage Map Builder Class. + + Builds HealSparse coverage maps from field corner coordinates. + """ + + def __init__(self): + """Initialize the builder.""" + self.params_default() + + def params_default(self): + """Set default parameters and command line options.""" + + self._params = { + "input_file": "exp_ra_dec.txt", + "output_file": "coverage.hsp", + "nside_coverage": 32, + "nside": 2048, + "apply_median_filter": False, + "n_median_iterations": 2, + "create_boolean": False, + "boolean_threshold": 3, + "boolean_output": None, + "create_plot": False, + "plot_output": None, + "plot_region": None, + "verbose": False, + } + + self._short_options = { + "input_file": "-i", + "output_file": "-o", + "nside_coverage": "-c", + "nside": "-n", + "apply_median_filter": "-m", + "n_median_iterations": "-N", + "create_boolean": "-b", + "boolean_threshold": "-t", + "boolean_output": "-B", + "create_plot": "-p", + "plot_output": "-P", + "plot_region": "-g", + } + + self._types = { + "nside_coverage": "int", + "nside": "int", + "apply_median_filter": "bool", + "n_median_iterations": "int", + "create_boolean": "bool", + "boolean_threshold": "int", + "create_plot": "bool", + "verbose": "bool", + } + + self._help_strings = { + "input_file": "input file with field corners; default is {}", + "output_file": "output HealSparse map file; default is {}", + "nside_coverage": "HealSparse coverage nside; default is {}", + "nside": "HealSparse map nside; default is {}", + "apply_median_filter": "apply median filter to smooth map; default is {}", + "n_median_iterations": "number of median filter iterations; default is {}", + "create_boolean": "create boolean coverage map; default is {}", + "boolean_threshold": "threshold value for boolean map; default is {}", + "boolean_output": "output file for boolean map; default is _bool.hsp", + "create_plot": "create plot of coverage map; default is {}", + "plot_output": "output file for plot; default is .png", + "plot_region": "predefined region for plot (NGC, SGC, fullsky); default is {}", + } + + def set_params_from_command_line(self, args): + """Set Params From Command line. + + Only use when calling using python from command line. + Does not work from ipython or jupyter. + + Parameters + ---------- + args : list + command line arguments + + """ + # Read command line options + options = cs_args.parse_options( + self._params, + self._short_options, + self._types, + self._help_strings, + args=args, + ) + self._params = options + + # Save calling command + logging.log_command(args) + + def update_params(self): + """Update parameters. + + Set derived parameters based on input parameters. + """ + # Set boolean output filename if not specified + if self._params["boolean_output"] is None: + output_file = self._params["output_file"] + if output_file.endswith(".hsp"): + base = output_file[:-4] + else: + base = output_file + self._params["boolean_output"] = f"{base}_bool.hsp" + + # Set plot output filename if not specified + if self._params["plot_output"] is None: + output_file = self._params["output_file"] + if output_file.endswith(".hsp"): + base = output_file[:-4] + else: + base = output_file + self._params["plot_output"] = f"{base}.png" + + def check_params(self): + """Check parameters for validity.""" + if not exists(self._params["input_file"]): + raise FileNotFoundError( + f"Input file not found: {self._params['input_file']}" + ) + + # Check nside values are powers of 2 + nside_coverage = self._params["nside_coverage"] + nside = self._params["nside"] + + if not (nside_coverage & (nside_coverage - 1) == 0): + raise ValueError( + f"nside_coverage must be a power of 2, got {nside_coverage}" + ) + + if not (nside & (nside - 1) == 0): + raise ValueError(f"nside must be a power of 2, got {nside}") + + def median_filter(self, hsp_map): + """Median Filter. + + Apply median filter to HealSparse map using neighbors. + + Parameters + ---------- + hsp_map : healsparse.HealSparseMap + input map + + Returns + ------- + healsparse.HealSparseMap + filtered map + + """ + nside = hsp_map.nside_sparse + + new_hsp = hsp_map.copy() + pixs = hsp_map.valid_pixels + n = hpg.neighbors(nside, pixs) + n = np.where(n >= 0, n, 0) + pix2 = pixs.reshape(len(pixs), 1) + n = np.hstack([n, pix2]) + mn = hsp_map[n] + new = np.median(mn, axis=1).astype(np.uint16) + new_hsp[pixs] = new + + return new_hsp + + def run(self, args=None): + """Run. + + Main execution method. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code (0 for success) + + """ + if args is None: + args = sys.argv[1:] + + # Set parameters from command line + self.set_params_from_command_line(args) + self.update_params() + self.check_params() + + # Get parameters + input_file = self._params["input_file"] + output_file = self._params["output_file"] + nside_coverage = self._params["nside_coverage"] + nside = self._params["nside"] + apply_median_filter = self._params["apply_median_filter"] + n_median_iterations = self._params["n_median_iterations"] + create_boolean = self._params["create_boolean"] + boolean_threshold = self._params["boolean_threshold"] + boolean_output = self._params["boolean_output"] + create_plot = self._params["create_plot"] + plot_output = self._params["plot_output"] + plot_region = self._params["plot_region"] + verbose = self._params["verbose"] + + if verbose: + print(f"Reading per-CCD corners from: {input_file}") + + # Load per-CCD corner data. Column 0 is a "-" string + # ID; the remaining 8 columns are the 4 RA then 4 Dec corners. + ccd_ids = np.atleast_1d(np.loadtxt(input_file, usecols=(0), dtype=str)) + corners = np.atleast_2d( + np.loadtxt(input_file, usecols=range(1, 9), dtype=float) + ) + ra_all = corners[:, :4] + dec_all = corners[:, 4:] + + print(f"Loaded {len(ccd_ids)} CCD footprints") + + if verbose: + print( + f"Creating HealSparse map (nside_coverage={nside_coverage}, nside={nside})" + ) + + # Create empty map + m = hsp.HealSparseMap.make_empty(nside_coverage, nside, np.uint16) + + # Add one polygon per CCD footprint + if verbose: + print("Adding polygons to map") + + n_added = 0 + n_skipped = 0 + for i in range(len(ccd_ids)): + dec = dec_all[i] + + # Pole guard: HealSparse's planar polygon fill degrades near the + # poles. CCD footprints never reach here, so warn and skip. + if np.any(np.abs(dec) >= _DEC_POLE_LIMIT): + print( + f"Warning: skipping CCD {ccd_ids[i]} with |dec| >= " + f"{_DEC_POLE_LIMIT} (too close to a pole)" + ) + n_skipped += 1 + continue + + # RA-wrap guard: put corners on a common branch across the seam. + ra = unwrap_ra(ra_all[i]) + + m += hsp.Polygon(ra=list(ra), dec=list(dec), value=1) + n_added += 1 + + if verbose and i % 1000 == 0: + print(f"{i:6d} / {len(ccd_ids):6d}") + + print(f"Added {n_added} polygons to map") + if n_skipped > 0: + print(f"Skipped {n_skipped} polygons near a pole") + + # Apply median filter if requested + if apply_median_filter: + if verbose: + print( + f"Applying median filter ({n_median_iterations} iterations)" + ) + + for i in range(n_median_iterations): + m = self.median_filter(m) + if verbose: + print(f" Iteration {i+1}/{n_median_iterations} complete") + + # Write main coverage map + if verbose: + print(f"Writing coverage map to: {output_file}") + + m.write(output_file, clobber=True) + print(f"Coverage map saved to {output_file}") + + # Create and write boolean map if requested + if create_boolean: + if verbose: + print( + f"Creating boolean map (threshold={boolean_threshold})" + ) + + c = hsp.HealSparseMap.make_empty(nside_coverage, nside, bool) + c[m.valid_pixels] = m[m.valid_pixels] >= boolean_threshold + + if verbose: + print(f"Writing boolean map to: {boolean_output}") + + c.write(boolean_output, clobber=True) + print(f"Boolean map saved to {boolean_output}") + + # Create plot if requested + if create_plot: + if verbose: + print(f"Creating plot of coverage map") + + try: + from shapepipe.utilities.coverage_plotter import CoveragePlotter + plotter = CoveragePlotter() + plotter.plot_coverage_map( + m, + plot_output, + region=plot_region, + vmax=boolean_threshold if create_boolean else 3, + colorbar=True, + colorbar_label="Coverage depth", + ) + print(f"Plot saved to {plot_output}") + except ImportError as e: + print(f"Warning: Could not create plot: {e}") + print("Install the cs_util package for plotting support.") + + return 0 diff --git a/src/shapepipe/utilities/coverage_plotter.py b/src/shapepipe/utilities/coverage_plotter.py new file mode 100644 index 000000000..7d837c2aa --- /dev/null +++ b/src/shapepipe/utilities/coverage_plotter.py @@ -0,0 +1,348 @@ +"""COVERAGE_PLOTTER + +Plot HealSparse coverage maps. + +Author: Mike Hudson, Martin Kilbinger + +""" + +import sys +from os.path import exists + +import numpy as np +import healsparse as hsp +import matplotlib +import matplotlib.pyplot as plt + +try: + from cs_util.plots import FootprintPlotter + HAS_FOOTPRINT_PLOTTER = True +except ImportError: + HAS_FOOTPRINT_PLOTTER = False + +from cs_util import args as cs_args +from cs_util import logging + + +class CoveragePlotter(object): + """Coverage Plotter Class. + + Plots HealSparse coverage maps. + """ + + def __init__(self): + """Initialize the plotter.""" + self.params_default() + + def params_default(self): + """Set default parameters and command line options.""" + + self._params = { + "input_file": None, + "output_file": "coverage_plot.png", + "region": None, + "ralo": 100.0, + "rahi": 270.0, + "declo": 28.0, + "dechi": 85.0, + "vmin": 0, + "vmax": 3, + "cmap": "rainbow", + "draw_milky_way": True, + "colorbar": True, + "colorbar_label": "Coverage depth", + "figsize_x": 12, + "figsize_y": 8, + "dpi": 200, + "verbose": False, + } + + self._short_options = { + "input_file": "-i", + "output_file": "-o", + "region": "-g", + "ralo": "-R", + "rahi": "-r", + "declo": "-D", + "dechi": "-d", + "vmin": "-m", + "vmax": "-M", + "cmap": "-c", + "draw_milky_way": "-w", + "colorbar": "-C", + "colorbar_label": "-L", + "figsize_x": "-x", + "figsize_y": "-y", + "dpi": "-p", + } + + self._types = { + "ralo": "float", + "rahi": "float", + "declo": "float", + "dechi": "float", + "vmin": "int", + "vmax": "int", + "draw_milky_way": "bool", + "colorbar": "bool", + "figsize_x": "float", + "figsize_y": "float", + "dpi": "int", + "verbose": "bool", + } + + self._help_strings = { + "input_file": "input HealSparse map file; required", + "output_file": "output plot file (png, pdf, etc.); default is {}", + "region": "predefined region (NGC, SGC, fullsky); overrides RA/Dec limits if specified; default is {}", + "ralo": "minimum RA for plot extent; default is {}", + "rahi": "maximum RA for plot extent; default is {}", + "declo": "minimum Dec for plot extent; default is {}", + "dechi": "maximum Dec for plot extent; default is {}", + "vmin": "minimum value for colormap; default is {}", + "vmax": "maximum value for colormap; default is {}", + "cmap": "matplotlib colormap name; default is {}", + "draw_milky_way": "draw Milky Way outline; default is {}", + "colorbar": "add colorbar to plot; default is {}", + "colorbar_label": "colorbar label; default is {}", + "figsize_x": "figure width in inches; default is {}", + "figsize_y": "figure height in inches; default is {}", + "dpi": "figure DPI; default is {}", + } + + def set_params_from_command_line(self, args): + """Set Params From Command line. + + Only use when calling using python from command line. + Does not work from ipython or jupyter. + + Parameters + ---------- + args : list + command line arguments + + """ + # Read command line options + options = cs_args.parse_options( + self._params, + self._short_options, + self._types, + self._help_strings, + args=args, + ) + self._params = options + + # Save calling command + logging.log_command(args) + + def update_params(self): + """Update parameters. + + Set derived parameters based on input parameters. + """ + pass + + def check_params(self): + """Check parameters for validity.""" + if self._params["input_file"] is None: + raise ValueError("Input file is required (use -i or --input_file)") + + if not exists(self._params["input_file"]): + raise FileNotFoundError( + f"Input file not found: {self._params['input_file']}" + ) + + if not HAS_FOOTPRINT_PLOTTER: + raise ImportError( + "cs_util package with FootprintPlotter is required for plotting. " + "Install with: pip install -e /path/to/cs_util" + ) + + # Check region validity. Reaches into FootprintPlotter._regions + # because cs_util doesn't expose a public region registry yet; + # update when it does. + region = self._params["region"] + if region is not None: + valid_regions = list(FootprintPlotter._regions.keys()) + if region not in valid_regions: + raise ValueError( + f"Invalid region '{region}'. Valid regions are: {', '.join(valid_regions)}" + ) + + def plot_coverage_map( + self, + hsp_map, + output_file, + region=None, + ralo=100.0, + rahi=270.0, + declo=28.0, + dechi=85.0, + vmin=0, + vmax=3, + title=None, + colorbar=True, + colorbar_label="Coverage depth", + figsize=(10, 10), + dpi=200, + ): + """Plot Coverage Map. + + Create a plot of the HealSparse coverage map using FootprintPlotter. + + Parameters + ---------- + hsp_map : healsparse.HealSparseMap + coverage map to plot + output_file : str + output file path + region : str, optional + predefined region name (NGC, SGC, fullsky) + ralo : float + minimum RA for plot extent (used if region is None) + rahi : float + maximum RA for plot extent (used if region is None) + declo : float + minimum Dec for plot extent (used if region is None) + dechi : float + maximum Dec for plot extent (used if region is None) + vmin : int + minimum value for colormap + vmax : int + maximum value for colormap + title : str, optional + plot title + colorbar : bool + add colorbar to plot + colorbar_label : str + colorbar label + figsize : tuple + figure size (width, height) in inches + dpi : int + figure DPI + + """ + # Set up matplotlib parameters + font = {"size": 16} + matplotlib.rc("font", **font) + matplotlib.rcParams["savefig.dpi"] = dpi + matplotlib.rcParams["figure.dpi"] = dpi + matplotlib.rcParams["figure.figsize"] = figsize + + # Create FootprintPlotter instance + plotter = FootprintPlotter( + nside_coverage=hsp_map.nside_coverage, + nside_map=hsp_map.nside_sparse + ) + + # Plot based on region or manual extent + if region is not None: + # Use predefined region + region_params = FootprintPlotter._regions[region] + plotter.plot_region( + hsp_map, + region_params, + outpath=output_file, + title=title, + colorbar=colorbar, + colorbar_label=colorbar_label + ) + else: + # Use manual RA/Dec limits + # Calculate ra_0 for projection + if ralo < rahi: + ra_0 = (ralo + rahi) / 2.0 + else: + ra_0 = 130.0 + + extend = [ralo, rahi, declo, dechi] + plotter.plot_area( + hsp_map, + ra_0=ra_0, + extend=extend, + vmin=vmin, + vmax=vmax, + outpath=output_file, + title=title, + colorbar=colorbar, + colorbar_label=colorbar_label + ) + + def run(self, args=None): + """Run. + + Main execution method. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code (0 for success) + + """ + if args is None: + args = sys.argv[1:] + + # Set parameters from command line + self.set_params_from_command_line(args) + self.update_params() + self.check_params() + + # Get parameters + input_file = self._params["input_file"] + output_file = self._params["output_file"] + region = self._params["region"] + ralo = self._params["ralo"] + rahi = self._params["rahi"] + declo = self._params["declo"] + dechi = self._params["dechi"] + vmin = self._params["vmin"] + vmax = self._params["vmax"] + colorbar = self._params["colorbar"] + colorbar_label = self._params["colorbar_label"] + figsize_x = self._params["figsize_x"] + figsize_y = self._params["figsize_y"] + dpi = self._params["dpi"] + verbose = self._params["verbose"] + + if verbose: + print(f"Reading coverage map from: {input_file}") + + # Load HealSparse map + hsp_map = hsp.HealSparseMap.read(input_file) + + print(f"Loaded map with nside={hsp_map.nside_sparse}") + print( + f"Valid pixels: {len(hsp_map.valid_pixels)} / {hsp_map.nside_sparse**2 * 12}" + ) + + if verbose: + if region: + print(f"Creating plot for region: {region}") + else: + print(f"Creating plot with extent: RA=[{ralo}, {rahi}], Dec=[{declo}, {dechi}]") + + # Create plot + self.plot_coverage_map( + hsp_map, + output_file, + region=region, + ralo=ralo, + rahi=rahi, + declo=declo, + dechi=dechi, + vmin=vmin, + vmax=vmax, + colorbar=colorbar, + colorbar_label=colorbar_label, + figsize=(figsize_x, figsize_y), + dpi=dpi, + ) + + print(f"Plot saved to {output_file}") + + return 0 diff --git a/src/shapepipe/utilities/field_corners_extractor.py b/src/shapepipe/utilities/field_corners_extractor.py new file mode 100644 index 000000000..7e3273456 --- /dev/null +++ b/src/shapepipe/utilities/field_corners_extractor.py @@ -0,0 +1,493 @@ +"""FIELD_CORNERS_EXTRACTOR + +Extract per-CCD sky-footprint corner coordinates from FITS headers. + +For each CCD (image HDU) in an exposure's multi-HDU header, the four corners +of the CCD are projected from pixel bounds to the sky, producing one row per +CCD keyed on its ``-`` ID. + +Author: Mike Hudson, Martin Kilbinger + +""" + +import sys +import os +import glob +import re +from os.path import exists +from multiprocessing import Pool, cpu_count + +import numpy as np +from astropy import wcs +from astropy.io.fits import Header + +from cs_util import args as cs_args +from cs_util import logging + +# Filename suffix convention for header files: <6+ digit exposure number>.txt +_EXPNUM_RE = re.compile(r'(\d+)\.txt$') + + +def _expnum_from_path(path): + """Extract exposure number from a header filename like ``1234567.txt``.""" + match = _EXPNUM_RE.search(path) + if match is None: + raise ValueError(f"Could not extract exposure number from {path!r}") + return int(match.group(1)) + + +def _image_shape(header): + """Return the CCD image ``(nx, ny)`` pixel shape from a header. + + For fpack tile-compressed HDUs (``ZIMAGE = T``), ``NAXIS1``/``NAXIS2`` + describe the compressed *binary table* (row width in bytes, row count), + not the image, and astropy's WCS never maps the true dimensions into + ``pixel_shape``. The true image size is carried by ``ZNAXIS1``/``ZNAXIS2``, + which are preferred here; a plain image HDU falls back to + ``NAXIS1``/``NAXIS2``. + + Parameters + ---------- + header : astropy.io.fits.Header + single-HDU header + + Returns + ------- + tuple + ``(nx, ny)`` image pixel dimensions + + Raises + ------ + ValueError + if neither the ``Z*`` nor the plain ``NAXIS`` dimensions are present + """ + if header.get("ZIMAGE", False): + keys = ("ZNAXIS1", "ZNAXIS2") + else: + keys = ("NAXIS1", "NAXIS2") + + if keys[0] not in header or keys[1] not in header: + raise ValueError( + f"Header is missing image dimensions ({keys[0]}/{keys[1]})" + ) + + return int(header[keys[0]]), int(header[keys[1]]) + + +def _parse_header_to_wcs(path): + """Parse a multi-HDU header text file into ``(wcs, (nx, ny))`` per HDU. + + The primary HDU is skipped. Each remaining HDU yields its WCS together with + the CCD image pixel shape (``ZNAXIS1/2`` for compressed HDUs, else + ``NAXIS1/2``); the shape is read from the header because the WCS drops the + ``Z*`` keywords. + """ + with open(path, "r") as f: + string = f.read() + tokens = re.split(r"^(END\s+)", string, flags=re.MULTILINE) + result = [] + for i in range(2, len(tokens) - 1, 2): + header = Header.fromstring(tokens[i] + tokens[i + 1], sep="\n") + result.append((wcs.WCS(header), _image_shape(header))) + return result + + +def _ccd_corners(w, shape): + """Return (ra, dec) of the 4 corners of a single CCD. + + The polygon is built from the CCD's pixel bounds ``shape = (nx, ny)`` using + pixel edges ``(-0.5, n - 0.5)`` so the quadrilateral covers the full CCD + area rather than pixel centres. Corners are returned in a consistent + counterclockwise pixel order (bottom-left, bottom-right, top-right, + top-left); since ``pixel_to_world`` maps this order to a simple, + non-self-intersecting sky quadrilateral for a CCD-sized field, and + HealSparse ``Polygon`` is orientation-agnostic, the resulting polygon is a + valid convex footprint. + + Parameters + ---------- + w : astropy.wcs.WCS + WCS of a single CCD + shape : tuple + ``(nx, ny)`` CCD image pixel dimensions + + Returns + ------- + tuple + ``(ra_list, dec_list)`` each of length 4, in degrees + """ + nx, ny = shape + + # Pixel-edge corners, counterclockwise: BL, BR, TR, TL + px = [-0.5, nx - 0.5, nx - 0.5, -0.5] + py = [-0.5, -0.5, ny - 0.5, ny - 0.5] + + sky = w.pixel_to_world(px, py) + return list(sky.ra.deg), list(sky.dec.deg) + + +class FieldCornersExtractor(object): + """Field Corners Extractor Class. + + Extracts RA/Dec coordinates of field corners from FITS headers. + """ + + def __init__(self): + """Initialize the extractor.""" + self.params_default() + + def params_default(self): + """Set default parameters and command line options.""" + + self._params = { + "input_dir": "header", + "output_file": "exp_ra_dec.txt", + "ccd_list": None, + "resume": False, + "n_processes": 1, + "verbose": False, + } + + self._short_options = { + "input_dir": "-i", + "output_file": "-o", + "ccd_list": "-l", + "resume": "-r", + "n_processes": "-n", + } + + self._types = { + "resume": "bool", + "n_processes": "int", + "verbose": "bool", + } + + self._help_strings = { + "input_dir": "input directory containing header files; default is {}", + "output_file": "output file for per-CCD corners; default is {}", + "ccd_list": "file of valid CCD IDs (output of get_ccds_with_psf); when given, only listed CCDs are written; on --resume, CCDs new to an expanded list are added without duplicating existing rows; default is all CCDs", + "resume": "resume from existing output file; default is {}", + "n_processes": f"number of parallel processes (1=serial, 0=auto={cpu_count()}); default is {{}}", + } + + def set_params_from_command_line(self, args): + """Set Params From Command line. + + Only use when calling using python from command line. + Does not work from ipython or jupyter. + + Parameters + ---------- + args : list + command line arguments + + """ + # Read command line options + options = cs_args.parse_options( + self._params, + self._short_options, + self._types, + self._help_strings, + args=args, + ) + self._params = options + + # Save calling command + logging.log_command(args) + + def update_params(self): + """Update parameters. + + Set derived parameters based on input parameters. + """ + # Ensure input directory ends without trailing slash + if self._params["input_dir"].endswith("/"): + self._params["input_dir"] = self._params["input_dir"][:-1] + + def check_params(self): + """Check parameters for validity.""" + if not exists(self._params["input_dir"]): + raise FileNotFoundError( + f"Input directory not found: {self._params['input_dir']}" + ) + + if self._params["ccd_list"] is not None and not exists( + self._params["ccd_list"] + ): + raise FileNotFoundError( + f"CCD list file not found: {self._params['ccd_list']}" + ) + + # Set n_processes to cpu_count if 0 + if self._params["n_processes"] == 0: + self._params["n_processes"] = cpu_count() + + if self._params["n_processes"] < 0: + raise ValueError( + f"n_processes must be >= 0, got {self._params['n_processes']}" + ) + + @staticmethod + def load_ccd_list(path): + """Load CCD List. + + Read valid CCD IDs (one ``-`` per line) into a set. + + Parameters + ---------- + path : str + path to the CCD list file + + Returns + ------- + set + valid CCD IDs + + """ + with open(path) as f: + return {line.strip() for line in f if line.strip()} + + @staticmethod + def process_single_header(args): + """Process Single Header. + + Worker function to process a single header file into per-CCD corners. + Static method so it can be pickled for multiprocessing. + + Parameters + ---------- + args : tuple + ``(path, verbose, valid_ccds)`` where ``path`` is the header file + path, ``verbose`` is a bool, and ``valid_ccds`` is a set of CCD IDs + to keep (or ``None`` to keep all) + + Returns + ------- + list or None + list of ``(ccd_id, ra_list, dec_list)`` for the exposure's CCDs on + success, ``None`` on failure + + """ + path, verbose, valid_ccds = args + expnum = _expnum_from_path(path) + + try: + wcs_shapes = _parse_header_to_wcs(path) + except Exception as e: + if verbose: + print(f"Failed to process {expnum}: {e}") + return None + + rows = [] + for ccd_idx, (w, shape) in enumerate(wcs_shapes): + ccd_id = f"{expnum}-{ccd_idx}" + if valid_ccds is not None and ccd_id not in valid_ccds: + continue + try: + ra, dec = _ccd_corners(w, shape) + except Exception as e: + if verbose: + print(f"Failed to process CCD {ccd_id}: {e}") + continue + rows.append((ccd_id, ra, dec)) + + return rows + + def get_done_ccds(self): + """Get Done CCDs. + + Read the set of CCD IDs already present in the output file. Resume is + keyed on individual CCD IDs, not exposure numbers: a CCD counts as done + only if its own row is present. This keeps resume correct when a write + was interrupted mid-exposure (the missing CCDs are filled in) and when + a rerun uses an expanded ``--ccd_list`` (the newly requested CCDs are + added), and it never duplicates a row. + + Returns + ------- + set + CCD IDs already written + + """ + output_file = self._params["output_file"] + + if not exists(output_file): + return set() + + try: + ids = np.atleast_1d( + np.loadtxt(output_file, usecols=(0), dtype=str) + ) + return set(ids.tolist()) + except Exception as e: + if self._params["verbose"]: + print(f"Could not read existing output file: {e}") + return set() + + def run(self, args=None): + """Run. + + Main execution method. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code (0 for success) + + """ + if args is None: + args = sys.argv[1:] + + # Set parameters from command line + self.set_params_from_command_line(args) + self.update_params() + self.check_params() + + # Get parameters + input_dir = self._params["input_dir"] + output_file = self._params["output_file"] + resume = self._params["resume"] + verbose = self._params["verbose"] + + # Load the set of valid CCD IDs to keep, if given + valid_ccds = None + if self._params["ccd_list"] is not None: + valid_ccds = self.load_ccd_list(self._params["ccd_list"]) + print(f"{len(valid_ccds)} valid CCDs in {self._params['ccd_list']}") + + # Find all header files + paths = glob.glob(f"{input_dir}/*.txt") + n = len(paths) + + if n == 0: + print(f"No header files found in {input_dir}/") + return 1 + + print(f"{n} header files found") + + # On resume, read the CCD IDs already written so we can skip them + # per-CCD (not per-exposure): a partially written exposure is completed + # rather than skipped, and no row is ever duplicated. + done_ccds = set() + if resume: + done_ccds = self.get_done_ccds() + print(f"{len(done_ccds)} CCDs already done") + + # Every header still needs parsing (a header may hold both done and + # not-yet-done CCDs); the per-CCD filter below decides what is written. + todo = paths + + # Get n_processes + n_processes = self._params["n_processes"] + + # Process headers + if n_processes == 1: + # Serial processing + results = self._process_serial(todo, verbose, valid_ccds) + else: + # Parallel processing + print(f"Using {n_processes} parallel processes") + results = self._process_parallel( + todo, n_processes, verbose, valid_ccds + ) + + # Flatten per-exposure CCD lists (dropping failed exposures), then drop + # CCDs already present in the output. + rows = [ + row + for res in results if res is not None + for row in res + if row[0] not in done_ccds + ] + + # Sort by exposure number, then CCD index + rows.sort(key=lambda r: (int(r[0].split("-")[0]), + int(r[0].split("-")[1]))) + + # Write results to file: " ra1 ra2 ra3 ra4 dec1 dec2 dec3 dec4" + mode = "a" if resume else "w" + with open(output_file, mode, buffering=1) as f: + for ccd_id, ra, dec in rows: + f.write(f"{ccd_id} ") + np.savetxt(f, ra, fmt="%9.5f", newline=" ") + np.savetxt(f, dec, fmt="%9.5f", newline=" ") + f.write("\n") + + n_exp_success = sum(1 for res in results if res is not None) + n_failed = len(todo) - n_exp_success + + print(f"Processed {n_exp_success} exposures, {len(rows)} new CCDs") + if n_failed > 0: + print(f"Failed to process {n_failed} exposures") + + print(f"Results written to {output_file}") + + return 0 + + def _process_serial(self, todo, verbose, valid_ccds): + """Process Serial. + + Process headers serially. + + Parameters + ---------- + todo : list + list of header file paths to process + verbose : bool + verbose output + valid_ccds : set or None + CCD IDs to keep, or ``None`` to keep all + + Returns + ------- + list + list of per-exposure CCD-row lists (or ``None`` for failures) + + """ + results = [] + n_todo = len(todo) + + for i, p in enumerate(todo): + result = self.process_single_header((p, verbose, valid_ccds)) + results.append(result) + + if verbose and i % 100 == 0: + print(f"{i:6d} / {n_todo:6d}") + + return results + + def _process_parallel(self, todo, n_processes, verbose, valid_ccds): + """Process Parallel. + + Process headers in parallel using multiprocessing. + + Parameters + ---------- + todo : list + list of header file paths to process + n_processes : int + number of parallel processes + verbose : bool + verbose output + valid_ccds : set or None + CCD IDs to keep, or ``None`` to keep all + + Returns + ------- + list + list of per-exposure CCD-row lists (or ``None`` for failures) + + """ + # Prepare arguments for worker function + args = [(p, verbose, valid_ccds) for p in todo] + + # Create pool and process + with Pool(processes=n_processes) as pool: + results = pool.map(self.process_single_header, args) + + return results diff --git a/src/shapepipe/utilities/header_downloader.py b/src/shapepipe/utilities/header_downloader.py new file mode 100644 index 000000000..122327a58 --- /dev/null +++ b/src/shapepipe/utilities/header_downloader.py @@ -0,0 +1,291 @@ +"""HEADER_DOWNLOADER + +Download FITS headers from VOSpace for exposures listed in a CCD file. + +Author: Mike Hudson, Martin Kilbinger + +""" + +import sys +import os +from os.path import exists + +import numpy as np +import vos + +from cs_util import args as cs_args +from cs_util import logging + + +class HeaderDownloader(object): + """Header Downloader Class. + + Downloads FITS headers from VOSpace for exposures in a CCD list. + """ + + def __init__(self): + """Initialize the downloader.""" + self.params_default() + + def params_default(self): + """Set default parameters and command line options.""" + + self._params = { + "input_file": None, + "output_dir": "header", + "vospace_path": "vos:cfis/pitcairn", # UNIONS/CFIS default; override for other surveys + "overwrite": False, + "dir_for_links": None, + "verbose": False, + } + + self._short_options = { + "input_file": "-i", + "output_dir": "-o", + "vospace_path": "-p", + "overwrite": "-O", + "dir_for_links": "-d", + } + + self._types = { + "overwrite": "bool", + "verbose": "bool", + } + + self._help_strings = { + "input_file": "input CCD list file (txt or csv); required", + "output_dir": "output directory for headers; default is {}", + "vospace_path": "VOSpace base path; default is {}", + "overwrite": "overwrite existing header files; default is {}", + "dir_for_links": "directory to check for existing headers to link instead of download; default is {}", + } + + def set_params_from_command_line(self, args): + """Set Params From Command line. + + Only use when calling using python from command line. + Does not work from ipython or jupyter. + + Parameters + ---------- + args : list + command line arguments + + """ + # Read command line options + options = cs_args.parse_options( + self._params, + self._short_options, + self._types, + self._help_strings, + args=args, + ) + self._params = options + + # Save calling command + logging.log_command(args) + + def update_params(self): + """Update parameters. + + Set derived parameters based on input parameters. + """ + # Ensure output directory ends without trailing slash + if self._params["output_dir"].endswith("/"): + self._params["output_dir"] = self._params["output_dir"][:-1] + + def check_params(self): + """Check parameters for validity.""" + if self._params["input_file"] is None: + raise ValueError("Input file is required (use -i or --input_file)") + + if not exists(self._params["input_file"]): + raise FileNotFoundError( + f"Input file not found: {self._params['input_file']}" + ) + + # Check if dir_for_links exists if specified + if self._params["dir_for_links"] is not None: + if not exists(self._params["dir_for_links"]): + raise FileNotFoundError( + f"Directory for links not found: {self._params['dir_for_links']}" + ) + if not os.path.isdir(self._params["dir_for_links"]): + raise ValueError( + f"Path is not a directory: {self._params['dir_for_links']}" + ) + + # Create output directory if it doesn't exist + if not exists(self._params["output_dir"]): + os.makedirs(self._params["output_dir"]) + if self._params["verbose"]: + print(f"Created output directory: {self._params['output_dir']}") + + def get_exposures(self, ccd_list_file): + """Get Exposures. + + Extract unique exposure numbers from CCD list file. + + Parameters + ---------- + ccd_list_file : str + path to CCD list file (txt or csv) + + Returns + ------- + np.array + unique exposure numbers + + """ + exps = [] + + # Check if CSV format + if ccd_list_file.endswith(".csv"): + import astropy.table + + t = astropy.table.Table.read(ccd_list_file) + expccd = t["CCD"].data + r = np.char.split(expccd, sep="-") + + for i, r1 in enumerate(r): + exp = int(r1[0]) + exps.append(exp) + else: + # Text format; atleast_1d so a single-line file (0-d array) is + # still iterable. + f = np.atleast_1d( + np.loadtxt(ccd_list_file, dtype="str", encoding="ascii") + ) + r = np.char.split(f, sep="-") + + for i, r1 in enumerate(r): + exp = int(r1[0]) + exps.append(exp) + + exps = np.array(exps) + uniq = np.unique(exps) + + return uniq + + def get_fits_header(self, expnum, client): + """Get FITS Header. + + Download FITS header from VOSpace, or create a symbolic link + if the header exists in dir_for_links. + + Parameters + ---------- + expnum : int + exposure number + client : vos.Client + VOSpace client + + Returns + ------- + bool + True if successful, False otherwise + + """ + vospace_path = self._params["vospace_path"] + output_dir = self._params["output_dir"] + overwrite = self._params["overwrite"] + dir_for_links = self._params["dir_for_links"] + + source = f"{vospace_path}/{expnum:d}p.fits.fz" + dest = f"{output_dir}/{expnum:d}.txt" + + if exists(dest) and not overwrite: + return True + + # Check if header exists in dir_for_links + if dir_for_links is not None: + link_source = os.path.abspath(f"{dir_for_links}/{expnum:d}.txt") + if exists(link_source): + try: + # Remove existing file/link if overwrite is True + if exists(dest): + os.remove(dest) + # Create symbolic link + os.symlink(link_source, dest) + return True + except Exception as e: + print(f"Could not create symlink from {link_source}: {e}") + # Fall through to download if symlink fails + + # Download from VOSpace atomically: copy to a temp file in the same + # directory, then rename on success. An interrupted transfer leaves + # only the temp file behind, so resume never treats a partial download + # as complete. + tmp_dest = f"{dest}.part" + try: + client.copy(source, tmp_dest, head=True) + os.rename(tmp_dest, dest) + return True + except Exception as e: + print(f"Could not copy {source}: {e}") + if exists(tmp_dest): + os.remove(tmp_dest) + return False + + def run(self, args=None): + """Run. + + Main execution method. + + Parameters + ---------- + args : list, optional + command line arguments + + Returns + ------- + int + exit code (0 for success) + + """ + if args is None: + args = sys.argv[1:] + + # Set parameters from command line + self.set_params_from_command_line(args) + self.update_params() + self.check_params() + + # Get parameters + input_file = self._params["input_file"] + output_dir = self._params["output_dir"] + verbose = self._params["verbose"] + + if verbose: + print(f"Reading CCD list from: {input_file}") + + # Extract unique exposures + exps = self.get_exposures(input_file) + + print(f"Found {len(exps)} unique exposures") + + if verbose: + print(f"Downloading headers to: {output_dir}/") + + # Initialize VOSpace client once + client = vos.Client() + + # Download headers + n_success = 0 + n_failed = 0 + + for i, exp in enumerate(exps): + success = self.get_fits_header(exp, client) + if success: + n_success += 1 + else: + n_failed += 1 + + if verbose and i % 100 == 0: + print(f"{i:6d} / {len(exps):6d}") + + print(f"Downloaded {n_success} headers") + if n_failed > 0: + print(f"Failed to download {n_failed} headers") + + return 0 diff --git a/tests/module/test_coverage.py b/tests/module/test_coverage.py new file mode 100644 index 000000000..7f308b928 --- /dev/null +++ b/tests/module/test_coverage.py @@ -0,0 +1,691 @@ +"""UNIT / PROPERTY TESTS FOR THE COVERAGE-MASK FEATURE. + +Covers the pure and lightly-fixtured logic behind the per-CCD coverage nexp +masks: exposure-number parsing, the CCD-list -> unique-exposure reduction, the +handler's missing-CCD subtraction (pinned against the real ID format), per-CCD +corner extraction from plain and fpack-compressed multi-HDU headers, +``--ccd_list`` filtering, the per-CCD resume path, the builder's per-CCD row +parsing, the accumulated exposure-count (nexp) contract, the RA-wrap and pole +guards, the power-of-two ``nside`` validation, the atomic-download rename, and +the ``-h``/argv handling of the console runners. The heavy paths (real VOSpace +download, production-resolution map building, plotting, multiprocessing) are +exercised end to end by the pipeline, not here. +""" + +import sys +from types import SimpleNamespace + +import numpy as np +import numpy.testing as npt +import pytest +from astropy import wcs +from hypothesis import given +from hypothesis import strategies as st + +from shapepipe.utilities import summary +from shapepipe.utilities.ccd_psf_handler import CcdPsfHandler +from shapepipe.utilities.coverage_map_builder import ( + CoverageMapBuilder, + unwrap_ra, +) +from shapepipe.utilities.field_corners_extractor import ( + FieldCornersExtractor, + _ccd_corners, + _expnum_from_path, + _image_shape, + _parse_header_to_wcs, +) +from shapepipe.utilities.header_downloader import HeaderDownloader + + +def _tan_wcs(crval_ra, crval_dec=0.0, nx=2080, ny=4612): + """Build a minimal 2-D TAN WCS with a populated pixel shape. + + ``nx``/``ny`` set ``pixel_shape`` so ``_header_text`` can emit matching + ``NAXIS1``/``NAXIS2`` keywords. + """ + w = wcs.WCS(naxis=2) + w.wcs.ctype = ["RA---TAN", "DEC--TAN"] + w.wcs.crval = [crval_ra, crval_dec] + w.wcs.crpix = [nx / 2.0, ny / 2.0] + w.wcs.cd = [[-1e-5, 0.0], [0.0, 1e-5]] + w.pixel_shape = (nx, ny) + return w + + +def _header_text(wcs_list): + """Render a multi-HDU header text file: a primary HDU then one plain image + HDU per WCS, each carrying ``NAXIS1``/``NAXIS2``. + """ + blocks = ["SIMPLE = T"] + for w in wcs_list: + nx, ny = w.pixel_shape + header = w.to_header() + header["NAXIS"] = 2 + header["NAXIS1"] = nx + header["NAXIS2"] = ny + blocks.append("\n".join(str(card) for card in header.cards)) + return "".join(f"{block}\nEND \n" for block in blocks) + + +def _compressed_header_text(w): + """Render a one-CCD header mimicking an fpack tile-compressed HDU. + + ``NAXIS1``/``NAXIS2`` describe the compressed binary table (byte width, row + count); the true image dimensions live in ``ZNAXIS1``/``ZNAXIS2``. This is + the shape of headers fetched from 'p.fits.fz' with ``head=True``. + """ + nx, ny = w.pixel_shape + wcs_cards = "\n".join(str(card) for card in w.to_header().cards) + ccd = ( + "XTENSION= 'BINTABLE'\n" + "BITPIX = 8\n" + "NAXIS = 2\n" + "NAXIS1 = 8\n" + f"NAXIS2 = {ny}\n" + "PCOUNT = 1000000\n" + "GCOUNT = 1\n" + "TFIELDS = 1\n" + "ZIMAGE = T\n" + "ZBITPIX = -32\n" + "ZNAXIS = 2\n" + f"ZNAXIS1 = {nx}\n" + f"ZNAXIS2 = {ny}\n" + f"{wcs_cards}" + ) + return f"SIMPLE = T\nEND \n{ccd}\nEND \n" + + +# --------------------------------------------------------------------------- +# _expnum_from_path +# --------------------------------------------------------------------------- + +@pytest.mark.parametrize( + "path, expected", + [ + ("1234567.txt", 1234567), + ("/a/b/2143523.txt", 2143523), + ("headers/0000042.txt", 42), + ], +) +def test_expnum_from_path_extracts_trailing_number(path, expected): + """The trailing ``.txt`` is parsed as the exposure number.""" + assert _expnum_from_path(path) == expected + + +@pytest.mark.parametrize("path", ["no_number.txt", "1234567.fits", "abc.txt"]) +def test_expnum_from_path_raises_without_number(path): + """A filename without a trailing numeric stem raises ``ValueError``.""" + with pytest.raises(ValueError): + _expnum_from_path(path) + + +@given(st.integers(min_value=0, max_value=99999999)) +def test_expnum_from_path_roundtrips(expnum): + """Any exposure number round-trips through the filename convention.""" + assert _expnum_from_path(f"vos_headers/{expnum}.txt") == expnum + + +# --------------------------------------------------------------------------- +# HeaderDownloader.get_exposures +# --------------------------------------------------------------------------- + +def test_get_exposures_reduces_to_unique_exposures(tmp_path): + """A ``-`` CCD list collapses to its unique exposure numbers.""" + ccd_list = tmp_path / "ccds.txt" + ccd_list.write_text("2143523-0\n2143523-5\n2143524-3\n2143524-8\n") + + exps = HeaderDownloader().get_exposures(str(ccd_list)) + + npt.assert_array_equal(exps, np.array([2143523, 2143524])) + + +def test_get_exposures_single_line(tmp_path): + """A one-line CCD list (0-d loadtxt array) still yields one exposure.""" + ccd_list = tmp_path / "ccds.txt" + ccd_list.write_text("2143523-0\n") + + exps = HeaderDownloader().get_exposures(str(ccd_list)) + + npt.assert_array_equal(exps, np.array([2143523])) + + +def test_get_exposures_csv_matches_txt(tmp_path): + """The CSV and text code paths yield the same unique exposures.""" + txt = tmp_path / "ccds.txt" + txt.write_text("2143523-0\n2143523-5\n2143524-3\n") + csv = tmp_path / "ccds.csv" + csv.write_text("CCD\n2143523-0\n2143523-5\n2143524-3\n") + + dl = HeaderDownloader() + + npt.assert_array_equal( + dl.get_exposures(str(txt)), dl.get_exposures(str(csv)) + ) + + +# --------------------------------------------------------------------------- +# HeaderDownloader.get_fits_header — atomic rename +# --------------------------------------------------------------------------- + +def test_get_fits_header_writes_atomically(tmp_path): + """A successful download copies to ``.part`` then renames to the dest.""" + dl = HeaderDownloader() + dl._params["output_dir"] = str(tmp_path) + dl._params["overwrite"] = False + dl._params["dir_for_links"] = None + + dest = tmp_path / "42.txt" + tmp_dest = tmp_path / "42.txt.part" + + def fake_copy(source, target, head=True): + # The copy must land on the temp path, not the final destination. + assert target == str(tmp_dest) + with open(target, "w") as f: + f.write("HEADER") + + client = SimpleNamespace(copy=fake_copy) + + assert dl.get_fits_header(42, client) is True + assert dest.exists() + assert not tmp_dest.exists() + assert dest.read_text() == "HEADER" + + +def test_get_fits_header_failed_copy_leaves_no_dest(tmp_path): + """A failed download leaves no destination file (only, if any, ``.part``).""" + dl = HeaderDownloader() + dl._params["output_dir"] = str(tmp_path) + dl._params["overwrite"] = False + dl._params["dir_for_links"] = None + + def failing_copy(source, target, head=True): + raise RuntimeError("transfer interrupted") + + client = SimpleNamespace(copy=failing_copy) + + assert dl.get_fits_header(42, client) is False + assert not (tmp_path / "42.txt").exists() + assert not (tmp_path / "42.txt.part").exists() + + +# --------------------------------------------------------------------------- +# CcdPsfHandler.get_ccds_with_psf — missing-CCD subtraction +# --------------------------------------------------------------------------- + +def test_get_ccds_with_psf_subtracts_missing(monkeypatch): + """Valid CCDs are all exposure single-HDUs minus the missing set. + + The real ``summary.get_all_shdus`` is used so the cross-component + ``-`` ID format is pinned end to end. + """ + handler = CcdPsfHandler() + + # Two exposures, 3 CCDs each -> 6 candidate CCDs; two are missing. + monkeypatch.setattr(handler, "get_exp", lambda patches: {"100", "200"}) + monkeypatch.setattr( + handler, + "get_exp_shdu_missing", + lambda patches: {"100-1", "200-2"}, + ) + + result = handler.get_ccds_with_psf(["P1"], n_CCD=3) + + # get_all_shdus yields "-" for ccd in range(n_CCD). + assert result == {"100-0", "100-2", "200-0", "200-1"} + # Guard the assumption that the missing IDs share the produced format. + assert set(summary.get_all_shdus({"100"}, 3)) == {"100-0", "100-1", "100-2"} + + +@pytest.mark.parametrize( + ("version", "n_patch"), + [("v1.3", 7), ("v1.4", 7), ("v1.5", 8), ("v1.6", 9)], +) +def test_version_to_patch_count(version, n_patch): + """Each v1.x catalogue version maps to its patch count.""" + handler = CcdPsfHandler() + handler._params["version_cat"] = version + handler.update_params() + assert handler._params["n_patch"] == n_patch + assert len(handler._params["patches"]) == n_patch + + +def test_v2_is_patchless(): + """v2.0 removes the patch concept: summaries read from the run root.""" + handler = CcdPsfHandler() + handler._params["version_cat"] = "v2.0" + handler.update_params() + assert handler._params["patches"] == ["."] + + +def test_n_patch_option_overrides_version_default(): + """An explicit -p patch count wins over the version mapping.""" + handler = CcdPsfHandler() + handler._params["version_cat"] = "v2.0" + handler._params["n_patch"] = 4 + handler.update_params() + assert handler._params["patches"] == ["P1", "P2", "P3", "P4"] + + +def test_invalid_version_raises(): + """An unknown catalogue version fails loudly.""" + handler = CcdPsfHandler() + handler._params["version_cat"] = "v9.9" + with pytest.raises(ValueError, match="v9.9"): + handler.update_params() + + +# --------------------------------------------------------------------------- +# image-shape resolution (fpack ZNAXIS vs plain NAXIS) +# --------------------------------------------------------------------------- + +def test_image_shape_prefers_znaxis_for_compressed_header(tmp_path): + """A compressed HDU reports ZNAXIS dims, not the binary-table NAXIS.""" + w = _tan_wcs(100.0, 20.0, nx=2080, ny=4612) + path = tmp_path / "1234567.txt" + path.write_text(_compressed_header_text(w)) + + (parsed_w, shape), = _parse_header_to_wcs(str(path)) + + # WCS pixel_shape would wrongly report the compressed byte width (8). + assert parsed_w.pixel_shape == (8, 4612) + # _image_shape recovers the true image dimensions. + assert shape == (2080, 4612) + + +def test_image_shape_falls_back_to_naxis(tmp_path): + """A plain image HDU (no ZIMAGE) uses NAXIS1/NAXIS2.""" + w = _tan_wcs(100.0, 20.0, nx=2080, ny=4612) + path = tmp_path / "1234567.txt" + path.write_text(_header_text([w])) + + (_, shape), = _parse_header_to_wcs(str(path)) + + assert shape == (2080, 4612) + + +def test_image_shape_raises_without_dimensions(): + """A header with no image dimensions raises a clear ``ValueError``.""" + from astropy.io.fits import Header + + header = Header() + header["CTYPE1"] = "RA---TAN" + + with pytest.raises(ValueError, match="image dimensions"): + _image_shape(header) + + +def test_compressed_header_corners_are_full_width(tmp_path): + """Corners from a compressed header span the true CCD width, not 8 px.""" + w = _tan_wcs(100.0, 20.0, nx=2080, ny=4612) + path = tmp_path / "1234567.txt" + path.write_text(_compressed_header_text(w)) + + (parsed_w, shape), = _parse_header_to_wcs(str(path)) + ra, dec = _ccd_corners(parsed_w, shape) + + # RA extent must reflect ~2080 px * 1e-5 deg/px * cos(dec), not 8 px. + ra_extent = max(ra) - min(ra) + assert ra_extent > 0.01 + + +# --------------------------------------------------------------------------- +# per-CCD corner geometry +# --------------------------------------------------------------------------- + +def test_ccd_corners_returns_four_corners_around_centre(): + """The 4 corners bracket the CCD centre and cover its full pixel extent.""" + nx, ny = 2080, 4612 + w = _tan_wcs(100.0, 20.0, nx=nx, ny=ny) + + ra, dec = _ccd_corners(w, (nx, ny)) + + assert len(ra) == 4 + assert len(dec) == 4 + # The footprint straddles the CRVAL centre in both coordinates. + assert min(ra) < 100.0 < max(ra) + assert min(dec) < 20.0 < max(dec) + # Extent matches the CD-scaled pixel span (with edge padding). + npt.assert_allclose(max(dec) - min(dec), ny * 1e-5, rtol=1e-3) + + +def test_parse_header_to_wcs_returns_one_wcs_per_extension(tmp_path): + """One (wcs, shape) pair is returned per CCD HDU; the primary is skipped.""" + ccd_wcs = [_tan_wcs(10.0), _tan_wcs(20.0), _tan_wcs(30.0)] + + path = tmp_path / "1234567.txt" + path.write_text(_header_text(ccd_wcs)) + + result = _parse_header_to_wcs(str(path)) + + assert len(result) == len(ccd_wcs) + assert all(shape == (2080, 4612) for _, shape in result) + + +# --------------------------------------------------------------------------- +# FieldCornersExtractor.process_single_header — per-CCD rows and filtering +# --------------------------------------------------------------------------- + +def test_process_single_header_emits_one_row_per_ccd(tmp_path): + """Without a CCD list, every HDU yields a ``-`` row.""" + ccd_wcs = [_tan_wcs(10.0), _tan_wcs(20.0), _tan_wcs(30.0)] + path = tmp_path / "1234567.txt" + path.write_text(_header_text(ccd_wcs)) + + rows = FieldCornersExtractor.process_single_header( + (str(path), False, None) + ) + + assert [r[0] for r in rows] == [ + "1234567-0", + "1234567-1", + "1234567-2", + ] + assert all(len(r[1]) == 4 and len(r[2]) == 4 for r in rows) + + +def test_process_single_header_filters_to_ccd_list(tmp_path): + """A ``valid_ccds`` set keeps only the listed CCDs of the exposure.""" + ccd_wcs = [_tan_wcs(10.0), _tan_wcs(20.0), _tan_wcs(30.0)] + path = tmp_path / "1234567.txt" + path.write_text(_header_text(ccd_wcs)) + + rows = FieldCornersExtractor.process_single_header( + (str(path), False, {"1234567-0", "1234567-2"}) + ) + + assert [r[0] for r in rows] == ["1234567-0", "1234567-2"] + + +def test_load_ccd_list_reads_ids(tmp_path): + """The CCD list loader returns the stripped, non-blank IDs as a set.""" + path = tmp_path / "ccds.txt" + path.write_text("100-0\n100-1\n\n200-5\n") + + assert FieldCornersExtractor.load_ccd_list(str(path)) == { + "100-0", + "100-1", + "200-5", + } + + +def test_run_extract_writes_per_ccd_rows(tmp_path): + """End to end: run() writes one per-CCD row filtered by the CCD list.""" + header_dir = tmp_path / "headers" + header_dir.mkdir() + ccd_wcs = [_tan_wcs(10.0), _tan_wcs(20.0), _tan_wcs(30.0)] + (header_dir / "1234567.txt").write_text(_header_text(ccd_wcs)) + + ccd_list = tmp_path / "ccds.txt" + ccd_list.write_text("1234567-0\n1234567-2\n") + + out = tmp_path / "corners.txt" + + extractor = FieldCornersExtractor() + extractor.run( + args=[ + "-i", str(header_dir), + "-l", str(ccd_list), + "-o", str(out), + ] + ) + + lines = out.read_text().splitlines() + assert len(lines) == 2 + ids = [line.split()[0] for line in lines] + assert ids == ["1234567-0", "1234567-2"] + # Each row: 1 ID + 4 RA + 4 Dec = 9 columns. + assert all(len(line.split()) == 9 for line in lines) + + +# --------------------------------------------------------------------------- +# FieldCornersExtractor resume path (per-CCD done-set) +# --------------------------------------------------------------------------- + +def _write_headers(header_dir, expnums, n_ccd=3): + """Write one plain multi-HDU header per exposure into ``header_dir``.""" + header_dir.mkdir(exist_ok=True) + for j, expnum in enumerate(expnums): + ccd_wcs = [_tan_wcs(10.0 + j + i) for i in range(n_ccd)] + (header_dir / f"{expnum}.txt").write_text(_header_text(ccd_wcs)) + + +def test_get_done_ccds_reads_present_ids(tmp_path): + """The done-set is the exact set of CCD IDs already in the output.""" + out = tmp_path / "corners.txt" + out.write_text( + "1234567-0 10 10 10 10 20 20 20 20\n" + "1234567-2 10 10 10 10 20 20 20 20\n" + ) + + extractor = FieldCornersExtractor() + extractor._params["output_file"] = str(out) + + assert extractor.get_done_ccds() == {"1234567-0", "1234567-2"} + + +def test_resume_adds_new_exposure_without_duplicating(tmp_path): + """Resume appends a new exposure and leaves existing rows untouched.""" + header_dir = tmp_path / "headers" + _write_headers(header_dir, [1000001, 1000002]) + + out = tmp_path / "corners.txt" + # Pre-populate with exposure 1's three CCD rows. + pre = FieldCornersExtractor().process_single_header( + (str(header_dir / "1000001.txt"), False, None) + ) + with open(out, "w") as f: + for ccd_id, ra, dec in pre: + f.write(f"{ccd_id} " + " ".join(f"{v:.5f}" for v in ra + dec) + "\n") + + FieldCornersExtractor().run( + args=["-i", str(header_dir), "-o", str(out), "-r"] + ) + + ids = [line.split()[0] for line in out.read_text().splitlines()] + # No duplicate exposure-1 rows; exposure 2's three CCDs added. + assert ids.count("1000001-0") == 1 + assert sorted(ids) == [ + "1000001-0", "1000001-1", "1000001-2", + "1000002-0", "1000002-1", "1000002-2", + ] + + +def test_resume_completes_partial_exposure(tmp_path): + """An exposure interrupted mid-write is completed, not skipped.""" + header_dir = tmp_path / "headers" + _write_headers(header_dir, [1000001]) + + out = tmp_path / "corners.txt" + # Simulate an interrupt: only the first of exposure 1's CCDs was written. + pre = FieldCornersExtractor().process_single_header( + (str(header_dir / "1000001.txt"), False, None) + ) + ccd_id, ra, dec = pre[0] + with open(out, "w") as f: + f.write(f"{ccd_id} " + " ".join(f"{v:.5f}" for v in ra + dec) + "\n") + + FieldCornersExtractor().run( + args=["-i", str(header_dir), "-o", str(out), "-r"] + ) + + ids = [line.split()[0] for line in out.read_text().splitlines()] + # The missing CCDs are filled in; the present one is not duplicated. + assert sorted(ids) == ["1000001-0", "1000001-1", "1000001-2"] + assert ids.count("1000001-0") == 1 + + +# --------------------------------------------------------------------------- +# CoverageMapBuilder.check_params (nside validation) +# --------------------------------------------------------------------------- + +def test_check_params_accepts_power_of_two_nside(tmp_path): + """Powers of two for both nside values pass validation.""" + infile = tmp_path / "corners.txt" + infile.write_text("100-0 0.0 1.0 1.0 0.0 0.0 0.0 1.0 1.0\n") + + builder = CoverageMapBuilder() + builder._params["input_file"] = str(infile) + builder._params["nside_coverage"] = 32 + builder._params["nside"] = 2048 + + builder.check_params() + + +@pytest.mark.parametrize( + "nside_coverage, nside", [(33, 2048), (32, 100), (32, 3000)] +) +def test_check_params_rejects_non_power_of_two_nside( + tmp_path, nside_coverage, nside +): + """A non-power-of-two nside raises ``ValueError``.""" + infile = tmp_path / "corners.txt" + infile.write_text("100-0 0.0 1.0 1.0 0.0 0.0 0.0 1.0 1.0\n") + + builder = CoverageMapBuilder() + builder._params["input_file"] = str(infile) + builder._params["nside_coverage"] = nside_coverage + builder._params["nside"] = nside + + with pytest.raises(ValueError): + builder.check_params() + + +def test_check_params_missing_input_file_raises(tmp_path): + """A missing input file raises ``FileNotFoundError``.""" + builder = CoverageMapBuilder() + builder._params["input_file"] = str(tmp_path / "does_not_exist.txt") + + with pytest.raises(FileNotFoundError): + builder.check_params() + + +# --------------------------------------------------------------------------- +# CoverageMapBuilder — parsing, nexp contract, RA-wrap and pole guards +# --------------------------------------------------------------------------- + +def _read_map(path): + import healsparse as hsp + + return hsp.HealSparseMap.read(str(path)) + + +def test_build_map_single_row(tmp_path): + """A one-row corners file exercises the atleast_1d/2d parsing guards.""" + infile = tmp_path / "corners.txt" + infile.write_text("100-0 9.9 10.1 10.1 9.9 0.0 0.0 0.2 0.2\n") + out = tmp_path / "cov.hsp" + + CoverageMapBuilder().run( + args=["-i", str(infile), "-o", str(out), "-c", "32", "-n", "1024"] + ) + + m = _read_map(out) + assert 0 < len(m.valid_pixels) < 200 + assert m[m.valid_pixels].max() == 1 + + +def test_build_map_nexp_counts_overlapping_exposures(tmp_path): + """Two overlapping CCDs from different exposures give value 2 in overlap.""" + # Two 0.4x0.4 deg CCDs offset by 0.2 deg in RA -> a central overlap strip. + infile = tmp_path / "corners.txt" + infile.write_text( + "100-0 9.8 10.2 10.2 9.8 19.8 19.8 20.2 20.2\n" + "200-0 10.0 10.4 10.4 10.0 19.8 19.8 20.2 20.2\n" + ) + out = tmp_path / "cov.hsp" + + CoverageMapBuilder().run( + args=["-i", str(infile), "-o", str(out), "-c", "32", "-n", "1024"] + ) + + m = _read_map(out) + values = m[m.valid_pixels] + # The overlap is covered by both exposures (value 2); the union edges by + # one (value 1). Both must be present; nothing exceeds 2. + assert values.max() == 2 + assert values.min() == 1 + assert (values == 2).sum() > 0 + assert (values == 1).sum() > 0 + + +def test_unwrap_ra_shifts_straddling_seam(): + """A polygon straddling RA=0 is put on a common (negative) branch.""" + out = unwrap_ra([359.9, 0.1, 0.1, 359.9]) + npt.assert_allclose(sorted(out), [-0.1, -0.1, 0.1, 0.1], atol=1e-6) + + +def test_unwrap_ra_leaves_normal_polygon_unchanged(): + """A polygon well away from the seam is returned unchanged.""" + npt.assert_allclose( + unwrap_ra([99.9, 100.1, 100.1, 99.9]), + [99.9, 100.1, 100.1, 99.9], + ) + + +def test_build_map_invariant_under_ra_shift(tmp_path): + """A seam CCD's footprint matches an identical CCD shifted +10 deg in RA. + + Rotating both the seam CCD (via +360/unwrap) and a reference CCD onto the + same RA and comparing pixel counts fails if the seam polygon were filling + the ~360 deg complement. This is the real RA-wrap regression guard. + """ + # Seam CCD straddling RA=0, and the same CCD translated to RA~10. + seam = tmp_path / "seam.txt" + seam.write_text("100-0 359.9 0.1 0.1 359.9 20.0 20.0 20.2 20.2\n") + ref = tmp_path / "ref.txt" + ref.write_text("200-0 9.9 10.1 10.1 9.9 20.0 20.0 20.2 20.2\n") + + m_seam = tmp_path / "seam.hsp" + m_ref = tmp_path / "ref.hsp" + CoverageMapBuilder().run( + args=["-i", str(seam), "-o", str(m_seam), "-c", "32", "-n", "1024"] + ) + CoverageMapBuilder().run( + args=["-i", str(ref), "-o", str(m_ref), "-c", "32", "-n", "1024"] + ) + + n_seam = len(_read_map(m_seam).valid_pixels) + n_ref = len(_read_map(m_ref).valid_pixels) + + # Same-size footprints at the same declination: pixel counts agree to + # within a few boundary pixels, and are nowhere near a hemisphere. + assert abs(n_seam - n_ref) <= max(3, int(0.1 * n_ref)) + assert 0 < n_seam < 200 + + +def test_build_map_pole_guard_skips_polygon(tmp_path, capsys): + """A polygon with |dec| near 90 deg is skipped with a warning.""" + infile = tmp_path / "corners.txt" + infile.write_text( + "100-0 10.0 10.2 10.2 10.0 89.5 89.5 89.7 89.7\n" + "200-0 10.0 10.2 10.2 10.0 20.0 20.0 20.2 20.2\n" + ) + out = tmp_path / "cov.hsp" + + CoverageMapBuilder().run( + args=["-i", str(infile), "-o", str(out), "-c", "32", "-n", "1024"] + ) + + captured = capsys.readouterr() + assert "skipping CCD 100-0" in captured.out + assert "Added 1 polygons" in captured.out + + +# --------------------------------------------------------------------------- +# console-runner argv handling (regression guard for the -h entry points) +# --------------------------------------------------------------------------- + +def test_run_help_flag_exits_cleanly(monkeypatch): + """``run()`` with no args reads ``sys.argv[1:]`` so ``-h`` exits 0. + + Guards the regression where the runners parsed the full ``sys.argv`` + (including ``argv[0]``), which made ``-h`` collide with the integer + ``-c`` option and exit non-zero. + """ + monkeypatch.setattr(sys, "argv", ["extract_field_corners", "-h"]) + + with pytest.raises(SystemExit) as excinfo: + FieldCornersExtractor().run() + + assert excinfo.value.code == 0 diff --git a/uv.lock b/uv.lock index 03deab0a8..524e16ca3 100644 --- a/uv.lock +++ b/uv.lock @@ -14,7 +14,7 @@ name = "accessible-pygments" version = "0.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } wheels = [ @@ -62,8 +62,8 @@ name = "anyio" version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "idna", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ @@ -75,7 +75,7 @@ name = "argon2-cffi" version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "argon2-cffi-bindings" }, + { name = "argon2-cffi-bindings", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ @@ -87,7 +87,7 @@ name = "argon2-cffi-bindings" version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi" }, + { name = "cffi", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } wheels = [ @@ -115,8 +115,8 @@ name = "arrow" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil" }, - { name = "tzdata" }, + { name = "python-dateutil", marker = "sys_platform == 'linux'" }, + { name = "tzdata", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } wheels = [ @@ -128,11 +128,11 @@ name = "astropy" version = "8.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy-iers-data" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pyerfa" }, - { name = "pyyaml" }, + { name = "astropy-iers-data", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pyerfa", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/c4/21be4313ddfde5f60e0607fd307f367b9e0f0bf153a89b10cbd036dd8cfd/astropy-8.0.1.tar.gz", hash = "sha256:45ca31d5b91fa294cd590a4791a32db94de7f9c8a343155f4d5877baa82351da", size = 7152500, upload-time = "2026-07-05T07:24:48.482Z" } wheels = [ @@ -146,8 +146,8 @@ name = "astropy-healpix" version = "1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "numpy" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/b1/7651302dd47b76d39c053c5a2f645b4a14a1ee1dfe1cc7491bcaa6b9fe20/astropy_healpix-1.1.3.tar.gz", hash = "sha256:f520d83abe8215d3e8e1a37b2bd91171ee36a6f55f110d5a2db863d75d81b3b7", size = 109331, upload-time = "2026-01-19T23:34:18.824Z" } wheels = [ @@ -173,13 +173,13 @@ name = "astroquery" version = "0.4.11" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "beautifulsoup4" }, - { name = "html5lib" }, - { name = "keyring" }, - { name = "numpy" }, - { name = "pyvo" }, - { name = "requests" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "beautifulsoup4", marker = "sys_platform == 'linux'" }, + { name = "html5lib", marker = "sys_platform == 'linux'" }, + { name = "keyring", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pyvo", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/32/48/273dbde090e071f9d264d084bc49193d126498d2906172b78febd9d62e28/astroquery-0.4.11.tar.gz", hash = "sha256:5537529bddc7fa07e773d5cd9baca593e3f5d93474edd1914f68e89506042b33", size = 12561055, upload-time = "2025-09-20T04:26:36.744Z" } wheels = [ @@ -227,8 +227,8 @@ name = "beautifulsoup4" version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, + { name = "soupsieve", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ @@ -240,7 +240,7 @@ name = "bleach" version = "6.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings" }, + { name = "webencodings", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/48/3c/e12ac860709702bd5ebeb9b56a4fe334f1001246ee1b8f2b7ee28912df7d/bleach-6.4.0.tar.gz", hash = "sha256:4202482733d85cedd04e59fcb2f89f4e4c7c385a78d3c3c23c30446843a37452", size = 204857, upload-time = "2026-06-05T13:01:13.734Z" } wheels = [ @@ -249,7 +249,7 @@ wheels = [ [package.optional-dependencies] css = [ - { name = "tinycss2" }, + { name = "tinycss2", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -257,8 +257,8 @@ name = "build" version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging" }, - { name = "pyproject-hooks" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pyproject-hooks", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } wheels = [ @@ -270,13 +270,13 @@ name = "cadcutils" version = "1.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "distro" }, - { name = "html2text" }, - { name = "lxml" }, - { name = "packaging" }, - { name = "pyopenssl" }, - { name = "requests" }, - { name = "setuptools" }, + { name = "distro", marker = "sys_platform == 'linux'" }, + { name = "html2text", marker = "sys_platform == 'linux'" }, + { name = "lxml", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pyopenssl", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f7/d6/1c3e4cb3f59cb2321d78f157723a22590a29854905487dcd994ea10cd63f/cadcutils-1.5.5.tar.gz", hash = "sha256:74600fdcdb4f2718576aa76b633a9e7b1989a8b37ca4cc9913cfd3ddd5415a75", size = 84921, upload-time = "2025-06-16T16:40:19.841Z" } wheels = [ @@ -288,10 +288,10 @@ name = "camb" version = "1.6.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "packaging" }, - { name = "scipy" }, - { name = "sympy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "sympy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/ad/f2e6446fbd94f7adc1474e1f4e40d2e7fa7e5e7143640cb8557d492f0a73/camb-1.6.6.tar.gz", hash = "sha256:9856202a5c05570256e52377b20431891c7b08b2e9c334e141fd08d2a085516f", size = 799344, upload-time = "2026-03-11T16:46:55.819Z" } wheels = [ @@ -304,18 +304,18 @@ name = "canfar" version = "1.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cadcutils" }, - { name = "defusedxml" }, - { name = "httpx", extra = ["http2"] }, - { name = "humanize" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyyaml" }, - { name = "questionary" }, - { name = "rich" }, - { name = "segno" }, - { name = "toml" }, - { name = "typer" }, + { name = "cadcutils", marker = "sys_platform == 'linux'" }, + { name = "defusedxml", marker = "sys_platform == 'linux'" }, + { name = "httpx", extra = ["http2"], marker = "sys_platform == 'linux'" }, + { name = "humanize", marker = "sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'linux'" }, + { name = "pydantic-settings", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, + { name = "questionary", marker = "sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'linux'" }, + { name = "segno", marker = "sys_platform == 'linux'" }, + { name = "toml", marker = "sys_platform == 'linux'" }, + { name = "typer", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/fc/4fa84863080e5db679c85a842794cae520d59a961a42969350d31569dcac/canfar-1.3.5.tar.gz", hash = "sha256:f4dd69e3426f5552f0cc4955642e8555d6c350bdf3598cd96251fb66241edaa4", size = 25689573, upload-time = "2026-04-28T15:36:01.33Z" } wheels = [ @@ -336,7 +336,7 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ @@ -466,7 +466,7 @@ name = "conda-inject" version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyyaml" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/a8/8dc86113c65c949cc72d651461d6e4c544b3302a85ed14a5298829e6a419/conda_inject-1.3.2.tar.gz", hash = "sha256:0b8cde8c47998c118d8ff285a04977a3abcf734caf579c520fca469df1cd0aac", size = 3635, upload-time = "2024-05-27T12:20:58.873Z" } wheels = [ @@ -493,7 +493,7 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -593,7 +593,7 @@ name = "cryptography" version = "48.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" } wheels = [ @@ -637,18 +637,18 @@ name = "cs-util" version = "0.2.2" source = { git = "https://github.com/CosmoStat/cs_util?branch=develop#bedcdbf50ac57fc4d0b72a62b251373cd1807824" } dependencies = [ - { name = "astropy" }, - { name = "camb" }, - { name = "datetime" }, - { name = "healpy" }, - { name = "healsparse" }, - { name = "keyring" }, - { name = "matplotlib" }, - { name = "numpy" }, - { name = "pyccl" }, - { name = "scipy" }, - { name = "swig" }, - { name = "vos" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "camb", marker = "sys_platform == 'linux'" }, + { name = "datetime", marker = "sys_platform == 'linux'" }, + { name = "healpy", marker = "sys_platform == 'linux'" }, + { name = "healsparse", marker = "sys_platform == 'linux'" }, + { name = "keyring", marker = "sys_platform == 'linux'" }, + { name = "matplotlib", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pyccl", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "swig", marker = "sys_platform == 'linux'" }, + { name = "vos", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -665,13 +665,13 @@ name = "dask" version = "2026.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "cloudpickle" }, - { name = "fsspec" }, - { name = "packaging" }, - { name = "partd" }, - { name = "pyyaml" }, - { name = "toolz" }, + { name = "click", marker = "sys_platform == 'linux'" }, + { name = "cloudpickle", marker = "sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "partd", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, + { name = "toolz", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d7/2a/5d8cc1579590af86576dde890254440e478c7174b93a02095ecfc2e6ba38/dask-2026.3.0.tar.gz", hash = "sha256:f7d96c8274e8a900d217c1ff6ea8d1bbf0b4c2c21e74a409644498d925eb8f85", size = 11000710, upload-time = "2026-03-18T07:10:14.945Z" } wheels = [ @@ -680,12 +680,12 @@ wheels = [ [package.optional-dependencies] array = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] dataframe = [ - { name = "numpy" }, - { name = "pandas" }, - { name = "pyarrow" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'linux'" }, + { name = "pyarrow", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -693,12 +693,12 @@ name = "dask-image" version = "2025.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "dask", extra = ["array", "dataframe"] }, - { name = "numpy" }, - { name = "pandas" }, - { name = "pims" }, - { name = "scipy" }, - { name = "tifffile" }, + { name = "dask", extra = ["array", "dataframe"], marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'linux'" }, + { name = "pims", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "tifffile", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/c4/7b83217443201469384a415687a8b89da8e55fc7a182e9507a69851a78b9/dask_image-2025.11.0.tar.gz", hash = "sha256:45cf1a9c3a8a1c143c75d43f1494e4fe0827564d3ec6efb93618fb04603ba0b3", size = 79561, upload-time = "2025-11-13T01:57:28.093Z" } wheels = [ @@ -710,8 +710,8 @@ name = "datetime" version = "6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pytz" }, - { name = "zope-interface" }, + { name = "pytz", marker = "sys_platform == 'linux'" }, + { name = "zope-interface", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/32/decbfd165e9985ba9d8c2d34a39afe5aeba2fc3fe390eb6e9ef1aab98fa8/datetime-6.0.tar.gz", hash = "sha256:c1514936d2f901e10c8e08d83bf04e6c9dbd7ca4f244da94fec980980a3bc4d5", size = 64167, upload-time = "2025-11-25T08:00:34.586Z" } wheels = [ @@ -771,7 +771,7 @@ name = "donfig" version = "0.8.1.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyyaml" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506, upload-time = "2024-05-23T14:14:31.513Z" } wheels = [ @@ -810,7 +810,7 @@ name = "fitsio" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/f1/319003b56bcf6c83ef299461d601e13ad5910c1d62fa683d86fdb2fec2c6/fitsio-1.3.0.tar.gz", hash = "sha256:e2394fb0dca62f46feaaf61a48ad89fad6d2428a5a96a78ebfb4299a084b9260", size = 4995996, upload-time = "2025-11-12T15:24:25.021Z" } wheels = [ @@ -881,11 +881,11 @@ name = "galsim" version = "2.8.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "lsstdesc-coord" }, - { name = "numpy" }, - { name = "pybind11" }, - { name = "setuptools" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "lsstdesc-coord", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pybind11", marker = "sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/59/c585e4de42d5b7f240a5bc4cd8ffd388b58cbde7515540f37d71da58623a/galsim-2.8.4.tar.gz", hash = "sha256:316299a0685e0bd6134ddf5642f39b439a2a35b7f116d6ebd979332fddbbad76", size = 8591085, upload-time = "2026-02-22T23:24:39.668Z" } wheels = [ @@ -904,7 +904,7 @@ name = "gitdb" version = "4.0.12" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "smmap" }, + { name = "smmap", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } wheels = [ @@ -916,7 +916,7 @@ name = "gitpython" version = "3.1.50" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "gitdb" }, + { name = "gitdb", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/f6/354ae6491228b5eb40e10d89c4d13c651fe1cf7556e35ebdded50cff57ce/gitpython-3.1.50.tar.gz", hash = "sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc", size = 219798, upload-time = "2026-05-06T04:01:26.571Z" } wheels = [ @@ -979,8 +979,8 @@ name = "h2" version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "hpack" }, - { name = "hyperframe" }, + { name = "hpack", marker = "sys_platform == 'linux'" }, + { name = "hyperframe", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } wheels = [ @@ -992,7 +992,7 @@ name = "h5py" version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } wheels = [ @@ -1019,8 +1019,8 @@ name = "healpy" version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "numpy" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e3/4e/6f5328f375f88c8c38314f24f82b7e1486ec0358c557779500382db1503c/healpy-1.19.0.tar.gz", hash = "sha256:28e839cb885a23d36c77fc3423a3cb9271a07fda94085bd12fc329f941130ec5", size = 4075006, upload-time = "2025-12-02T08:27:19.766Z" } wheels = [ @@ -1043,9 +1043,9 @@ name = "healsparse" version = "1.12.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "hpgeom" }, - { name = "numpy" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "hpgeom", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/c1/ba1dcceb6fc7d10f2f92e0019e865fef7e11ea8bbd544b5691d94d03b41c/healsparse-1.12.2.tar.gz", hash = "sha256:364e2c9966698843139c53ad878dcc8d544658d13005545d11fbc0935bc6a4b6", size = 131452, upload-time = "2026-03-17T15:04:37.646Z" } wheels = [ @@ -1066,7 +1066,7 @@ name = "hpgeom" version = "1.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/72/27640de7d85a566b8ecd27e7894f371a19669c56c268756e18111a137df8/hpgeom-1.5.4.tar.gz", hash = "sha256:85ac73e267c11f3f248920d3541a0e6ffab821cae54f0d44414d1939dfb91200", size = 153906, upload-time = "2026-02-24T19:03:01.954Z" } wheels = [ @@ -1092,8 +1092,8 @@ name = "html5lib" version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six" }, - { name = "webencodings" }, + { name = "six", marker = "sys_platform == 'linux'" }, + { name = "webencodings", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } wheels = [ @@ -1105,8 +1105,8 @@ name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "h11" }, + { name = "certifi", marker = "sys_platform == 'linux'" }, + { name = "h11", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ @@ -1118,10 +1118,10 @@ name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, + { name = "anyio", marker = "sys_platform == 'linux'" }, + { name = "certifi", marker = "sys_platform == 'linux'" }, + { name = "httpcore", marker = "sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ @@ -1130,7 +1130,7 @@ wheels = [ [package.optional-dependencies] http2 = [ - { name = "h2" }, + { name = "h2", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -1165,7 +1165,7 @@ name = "hypothesis" version = "6.156.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sortedcontainers" }, + { name = "sortedcontainers", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/83/8dbe89bdb8c6f25a7a52e7898af6d82fe35dfef08e5c702f6e33231ce6c6/hypothesis-6.156.6.tar.gz", hash = "sha256:96de02faefa3ce079873541da96f42595583bb001e8e4219294ed7d4501cc4cc", size = 476304, upload-time = "2026-07-10T20:56:49.96Z" } wheels = [ @@ -1198,7 +1198,7 @@ name = "id" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "urllib3" }, + { name = "urllib3", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } wheels = [ @@ -1219,8 +1219,8 @@ name = "imageio" version = "2.37.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "pillow" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } wheels = [ @@ -1257,7 +1257,7 @@ name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -1278,18 +1278,18 @@ name = "ipykernel" version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "comm" }, - { name = "debugpy" }, - { name = "ipython" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, + { name = "comm", marker = "sys_platform == 'linux'" }, + { name = "debugpy", marker = "sys_platform == 'linux'" }, + { name = "ipython", marker = "sys_platform == 'linux'" }, + { name = "jupyter-client", marker = "sys_platform == 'linux'" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "matplotlib-inline", marker = "sys_platform == 'linux'" }, + { name = "nest-asyncio", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "psutil", marker = "sys_platform == 'linux'" }, + { name = "pyzmq", marker = "sys_platform == 'linux'" }, + { name = "tornado", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } wheels = [ @@ -1301,16 +1301,16 @@ name = "ipython" version = "9.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect" }, - { name = "prompt-toolkit" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, + { name = "decorator", marker = "sys_platform == 'linux'" }, + { name = "ipython-pygments-lexers", marker = "sys_platform == 'linux'" }, + { name = "jedi", marker = "sys_platform == 'linux'" }, + { name = "matplotlib-inline", marker = "sys_platform == 'linux'" }, + { name = "pexpect", marker = "sys_platform == 'linux'" }, + { name = "prompt-toolkit", marker = "sys_platform == 'linux'" }, + { name = "psutil", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, + { name = "stack-data", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } wheels = [ @@ -1322,7 +1322,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -1334,7 +1334,7 @@ name = "isoduration" version = "20.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "arrow" }, + { name = "arrow", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } wheels = [ @@ -1346,7 +1346,7 @@ name = "jaraco-classes" version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools" }, + { name = "more-itertools", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } wheels = [ @@ -1367,7 +1367,7 @@ name = "jaraco-functools" version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools" }, + { name = "more-itertools", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ @@ -1379,7 +1379,7 @@ name = "jedi" version = "0.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "parso" }, + { name = "parso", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } wheels = [ @@ -1400,7 +1400,7 @@ name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe" }, + { name = "markupsafe", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ @@ -1439,10 +1439,10 @@ name = "jsonschema" version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, + { name = "attrs", marker = "sys_platform == 'linux'" }, + { name = "jsonschema-specifications", marker = "sys_platform == 'linux'" }, + { name = "referencing", marker = "sys_platform == 'linux'" }, + { name = "rpds-py", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ @@ -1451,15 +1451,15 @@ wheels = [ [package.optional-dependencies] format-nongpl = [ - { name = "fqdn" }, - { name = "idna" }, - { name = "isoduration" }, - { name = "jsonpointer" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "rfc3987-syntax" }, - { name = "uri-template" }, - { name = "webcolors" }, + { name = "fqdn", marker = "sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'linux'" }, + { name = "isoduration", marker = "sys_platform == 'linux'" }, + { name = "jsonpointer", marker = "sys_platform == 'linux'" }, + { name = "rfc3339-validator", marker = "sys_platform == 'linux'" }, + { name = "rfc3986-validator", marker = "sys_platform == 'linux'" }, + { name = "rfc3987-syntax", marker = "sys_platform == 'linux'" }, + { name = "uri-template", marker = "sys_platform == 'linux'" }, + { name = "webcolors", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -1467,7 +1467,7 @@ name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "referencing" }, + { name = "referencing", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ @@ -1479,8 +1479,8 @@ name = "jupyter-builder" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-core" }, - { name = "traitlets" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/df/db5efc4e28803a1421350445e53d85ec571a4e6928e3524ccc39f4e16584/jupyter_builder-1.1.0.tar.gz", hash = "sha256:b996e8af616900f18724fa34883169d869be2205497940bec78a3a1031eb897d", size = 971142, upload-time = "2026-07-11T07:24:02.4Z" } wheels = [ @@ -1492,11 +1492,11 @@ name = "jupyter-client" version = "8.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-core" }, - { name = "python-dateutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'linux'" }, + { name = "pyzmq", marker = "sys_platform == 'linux'" }, + { name = "tornado", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } wheels = [ @@ -1508,8 +1508,8 @@ name = "jupyter-core" version = "5.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, - { name = "traitlets" }, + { name = "platformdirs", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } wheels = [ @@ -1521,14 +1521,14 @@ name = "jupyter-events" version = "0.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jsonschema", extra = ["format-nongpl"] }, - { name = "packaging" }, - { name = "python-json-logger" }, - { name = "pyyaml" }, - { name = "referencing" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "traitlets" }, + { name = "jsonschema", extra = ["format-nongpl"], marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "python-json-logger", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, + { name = "referencing", marker = "sys_platform == 'linux'" }, + { name = "rfc3339-validator", marker = "sys_platform == 'linux'" }, + { name = "rfc3986-validator", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/f8/475c4241b2b75af0deaae453ed003c6c851766dbc44d332d8baf245dc931/jupyter_events-0.12.1.tar.gz", hash = "sha256:faff25f77218335752f35f23c5fe6e4a392a7bd99a5939ccb9b8fbf594636cf3", size = 62854, upload-time = "2026-04-20T23:17:50.66Z" } wheels = [ @@ -1540,7 +1540,7 @@ name = "jupyter-lsp" version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-server" }, + { name = "jupyter-server", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/ff/1e4a61f5170a9a1d978f3ac3872449de6c01fc71eaf89657824c878b1549/jupyter_lsp-2.3.1.tar.gz", hash = "sha256:fdf8a4aa7d85813976d6e29e95e6a2c8f752701f926f2715305249a3829805a6", size = 55677, upload-time = "2026-04-02T08:10:06.749Z" } wheels = [ @@ -1552,23 +1552,23 @@ name = "jupyter-server" version = "2.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "argon2-cffi" }, - { name = "jinja2" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "jupyter-events" }, - { name = "jupyter-server-terminals" }, - { name = "nbconvert" }, - { name = "nbformat" }, - { name = "packaging" }, - { name = "prometheus-client" }, - { name = "pyzmq" }, - { name = "send2trash" }, - { name = "terminado" }, - { name = "tornado" }, - { name = "traitlets" }, - { name = "websocket-client" }, + { name = "anyio", marker = "sys_platform == 'linux'" }, + { name = "argon2-cffi", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "jupyter-client", marker = "sys_platform == 'linux'" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "jupyter-events", marker = "sys_platform == 'linux'" }, + { name = "jupyter-server-terminals", marker = "sys_platform == 'linux'" }, + { name = "nbconvert", marker = "sys_platform == 'linux'" }, + { name = "nbformat", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "prometheus-client", marker = "sys_platform == 'linux'" }, + { name = "pyzmq", marker = "sys_platform == 'linux'" }, + { name = "send2trash", marker = "sys_platform == 'linux'" }, + { name = "terminado", marker = "sys_platform == 'linux'" }, + { name = "tornado", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, + { name = "websocket-client", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6b/dc/db3a582633170186f8c8b31298d7eb26ad0eb031a1f53476c258b64eed05/jupyter_server-2.20.0.tar.gz", hash = "sha256:b5778ba337d8015a3dc2b80803ecdd5ac18d3797fddf61a50ea5fb472b4ebe14", size = 756523, upload-time = "2026-06-17T12:09:09.435Z" } wheels = [ @@ -1580,7 +1580,7 @@ name = "jupyter-server-terminals" version = "0.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "terminado" }, + { name = "terminado", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } wheels = [ @@ -1592,19 +1592,19 @@ name = "jupyterlab" version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "async-lru" }, - { name = "httpx" }, - { name = "ipykernel" }, - { name = "jinja2" }, - { name = "jupyter-builder" }, - { name = "jupyter-core" }, - { name = "jupyter-lsp" }, - { name = "jupyter-server" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "packaging" }, - { name = "tornado" }, - { name = "traitlets" }, + { name = "async-lru", marker = "sys_platform == 'linux'" }, + { name = "httpx", marker = "sys_platform == 'linux'" }, + { name = "ipykernel", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "jupyter-builder", marker = "sys_platform == 'linux'" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "jupyter-lsp", marker = "sys_platform == 'linux'" }, + { name = "jupyter-server", marker = "sys_platform == 'linux'" }, + { name = "jupyterlab-server", marker = "sys_platform == 'linux'" }, + { name = "notebook-shim", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "tornado", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/2a/d6af53bfd45a43a5bfe7e40ba47ee7a8921a807daf4bb708e3a295bbb54d/jupyterlab-4.6.1.tar.gz", hash = "sha256:75315982ed28427edaa62bb85eadb5105e4043a757643c910efd787fe6ed0837", size = 28179125, upload-time = "2026-06-29T12:48:45.402Z" } wheels = [ @@ -1625,13 +1625,13 @@ name = "jupyterlab-server" version = "2.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "babel" }, - { name = "jinja2" }, - { name = "json5" }, - { name = "jsonschema" }, - { name = "jupyter-server" }, - { name = "packaging" }, - { name = "requests" }, + { name = "babel", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "json5", marker = "sys_platform == 'linux'" }, + { name = "jsonschema", marker = "sys_platform == 'linux'" }, + { name = "jupyter-server", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } wheels = [ @@ -1643,11 +1643,11 @@ name = "keyring" version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jaraco-classes" }, - { name = "jaraco-context" }, - { name = "jaraco-functools" }, - { name = "jeepney" }, - { name = "secretstorage" }, + { name = "jaraco-classes", marker = "sys_platform == 'linux'" }, + { name = "jaraco-context", marker = "sys_platform == 'linux'" }, + { name = "jaraco-functools", marker = "sys_platform == 'linux'" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ @@ -1736,7 +1736,7 @@ name = "lazy-loader" version = "0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } wheels = [ @@ -1773,8 +1773,8 @@ name = "lsstdesc-coord" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "future" }, - { name = "numpy" }, + { name = "future", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/e0/6806df4cfa54927a2d8e425407611b4fc031dd125bc952b4a31deb743305/lsstdesc_coord-1.3.1.tar.gz", hash = "sha256:60f878c29e1f30a9b50bf60dca3c466dc9cfb2cbc71f0a27c575ced969de57ab", size = 41647, upload-time = "2026-02-13T18:37:32.611Z" } wheels = [ @@ -1846,7 +1846,7 @@ name = "markdown-it-py" version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl" }, + { name = "mdurl", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ @@ -1896,15 +1896,15 @@ name = "matplotlib" version = "3.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "contourpy" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, + { name = "contourpy", marker = "sys_platform == 'linux'" }, + { name = "cycler", marker = "sys_platform == 'linux'" }, + { name = "fonttools", marker = "sys_platform == 'linux'" }, + { name = "kiwisolver", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'linux'" }, + { name = "pyparsing", marker = "sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } wheels = [ @@ -1930,7 +1930,7 @@ name = "matplotlib-inline" version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "traitlets" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } wheels = [ @@ -1942,11 +1942,11 @@ name = "mccd" version = "1.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "modopt" }, - { name = "numpy" }, - { name = "python-pysap" }, - { name = "scipy" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "modopt", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "python-pysap", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/b4/2d5618bff92bcc1b928dcc356cbce19cd4ade689d046ba0267da10247f38/mccd-1.2.4.tar.gz", hash = "sha256:e146c84867f4f97e58a0d3de6ead25feceb46f58a1a24e29d102007723e8719f", size = 75762, upload-time = "2023-02-17T19:09:17.039Z" } wheels = [ @@ -1958,7 +1958,7 @@ name = "mdit-py-plugins" version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py" }, + { name = "markdown-it-py", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ @@ -1988,10 +1988,10 @@ name = "modopt" version = "1.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "tqdm" }, + { name = "importlib-metadata", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/cf/7665c4990b8623b9e27733edd17a69c63e2d35c5f05d1a253d5ed2f98e52/modopt-1.7.2.tar.gz", hash = "sha256:d5e8edd935b813c3677beeed3245ef4894e7ac09e180150368eda044914488b4", size = 778069, upload-time = "2024-04-12T10:22:11.24Z" } wheels = [ @@ -2041,12 +2041,12 @@ name = "myst-parser" version = "5.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, - { name = "jinja2" }, - { name = "markdown-it-py" }, - { name = "mdit-py-plugins" }, - { name = "pyyaml" }, - { name = "sphinx" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "markdown-it-py", marker = "sys_platform == 'linux'" }, + { name = "mdit-py-plugins", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/dc/603751677fff302f34396e206b610f556a59d7fe58b9a2145f54e96b48e8/myst_parser-5.1.0.tar.gz", hash = "sha256:ab69322dc6719dcc7f296479dbb70181b66df6ed315064f92dbc85c0e1bf2f02", size = 101182, upload-time = "2026-05-13T09:38:19.361Z" } wheels = [ @@ -2058,10 +2058,10 @@ name = "nbclient" version = "0.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "nbformat" }, - { name = "traitlets" }, + { name = "jupyter-client", marker = "sys_platform == 'linux'" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "nbformat", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } wheels = [ @@ -2073,20 +2073,20 @@ name = "nbconvert" version = "7.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "beautifulsoup4" }, - { name = "bleach", extra = ["css"] }, - { name = "defusedxml" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyterlab-pygments" }, - { name = "markupsafe" }, - { name = "mistune" }, - { name = "nbclient" }, - { name = "nbformat" }, - { name = "packaging" }, - { name = "pandocfilters" }, - { name = "pygments" }, - { name = "traitlets" }, + { name = "beautifulsoup4", marker = "sys_platform == 'linux'" }, + { name = "bleach", extra = ["css"], marker = "sys_platform == 'linux'" }, + { name = "defusedxml", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "jupyterlab-pygments", marker = "sys_platform == 'linux'" }, + { name = "markupsafe", marker = "sys_platform == 'linux'" }, + { name = "mistune", marker = "sys_platform == 'linux'" }, + { name = "nbclient", marker = "sys_platform == 'linux'" }, + { name = "nbformat", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pandocfilters", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } wheels = [ @@ -2098,10 +2098,10 @@ name = "nbformat" version = "5.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fastjsonschema" }, - { name = "jsonschema" }, - { name = "jupyter-core" }, - { name = "traitlets" }, + { name = "fastjsonschema", marker = "sys_platform == 'linux'" }, + { name = "jsonschema", marker = "sys_platform == 'linux'" }, + { name = "jupyter-core", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } wheels = [ @@ -2162,9 +2162,9 @@ name = "nibabel" version = "5.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "packaging" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/01/3d2cc510c616bc8e27be17a063070d9126f69407961594a9ae734ea51121/nibabel-5.4.2.tar.gz", hash = "sha256:d5f4b9076a13178ae7f7acf18c8dbd503ee1c4d5c0c23b85df7be87efcbb49da", size = 4663132, upload-time = "2026-03-11T13:31:52.42Z" } wheels = [ @@ -2176,7 +2176,7 @@ name = "notebook-shim" version = "0.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jupyter-server" }, + { name = "jupyter-server", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } wheels = [ @@ -2188,8 +2188,8 @@ name = "numba" version = "0.66.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "llvmlite" }, - { name = "numpy" }, + { name = "llvmlite", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/a0/570e3dc53e5602b49108f62a13e529f1eec8bfc7ef37d49c825924dcf546/numba-0.66.0.tar.gz", hash = "sha256:b900e63a0e26c05ea9a6d5a3a5a0a177cb64c5011887bf43edb8c3ed2c38d363", size = 2806181, upload-time = "2026-07-01T23:12:46.36Z" } wheels = [ @@ -2208,8 +2208,8 @@ name = "numcodecs" version = "0.16.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "typing-extensions" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } wheels = [ @@ -2254,7 +2254,7 @@ name = "numpydoc" version = "1.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/3c/dfccc9e7dee357fb2aa13c3890d952a370dd0ed071e0f7ed62ed0df567c1/numpydoc-1.10.0.tar.gz", hash = "sha256:3f7970f6eee30912260a6b31ac72bba2432830cd6722569ec17ee8d3ef5ffa01", size = 94027, upload-time = "2025-12-02T16:39:12.937Z" } wheels = [ @@ -2275,8 +2275,8 @@ name = "pandas" version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } wheels = [ @@ -2325,8 +2325,8 @@ name = "partd" version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "locket" }, - { name = "toolz" }, + { name = "locket", marker = "sys_platform == 'linux'" }, + { name = "toolz", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" } wheels = [ @@ -2338,7 +2338,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess" }, + { name = "ptyprocess", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -2394,11 +2394,11 @@ name = "pims" version = "0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "imageio" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "slicerator" }, - { name = "tifffile" }, + { name = "imageio", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "slicerator", marker = "sys_platform == 'linux'" }, + { name = "tifffile", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/02/5bf3639f5b77e9b183011c08541c5039ba3d04f5316c70312b48a8e003a9/pims-0.7.tar.gz", hash = "sha256:55907a4c301256086d2aa4e34a5361b9109f24e375c2071e1117b9491e82946b", size = 87779, upload-time = "2024-06-10T19:20:42.842Z" } @@ -2425,7 +2425,7 @@ name = "progressbar2" version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-utils" }, + { name = "python-utils", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/24/3587e795fc590611434e4bcb9fbe0c3dddb5754ce1a20edfd86c587c0004/progressbar2-4.5.0.tar.gz", hash = "sha256:6662cb624886ed31eb94daf61e27583b5144ebc7383a17bae076f8f4f59088fb", size = 101449, upload-time = "2024-08-28T22:50:12.391Z" } wheels = [ @@ -2446,7 +2446,7 @@ name = "prompt-toolkit" version = "3.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "wcwidth" }, + { name = "wcwidth", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ @@ -2547,8 +2547,8 @@ name = "pybtex" version = "0.26.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "latexcodec" }, - { name = "pyyaml" }, + { name = "latexcodec", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/f5/f30da9c93f0fa6d619332b2f69597219b625f35780473a05164a9981fd9a/pybtex-0.26.1.tar.gz", hash = "sha256:2e5543bea424e60e9e42eef70bff597be48649d8f68ba061a7a092b2477d5464", size = 692991, upload-time = "2026-04-03T13:05:39.014Z" } wheels = [ @@ -2560,8 +2560,8 @@ name = "pybtex-docutils" version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, - { name = "pybtex" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "pybtex", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" } wheels = [ @@ -2573,10 +2573,10 @@ name = "pyccl" version = "3.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "scipy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6a/b5/8bc08fd09c5a37b888978ba1bcb0769a30f08d89d526d786e5434d7f5c68/pyccl-3.3.3.tar.gz", hash = "sha256:a55f9f3b6fd57af291294601e22e0c4b21c46b9bba6c659c63b1dbe288dc0824", size = 16783008, upload-time = "2026-04-08T19:06:56.861Z" } @@ -2594,10 +2594,10 @@ name = "pydantic" version = "2.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, + { name = "annotated-types", marker = "sys_platform == 'linux'" }, + { name = "pydantic-core", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, + { name = "typing-inspection", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } wheels = [ @@ -2609,7 +2609,7 @@ name = "pydantic-core" version = "2.46.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } wheels = [ @@ -2662,9 +2662,9 @@ name = "pydantic-settings" version = "2.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, + { name = "pydantic", marker = "sys_platform == 'linux'" }, + { name = "python-dotenv", marker = "sys_platform == 'linux'" }, + { name = "typing-inspection", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } wheels = [ @@ -2676,13 +2676,13 @@ name = "pydata-sphinx-theme" version = "0.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "accessible-pygments" }, - { name = "babel" }, - { name = "beautifulsoup4" }, - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "typing-extensions" }, + { name = "accessible-pygments", marker = "sys_platform == 'linux'" }, + { name = "babel", marker = "sys_platform == 'linux'" }, + { name = "beautifulsoup4", marker = "sys_platform == 'linux'" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } wheels = [ @@ -2694,7 +2694,7 @@ name = "pyerfa" version = "2.0.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/71/39/63cc8291b0cf324ae710df41527faf7d331bce573899199d926b3e492260/pyerfa-2.0.1.5.tar.gz", hash = "sha256:17d6b24fe4846c65d5e7d8c362dcb08199dc63b30a236aedd73875cc83e1f6c0", size = 818430, upload-time = "2024-11-11T15:22:30.852Z" } wheels = [ @@ -2717,8 +2717,8 @@ name = "pyopenssl" version = "26.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "cryptography", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" } wheels = [ @@ -2748,8 +2748,8 @@ name = "pyqt5" version = "5.15.11" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyqt5-qt5" }, - { name = "pyqt5-sip" }, + { name = "pyqt5-qt5", marker = "sys_platform == 'linux'" }, + { name = "pyqt5-sip", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/07/c9ed0bd428df6f87183fca565a79fee19fa7c88c7f00a7f011ab4379e77a/PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52", size = 3216775, upload-time = "2024-07-19T08:39:57.756Z" } wheels = [ @@ -2783,8 +2783,8 @@ name = "pyqtgraph" version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama" }, - { name = "numpy" }, + { name = "colorama", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/32/36/4c242f81fdcbfa4fb62a5645f6af79191f4097a0577bd5460c24f19cc4ef/pyqtgraph-0.14.0-py3-none-any.whl", hash = "sha256:7abb7c3e17362add64f8711b474dffac5e7b0e9245abdf992e9a44119b7aa4f5", size = 1924755, upload-time = "2025-11-16T19:43:22.251Z" }, @@ -2795,10 +2795,10 @@ name = "pytest" version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, + { name = "iniconfig", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pluggy", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ @@ -2810,9 +2810,9 @@ name = "pytest-cov" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage" }, - { name = "pluggy" }, - { name = "pytest" }, + { name = "coverage", marker = "sys_platform == 'linux'" }, + { name = "pluggy", marker = "sys_platform == 'linux'" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ @@ -2824,7 +2824,7 @@ name = "python-dateutil" version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six" }, + { name = "six", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ @@ -2854,16 +2854,16 @@ name = "python-pysap" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "matplotlib" }, - { name = "modopt" }, - { name = "nibabel" }, - { name = "numpy" }, - { name = "progressbar2" }, - { name = "pywavelets" }, - { name = "scikit-image" }, - { name = "scikit-learn" }, - { name = "scipy" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "matplotlib", marker = "sys_platform == 'linux'" }, + { name = "modopt", marker = "sys_platform == 'linux'" }, + { name = "nibabel", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "progressbar2", marker = "sys_platform == 'linux'" }, + { name = "pywavelets", marker = "sys_platform == 'linux'" }, + { name = "scikit-image", marker = "sys_platform == 'linux'" }, + { name = "scikit-learn", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/90/29b10f180876a322c7cfbadd2552efef34374dceb618fdc3f48ef792666e/python_pysap-0.3.0.tar.gz", hash = "sha256:55cdfec05eacc410723f31df754b67728224935aef7d4332b89ef6e90f2aab97", size = 775664, upload-time = "2026-01-06T10:12:58.287Z" } @@ -2872,7 +2872,7 @@ name = "python-utils" version = "3.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/13/4c/ef8b7b1046d65c1f18ca31e5235c7d6627ca2b3f389ab1d44a74d22f5cc9/python_utils-3.9.1.tar.gz", hash = "sha256:eb574b4292415eb230f094cbf50ab5ef36e3579b8f09e9f2ba74af70891449a0", size = 35403, upload-time = "2024-11-26T00:38:58.736Z" } wheels = [ @@ -2893,8 +2893,8 @@ name = "pyvo" version = "1.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "requests" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/33/51077325e08b68d13498ec1a63134f1feef6659ea4ad437649c942cac916/pyvo-1.8.1.tar.gz", hash = "sha256:d3cc60aa3d3416d22c89e465a04dfa9f521085fdd5228cce2cffd2fee3a9e709", size = 2120129, upload-time = "2026-02-12T21:20:41.983Z" } wheels = [ @@ -2906,7 +2906,7 @@ name = "pywavelets" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" } wheels = [ @@ -2965,7 +2965,7 @@ name = "pyzmq" version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, + { name = "cffi", marker = "implementation_name == 'pypy' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ @@ -2994,7 +2994,7 @@ name = "questionary" version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "prompt-toolkit" }, + { name = "prompt-toolkit", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } wheels = [ @@ -3006,9 +3006,9 @@ name = "readme-renderer" version = "44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, - { name = "nh3" }, - { name = "pygments" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "nh3", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } wheels = [ @@ -3020,9 +3020,9 @@ name = "referencing" version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "attrs", marker = "sys_platform == 'linux'" }, + { name = "rpds-py", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ @@ -3034,16 +3034,16 @@ name = "reproject" version = "0.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astropy" }, - { name = "astropy-healpix" }, - { name = "dask", extra = ["array"] }, - { name = "dask-image" }, - { name = "fsspec" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "pyavm" }, - { name = "scipy" }, - { name = "zarr" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "astropy-healpix", marker = "sys_platform == 'linux'" }, + { name = "dask", extra = ["array"], marker = "sys_platform == 'linux'" }, + { name = "dask-image", marker = "sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'linux'" }, + { name = "pyavm", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "zarr", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/44/6fd820ba336484277a91a2f4808b60d6ec0b9f033f588c237e778f20fe89/reproject-0.21.0.tar.gz", hash = "sha256:01ede715a1993c29431f52ff74189ef30f5e7b2e8b4dc88c1b002145a971dc1c", size = 1622661, upload-time = "2026-06-25T15:11:34.886Z" } wheels = [ @@ -3056,10 +3056,10 @@ name = "requests" version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, + { name = "certifi", marker = "sys_platform == 'linux'" }, + { name = "charset-normalizer", marker = "sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'linux'" }, + { name = "urllib3", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ @@ -3071,7 +3071,7 @@ name = "requests-toolbelt" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "requests" }, + { name = "requests", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } wheels = [ @@ -3083,7 +3083,7 @@ name = "rfc3339-validator" version = "0.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six" }, + { name = "six", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } wheels = [ @@ -3113,7 +3113,7 @@ name = "rfc3987-syntax" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lark" }, + { name = "lark", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } wheels = [ @@ -3125,8 +3125,8 @@ name = "rich" version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, + { name = "markdown-it-py", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ @@ -3225,14 +3225,14 @@ name = "scikit-image" version = "0.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "imageio" }, - { name = "lazy-loader" }, - { name = "networkx" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "scipy" }, - { name = "tifffile" }, + { name = "imageio", marker = "sys_platform == 'linux'" }, + { name = "lazy-loader", marker = "sys_platform == 'linux'" }, + { name = "networkx", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "tifffile", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } wheels = [ @@ -3263,10 +3263,10 @@ name = "scikit-learn" version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "joblib" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "threadpoolctl" }, + { name = "joblib", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "scipy", marker = "sys_platform == 'linux'" }, + { name = "threadpoolctl", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } wheels = [ @@ -3287,7 +3287,7 @@ name = "scipy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3318,8 +3318,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography" }, - { name = "jeepney" }, + { name = "cryptography", marker = "sys_platform == 'linux'" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -3358,9 +3358,9 @@ name = "sf-tools" version = "2.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "future" }, - { name = "modopt" }, - { name = "numpy" }, + { name = "future", marker = "sys_platform == 'linux'" }, + { name = "modopt", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c2/41/6281d12b863867fc7d7db0aeb55db7ec94fb8f35873b9940edeeb3c29d0f/sf_tools-2.0.4.tar.gz", hash = "sha256:607204b7369cb381c02ba95f49f13d2733877a625026fac340dcddcca6700d24", size = 10426, upload-time = "2019-08-22T08:41:46.961Z" } @@ -3369,78 +3369,87 @@ name = "shapepipe" version = "1.1.0" source = { editable = "." } dependencies = [ - { name = "astropy" }, - { name = "astroquery" }, - { name = "canfar" }, - { name = "cs-util" }, - { name = "galsim" }, - { name = "h5py" }, - { name = "joblib" }, - { name = "matplotlib" }, - { name = "mccd" }, - { name = "modopt" }, - { name = "mpi4py" }, - { name = "ngmix" }, - { name = "numba" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "pyqt5" }, - { name = "pyqtgraph" }, - { name = "python-dateutil" }, - { name = "python-pysap" }, - { name = "reproject" }, - { name = "sf-tools" }, - { name = "skaha" }, - { name = "sqlitedict" }, - { name = "termcolor" }, - { name = "tqdm" }, - { name = "vos" }, + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "astroquery", marker = "sys_platform == 'linux'" }, + { name = "canfar", marker = "sys_platform == 'linux'" }, + { name = "cs-util", marker = "sys_platform == 'linux'" }, + { name = "galsim", marker = "sys_platform == 'linux'" }, + { name = "h5py", marker = "sys_platform == 'linux'" }, + { name = "healsparse", marker = "sys_platform == 'linux'" }, + { name = "hpgeom", marker = "sys_platform == 'linux'" }, + { name = "joblib", marker = "sys_platform == 'linux'" }, + { name = "matplotlib", marker = "sys_platform == 'linux'" }, + { name = "mccd", marker = "sys_platform == 'linux'" }, + { name = "modopt", marker = "sys_platform == 'linux'" }, + { name = "mpi4py", marker = "sys_platform == 'linux'" }, + { name = "ngmix", marker = "sys_platform == 'linux'" }, + { name = "numba", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'linux'" }, + { name = "pyqt5", marker = "sys_platform == 'linux'" }, + { name = "pyqtgraph", marker = "sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'linux'" }, + { name = "python-pysap", marker = "sys_platform == 'linux'" }, + { name = "reproject", marker = "sys_platform == 'linux'" }, + { name = "sf-tools", marker = "sys_platform == 'linux'" }, + { name = "skaha", marker = "sys_platform == 'linux'" }, + { name = "skyproj", marker = "sys_platform == 'linux'" }, + { name = "sqlitedict", marker = "sys_platform == 'linux'" }, + { name = "termcolor", marker = "sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'linux'" }, + { name = "vos", marker = "sys_platform == 'linux'" }, ] [package.optional-dependencies] dev = [ - { name = "build" }, - { name = "fitsio" }, - { name = "hypothesis" }, - { name = "ipython" }, - { name = "jupyterlab" }, - { name = "myst-parser" }, - { name = "numpydoc" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "ruff" }, - { name = "snakemake" }, - { name = "sphinx" }, - { name = "sphinx-book-theme" }, - { name = "sphinxcontrib-bibtex" }, - { name = "twine" }, + { name = "build", marker = "sys_platform == 'linux'" }, + { name = "fitsio", marker = "sys_platform == 'linux'" }, + { name = "hypothesis", marker = "sys_platform == 'linux'" }, + { name = "ipython", marker = "sys_platform == 'linux'" }, + { name = "jupyterlab", marker = "sys_platform == 'linux'" }, + { name = "matplotlib", marker = "sys_platform == 'linux'" }, + { name = "myst-parser", marker = "sys_platform == 'linux'" }, + { name = "numpydoc", marker = "sys_platform == 'linux'" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, + { name = "pytest-cov", marker = "sys_platform == 'linux'" }, + { name = "ruff", marker = "sys_platform == 'linux'" }, + { name = "skyproj", marker = "sys_platform == 'linux'" }, + { name = "snakemake", marker = "sys_platform == 'linux'" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, + { name = "sphinx-book-theme", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-bibtex", marker = "sys_platform == 'linux'" }, + { name = "twine", marker = "sys_platform == 'linux'" }, ] doc = [ - { name = "myst-parser" }, - { name = "numpydoc" }, - { name = "sphinx" }, - { name = "sphinx-book-theme" }, - { name = "sphinxcontrib-bibtex" }, + { name = "myst-parser", marker = "sys_platform == 'linux'" }, + { name = "numpydoc", marker = "sys_platform == 'linux'" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, + { name = "sphinx-book-theme", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-bibtex", marker = "sys_platform == 'linux'" }, ] fitsio = [ - { name = "fitsio" }, + { name = "fitsio", marker = "sys_platform == 'linux'" }, ] jupyter = [ - { name = "ipython" }, - { name = "jupyterlab" }, - { name = "snakemake" }, + { name = "ipython", marker = "sys_platform == 'linux'" }, + { name = "jupyterlab", marker = "sys_platform == 'linux'" }, + { name = "snakemake", marker = "sys_platform == 'linux'" }, ] lint = [ - { name = "ruff" }, + { name = "ruff", marker = "sys_platform == 'linux'" }, +] +plot = [ + { name = "matplotlib", marker = "sys_platform == 'linux'" }, + { name = "skyproj", marker = "sys_platform == 'linux'" }, ] release = [ - { name = "build" }, - { name = "twine" }, + { name = "build", marker = "sys_platform == 'linux'" }, + { name = "twine", marker = "sys_platform == 'linux'" }, ] test = [ - { name = "hypothesis" }, - { name = "pytest" }, - { name = "pytest-cov" }, + { name = "hypothesis", marker = "sys_platform == 'linux'" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, + { name = "pytest-cov", marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -3453,11 +3462,14 @@ requires-dist = [ { name = "fitsio", marker = "extra == 'fitsio'" }, { name = "galsim", specifier = ">=2.8" }, { name = "h5py" }, + { name = "healsparse" }, + { name = "hpgeom" }, { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.155.2" }, { name = "ipython", marker = "extra == 'jupyter'", specifier = ">=9.14.1" }, { name = "joblib", specifier = ">=1.4" }, { name = "jupyterlab", marker = "extra == 'jupyter'", specifier = ">=4.5.9" }, { name = "matplotlib", specifier = ">=3.10" }, + { name = "matplotlib", marker = "extra == 'plot'", specifier = ">=3.10" }, { name = "mccd", specifier = ">=1.2.4" }, { name = "modopt", specifier = ">=1.6" }, { name = "mpi4py", specifier = ">=4.1.2" }, @@ -3476,8 +3488,10 @@ requires-dist = [ { name = "reproject", specifier = ">=0.19" }, { name = "ruff", marker = "extra == 'lint'" }, { name = "sf-tools", specifier = ">=2.0.4" }, - { name = "shapepipe", extras = ["doc", "jupyter", "lint", "release", "test", "fitsio"], marker = "extra == 'dev'" }, + { name = "shapepipe", extras = ["doc", "jupyter", "lint", "plot", "release", "test", "fitsio"], marker = "extra == 'dev'" }, { name = "skaha", specifier = ">=1.7" }, + { name = "skyproj" }, + { name = "skyproj", marker = "extra == 'plot'" }, { name = "snakemake", marker = "extra == 'jupyter'", specifier = ">=9.22.0" }, { name = "sphinx", marker = "extra == 'doc'" }, { name = "sphinx-book-theme", marker = "extra == 'doc'" }, @@ -3488,7 +3502,7 @@ requires-dist = [ { name = "twine", marker = "extra == 'release'" }, { name = "vos", specifier = ">=3.6" }, ] -provides-extras = ["doc", "jupyter", "lint", "release", "test", "fitsio", "dev"] +provides-extras = ["doc", "jupyter", "lint", "plot", "release", "test", "fitsio", "dev"] [[package]] name = "shellingham" @@ -3513,19 +3527,36 @@ name = "skaha" version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "defusedxml" }, - { name = "httpx" }, - { name = "pydantic" }, - { name = "rich" }, - { name = "toml" }, - { name = "typer" }, - { name = "vos" }, + { name = "defusedxml", marker = "sys_platform == 'linux'" }, + { name = "httpx", marker = "sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'linux'" }, + { name = "toml", marker = "sys_platform == 'linux'" }, + { name = "typer", marker = "sys_platform == 'linux'" }, + { name = "vos", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/62/a733508ff88200c41459f5cdb72a797eb8795c833b4e9d51d31b5efe4073/skaha-1.7.0.tar.gz", hash = "sha256:ef9d69e7a4da8653cdbde3e62f18caef137130064ccb6fabed57c7c6f8bf8ef9", size = 54414, upload-time = "2025-05-28T21:17:03.631Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d9/d3/a3c1d569ae714ed9985adccefdfe2d6dd9f8ad7a297bd8fa4c7ed30c587b/skaha-1.7.0-py3-none-any.whl", hash = "sha256:68b0d3c925b98bf145c5f695237474fe1cea07cf9b69b4a9bae9910375dfa01a", size = 40795, upload-time = "2025-05-28T21:17:02.296Z" }, ] +[[package]] +name = "skyproj" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astropy", marker = "sys_platform == 'linux'" }, + { name = "healsparse", marker = "sys_platform == 'linux'" }, + { name = "hpgeom", marker = "sys_platform == 'linux'" }, + { name = "matplotlib", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/cf/5158151ae6fc60459f430451b612a4bf2da07ebff2a9f52a37e73715a5ee/skyproj-2.5.0.tar.gz", hash = "sha256:cb8d5115927ca43cacdb6d92f00bb4fcc8563ee71028f755203ce1752e67c140", size = 8521587, upload-time = "2026-06-26T19:51:15.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b1/1d86ee4ae4ae7a47e31cc770dc83057388ef16311498283ca90046b3bd04/skyproj-2.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cbfcbd4d3d600557ab19bb5cf79b750ec552da054e3be781bce88e996b9955d", size = 4146686, upload-time = "2026-06-26T19:51:04.011Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e9/f2dd235dcb9f6c3b2fec36ef04d7dd36aef4daf9afc4ab4390d6230c96dc/skyproj-2.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78618084c9362b62ddbfd336ca1864c94d99e2a1f26d2fbf3466fd19dc702c58", size = 4146630, upload-time = "2026-06-26T19:51:10.552Z" }, +] + [[package]] name = "slicerator" version = "1.1.0" @@ -3540,7 +3571,7 @@ name = "smart-open" version = "7.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "wrapt" }, + { name = "wrapt", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/33/7a00ac9b4a63afb4279b99a766f6cbe56c443526dcbf5db97b219e21fde9/smart_open-7.6.0.tar.gz", hash = "sha256:44717f46b5ff276fac03b88e5d13d1c416f064f3b7b081381b0fa8889004bd7e", size = 54548, upload-time = "2026-04-13T09:48:04.347Z" } wheels = [ @@ -3561,37 +3592,37 @@ name = "snakemake" version = "9.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "conda-inject" }, - { name = "configargparse" }, - { name = "connection-pool" }, - { name = "docutils" }, - { name = "dpath" }, - { name = "gitpython" }, - { name = "humanfriendly" }, - { name = "immutables" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "nbformat" }, - { name = "packaging" }, - { name = "platformdirs" }, - { name = "psutil" }, - { name = "pulp" }, - { name = "pyyaml" }, - { name = "referencing" }, - { name = "requests" }, - { name = "smart-open" }, - { name = "snakemake-interface-common" }, - { name = "snakemake-interface-executor-plugins" }, - { name = "snakemake-interface-logger-plugins" }, - { name = "snakemake-interface-report-plugins" }, - { name = "snakemake-interface-scheduler-plugins" }, - { name = "snakemake-interface-storage-plugins" }, - { name = "sqlmodel" }, - { name = "tabulate" }, - { name = "tenacity" }, - { name = "throttler" }, - { name = "wrapt" }, - { name = "yte" }, + { name = "conda-inject", marker = "sys_platform == 'linux'" }, + { name = "configargparse", marker = "sys_platform == 'linux'" }, + { name = "connection-pool", marker = "sys_platform == 'linux'" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "dpath", marker = "sys_platform == 'linux'" }, + { name = "gitpython", marker = "sys_platform == 'linux'" }, + { name = "humanfriendly", marker = "sys_platform == 'linux'" }, + { name = "immutables", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "jsonschema", marker = "sys_platform == 'linux'" }, + { name = "nbformat", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'linux'" }, + { name = "psutil", marker = "sys_platform == 'linux'" }, + { name = "pulp", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, + { name = "referencing", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, + { name = "smart-open", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-common", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-executor-plugins", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-logger-plugins", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-report-plugins", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-scheduler-plugins", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-storage-plugins", marker = "sys_platform == 'linux'" }, + { name = "sqlmodel", marker = "sys_platform == 'linux'" }, + { name = "tabulate", marker = "sys_platform == 'linux'" }, + { name = "tenacity", marker = "sys_platform == 'linux'" }, + { name = "throttler", marker = "sys_platform == 'linux'" }, + { name = "wrapt", marker = "sys_platform == 'linux'" }, + { name = "yte", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/c2/45aa858e55edbb7dbc243ed60859f4f3d92d63ecdd67a80632aa3659fe6b/snakemake-9.23.1.tar.gz", hash = "sha256:ef8d698bfce66a6669cc29df7e344b0b367fd90956c725e5ae0a79a556e8e93f", size = 6802587, upload-time = "2026-06-18T09:42:57.653Z" } wheels = [ @@ -3603,9 +3634,9 @@ name = "snakemake-interface-common" version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "argparse-dataclass" }, - { name = "configargparse" }, - { name = "packaging" }, + { name = "argparse-dataclass", marker = "sys_platform == 'linux'" }, + { name = "configargparse", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/c3/592f832f6e5d2d31f749392e48e8401b7625dec668d3d365d8d28f2b6c30/snakemake_interface_common-1.23.0.tar.gz", hash = "sha256:6ed14531a461417659364a0dd0acc51b786af4e26fc15cc5e00ff3d9fcaffacc", size = 13960, upload-time = "2026-03-08T21:54:29.251Z" } wheels = [ @@ -3617,9 +3648,9 @@ name = "snakemake-interface-executor-plugins" version = "9.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "argparse-dataclass" }, - { name = "snakemake-interface-common" }, - { name = "throttler" }, + { name = "argparse-dataclass", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-common", marker = "sys_platform == 'linux'" }, + { name = "throttler", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/50/de06b284c45a8e94fb8e4a12d5235065e78b49b8f84329dc10fe39f4b7dd/snakemake_interface_executor_plugins-9.4.0.tar.gz", hash = "sha256:9d4138897beacbaadaedad94b63f948eaeb604b7fc78f9cf65ac57f090f2c066", size = 16549, upload-time = "2026-03-08T17:04:02.644Z" } wheels = [ @@ -3631,7 +3662,7 @@ name = "snakemake-interface-logger-plugins" version = "2.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "snakemake-interface-common" }, + { name = "snakemake-interface-common", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/39/1d/a489e36b4444573cc5c2c4345ed61827be5d6480ff29c489a0b43dc9749a/snakemake_interface_logger_plugins-2.0.1.tar.gz", hash = "sha256:d639cc6b3c18993d52255d8edc59ef21b17b4e4dc6ff8777f04018fbf0b430e1", size = 14601, upload-time = "2026-03-19T15:06:31.434Z" } wheels = [ @@ -3643,7 +3674,7 @@ name = "snakemake-interface-report-plugins" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "snakemake-interface-common" }, + { name = "snakemake-interface-common", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/d6/6160ed98de665d6871dd356597dbf726688cc786e88668359ca37b7d9f54/snakemake_interface_report_plugins-1.3.0.tar.gz", hash = "sha256:fc9495298bec4e69721ab8afe6d6d88a86966fda2eeb003db56b9a88b86d5934", size = 4283, upload-time = "2025-10-31T10:52:36.55Z" } wheels = [ @@ -3655,7 +3686,7 @@ name = "snakemake-interface-scheduler-plugins" version = "2.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "snakemake-interface-common" }, + { name = "snakemake-interface-common", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/88/d9/d480807d2cfc2d132bc760d877d45ec8fbe620a24200ec4d2697c4a26031/snakemake_interface_scheduler_plugins-2.0.2.tar.gz", hash = "sha256:2797e8fa9019d983132c2b403f14d6fcd3c5ad4c8d8a66b984b4740a71cacc46", size = 8642, upload-time = "2025-10-20T13:58:12.988Z" } wheels = [ @@ -3667,11 +3698,11 @@ name = "snakemake-interface-storage-plugins" version = "4.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "humanfriendly" }, - { name = "snakemake-interface-common" }, - { name = "tenacity" }, - { name = "throttler" }, - { name = "wrapt" }, + { name = "humanfriendly", marker = "sys_platform == 'linux'" }, + { name = "snakemake-interface-common", marker = "sys_platform == 'linux'" }, + { name = "tenacity", marker = "sys_platform == 'linux'" }, + { name = "throttler", marker = "sys_platform == 'linux'" }, + { name = "wrapt", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/6e/f3c5b2d621fd6a6b78d8cfc01fef6b926fe2c277f5ed77c5e4deeacb94eb/snakemake_interface_storage_plugins-4.4.1.tar.gz", hash = "sha256:b2b5bf05318af36955ebf2ce76c921c0fb06904ca98fb30e1657d88b7b7b6945", size = 14924, upload-time = "2026-03-16T11:16:01.075Z" } wheels = [ @@ -3710,22 +3741,22 @@ name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, + { name = "alabaster", marker = "sys_platform == 'linux'" }, + { name = "babel", marker = "sys_platform == 'linux'" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "imagesize", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, + { name = "roman-numerals", marker = "sys_platform == 'linux'" }, + { name = "snowballstemmer", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-applehelp", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-devhelp", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-htmlhelp", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-jsmath", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-qthelp", marker = "sys_platform == 'linux'" }, + { name = "sphinxcontrib-serializinghtml", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ @@ -3737,8 +3768,8 @@ name = "sphinx-book-theme" version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydata-sphinx-theme" }, - { name = "sphinx" }, + { name = "pydata-sphinx-theme", marker = "sys_platform == 'linux'" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/f7/154786f3cfb7692cd7acc24b6dfe4dcd1146b66f376b17df9e47125555e9/sphinx_book_theme-1.2.0.tar.gz", hash = "sha256:4a7ebfc7da4395309ac942ddfc38fbec5c5254c3be22195e99ad12586fbda9e3", size = 443962, upload-time = "2026-03-09T23:20:30.442Z" } wheels = [ @@ -3759,10 +3790,10 @@ name = "sphinxcontrib-bibtex" version = "2.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, - { name = "pybtex" }, - { name = "pybtex-docutils" }, - { name = "sphinx" }, + { name = "docutils", marker = "sys_platform == 'linux'" }, + { name = "pybtex", marker = "sys_platform == 'linux'" }, + { name = "pybtex-docutils", marker = "sys_platform == 'linux'" }, + { name = "sphinx", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/15/6a/8e0b2c2420286389e7fed78ff361ec30e2f1d58c8560af8d64df5e7b61e0/sphinxcontrib_bibtex-2.7.0.tar.gz", hash = "sha256:fee700f7aae29bb8f654c62913f00d34ac44fc0b8ca0fa67ac922ff4453addee", size = 120669, upload-time = "2026-05-06T09:29:24.935Z" } wheels = [ @@ -3819,8 +3850,8 @@ name = "sqlalchemy" version = "2.0.49" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, - { name = "typing-extensions" }, + { name = "greenlet", marker = "(platform_machine == 'AMD64' and sys_platform == 'linux') or (platform_machine == 'WIN32' and sys_platform == 'linux') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'amd64' and sys_platform == 'linux') or (platform_machine == 'ppc64le' and sys_platform == 'linux') or (platform_machine == 'win32' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } wheels = [ @@ -3858,8 +3889,8 @@ name = "sqlmodel" version = "0.0.37" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic" }, - { name = "sqlalchemy" }, + { name = "pydantic", marker = "sys_platform == 'linux'" }, + { name = "sqlalchemy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/26/1d2faa0fd5a765267f49751de533adac6b9ff9366c7c6e7692df4f32230f/sqlmodel-0.0.37.tar.gz", hash = "sha256:d2c19327175794faf50b1ee31cc966764f55b1dedefc046450bc5741a3d68352", size = 85527, upload-time = "2026-02-21T16:39:47.038Z" } wheels = [ @@ -3871,9 +3902,9 @@ name = "stack-data" version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, + { name = "asttokens", marker = "sys_platform == 'linux'" }, + { name = "executing", marker = "sys_platform == 'linux'" }, + { name = "pure-eval", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } wheels = [ @@ -3907,7 +3938,7 @@ name = "sympy" version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mpmath" }, + { name = "mpmath", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } wheels = [ @@ -3946,8 +3977,8 @@ name = "terminado" version = "0.18.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "os_name != 'nt'" }, - { name = "tornado" }, + { name = "ptyprocess", marker = "os_name != 'nt' and sys_platform == 'linux'" }, + { name = "tornado", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } wheels = [ @@ -3977,7 +4008,7 @@ name = "tifffile" version = "2026.4.11" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d7/4a/e687f5957fead200faad58dbf9c9431a2bbb118040e96f5fb8a55f7ebc50/tifffile-2026.4.11.tar.gz", hash = "sha256:17758ff0c0d4db385792a083ad3ca51fcb0f4d942642f4d8f8bc1287fdcf17bc", size = 394956, upload-time = "2026-04-12T01:57:28.793Z" } wheels = [ @@ -3989,7 +4020,7 @@ name = "tinycss2" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings" }, + { name = "webencodings", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } wheels = [ @@ -4049,15 +4080,15 @@ name = "twine" version = "6.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "id" }, - { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, - { name = "packaging" }, - { name = "readme-renderer" }, - { name = "requests" }, - { name = "requests-toolbelt" }, - { name = "rfc3986" }, - { name = "rich" }, - { name = "urllib3" }, + { name = "id", marker = "sys_platform == 'linux'" }, + { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x' and sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "readme-renderer", marker = "sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'linux'" }, + { name = "requests-toolbelt", marker = "sys_platform == 'linux'" }, + { name = "rfc3986", marker = "sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'linux'" }, + { name = "urllib3", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } wheels = [ @@ -4069,10 +4100,10 @@ name = "typer" version = "0.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, + { name = "annotated-doc", marker = "sys_platform == 'linux'" }, + { name = "click", marker = "sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'linux'" }, + { name = "shellingham", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/27/ede8cec7596e0041ba7e7b80b47d132562f56ff454313a16f6084e555c9f/typer-0.25.0.tar.gz", hash = "sha256:123eaf9f19bb40fd268310e12a542c0c6b4fab9c98d9d23342a01ff95e3ce930", size = 120150, upload-time = "2026-04-26T08:46:14.767Z" } wheels = [ @@ -4093,7 +4124,7 @@ name = "typing-inspection" version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ @@ -4132,9 +4163,9 @@ name = "vos" version = "3.6.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aenum" }, - { name = "cadcutils" }, - { name = "html2text" }, + { name = "aenum", marker = "sys_platform == 'linux'" }, + { name = "cadcutils", marker = "sys_platform == 'linux'" }, + { name = "html2text", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3e/6c/445bbedf2f90e71c09ff890aa8b409af5d074b1578eef383117cbcdfd09a/vos-3.6.4.tar.gz", hash = "sha256:67e4e24689ee881124b6670d7d2fc6527cc404687fc527fffc20cf5b1bd4c0bd", size = 103445, upload-time = "2025-12-12T00:20:56.398Z" } wheels = [ @@ -4221,9 +4252,9 @@ name = "yte" version = "1.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "argparse-dataclass" }, - { name = "dpath" }, - { name = "pyyaml" }, + { name = "argparse-dataclass", marker = "sys_platform == 'linux'" }, + { name = "dpath", marker = "sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/f5/7e44620e6e077bfe624b9a17c329b8e0d0159e176e1f1a93c2790428ab2c/yte-1.9.4.tar.gz", hash = "sha256:86a47e6d722cec9419a7ac88be57d0d6c4ce28f02860393b71a66f2c674069f6", size = 8101, upload-time = "2025-11-27T12:55:00.85Z" } wheels = [ @@ -4235,12 +4266,12 @@ name = "zarr" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "donfig" }, - { name = "google-crc32c" }, - { name = "numcodecs" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "typing-extensions" }, + { name = "donfig", marker = "sys_platform == 'linux'" }, + { name = "google-crc32c", marker = "sys_platform == 'linux'" }, + { name = "numcodecs", marker = "sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/5a/b8a0cf39a14c770c30bd1f2d120c54000c8cd9e84e8e79f38d9a7ce58071/zarr-3.1.6.tar.gz", hash = "sha256:d95e72cbea4b90e9a70679468b8266400331756232576ae2b43400ac5108d0eb", size = 386531, upload-time = "2026-03-23T17:25:18.748Z" } wheels = [