From f27d4c2b426f47fd6b8a77b678ae6c373ee5ca4b Mon Sep 17 00:00:00 2001 From: Lucie Baumont Date: Mon, 9 Jan 2023 14:07:52 +0100 Subject: [PATCH 01/80] ngmix update runs, with issues --- environment.yml | 2 +- shapepipe/modules/ngmix_package/ngmix.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/environment.yml b/environment.yml index 24f6dfd5f..1f79387a1 100644 --- a/environment.yml +++ b/environment.yml @@ -14,6 +14,7 @@ dependencies: - matplotlib==3.5.1 - numba==0.54.1 - pandas==1.4.1 + - ngmix==2.3.0 - pip: - mccd==1.2.3 - modopt==1.6.0 @@ -27,5 +28,4 @@ dependencies: - termcolor==1.1.0 - tqdm==4.63.0 - treecorr==4.2.6 - - git+https://github.com/aguinot/ngmix@stable_version - git+https://github.com/tobias-liaudat/Stile@v0.1 diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index 7cb614a42..ac3c2508c 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -13,7 +13,6 @@ import numpy as np from astropy.io import fits from modopt.math.stats import sigma_mad -from ngmix.fitting import LMSimple from ngmix.observation import MultiBandObsList, Observation, ObsList from numpy.random import uniform as urand from sqlitedict import SqliteDict @@ -134,13 +133,14 @@ def get_prior(self): Returns ------- ngmix.priors - Priors for the different parameters + Priors for the different parameters (ellipticity,center, size, flux) """ # Prior on ellipticity. Details do not matter, as long # as it regularizes the fit. From Bernstein & Armstrong 2014 g_sigma = 0.4 - g_prior = ngmix.priors.GPriorBA(g_sigma) + rng = np.random.RandomState(932) + g_prior = ngmix.priors.GPriorBA(g_sigma,rng) # 2-d Gaussian prior on the center row and column center # (relative to the center of the jacobian, which @@ -148,19 +148,19 @@ def get_prior(self): # Units same as jacobian, probably arcsec row, col = 0.0, 0.0 row_sigma, col_sigma = self._pixel_scale, self._pixel_scale - cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma) + cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma, rng) # Size prior. Instead of flat, two-sided error function (TwoSidedErf) # could be used Tminval = -10.0 # arcsec squared Tmaxval = 1.e6 - T_prior = ngmix.priors.FlatPrior(Tminval, Tmaxval) + T_prior = ngmix.priors.FlatPrior(Tminval, Tmaxval, rng) # Flux prior. Bounds need to make sense for # images in question Fminval = -1.e4 Fmaxval = 1.e9 - F_prior = ngmix.priors.FlatPrior(Fminval, Fmaxval) + F_prior = ngmix.priors.FlatPrior(Fminval, Fmaxval, rng) # Joint prior, combine all individual priors prior = ngmix.joint_prior.PriorSimpleSep( @@ -640,7 +640,7 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): guess[5:] *= (1 + urand(low=-limit, high=limit)) fres['flags'] = 1 try: - fitter = ngmix.galsimfit.GalsimSimple( + fitter = ngmix.fitting.GalsimFitter( obs, model, prior=prior, From b36b92b402af640caff25b86c9d2e40a41c70b4c Mon Sep 17 00:00:00 2001 From: Lucie Baumont Date: Thu, 12 Jan 2023 14:35:02 +0100 Subject: [PATCH 02/80] still debugging ngmix --- shapepipe/modules/ngmix_package/ngmix.py | 101 +++++++++++++++-------- 1 file changed, 68 insertions(+), 33 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index ac3c2508c..b8beeb7b3 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -102,8 +102,9 @@ def __init__( def MegaCamFlip(self, vign, ccd_nb): """Flip for MegaCam. - MegaCam has CCDs that are upside down. This function flips the - postage stamps in these CCDs. + MegaPipe has CCDs that are upside down. This function flips the + postage stamps in these CCDs. TO DO: This will give incorrect results + when used with THELI ccds. Fix this. Parameters ---------- @@ -121,6 +122,7 @@ def MegaCamFlip(self, vign, ccd_nb): if ccd_nb < 18 or ccd_nb in [36, 37]: # swap x axis so origin is on top-right return np.rot90(vign, k=2) + print('rotating megapipe image') else: # swap y axis so origin is on bottom-left return vign @@ -129,19 +131,22 @@ def get_prior(self): """Get Prior. Return prior for the different parameters. + Prior on ellipticity is from Bernstein and Armstrong 2014. + Prior on centers is a 2d gaussian. + Priors on size and flux are flat. + Returns ------- ngmix.priors Priors for the different parameters (ellipticity,center, size, flux) - """ # Prior on ellipticity. Details do not matter, as long # as it regularizes the fit. From Bernstein & Armstrong 2014 g_sigma = 0.4 rng = np.random.RandomState(932) g_prior = ngmix.priors.GPriorBA(g_sigma,rng) - + print('getting prior') # 2-d Gaussian prior on the center row and column center # (relative to the center of the jacobian, which # would be zero) and the sigma of the Gaussians. @@ -175,7 +180,7 @@ def get_prior(self): def compile_results(self, results): """Compile Results. - Prepare the results of NGMIX before saving. + Prepare the results of NGMIX before saving. TODO: add snr_r and T_r Parameters ---------- @@ -186,6 +191,7 @@ def compile_results(self, results): ------- dict Compiled results ready to be written to a file + note: psfo is the original image psf from psfex or mccd Raises ------ @@ -330,13 +336,20 @@ def process(self): """Process. Funcion to processs NGMIX. - + organizes object cutouts from detection catalog in image, + weight, and flag files + per object: + gathers wcs and psf info from exposures + background subtracts + scales by relative zeropoints + runs metacal convolutions and ngmix fitting Returns ------- dict Dictionary containing the NGMIX metacal results """ + # sextractor detection catalog for the tile tile_cat = file_io.FITSCatalogue( self._tile_cat_path, SEx_catalogue=True, @@ -363,7 +376,6 @@ def process(self): id_last = -1 for i_tile, id_tmp in enumerate(obj_id): - if self._id_obj_min > 0 and id_tmp < self._id_obj_min: continue if self._id_obj_max > 0 and id_tmp > self._id_obj_max: @@ -386,6 +398,7 @@ def process(self): or (gal_vign_cat[str(id_tmp)] == 'empty') ): continue + #identify exposure and ccd number from psf catalog psf_expccd_name = list(psf_vign_cat[str(id_tmp)].keys()) for expccd_name_tmp in psf_expccd_name: exp_name, ccd_n = re.split('-', expccd_name_tmp) @@ -395,12 +408,12 @@ def process(self): ) if len(np.where(gal_vign_tmp.ravel() == 0)[0]) != 0: continue - + # background subtraction bkg_vign_tmp = ( bkg_vign_cat[str(id_tmp)][expccd_name_tmp]['VIGNET'] ) gal_vign_sub_bkg = gal_vign_tmp - bkg_vign_tmp - + # skip this step for THELI tile_vign_tmp = ( Ngmix.MegaCamFlip(np.copy(tile_vign[i_tile]), int(ccd_n)) ) @@ -426,6 +439,7 @@ def process(self): header_tmp = fits.Header.fromstring( f_wcs_file[exp_name][int(ccd_n)]['header'] ) + #rescale images and weights by relative flux scale Fscale = header_tmp['FSCALE'] gal_vign_scaled = gal_vign_sub_bkg * Fscale @@ -443,10 +457,13 @@ def process(self): weight_vign.append(weight_vign_scaled) flag_vign.append(flag_vign_tmp) jacob_list.append(jacob_tmp) - + + #if object is observed, carry out metacal operations and run ngmix if len(gal_vign) == 0: + print('gal_vign vanishes') continue try: + print('gal_vign exists') res = do_ngmix_metacal( gal_vign, psf_vign, @@ -457,6 +474,7 @@ def process(self): prior, self._pixel_scale ) + except Exception as ee: self._w_log.info( f'ngmix failed for object ID={id_tmp}.\nMessage: {ee}' @@ -495,7 +513,7 @@ def get_guess( guess_centroid=True, guess_centroid_unit='sky' ): - r"""Get Guess. + r"""Get Guess using galsim hsm shapes. Get the guess vector for the NGMIX shape measurement ``[center_x, center_y, g1, g2, size_T, flux]``. @@ -542,7 +560,7 @@ def get_guess( hsm_shape = galsim.hsm.FindAdaptiveMom(galsim_img, strict=False) error_msg = hsm_shape.error_message - + if error_msg != '': raise galsim.hsm.GalSimHSMError( f'Error in adaptive moments :\n{error_msg}' @@ -605,7 +623,7 @@ def get_guess( def make_galsimfit(obs, model, guess0, prior=None, ntry=5): """Make GalSim Fit. - Fit image using simple GalSim model. + wrapper for ngmix image fit using simple GalSim model. Parameters ---------- @@ -616,7 +634,7 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): guess0 : numpy.ndarray Parameters of first model guess prior : ngmix.prior, optional - Prior for fit paraemeters + Prior for fit parameters ntry : int, optional Number of tries for fit, the default is ``5`` @@ -631,26 +649,33 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): Failure to bootstrap galaxy """ + print('running make_galsimfit') limit = 0.1 guess = np.copy(guess0) + print('here is the guess') + print(guess) fres = {} for it in range(ntry): guess[0:5] += urand(low=-limit, high=limit) guess[5:] *= (1 + urand(low=-limit, high=limit)) fres['flags'] = 1 - try: - fitter = ngmix.fitting.GalsimFitter( - obs, - model, + OLD_LM_PARS = {"maxfev": 1000, "ftol": 1.0e6, "xtol": 1.0e-6} + #try: + fitter = ngmix.fitting.GalsimFitter( + model=model, prior=prior, + fit_pars=OLD_LM_PARS ) - fitter.go(guess) - fres = fitter.get_result() - except Exception: - continue + print('testing fitter') + fres = fitter.go(obs=obs,guess=guess) + print(fres) + #except Exception: + # print('bwahahahaha galsimfitter is failing bad') + # continue if fres['flags'] == 0: + print('ohno') break if fres['flags'] != 0: @@ -659,7 +684,7 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): ) fres['ntry'] = it + 1 - + print(fres['flags']) return fres @@ -722,7 +747,6 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): """ img_shape = gal.shape - m_weight = weight != 0 sig_tmp = sigma_mad(gal[m_weight]) @@ -738,7 +762,8 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): m_weight = weight[gauss_win < thresh * sig_tmp] != 0 sig_noise = sigma_mad(gal[gauss_win < thresh * sig_tmp][m_weight]) - + print('this is the noise') + print( sig_noise) return sig_noise @@ -754,13 +779,13 @@ def do_ngmix_metacal( ): """Do Ngmix Metacal. - Perform the metacalibration on a multi-epoch object and return the joint + Performs metacalibration on a multi-epoch object and return the joint shape measurement with NGMIX. Parameters ---------- gals : list - List of the galaxy vignets + List of the galaxy vignets. List indices run over epochs psfs : list List of the PSF vignets psfs_sigma : list @@ -787,7 +812,7 @@ def do_ngmix_metacal( if n_epoch == 0: raise ValueError("0 epoch to process") - # Make observation + # Construct observation objects to pass to ngmix gal_obs_list = ObsList() T_guess_psf = [] psf_res_gT = { @@ -806,23 +831,29 @@ def do_ngmix_metacal( col=(psfs[0].shape[1] - 1) / 2, wcs=jacob_list[n_e] ) - + # psf observation is part of ngmix observation psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) - + # convert sigma to T psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale weight_map = np.copy(weights[n_e]) weight_map[np.where(flags[n_e] != 0)] = 0. weight_map[weight_map != 0] = 1 + # fit gaussian to psf psf_guess = np.array([0., 0., 0., 0., psf_T, 1.]) + print('psfguess') try: + print('run galsimfit for psf') psf_res = make_galsimfit(psf_obs, 'gauss', psf_guess) - except Exception: - continue + print('galsimfit ran') + except Exception as e: print(e) + #print('galsimfit failed') + #continue # Gal guess try: + print('gal_guess_tmp defined') gal_guess_tmp = get_guess( gals[n_e], pixel_scale, @@ -840,6 +871,9 @@ def do_ngmix_metacal( ) # Noise handling + # Megapipe noise images are somewhat mysterious? This code combines + # the flag information, then sets all non-zero weights to 1. It then + # rescales the weights by the inverse variance. if gal_guess_flag: sig_noise = get_noise( gals[n_e], @@ -849,7 +883,7 @@ def do_ngmix_metacal( ) else: sig_noise = sigma_mad(gals[n_e]) - + print('sig_noise') noise_img = np.random.randn(*gals[n_e].shape) * sig_noise noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise @@ -861,6 +895,7 @@ def do_ngmix_metacal( # Original PSF fit w_tmp = np.sum(weight_map) + print(w_tmp) psf_res_gT['g_PSFo'] += psf_res['g'] * w_tmp psf_res_gT['g_err_PSFo'] += np.array([ psf_res['pars_err'][2], From 56e60ba616ff5fee1e65bd9f33ffdcc9e4d82984 Mon Sep 17 00:00:00 2001 From: Lucie Baumont Date: Tue, 17 Jan 2023 12:33:11 +0100 Subject: [PATCH 03/80] change T to r50 --- shapepipe/modules/ngmix_package/ngmix.py | 99 +++++++++++++----------- 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index b8beeb7b3..6be384c22 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -157,9 +157,9 @@ def get_prior(self): # Size prior. Instead of flat, two-sided error function (TwoSidedErf) # could be used - Tminval = -10.0 # arcsec squared - Tmaxval = 1.e6 - T_prior = ngmix.priors.FlatPrior(Tminval, Tmaxval, rng) + r50minval = -10.0 # arcsec squared + r50maxval = 1.e6 + r50_prior = ngmix.priors.FlatPrior(r50minval, r50maxval, rng) # Flux prior. Bounds need to make sense for # images in question @@ -171,7 +171,7 @@ def get_prior(self): prior = ngmix.joint_prior.PriorSimpleSep( cen_prior, g_prior, - T_prior, + r50_prior, F_prior ) @@ -192,6 +192,7 @@ def compile_results(self, results): dict Compiled results ready to be written to a file note: psfo is the original image psf from psfex or mccd + T is NOT 2*sigma^2, T is r50! Raises ------ @@ -207,17 +208,17 @@ def compile_results(self, results): 'ntry_fit', 'g1_psfo_ngmix', 'g2_psfo_ngmix', - 'T_psfo_ngmix', + 'r50_psfo_ngmix', 'g1_err_psfo_ngmix', 'g2_err_psfo_ngmix', - 'T_err_psfo_ngmix', + 'r50_err_psfo_ngmix', 'g1', 'g1_err', 'g2', 'g2_err', - 'T', - 'T_err', - 'Tpsf', + 'r50', + 'r50_err', + 'r50psf', 'g1_psf', 'g2_psf', 'flux', @@ -263,11 +264,11 @@ def compile_results(self, results): output_dict[name]['g2_err_psfo_ngmix'].append( results[idx]['g_err_PSFo'][1] ) - output_dict[name]['T_psfo_ngmix'].append( - results[idx]['T_PSFo'] + output_dict[name]['r50_psfo_ngmix'].append( + results[idx]['r50_PSFo'] ) - output_dict[name]['T_err_psfo_ngmix'].append( - results[idx]['T_err_PSFo'] + output_dict[name]['r50_err_psfo_ngmix'].append( + results[idx]['r50_err_PSFo'] ) output_dict[name]['g1'].append(results[idx][name]['g'][0]) output_dict[name]['g1_err'].append( @@ -277,9 +278,9 @@ def compile_results(self, results): output_dict[name]['g2_err'].append( results[idx][name]['pars_err'][3] ) - output_dict[name]['T'].append(results[idx][name]['T']) - output_dict[name]['T_err'].append(results[idx][name]['T_err']) - output_dict[name]['Tpsf'].append(results[idx][name]['Tpsf']) + output_dict[name]['r50'].append(results[idx][name]['r50']) + output_dict[name]['r50_err'].append(results[idx][name]['r50_err']) + output_dict[name]['r50psf'].append(results[idx][name]['r50psf']) output_dict[name]['g1_psf'].append( results[idx][name]['gpsf'][0] ) @@ -440,6 +441,7 @@ def process(self): f_wcs_file[exp_name][int(ccd_n)]['header'] ) #rescale images and weights by relative flux scale + # this should be it's own function Fscale = header_tmp['FSCALE'] gal_vign_scaled = gal_vign_sub_bkg * Fscale @@ -460,10 +462,8 @@ def process(self): #if object is observed, carry out metacal operations and run ngmix if len(gal_vign) == 0: - print('gal_vign vanishes') continue try: - print('gal_vign exists') res = do_ngmix_metacal( gal_vign, psf_vign, @@ -504,7 +504,7 @@ def process(self): self.save_results(res_dict) -def get_guess( +def get_guess_shapeHSM( img, pixel_scale, guess_flux_unit='img', @@ -660,12 +660,11 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): guess[0:5] += urand(low=-limit, high=limit) guess[5:] *= (1 + urand(low=-limit, high=limit)) fres['flags'] = 1 - OLD_LM_PARS = {"maxfev": 1000, "ftol": 1.0e6, "xtol": 1.0e-6} + #OLD_LM_PARS = {"maxfev": 1000, "ftol": 1.0e6, "xtol": 1.0e-6} #try: fitter = ngmix.fitting.GalsimFitter( model=model, - prior=prior, - fit_pars=OLD_LM_PARS + prior=prior ) print('testing fitter') fres = fitter.go(obs=obs,guess=guess) @@ -733,7 +732,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): Weight image guess : list Gaussian parameters fot the window function - ``[x0, y0, g1, g2, T, flux]`` + ``[x0, y0, g1, g2, r50, flux]`` pixel_scale : float Pixel scale of the galaxy image thresh : float, optional @@ -814,12 +813,12 @@ def do_ngmix_metacal( # Construct observation objects to pass to ngmix gal_obs_list = ObsList() - T_guess_psf = [] + r50_guess_psf = [] psf_res_gT = { 'g_PSFo': np.array([0., 0.]), 'g_err_PSFo': np.array([0., 0.]), - 'T_PSFo': 0., - 'T_err_PSFo': 0. + 'r50_PSFo': 0., + 'r50_err_PSFo': 0. } gal_guess = [] gal_guess_flag = True @@ -833,15 +832,17 @@ def do_ngmix_metacal( ) # psf observation is part of ngmix observation psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) - # convert sigma to T - psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale + # convert sigma to T, which is the half-light radius + # NOT the usual definition T=2*sigma^2 + psf_r50 = psfs_sigma[n_e] * 1.17741 * pixel_scale + # integrate flag info into weights weight_map = np.copy(weights[n_e]) weight_map[np.where(flags[n_e] != 0)] = 0. - weight_map[weight_map != 0] = 1 + #weight_map[weight_map != 0] = 1 # fit gaussian to psf - psf_guess = np.array([0., 0., 0., 0., psf_T, 1.]) + psf_guess = np.array([0., 0., 0., 0., psf_r50, 1.]) print('psfguess') try: print('run galsimfit for psf') @@ -854,7 +855,7 @@ def do_ngmix_metacal( # Gal guess try: print('gal_guess_tmp defined') - gal_guess_tmp = get_guess( + gal_guess_tmp = get_guess_shapeHSM( gals[n_e], pixel_scale, guess_size_type='sigma' @@ -870,10 +871,10 @@ def do_ngmix_metacal( wcs=jacob_list[n_e] ) - # Noise handling - # Megapipe noise images are somewhat mysterious? This code combines - # the flag information, then sets all non-zero weights to 1. It then - # rescales the weights by the inverse variance. + # Noise handling: this should be it's own function + # This code combines integrates flag information into the weights. + # Both THELI and Megapipe weights must be rescaled by the + # inverse variance because ngmix expects inverse variance maps. if gal_guess_flag: sig_noise = get_noise( gals[n_e], @@ -901,8 +902,8 @@ def do_ngmix_metacal( psf_res['pars_err'][2], psf_res['pars_err'][3] ]) * w_tmp - psf_res_gT['T_PSFo'] += psf_res['T'] * w_tmp - psf_res_gT['T_err_PSFo'] += psf_res['T_err'] * w_tmp + psf_res_gT['r50_PSFo'] += psf_res['r50'] * w_tmp + psf_res_gT['r50_err_PSFo'] += psf_res['r50_err'] * w_tmp wsum += w_tmp gal_obs = Observation( @@ -918,7 +919,7 @@ def do_ngmix_metacal( gal_guess.append(gal_guess_tmp) gal_obs_list.append(gal_obs) - T_guess_psf.append(psf_T) + r50_guess_psf.append(psf_r50) gal_guess_flag = True if wsum == 0: @@ -950,11 +951,17 @@ def do_ngmix_metacal( 'use_noise_image': True } - Tguess = np.mean(T_guess_psf) + r50guess = np.mean(r50_guess_psf) # retry the fit twice ntry = 2 - + ###### put in options for ngmix.metacal.metacal_bootstrap obs, + # runner, + # psf_runner=None, + # ignore_failed_psf=True, + # rng=None, + # **metacal_kws +#):) obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) res = {'mcal_flags': 0} @@ -987,24 +994,24 @@ def do_ngmix_metacal( psf_res = make_galsimfit( obs.psf_nopix, psf_model, - np.array([0., 0., 0., 0., Tguess, 1.]), + np.array([0., 0., 0., 0., r50guess, 1.]), ntry=ntry ) except Exception: continue g1, g2 = psf_res['g'] - T = psf_res['T'] + r50 = psf_res['r50'] else: try: psf_res = make_galsimfit( obs.psf, psf_model, - np.array([0., 0., 0., 0., Tguess, 1.]), + np.array([0., 0., 0., 0., r50guess, 1.]), ) except Exception: continue g1, g2 = psf_res['g'] - T = psf_res['T'] + r50 = psf_res['r50'] # TODO we sometimes use other weights twsum = obs.weight.sum() @@ -1012,11 +1019,11 @@ def do_ngmix_metacal( wsum += twsum gpsf_sum[0] += g1 * twsum gpsf_sum[1] += g2 * twsum - Tpsf_sum += T * twsum + r50psf_sum += r50 * twsum npsf += 1 tres['gpsf'] = gpsf_sum / wsum - tres['Tpsf'] = Tpsf_sum / wsum + tres['r50psf'] = r50psf_sum / wsum res[key] = tres From d86c88a70c418c3ac431de98e92becd20001de83 Mon Sep 17 00:00:00 2001 From: Lucie Baumont Date: Tue, 17 Jan 2023 12:43:53 +0100 Subject: [PATCH 04/80] added r50 as galaxy guess option --- shapepipe/modules/ngmix_package/ngmix.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index 6be384c22..1b99e61ca 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -530,8 +530,8 @@ def get_guess_shapeHSM( returns the flux in :math:`{\rm arcsec}^{-2}` guess_size_type : str If ``T`` returns the size in quadrupole moments definition - :math:`2\sigma^2`, otherwise if ``sigma`` returns the moments - :math:`\sigma` + :math:`2\sigma^2`, if ``sigma`` returns the moments + :math:`\sigma`, if ``r50`` returns the half light radius guess_size_unit : str If ``img`` returns the size in pixel units, otherwise if ``sky`` returns the size in arcsec @@ -590,6 +590,13 @@ def get_guess_shapeHSM( guess_size = hsm_shape.moments_sigma * size_unit elif guess_size_type == 'T': guess_size = 2 * (hsm_shape.moments_sigma * size_unit) ** 2 + elif guess_size_type == 'r50': + guess_size = hsm_shape.moments_sigma * size_unit * 1.17741002252 + else: + raise ValueError( + 'invalid guess_size_type \'{guess_size_type}\',' + + 'must be one of \'sigma\', \'T\', or \'r50\'' + ) if guess_centroid_unit == 'img': centroid_unit = 1 @@ -858,7 +865,7 @@ def do_ngmix_metacal( gal_guess_tmp = get_guess_shapeHSM( gals[n_e], pixel_scale, - guess_size_type='sigma' + guess_size_type='r50' ) except Exception: gal_guess_flag = False From 52e411ee976aa0809537432b1d7f8e0768ea3686 Mon Sep 17 00:00:00 2001 From: Lucie Baumont Date: Tue, 17 Jan 2023 14:25:06 +0100 Subject: [PATCH 05/80] new version of ngmix produces output. not yet tested the quality --- shapepipe/modules/ngmix_package/ngmix.py | 62 +++++++++--------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index 1b99e61ca..ef5d3c51b 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -146,7 +146,7 @@ def get_prior(self): g_sigma = 0.4 rng = np.random.RandomState(932) g_prior = ngmix.priors.GPriorBA(g_sigma,rng) - print('getting prior') + # 2-d Gaussian prior on the center row and column center # (relative to the center of the jacobian, which # would be zero) and the sigma of the Gaussians. @@ -156,7 +156,7 @@ def get_prior(self): cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma, rng) # Size prior. Instead of flat, two-sided error function (TwoSidedErf) - # could be used + # could be used, though they are not used here r50minval = -10.0 # arcsec squared r50maxval = 1.e6 r50_prior = ngmix.priors.FlatPrior(r50minval, r50maxval, rng) @@ -192,7 +192,6 @@ def compile_results(self, results): dict Compiled results ready to be written to a file note: psfo is the original image psf from psfex or mccd - T is NOT 2*sigma^2, T is r50! Raises ------ @@ -278,8 +277,8 @@ def compile_results(self, results): output_dict[name]['g2_err'].append( results[idx][name]['pars_err'][3] ) - output_dict[name]['r50'].append(results[idx][name]['r50']) - output_dict[name]['r50_err'].append(results[idx][name]['r50_err']) + output_dict[name]['r50'].append(results[idx][name]['pars'][4]) + output_dict[name]['r50_err'].append(results[idx][name]['pars_err'][4]) output_dict[name]['r50psf'].append(results[idx][name]['r50psf']) output_dict[name]['g1_psf'].append( results[idx][name]['gpsf'][0] @@ -341,7 +340,7 @@ def process(self): weight, and flag files per object: gathers wcs and psf info from exposures - background subtracts + background subtracts (make this an option) scales by relative zeropoints runs metacal convolutions and ngmix fitting Returns @@ -656,32 +655,27 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): Failure to bootstrap galaxy """ - print('running make_galsimfit') + limit = 0.1 guess = np.copy(guess0) - print('here is the guess') - print(guess) fres = {} for it in range(ntry): guess[0:5] += urand(low=-limit, high=limit) guess[5:] *= (1 + urand(low=-limit, high=limit)) fres['flags'] = 1 #OLD_LM_PARS = {"maxfev": 1000, "ftol": 1.0e6, "xtol": 1.0e-6} - #try: - fitter = ngmix.fitting.GalsimFitter( + try: + fitter = ngmix.fitting.GalsimFitter( model=model, prior=prior ) - print('testing fitter') - fres = fitter.go(obs=obs,guess=guess) - print(fres) - #except Exception: - # print('bwahahahaha galsimfitter is failing bad') - # continue + fres = fitter.go(obs=obs,guess=guess) + print(fres.items()) + except Exception: + continue if fres['flags'] == 0: - print('ohno') break if fres['flags'] != 0: @@ -690,7 +684,6 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): ) fres['ntry'] = it + 1 - print(fres['flags']) return fres @@ -768,8 +761,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): m_weight = weight[gauss_win < thresh * sig_tmp] != 0 sig_noise = sigma_mad(gal[gauss_win < thresh * sig_tmp][m_weight]) - print('this is the noise') - print( sig_noise) + return sig_noise @@ -850,18 +842,13 @@ def do_ngmix_metacal( # fit gaussian to psf psf_guess = np.array([0., 0., 0., 0., psf_r50, 1.]) - print('psfguess') try: - print('run galsimfit for psf') psf_res = make_galsimfit(psf_obs, 'gauss', psf_guess) - print('galsimfit ran') - except Exception as e: print(e) - #print('galsimfit failed') - #continue + except Exception: + continue # Gal guess try: - print('gal_guess_tmp defined') gal_guess_tmp = get_guess_shapeHSM( gals[n_e], pixel_scale, @@ -891,7 +878,7 @@ def do_ngmix_metacal( ) else: sig_noise = sigma_mad(gals[n_e]) - print('sig_noise') + noise_img = np.random.randn(*gals[n_e].shape) * sig_noise noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise @@ -901,16 +888,17 @@ def do_ngmix_metacal( weight_map *= 1 / sig_noise ** 2 - # Original PSF fit + # gaussian fit to the original psf + #(pre-counterfactual image operations) w_tmp = np.sum(weight_map) - print(w_tmp) + psf_res_gT['g_PSFo'] += psf_res['g'] * w_tmp psf_res_gT['g_err_PSFo'] += np.array([ psf_res['pars_err'][2], psf_res['pars_err'][3] ]) * w_tmp - psf_res_gT['r50_PSFo'] += psf_res['r50'] * w_tmp - psf_res_gT['r50_err_PSFo'] += psf_res['r50_err'] * w_tmp + psf_res_gT['r50_PSFo'] += psf_res['pars'][4] * w_tmp + psf_res_gT['r50_err_PSFo'] += psf_res['pars_err'][4] * w_tmp wsum += w_tmp gal_obs = Observation( @@ -953,8 +941,6 @@ def do_ngmix_metacal( 'step': 0.01, 'psf': 'gauss', 'fixnoise': True, - 'cheatnoise': False, - 'symmetrize_psf': False, 'use_noise_image': True } @@ -991,7 +977,7 @@ def do_ngmix_metacal( tres['flags'] = fres['flags'] wsum = 0 - Tpsf_sum = 0 + r50psf_sum = 0 gpsf_sum = np.zeros(2) npsf = 0 for obs in obs_dict_mcal[key]: @@ -1007,7 +993,7 @@ def do_ngmix_metacal( except Exception: continue g1, g2 = psf_res['g'] - r50 = psf_res['r50'] + r50 = psf_res['pars'][4] else: try: psf_res = make_galsimfit( @@ -1018,7 +1004,7 @@ def do_ngmix_metacal( except Exception: continue g1, g2 = psf_res['g'] - r50 = psf_res['r50'] + r50 = psf_res['pars'][4] # TODO we sometimes use other weights twsum = obs.weight.sum() From 9b7c8d7631b52670e2592c9c87c206e8213cf609 Mon Sep 17 00:00:00 2001 From: lbaumo Date: Mon, 16 Oct 2023 00:19:51 +0200 Subject: [PATCH 06/80] unfinished overhaul of ngmix module --- shapepipe/modules/ngmix_package/ngmix.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index ef5d3c51b..e7950c077 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -1020,8 +1020,6 @@ def do_ngmix_metacal( res[key] = tres - # result dictionary, keyed by the types in metacal_pars above - metacal_res = res metacal_res.update(psf_res_gT) metacal_res['moments_fail'] = fail_get_guess From 441797e20bad587a035960f10d2fd5e56fe180e8 Mon Sep 17 00:00:00 2001 From: lbaumo Date: Mon, 16 Oct 2023 00:44:04 +0200 Subject: [PATCH 07/80] ngmix module overhaul --- shapepipe/modules/ngmix_package/ngmix.py | 661 ++++++++--------------- 1 file changed, 237 insertions(+), 424 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index e7950c077..d79ce4f8c 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -2,19 +2,17 @@ This module contains a class for ngmix shape measurement. -:Author: Axel Guinot +:Authors: Lucie Baumont Axel Guinot """ import re - -import galsim import ngmix +import galsim import numpy as np from astropy.io import fits from modopt.math.stats import sigma_mad -from ngmix.observation import MultiBandObsList, Observation, ObsList -from numpy.random import uniform as urand +from ngmix.observation import Observation, ObsList from sqlitedict import SqliteDict from shapepipe.pipeline import file_io @@ -93,9 +91,10 @@ def __init__( self._w_log = w_log - # Initiatlise random generator + # Initiatlise random generator (this should be less stupid) seed = int(''.join(re.findall(r'\d+', self._file_number_string))) np.random.seed(seed) + rng = np.random.RandomState(932) self._w_log.info(f'Random generator initialisation seed = {seed}') @classmethod @@ -127,52 +126,55 @@ def MegaCamFlip(self, vign, ccd_nb): # swap y axis so origin is on bottom-left return vign - def get_prior(self): + def get_prior(self, rng, scale, T_range=None, F_range=None): """Get Prior. - Return prior for the different parameters. - Prior on ellipticity is from Bernstein and Armstrong 2014. - Prior on centers is a 2d gaussian. - Priors on size and flux are flat. - + get a prior for use with the maximum likelihood fitter + + Parameters + ---------- + rng: np.random.RandomState + The random number generator + scale: float + Pixel scale + T_range: (float, float), optional + The range for the prior on T + F_range: (float, float), optional + Fhe range for the prior on flux Returns ------- ngmix.priors Priors for the different parameters (ellipticity,center, size, flux) """ + # 2-d Gaussian prior on the object center + # centered with respect to jacobian center + # Units same as jacobian, probably arcsec + cen_prior = ngmix.priors.CenPrior(cen1=0.0, cen2=0.0, sigma1=self._pixel_scale, sigma2=self._pixel_scale, rng=rng) + # Prior on ellipticity. Details do not matter, as long # as it regularizes the fit. From Bernstein & Armstrong 2014 g_sigma = 0.4 - rng = np.random.RandomState(932) - g_prior = ngmix.priors.GPriorBA(g_sigma,rng) + g_prior = ngmix.priors.GPriorBA(sigma=g_sigma,rng=rng) - # 2-d Gaussian prior on the center row and column center - # (relative to the center of the jacobian, which - # would be zero) and the sigma of the Gaussians. - # Units same as jacobian, probably arcsec - row, col = 0.0, 0.0 - row_sigma, col_sigma = self._pixel_scale, self._pixel_scale - cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma, rng) + if T_range is None: + T_range = [-1.0, 1.e3] + if F_range is None: + F_range = [-100.0, 1.e9] - # Size prior. Instead of flat, two-sided error function (TwoSidedErf) - # could be used, though they are not used here - r50minval = -10.0 # arcsec squared - r50maxval = 1.e6 - r50_prior = ngmix.priors.FlatPrior(r50minval, r50maxval, rng) + # Flat Size prior in arcsec squared. Instead of flat, TwoSidedErf could be used + T_prior = ngmix.priors.FlatPrior(minval=T_range[0], maxval=T_range[1], rng=rng) - # Flux prior. Bounds need to make sense for + # Flat Flux prior. Bounds need to make sense for # images in question - Fminval = -1.e4 - Fmaxval = 1.e9 - F_prior = ngmix.priors.FlatPrior(Fminval, Fmaxval, rng) + F_prior = ngmix.priors.FlatPrior(minval=F_range[0], maxval=F_range[1], rng=rng) # Joint prior, combine all individual priors prior = ngmix.joint_prior.PriorSimpleSep( - cen_prior, - g_prior, - r50_prior, - F_prior + cen_prior=cen_prior, + g_prior=g_prior, + T_prior=T_prior, + F_prior=F_prior, ) return prior @@ -180,8 +182,8 @@ def get_prior(self): def compile_results(self, results): """Compile Results. - Prepare the results of NGMIX before saving. TODO: add snr_r and T_r - + Prepare the results of NGMIX before saving. TO DO: add snr_r and T_r + This needs to be updated Parameters ---------- results : dict @@ -359,6 +361,7 @@ def process(self): tile_vign = np.copy(tile_cat.get_data()['VIGNET']) tile_ra = np.copy(tile_cat.get_data()['XWIN_WORLD']) tile_dec = np.copy(tile_cat.get_data()['YWIN_WORLD']) + # TO DO: get fluxes and sizes here tile_cat.close() f_wcs_file = SqliteDict(self._f_wcs_path) @@ -502,195 +505,10 @@ def process(self): # Save results self.save_results(res_dict) - -def get_guess_shapeHSM( - img, - pixel_scale, - guess_flux_unit='img', - guess_size_type='T', - guess_size_unit='sky', - guess_centroid=True, - guess_centroid_unit='sky' -): - r"""Get Guess using galsim hsm shapes. - - Get the guess vector for the NGMIX shape measurement - ``[center_x, center_y, g1, g2, size_T, flux]``. - No guesses are given for the ellipticity ``(0, 0)``. - - Parameters - ---------- - img : numpy.ndarray - Array containing the image - pixel_scale : float - Approximation of the pixel scale - guess_flux_unit : str - If ``img`` returns the flux in pixel units, otherwise if ``sky`` - returns the flux in :math:`{\rm arcsec}^{-2}` - guess_size_type : str - If ``T`` returns the size in quadrupole moments definition - :math:`2\sigma^2`, if ``sigma`` returns the moments - :math:`\sigma`, if ``r50`` returns the half light radius - guess_size_unit : str - If ``img`` returns the size in pixel units, otherwise if ``sky`` - returns the size in arcsec - guess_centroid : bool - If ``True``, will return a guess on the object centroid, otherwise if - ``False``, will return the image centre - guess_centroid_unit : str - If ``img`` returns the centroid in pixel unit, otherwise if ``sky`` - returns the centroid in arcsec - - Returns - ------- - numpy.ndarray - Return the guess array ``[center_x, center_y, g1, g2, size_T, flux]`` - - Raises - ------ - GalSimHSMError - For an error in the computation of adaptive moments - ValueError - For invalid unit guess types - - """ - galsim_img = galsim.Image(img, scale=pixel_scale) - - hsm_shape = galsim.hsm.FindAdaptiveMom(galsim_img, strict=False) - - error_msg = hsm_shape.error_message - - if error_msg != '': - raise galsim.hsm.GalSimHSMError( - f'Error in adaptive moments :\n{error_msg}' - ) - - if guess_flux_unit == 'img': - guess_flux = hsm_shape.moments_amp - elif guess_flux_unit == 'sky': - guess_flux = hsm_shape.moments_amp / pixel_scale ** 2 - else: - raise ValueError( - f'invalid guess_flux_unit \'{guess_flux_unit}\',' - + ' must be one of \'img\', \'sky\'' - ) - - if guess_size_unit == 'img': - size_unit = 1. - elif guess_size_unit == 'sky': - size_unit = pixel_scale - else: - raise ValueError( - 'invalid guess_size_unit \'{guess_size_unit}\',' - + 'must be one of \'img\', \'sky\'' - ) - - if guess_size_type == 'sigma': - guess_size = hsm_shape.moments_sigma * size_unit - elif guess_size_type == 'T': - guess_size = 2 * (hsm_shape.moments_sigma * size_unit) ** 2 - elif guess_size_type == 'r50': - guess_size = hsm_shape.moments_sigma * size_unit * 1.17741002252 - else: - raise ValueError( - 'invalid guess_size_type \'{guess_size_type}\',' - + 'must be one of \'sigma\', \'T\', or \'r50\'' - ) - - if guess_centroid_unit == 'img': - centroid_unit = 1 - elif guess_centroid_unit == 'sky': - centroid_unit = pixel_scale - else: - raise ValueError( - f'invalid guess_centroid_unit \'{guess_centroid_unit}\',' - + ' must be one of \'img\', \'sky\'' - ) - - if guess_centroid: - guess_centroid = ( - (hsm_shape.moments_centroid - galsim_img.center) * centroid_unit - ) - else: - guess_centroid = galsim_img.center * centroid_unit - - guess = np.array([ - guess_centroid.x, - guess_centroid.y, - 0., - 0., - guess_size, - guess_flux - ]) - - return guess - - -def make_galsimfit(obs, model, guess0, prior=None, ntry=5): - """Make GalSim Fit. - - wrapper for ngmix image fit using simple GalSim model. - - Parameters - ---------- - obs : ngmix.observation.Observation - Image to fit - model : str - Model for fit - guess0 : numpy.ndarray - Parameters of first model guess - prior : ngmix.prior, optional - Prior for fit parameters - ntry : int, optional - Number of tries for fit, the default is ``5`` - - Returns - ------- - dict - Results - - Raises - ------ - ngmix.BootGalFailure - Failure to bootstrap galaxy - - """ - - limit = 0.1 - - guess = np.copy(guess0) - fres = {} - for it in range(ntry): - guess[0:5] += urand(low=-limit, high=limit) - guess[5:] *= (1 + urand(low=-limit, high=limit)) - fres['flags'] = 1 - #OLD_LM_PARS = {"maxfev": 1000, "ftol": 1.0e6, "xtol": 1.0e-6} - try: - fitter = ngmix.fitting.GalsimFitter( - model=model, - prior=prior - ) - fres = fitter.go(obs=obs,guess=guess) - print(fres.items()) - except Exception: - continue - - if fres['flags'] == 0: - break - - if fres['flags'] != 0: - raise ngmix.gexceptions.BootGalFailure( - 'Failed to fit galaxy image with galsimfit' - ) - - fres['ntry'] = it + 1 - return fres - - def get_jacob(wcs, ra, dec): """Get Jacobian. - Return the Jacobian of the WCS at the required position. + TO DO: can we do this within ngmix? Parameters ---------- @@ -718,7 +536,7 @@ def get_jacob(wcs, ra, dec): def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): - r"""Get Noise. + """Get Noise. Compute the sigma of the noise from an object postage stamp. Use a guess on the object size, ellipticity and flux to create a window @@ -764,21 +582,154 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): return sig_noise +def prepare_ngmix_weights(gal,weight,flag): + """bookkeeping for ngmix weights. runs on a single galaxy and epoch + pixel scale and galaxy guess + + Parameters + ---------- + gal : numpy.ndarray + galaxy image. List indices run over epochs + weight : numpy.ndarray + weight image List indices run over epochs + flag : numpy.ndarray + flag image. List indices run over epochs + + Returns + ------- + numpy.ndarray + galaxy image where noise replaces masked regions + numpy.ndarray + variance map for NGMIX + numpy.ndarray + noise image + + """ + # integrate flag info into weights + weight_map = np.copy(weight) + weight_map[np.where(flag != 0)] = 0. + # Noise handling: this should be it's own function + # This code combines integrates flag information into the weights. + # Both THELI and Megapipe weights must be rescaled by the + # inverse variance because ngmix expects inverse variance maps. + #if gal_guess_flag: + # sig_noise = get_noise( + # gal, + # weight, + # gal_guess_tmp, + # pixel_scale, + # ) + #else: + sig_noise = sigma_mad(gal) + + noise_img = np.random.randn(*gal.shape) * sig_noise + noise_img_gal = np.random.randn(*gal.shape) * sig_noise + + gal_masked = np.copy(gal) + if (len(np.where(weight_map == 0)[0]) != 0): + gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] + + weight_map *= 1 / sig_noise ** 2 + + return gal_masked, weight_map, noise_img + +def prepare_galaxy_data(gal,weight,flag,psf,wcs): + """single galaxy and epoch to be passed to ngmix + TO DO: deal with gal_guess_tmp, pixel scale + Parameters + ---------- + gal : numpy.ndarray + List of the galaxy vignets. List indices run over epochs + weight : numpy.ndarray + List of the PSF vignets + flag : numpy.ndarray + flag image + psf : numpy.ndarray + psf vignett + wcs : numpy.ndarray + Jacobian + Returns + ------- + ngmix.observation.Observation + observation to fit using ngmix + float + sum of pixels on weight map + + """ + # prepare psf + psf_jacob = ngmix.Jacobian( + row=(psf.shape[0] - 1) / 2, + col=(psf.shape[1] - 1) / 2, + wcs=wcs + ) + + psf_obs = Observation(psf, jacobian=psf_jacob) + + # prepare weight map + gal_masked, weight_map, noise_img = prepare_ngmix_weights( + gal, + weight, + flag + ) + wsum = np.sum(weight_map) + + # Recenter jacobian if necessary + gal_jacob = ngmix.Jacobian( + row=(gal.shape[0] - 1) / 2, + col=(gal.shape[1] - 1) / 2, + wcs=wcs + ) + # define ngmix observation + gal_obs = Observation( + gal_masked, + weight=weight_map, + jacobian=gal_jacob, + psf=psf_obs, + noise=noise_img + ) + + return gal_obs, wsum + +def parse_ngmix_results(resdict,obsdict): + """ averages results over multiple epochs + + Parameters + ---------- + res : dict + dictionary of metacal fitting results + + dict_keys(['noshear', '1p', '1m', '2p', '2m']) + + dict_keys(['model', 'flags', 'nfev', 'ier', 'errmsg', 'pars', 'pars_err', 'pars_cov0', 'pars_cov', 'lnprob', 's2n_numer', 's2n_denom', 'npix', 'chi2per', 'dof', 's2n_w', 's2n', 'g', 'g_cov', 'g_err', 'T', 'T_err', 'flux', 'flux_err']) + we also want psf information + Returns + ------- + dict + Dictionary containing the results of NGMIX metacal + + """ + # include relevant psf quantities- check how they are presented for multi-epoch observations + T_psf=obsdict['noshear'].psf.meta['result']['T'] + T_psf_err=obsdict['noshear'].psf.meta['result']['g_err'] + g_psf=obsdict['noshear'].psf.meta['result']['g'] + + # result dictionary, keyed by the types in metacal_pars above + metacal_res = resdict + return metacal_res def do_ngmix_metacal( gals, psfs, - psfs_sigma, weights, flags, jacob_list, prior, - pixel_scale + pixel_scale, + rng ): """Do Ngmix Metacal. - Performs metacalibration on a multi-epoch object and return the joint - shape measurement with NGMIX. + Performs metacalibration on a sigle multi-epoch object and returns the joint shape measurement with NGMIX. Parameters ---------- @@ -786,8 +737,6 @@ def do_ngmix_metacal( List of the galaxy vignets. List indices run over epochs psfs : list List of the PSF vignets - psfs_sigma : list - List of the sigma PSFs weights : list List of the weight vignets flags : list @@ -798,6 +747,8 @@ def do_ngmix_metacal( Priors for the fitting parameters pixel_scale : float pixel scale in arcsec + rng : numpy.random.RandomState + Random state for guesses and priors Returns ------- @@ -807,221 +758,83 @@ def do_ngmix_metacal( """ n_epoch = len(gals) + # are there galaxies to fit? if n_epoch == 0: raise ValueError("0 epoch to process") + # fitting options go here, make an option for the future + psf_model = 'gauss' + gal_model = 'gauss' + # Construct observation objects to pass to ngmix gal_obs_list = ObsList() - r50_guess_psf = [] - psf_res_gT = { - 'g_PSFo': np.array([0., 0.]), - 'g_err_PSFo': np.array([0., 0.]), - 'r50_PSFo': 0., - 'r50_err_PSFo': 0. - } - gal_guess = [] - gal_guess_flag = True + # initialize galaxy weight counter wsum = 0 - for n_e in range(n_epoch): - - psf_jacob = ngmix.Jacobian( - row=(psfs[0].shape[0] - 1) / 2, - col=(psfs[0].shape[1] - 1) / 2, - wcs=jacob_list[n_e] - ) - # psf observation is part of ngmix observation - psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) - # convert sigma to T, which is the half-light radius - # NOT the usual definition T=2*sigma^2 - psf_r50 = psfs_sigma[n_e] * 1.17741 * pixel_scale - - # integrate flag info into weights - weight_map = np.copy(weights[n_e]) - weight_map[np.where(flags[n_e] != 0)] = 0. - #weight_map[weight_map != 0] = 1 - - # fit gaussian to psf - psf_guess = np.array([0., 0., 0., 0., psf_r50, 1.]) - try: - psf_res = make_galsimfit(psf_obs, 'gauss', psf_guess) - except Exception: - continue - - # Gal guess - try: - gal_guess_tmp = get_guess_shapeHSM( - gals[n_e], - pixel_scale, - guess_size_type='r50' - ) - except Exception: - gal_guess_flag = False - gal_guess_tmp = np.array([0., 0., 0., 0., 1, 100]) - - # Recenter jacobian if necessary - gal_jacob = ngmix.Jacobian( - row=(gals[0].shape[0] - 1) / 2 + gal_guess_tmp[0], - col=(gals[0].shape[1] - 1) / 2 + gal_guess_tmp[1], - wcs=jacob_list[n_e] - ) - # Noise handling: this should be it's own function - # This code combines integrates flag information into the weights. - # Both THELI and Megapipe weights must be rescaled by the - # inverse variance because ngmix expects inverse variance maps. - if gal_guess_flag: - sig_noise = get_noise( - gals[n_e], - weight_map, - gal_guess_tmp, - pixel_scale, - ) - else: - sig_noise = sigma_mad(gals[n_e]) - - noise_img = np.random.randn(*gals[n_e].shape) * sig_noise - noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise - - gal_masked = np.copy(gals[n_e]) - if (len(np.where(weight_map == 0)[0]) != 0): - gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] - - weight_map *= 1 / sig_noise ** 2 - - # gaussian fit to the original psf - #(pre-counterfactual image operations) - w_tmp = np.sum(weight_map) - - psf_res_gT['g_PSFo'] += psf_res['g'] * w_tmp - psf_res_gT['g_err_PSFo'] += np.array([ - psf_res['pars_err'][2], - psf_res['pars_err'][3] - ]) * w_tmp - psf_res_gT['r50_PSFo'] += psf_res['pars'][4] * w_tmp - psf_res_gT['r50_err_PSFo'] += psf_res['pars_err'][4] * w_tmp - wsum += w_tmp - - gal_obs = Observation( - gal_masked, - weight=weight_map, - jacobian=gal_jacob, - psf=psf_obs, - noise=noise_img + # create list of ngmix observations for each galaxy + for n_e in range(n_epoch): + gal_obs, wtmp = prepare_galaxy_data( + gals[n_e], + weights[n_e], + flags[n_e], + psfs[n_e], + jacob_list[n_e] ) - - if gal_guess_flag: - gal_guess_tmp[:2] = 0 - gal_guess.append(gal_guess_tmp) - + wsum += wtmp gal_obs_list.append(gal_obs) - r50_guess_psf.append(psf_r50) - gal_guess_flag = True - + + # keep track of weights, in case galaxy has zero weight- we may scrap this depending on how ngmix returns Tpsf if wsum == 0: raise ZeroDivisionError('Sum of weights = 0, division by zero') - # Normalize PSF fit output - for key in psf_res_gT.keys(): - psf_res_gT[key] /= wsum - - # Gal guess handling - fail_get_guess = False - if len(gal_guess) == 0: - fail_get_guess = True - gal_pars = [0., 0., 0., 0., 1, 100] - else: - gal_pars = np.mean(gal_guess, 0) + # decide on fitting options + fitter = ngmix.fitting.Fitter(model=gal_model, prior=prior) + # make parameter guesses based on a psf flux and a rough T + guesser = ngmix.guessers.TPSFFluxAndPriorGuesser( + rng=rng, + T=0.25, + prior=prior, + ) - psf_model = 'gauss' - gal_model = 'gauss' + # psf fitting a gaussian + psf_fitter = ngmix.fitting.Fitter(model=psf_model, prior=prior) + # TO DO! update flux to sextractor flux + psf_guesser = ngmix.guessers.TFluxGuesser( + rng=rng, + T=0.25, + prior=prior, + flux=100, + ) + # this runs the fitter. We set ntry=2 to retry the fit if it fails + psf_runner = ngmix.runners.PSFRunner( + fitter=psf_fitter, guesser=psf_guesser, + ntry=2, + ) + runner = ngmix.runners.Runner( + fitter=fitter, guesser=guesser, + ntry=5, + ) # metacal specific parameters metacal_pars = { 'types': ['noshear', '1p', '1m', '2p', '2m'], 'step': 0.01, - 'psf': 'gauss', + 'psf': 'fitgauss', 'fixnoise': True, 'use_noise_image': True } - r50guess = np.mean(r50_guess_psf) - - # retry the fit twice - ntry = 2 - ###### put in options for ngmix.metacal.metacal_bootstrap obs, - # runner, - # psf_runner=None, - # ignore_failed_psf=True, - # rng=None, - # **metacal_kws -#):) - obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) - res = {'mcal_flags': 0} - - ntry = 5 - - for key in sorted(obs_dict_mcal): - - fres = make_galsimfit( - obs_dict_mcal[key], - gal_model, - gal_pars, - prior=prior - ) - - res['mcal_flags'] |= fres['flags'] - tres = {} - - for name in fres.keys(): - tres[name] = fres[name] - tres['flags'] = fres['flags'] - - wsum = 0 - r50psf_sum = 0 - gpsf_sum = np.zeros(2) - npsf = 0 - for obs in obs_dict_mcal[key]: - - if hasattr(obs, 'psf_nopix'): - try: - psf_res = make_galsimfit( - obs.psf_nopix, - psf_model, - np.array([0., 0., 0., 0., r50guess, 1.]), - ntry=ntry - ) - except Exception: - continue - g1, g2 = psf_res['g'] - r50 = psf_res['pars'][4] - else: - try: - psf_res = make_galsimfit( - obs.psf, - psf_model, - np.array([0., 0., 0., 0., r50guess, 1.]), - ) - except Exception: - continue - g1, g2 = psf_res['g'] - r50 = psf_res['pars'][4] - - # TODO we sometimes use other weights - twsum = obs.weight.sum() - - wsum += twsum - gpsf_sum[0] += g1 * twsum - gpsf_sum[1] += g2 * twsum - r50psf_sum += r50 * twsum - npsf += 1 - - tres['gpsf'] = gpsf_sum / wsum - tres['r50psf'] = r50psf_sum / wsum - - res[key] = tres - - - metacal_res.update(psf_res_gT) - metacal_res['moments_fail'] = fail_get_guess - + # this "bootstrapper" runs the metacal image shearing as well as both psf + # and object measurements + boot = ngmix.metacal.MetacalBootstrapper( + metacal_pars, + runner=runner, + psf_runner=psf_runner, + ignore_failed_psf=True, + rng=rng + ) + # this is the actual fit + resdict, obsdict = boot.go(gal_obs_list) + # compile results to include psf information + metacal_res = parse_ngmix_results(resdict,obsdict) return metacal_res From 396035c587732c231c0f8b3241849037f44f574d Mon Sep 17 00:00:00 2001 From: lbaumo Date: Mon, 16 Oct 2023 11:57:55 +0200 Subject: [PATCH 08/80] ngmix update psf stuff --- shapepipe/modules/ngmix_package/ngmix.py | 55 ++++++++++++++---------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index d79ce4f8c..e2b5a1a39 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -635,7 +635,7 @@ def prepare_ngmix_weights(gal,weight,flag): def prepare_galaxy_data(gal,weight,flag,psf,wcs): """single galaxy and epoch to be passed to ngmix - TO DO: deal with gal_guess_tmp, pixel scale + TO DO: pixel scale Parameters ---------- gal : numpy.ndarray @@ -652,8 +652,6 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): ------- ngmix.observation.Observation observation to fit using ngmix - float - sum of pixels on weight map """ # prepare psf @@ -671,7 +669,6 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): weight, flag ) - wsum = np.sum(weight_map) # Recenter jacobian if necessary gal_jacob = ngmix.Jacobian( @@ -688,34 +685,49 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): noise=noise_img ) - return gal_obs, wsum + return gal_obs -def parse_ngmix_results(resdict,obsdict): - """ averages results over multiple epochs +def average_multiepoch_psf(obsdict): + """ averages psf information over multiple epochs Parameters ---------- - res : dict - dictionary of metacal fitting results + obsdict : dict + dictionary of metacal observations after fit dict_keys(['noshear', '1p', '1m', '2p', '2m']) dict_keys(['model', 'flags', 'nfev', 'ier', 'errmsg', 'pars', 'pars_err', 'pars_cov0', 'pars_cov', 'lnprob', 's2n_numer', 's2n_denom', 'npix', 'chi2per', 'dof', 's2n_w', 's2n', 'g', 'g_cov', 'g_err', 'T', 'T_err', 'flux', 'flux_err']) - we also want psf information Returns ------- dict - Dictionary containing the results of NGMIX metacal + Average psf size, shape over n_epochs """ + # create dictionary + names = ['T_psf', 'T_psf_err', 'g_psf', 'g_psf_err'] + psf_dict = {k: for k in names} # include relevant psf quantities- check how they are presented for multi-epoch observations - T_psf=obsdict['noshear'].psf.meta['result']['T'] - T_psf_err=obsdict['noshear'].psf.meta['result']['g_err'] - g_psf=obsdict['noshear'].psf.meta['result']['g'] - - # result dictionary, keyed by the types in metacal_pars above - metacal_res = resdict - return metacal_res + wsum = 0 + gpsf_sum = 0 + Tpsf_sum = 0 + for n_e in np.arange(nepoch): + T_psf=obsdict['noshear'][n_e].psf.meta['result']['T'] + T_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] + g_psf=obsdict['noshear'][n_e].psf.meta['result']['g'] + ne_wsum = obsdict['noshear'][0].weight.sum() + + # we probably want to handle cases when there is no psf + # how are we dealing with the error, what is npsf + wsum += ne_wsum + gpsf_sum += g_psf * ne_wsum + Tpsf_sum += T_psf * ne_wsum + #npsf += 1 + + psf_dict['g_psf'] = gpsf_sum / wsum + psf_dict['T_psf'] = Tpsf_sum / wsum + + return psf_dict def do_ngmix_metacal( gals, @@ -768,8 +780,6 @@ def do_ngmix_metacal( # Construct observation objects to pass to ngmix gal_obs_list = ObsList() - # initialize galaxy weight counter - wsum = 0 # create list of ngmix observations for each galaxy for n_e in range(n_epoch): @@ -780,7 +790,6 @@ def do_ngmix_metacal( psfs[n_e], jacob_list[n_e] ) - wsum += wtmp gal_obs_list.append(gal_obs) # keep track of weights, in case galaxy has zero weight- we may scrap this depending on how ngmix returns Tpsf @@ -836,5 +845,5 @@ def do_ngmix_metacal( # this is the actual fit resdict, obsdict = boot.go(gal_obs_list) # compile results to include psf information - metacal_res = parse_ngmix_results(resdict,obsdict) - return metacal_res + psf_res = average_multiepoch_psf(obsdict) + return resdict, psf_res From 139ffae4ca190d3e9465ae27a1980f4b760b32f4 Mon Sep 17 00:00:00 2001 From: lbaumo Date: Mon, 16 Oct 2023 13:09:46 +0200 Subject: [PATCH 09/80] ngmix module random numbers --- shapepipe/modules/ngmix_package/ngmix.py | 34 +++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index e2b5a1a39..b40e4b0ab 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -62,6 +62,7 @@ def __init__( pixel_scale, f_wcs_path, w_log, + rng, id_obj_min=-1, id_obj_max=-1 ): @@ -91,10 +92,9 @@ def __init__( self._w_log = w_log - # Initiatlise random generator (this should be less stupid) + # Initiatlise random generator seed = int(''.join(re.findall(r'\d+', self._file_number_string))) - np.random.seed(seed) - rng = np.random.RandomState(932) + self._rng = np.random.RandomState(seed) self._w_log.info(f'Random generator initialisation seed = {seed}') @classmethod @@ -150,7 +150,12 @@ def get_prior(self, rng, scale, T_range=None, F_range=None): # 2-d Gaussian prior on the object center # centered with respect to jacobian center # Units same as jacobian, probably arcsec - cen_prior = ngmix.priors.CenPrior(cen1=0.0, cen2=0.0, sigma1=self._pixel_scale, sigma2=self._pixel_scale, rng=rng) + cen_prior = ngmix.priors.CenPrior( + cen1=0.0, + cen2=0.0, + sigma1=self.scale, + sigma2=self.scale, + rng=rng) # Prior on ellipticity. Details do not matter, as long # as it regularizes the fit. From Bernstein & Armstrong 2014 @@ -372,7 +377,7 @@ def process(self): flag_vign_cat = SqliteDict(self._flag_vignet_path) final_res = [] - prior = self.get_prior() + prior = self.get_prior(self._rng, self._pixel_scale) count = 0 id_first = -1 @@ -466,15 +471,15 @@ def process(self): if len(gal_vign) == 0: continue try: - res = do_ngmix_metacal( + res, psf_res = do_ngmix_metacal( gal_vign, psf_vign, - sigma_psf, weight_vign, flag_vign, jacob_list, prior, - self._pixel_scale + self._pixel_scale, + self._rng ) except Exception as ee: @@ -537,7 +542,7 @@ def get_jacob(wcs, ra, dec): def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): """Get Noise. - + TO DO: modify guess, pixel scale Compute the sigma of the noise from an object postage stamp. Use a guess on the object size, ellipticity and flux to create a window function. @@ -585,6 +590,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): def prepare_ngmix_weights(gal,weight,flag): """bookkeeping for ngmix weights. runs on a single galaxy and epoch pixel scale and galaxy guess + TO DO: decide if we want galaxy guess stuff Parameters ---------- @@ -706,7 +712,7 @@ def average_multiepoch_psf(obsdict): """ # create dictionary names = ['T_psf', 'T_psf_err', 'g_psf', 'g_psf_err'] - psf_dict = {k: for k in names} + psf_dict = {k: [] for k in names} # include relevant psf quantities- check how they are presented for multi-epoch observations wsum = 0 gpsf_sum = 0 @@ -723,6 +729,8 @@ def average_multiepoch_psf(obsdict): gpsf_sum += g_psf * ne_wsum Tpsf_sum += T_psf * ne_wsum #npsf += 1 + if wsum == 0: + raise ZeroDivisionError('Sum of weights = 0, division by zero') psf_dict['g_psf'] = gpsf_sum / wsum psf_dict['T_psf'] = Tpsf_sum / wsum @@ -778,7 +786,7 @@ def do_ngmix_metacal( psf_model = 'gauss' gal_model = 'gauss' - # Construct observation objects to pass to ngmix + # Construct multi-epoch observation object to pass to ngmix gal_obs_list = ObsList() # create list of ngmix observations for each galaxy @@ -792,10 +800,6 @@ def do_ngmix_metacal( ) gal_obs_list.append(gal_obs) - # keep track of weights, in case galaxy has zero weight- we may scrap this depending on how ngmix returns Tpsf - if wsum == 0: - raise ZeroDivisionError('Sum of weights = 0, division by zero') - # decide on fitting options fitter = ngmix.fitting.Fitter(model=gal_model, prior=prior) # make parameter guesses based on a psf flux and a rough T From ccf6e1a9d430831e10c00cbe049c3b6ac622786a Mon Sep 17 00:00:00 2001 From: lbaumo Date: Tue, 17 Oct 2023 10:48:49 +0200 Subject: [PATCH 10/80] ngmix module new classes --- shapepipe/modules/ngmix_package/__init__.py | 2 +- shapepipe/modules/ngmix_package/ngmix.py | 461 +++++++++++++------- 2 files changed, 299 insertions(+), 164 deletions(-) diff --git a/shapepipe/modules/ngmix_package/__init__.py b/shapepipe/modules/ngmix_package/__init__.py index e74d71f76..d051cb5b5 100644 --- a/shapepipe/modules/ngmix_package/__init__.py +++ b/shapepipe/modules/ngmix_package/__init__.py @@ -2,7 +2,7 @@ This package contains the module for ``ngmix``. -:Author: Axel Guinot +:Author: Lucie Baumont, Axel Guinot :Parent modules: diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index b40e4b0ab..06c6351c3 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -2,7 +2,7 @@ This module contains a class for ngmix shape measurement. -:Authors: Lucie Baumont Axel Guinot +:Authors: Lucie Baumont, Axel Guinot """ @@ -17,6 +17,97 @@ from shapepipe.pipeline import file_io +class Tile_cat(object): + """Tile_cat. + + catalog measured on a tile + + Parameters + ---------- + cat_path + + """ + def __init__(self, cat_path): + self.cat_path = cat_path + # sextractor detection catalog for the tile + tile_cat = file_io.FITSCatalogue( + self.cat_path, + SEx_catalogue=True, + ) + tile_cat.open() + # I would like to make this into an object cat + self.obj_id = np.copy(tile_cat.get_data()['NUMBER']) + self.vign = np.copy(tile_cat.get_data()['VIGNET']) + self.ra = np.copy(tile_cat.get_data()['XWIN_WORLD']) + self.dec = np.copy(tile_cat.get_data()['YWIN_WORLD']) + self.flux = np.copy(tile_cat.get_data()['FLUX_AUTO']) + tile_cat.close() + +class Postage_stamp(object): + """Galaxy Postage Stamp. + + Class to hold catalog of postage stamps for a single galaxy + + Parameters + ---------- + bkg_subtraction: bool + + megacam_flip: bool + + """ + def __init__( + self, + bkg_sub=True, + megacam_flip=True + + ): + self.gals = [] + self.psfs = [] + self.weights = [] + self.flags = [] + self.jacobs = [] + self.bkg_sub = bkg_sub + self.megacam_flip = megacam_flip + +class Vignet(object): + """Vignet. + + Class to hold catalog of postage stamps + + Parameters + ---------- + gal_vignet_path + bkg_vignet_path + psf_vignet_path + weight_vignet_path + flag_vignet_path + f_wcs_path + """ + def __init__( + self, + gal_vignet_path, + bkg_vignet_path, + psf_vignet_path, + weight_vignet_path, + flag_vignet_path, + f_wcs_path + + ): + self.f_wcs_file = SqliteDict(f_wcs_path) + self.gal_vign_cat = SqliteDict(gal_vignet_path) + self.bkg_vign_cat = SqliteDict(bkg_vignet_path) + self.psf_vign_cat = SqliteDict(psf_vignet_path) + self.weight_vign_cat = SqliteDict(weight_vignet_path) + self.flag_vign_cat = SqliteDict(flag_vignet_path) + + @classmethod + def close(self): + self.f_wcs_file.close() + self.gal_vign_cat.close() + self.bkg_vign_cat.close() + self.flag_vign_cat.close() + self.weight_vign_cat.close() + self.psf_vign_cat.close() class Ngmix(object): """Ngmix. @@ -62,7 +153,6 @@ def __init__( pixel_scale, f_wcs_path, w_log, - rng, id_obj_min=-1, id_obj_max=-1 ): @@ -74,6 +164,7 @@ def __init__( ) self._tile_cat_path = input_file_list[0] + self._gal_vignet_path = input_file_list[1] self._bkg_vignet_path = input_file_list[2] self._psf_vignet_path = input_file_list[3] @@ -126,17 +217,13 @@ def MegaCamFlip(self, vign, ccd_nb): # swap y axis so origin is on bottom-left return vign - def get_prior(self, rng, scale, T_range=None, F_range=None): + def get_prior(self, T_range=None, F_range=None): """Get Prior. get a prior for use with the maximum likelihood fitter Parameters ---------- - rng: np.random.RandomState - The random number generator - scale: float - Pixel scale T_range: (float, float), optional The range for the prior on T F_range: (float, float), optional @@ -155,12 +242,13 @@ def get_prior(self, rng, scale, T_range=None, F_range=None): cen2=0.0, sigma1=self.scale, sigma2=self.scale, - rng=rng) + rng=self._rng + ) # Prior on ellipticity. Details do not matter, as long # as it regularizes the fit. From Bernstein & Armstrong 2014 g_sigma = 0.4 - g_prior = ngmix.priors.GPriorBA(sigma=g_sigma,rng=rng) + g_prior = ngmix.priors.GPriorBA(sigma=g_sigma,rng=self._rng) if T_range is None: T_range = [-1.0, 1.e3] @@ -168,11 +256,19 @@ def get_prior(self, rng, scale, T_range=None, F_range=None): F_range = [-100.0, 1.e9] # Flat Size prior in arcsec squared. Instead of flat, TwoSidedErf could be used - T_prior = ngmix.priors.FlatPrior(minval=T_range[0], maxval=T_range[1], rng=rng) + T_prior = ngmix.priors.FlatPrior( + minval=T_range[0], + maxval=T_range[1], + rng=self._rng + ) # Flat Flux prior. Bounds need to make sense for # images in question - F_prior = ngmix.priors.FlatPrior(minval=F_range[0], maxval=F_range[1], rng=rng) + F_prior = ngmix.priors.FlatPrior( + minval=F_range[0], + maxval=F_range[1], + rng=self._rng + ) # Joint prior, combine all individual priors prior = ngmix.joint_prior.PriorSimpleSep( @@ -356,140 +452,70 @@ def process(self): Dictionary containing the NGMIX metacal results """ - # sextractor detection catalog for the tile - tile_cat = file_io.FITSCatalogue( - self._tile_cat_path, - SEx_catalogue=True, + + tile_cat = Tile_cat('') + # i would like to make this into an object vignet + vignet_cat = Vignet( + self._gal_vignet_path, + self._bkg_vignet_path, + self._psf_vignet_path, + self._weight_vignet_path, + self._flag_vignet_path, + self._f_wcs_path ) - tile_cat.open() - obj_id = np.copy(tile_cat.get_data()['NUMBER']) - tile_vign = np.copy(tile_cat.get_data()['VIGNET']) - tile_ra = np.copy(tile_cat.get_data()['XWIN_WORLD']) - tile_dec = np.copy(tile_cat.get_data()['YWIN_WORLD']) - # TO DO: get fluxes and sizes here - tile_cat.close() - - f_wcs_file = SqliteDict(self._f_wcs_path) - gal_vign_cat = SqliteDict(self._gal_vignet_path) - bkg_vign_cat = SqliteDict(self._bkg_vignet_path) - psf_vign_cat = SqliteDict(self._psf_vignet_path) - weight_vign_cat = SqliteDict(self._weight_vignet_path) - flag_vign_cat = SqliteDict(self._flag_vignet_path) - + final_res = [] - prior = self.get_prior(self._rng, self._pixel_scale) + prior = self.get_prior() count = 0 id_first = -1 id_last = -1 - for i_tile, id_tmp in enumerate(obj_id): - if self._id_obj_min > 0 and id_tmp < self._id_obj_min: + for i_tile, obj_id in enumerate(tile_cat.obj_id): + # only run on objects in config file if they are specified (-1 means not set) + if self._id_obj_min > 0 and obj_id < self._id_obj_min: continue - if self._id_obj_max > 0 and id_tmp > self._id_obj_max: + if self._id_obj_max > 0 and obj_id > self._id_obj_max: continue - if id_first == -1: - id_first = id_tmp - id_last = id_tmp + id_first = obj_id + id_last = obj_id count = count + 1 - gal_vign = [] - psf_vign = [] - sigma_psf = [] - weight_vign = [] - flag_vign = [] - jacob_list = [] - if ( - (psf_vign_cat[str(id_tmp)] == 'empty') - or (gal_vign_cat[str(id_tmp)] == 'empty') - ): - continue - #identify exposure and ccd number from psf catalog - psf_expccd_name = list(psf_vign_cat[str(id_tmp)].keys()) - for expccd_name_tmp in psf_expccd_name: - exp_name, ccd_n = re.split('-', expccd_name_tmp) - - gal_vign_tmp = ( - gal_vign_cat[str(id_tmp)][expccd_name_tmp]['VIGNET'] - ) - if len(np.where(gal_vign_tmp.ravel() == 0)[0]) != 0: - continue - # background subtraction - bkg_vign_tmp = ( - bkg_vign_cat[str(id_tmp)][expccd_name_tmp]['VIGNET'] - ) - gal_vign_sub_bkg = gal_vign_tmp - bkg_vign_tmp - # skip this step for THELI - tile_vign_tmp = ( - Ngmix.MegaCamFlip(np.copy(tile_vign[i_tile]), int(ccd_n)) - ) - - flag_vign_tmp = ( - flag_vign_cat[str(id_tmp)][expccd_name_tmp]['VIGNET'] - ) - flag_vign_tmp[np.where(tile_vign_tmp == -1e30)] = 2**10 - v_flag_tmp = flag_vign_tmp.ravel() - if len(np.where(v_flag_tmp != 0)[0]) / (51 * 51) > 1 / 3.0: - continue - - weight_vign_tmp = ( - weight_vign_cat[str(id_tmp)][expccd_name_tmp]['VIGNET'] - ) - - jacob_tmp = get_jacob( - f_wcs_file[exp_name][int(ccd_n)]['WCS'], - tile_ra[i_tile], - tile_dec[i_tile] - ) - - header_tmp = fits.Header.fromstring( - f_wcs_file[exp_name][int(ccd_n)]['header'] - ) - #rescale images and weights by relative flux scale - # this should be it's own function - Fscale = header_tmp['FSCALE'] + try: - gal_vign_scaled = gal_vign_sub_bkg * Fscale - weight_vign_scaled = weight_vign_tmp * 1 / Fscale ** 2 + stamp = prepare_postage_stamps(vignet_cat) - gal_vign.append(gal_vign_scaled) - psf_vign.append( - psf_vign_cat[str(id_tmp)][expccd_name_tmp]['VIGNET'] - ) - sigma_psf.append( - psf_vign_cat[ - str(id_tmp) - ][expccd_name_tmp]['SHAPES']['SIGMA_PSF_HSM'] - ) - weight_vign.append(weight_vign_scaled) - flag_vign.append(flag_vign_tmp) - jacob_list.append(jacob_tmp) - + except Exception as ee: + #make an explicit exception here + #if ( + # (psf_vign_cat[str(obj_id)] == 'empty') + # or (gal_vign_cat[str(obj_id)] == 'empty') + #): + continue + #if object is observed, carry out metacal operations and run ngmix - if len(gal_vign) == 0: + + if len(stamp.gals) == 0: continue try: res, psf_res = do_ngmix_metacal( - gal_vign, - psf_vign, - weight_vign, - flag_vign, - jacob_list, + stamp, prior, + tile_cat.flux[i_tile], self._pixel_scale, self._rng ) except Exception as ee: self._w_log.info( - f'ngmix failed for object ID={id_tmp}.\nMessage: {ee}' + f'ngmix failed for object ID={obj_id}.\nMessage: {ee}' ) continue - res['obj_id'] = id_tmp - res['n_epoch_model'] = len(gal_vign) + res['obj_id'] = obj_id + res['n_epoch_model'] = len(stamp.gal_vign_list) final_res.append(res) self._w_log.info( @@ -497,19 +523,136 @@ def process(self): + f'objects, id first/last={id_first}/{id_last}' ) - f_wcs_file.close() - gal_vign_cat.close() - bkg_vign_cat.close() - flag_vign_cat.close() - weight_vign_cat.close() - psf_vign_cat.close() - + vignet_cat.close + # Put all results together - res_dict = self.compile_results(final_res) + res_dict = self.compile_results(final_res,psf_res) # Save results self.save_results(res_dict) +def prepare_postage_stamps(vignet, tile_cat, backgroud_subtract=True): + i_tile = tile_cat.obj_id - 1 + obj_id = tile_cat.obj_id + # define per-object lists of individual exposures to go into ngmix + stamp = Postage_stamp() + + #identify exposure and ccd number from psf catalog + psf_expccd_names = list(vignet.psf_vign_cat[str(obj_id)].keys()) + for expccd_name in psf_expccd_names: + exp_name, ccd_n = re.split('-', expccd_name) + + gal_vign = ( + vignet.gal_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + ) + + if len(np.where(gal_vign.ravel() == 0)[0]) != 0: + continue + + if stamp.bkg_sub: + bkg_vign = ( + vignet.bkg_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + ) + gal_vign_sub_bkg = background_subtract( + gal_vign, + bkg_vign + ) + else: + gal_vign_sub_bkg = gal_vign + + if stamp.megacam_flip: + tile_vign = ( + Ngmix.MegaCamFlip(np.copy(tile_vign[i_tile]), int(ccd_n)) + ) + + flag_vign = ( + vignet.flag_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + ) + flag_vign[np.where(tile_vign == -1e30)] = 2**10 + v_flag_tmp = flag_vign.ravel() + # remove objects that are more than 1/3 masked + if len(np.where(v_flag_tmp != 0)[0]) / (51 * 51) > 1 / 3.0: + continue + + weight_vign = ( + vignet.weight_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + ) + + jacob = get_jacob( + vignet.f_wcs_file[exp_name][int(ccd_n)]['WCS'], + tile_cat.ra[i_tile], + tile_cat.dec[i_tile] + ) + + header = fits.Header.fromstring( + vignet.f_wcs_file[exp_name][int(ccd_n)]['header'] + ) + + # rescale by relative zero-points + gal_vign_scaled, weight_vign_scaled = rescale_epoch_fluxes( + gal_vign_sub_bkg, + weight_vign, + header + ) + + # gather postage stamps in all of the epochs + stamp.gal_vign_list.append(gal_vign_scaled) + stamp.psf_vign_list.append( + vignet.psf_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + ) + + stamp.weight_vign_list.append(weight_vign_scaled) + stamp.flag_vign_list.append(flag_vign) + stamp.jacob_list.append(jacob) + + return stamp + +def background_subtract(gal,bkg): + """background subtraction. + + Parameters + ---------- + gal : numpy.ndarray + galaxy image + bkg : numpy.ndarray + background + + Returns + ------- + numpy.ndarray + background subtracted galaxy + """ + + # background subtraction + gal_vign_sub_bkg = gal - bkg + + return gal_vign_sub_bkg + +def rescale_epoch_fluxes(gal,weight,header): + """rescale epochs by relative zeropoints to be on the same flux scale + + Parameters + ---------- + gal : numpy.ndarray + background subtracted galaxy image + weight : numpy.ndarray + weight image + header : + + Returns + ------- + numpy.ndarray + rescaled galaxy image + numpy.ndarray + rescaled weight image + """ + Fscale = header['FSCALE'] + + gal_scaled = gal * Fscale + weight_scaled = weight * 1 / Fscale ** 2 + + return gal_scaled, weight_scaled + def get_jacob(wcs, ra, dec): """Get Jacobian. Return the Jacobian of the WCS at the required position. @@ -614,10 +757,8 @@ def prepare_ngmix_weights(gal,weight,flag): # integrate flag info into weights weight_map = np.copy(weight) weight_map[np.where(flag != 0)] = 0. - # Noise handling: this should be it's own function # This code combines integrates flag information into the weights. - # Both THELI and Megapipe weights must be rescaled by the - # inverse variance because ngmix expects inverse variance maps. + #if gal_guess_flag: # sig_noise = get_noise( # gal, @@ -693,17 +834,14 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): return gal_obs -def average_multiepoch_psf(obsdict): +def average_multiepoch_psf(obsdict,nepoch): """ averages psf information over multiple epochs - + we may need to do this for original psf as well Parameters ---------- obsdict : dict dictionary of metacal observations after fit - dict_keys(['noshear', '1p', '1m', '2p', '2m']) - - dict_keys(['model', 'flags', 'nfev', 'ier', 'errmsg', 'pars', 'pars_err', 'pars_cov0', 'pars_cov', 'lnprob', 's2n_numer', 's2n_denom', 'npix', 'chi2per', 'dof', 's2n_w', 's2n', 'g', 'g_cov', 'g_err', 'T', 'T_err', 'flux', 'flux_err']) Returns ------- dict @@ -715,56 +853,53 @@ def average_multiepoch_psf(obsdict): psf_dict = {k: [] for k in names} # include relevant psf quantities- check how they are presented for multi-epoch observations wsum = 0 - gpsf_sum = 0 - Tpsf_sum = 0 + g_psf_sum = np.array([0., 0.]) + g_psf_err_sum = np.array([0., 0.]) + T_psf_sum = 0 + T_psf_err_sum = 0 for n_e in np.arange(nepoch): T_psf=obsdict['noshear'][n_e].psf.meta['result']['T'] - T_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] + T_psf_err=obsdict['noshear'][n_e].psf.meta['result']['T_err'] g_psf=obsdict['noshear'][n_e].psf.meta['result']['g'] + g_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] ne_wsum = obsdict['noshear'][0].weight.sum() # we probably want to handle cases when there is no psf # how are we dealing with the error, what is npsf wsum += ne_wsum - gpsf_sum += g_psf * ne_wsum - Tpsf_sum += T_psf * ne_wsum + g_psf_sum += g_psf * ne_wsum + g_psf_err_sum += g_psf_err * ne_wsum + T_psf_sum += T_psf * ne_wsum + T_psf_err_sum += T_psf_err * ne_wsum #npsf += 1 if wsum == 0: raise ZeroDivisionError('Sum of weights = 0, division by zero') - psf_dict['g_psf'] = gpsf_sum / wsum - psf_dict['T_psf'] = Tpsf_sum / wsum + psf_dict['g_psf'] = g_psf_sum / wsum + psf_dict['g_psf_err'] = g_psf_err_sum / wsum + psf_dict['T_psf'] = T_psf_sum / wsum + psf_dict['T_psf_err'] = T_psf_err_sum / wsum return psf_dict def do_ngmix_metacal( - gals, - psfs, - weights, - flags, - jacob_list, + stamp, prior, - pixel_scale, + flux_guess, rng ): """Do Ngmix Metacal. Performs metacalibration on a sigle multi-epoch object and returns the joint shape measurement with NGMIX. - + TO DO: get pixel scale from jacob_list Parameters ---------- - gals : list + stamp : Postage_stamp List of the galaxy vignets. List indices run over epochs - psfs : list - List of the PSF vignets - weights : list - List of the weight vignets - flags : list - List of the flag vignets - jacob_list : list - List of the Jacobians prior : ngmix.priors Priors for the fitting parameters + flux_guess : np.ndarray + guess for flux pixel_scale : float pixel scale in arcsec rng : numpy.random.RandomState @@ -776,7 +911,7 @@ def do_ngmix_metacal( Dictionary containing the results of NGMIX metacal """ - n_epoch = len(gals) + n_epoch = len(stamp.gals) # are there galaxies to fit? if n_epoch == 0: @@ -791,12 +926,12 @@ def do_ngmix_metacal( # create list of ngmix observations for each galaxy for n_e in range(n_epoch): - gal_obs, wtmp = prepare_galaxy_data( - gals[n_e], - weights[n_e], - flags[n_e], - psfs[n_e], - jacob_list[n_e] + gal_obs = prepare_galaxy_data( + stamp.gals[n_e], + stamp.weights[n_e], + stamp.flags[n_e], + stamp.psfs[n_e], + stamp.jacobs[n_e] ) gal_obs_list.append(gal_obs) @@ -816,7 +951,7 @@ def do_ngmix_metacal( rng=rng, T=0.25, prior=prior, - flux=100, + flux=flux_guess, ) # this runs the fitter. We set ntry=2 to retry the fit if it fails From 75ddcc879a3c88c963597b4a6181644c219a11e3 Mon Sep 17 00:00:00 2001 From: lbaumo Date: Wed, 18 Oct 2023 09:57:30 +0200 Subject: [PATCH 11/80] more ngmix updates --- shapepipe/modules/ngmix_package/ngmix.py | 93 ++++++++++++++---------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index 06c6351c3..abc520214 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -27,17 +27,28 @@ class Tile_cat(object): cat_path """ - def __init__(self, cat_path): + def __init__( + self, + cat_path + ): self.cat_path = cat_path - # sextractor detection catalog for the tile + + # sextractor detection catalog for the tile + self.tile_vignet + dtype = [('obj_id','i8'),('ra',''),('dec',''),('flux','')] + self.tile_data = np.recarray(()) + + @classmethod + def get_data(self, cat_path): tile_cat = file_io.FITSCatalogue( - self.cat_path, + cat_path, SEx_catalogue=True, ) tile_cat.open() # I would like to make this into an object cat - self.obj_id = np.copy(tile_cat.get_data()['NUMBER']) self.vign = np.copy(tile_cat.get_data()['VIGNET']) + + self.obj_id = np.copy(tile_cat.get_data()['NUMBER']) self.ra = np.copy(tile_cat.get_data()['XWIN_WORLD']) self.dec = np.copy(tile_cat.get_data()['YWIN_WORLD']) self.flux = np.copy(tile_cat.get_data()['FLUX_AUTO']) @@ -50,9 +61,10 @@ class Postage_stamp(object): Parameters ---------- - bkg_subtraction: bool + bkg_sub: bool megacam_flip: bool + We probably want to put weight and flag options here too """ def __init__( @@ -164,12 +176,21 @@ def __init__( ) self._tile_cat_path = input_file_list[0] + self._vignet_cat = Vignet( + input_file_list[1], + input_file_list[2], + input_file_list[3], + input_file_list[4], + input_file_list[5], + f_wcs_path + ) + #self._gal_vignet_path = input_file_list[1] + #self._bkg_vignet_path = input_file_list[2] + #self._psf_vignet_path = input_file_list[3] + #self._weight_vignet_path = input_file_list[4] + #self._flag_vignet_path = input_file_list[5] - self._gal_vignet_path = input_file_list[1] - self._bkg_vignet_path = input_file_list[2] - self._psf_vignet_path = input_file_list[3] - self._weight_vignet_path = input_file_list[4] - self._flag_vignet_path = input_file_list[5] + self._output_dir = output_dir self._file_number_string = file_number_string @@ -177,7 +198,7 @@ def __init__( self._zero_point = zero_point self._pixel_scale = pixel_scale - self._f_wcs_path = f_wcs_path + #self._f_wcs_path = f_wcs_path self._id_obj_min = id_obj_min self._id_obj_max = id_obj_max @@ -455,15 +476,8 @@ def process(self): tile_cat = Tile_cat('') # i would like to make this into an object vignet - vignet_cat = Vignet( - self._gal_vignet_path, - self._bkg_vignet_path, - self._psf_vignet_path, - self._weight_vignet_path, - self._flag_vignet_path, - self._f_wcs_path - ) - + vignet_cat = self._vignet_cat + final_res = [] prior = self.get_prior() @@ -482,17 +496,11 @@ def process(self): id_last = obj_id count = count + 1 - + + # make postage stamp, skip if not observed try: - stamp = prepare_postage_stamps(vignet_cat) - - except Exception as ee: - #make an explicit exception here - #if ( - # (psf_vign_cat[str(obj_id)] == 'empty') - # or (gal_vign_cat[str(obj_id)] == 'empty') - #): + except AttributeError: continue #if object is observed, carry out metacal operations and run ngmix @@ -513,7 +521,7 @@ def process(self): f'ngmix failed for object ID={obj_id}.\nMessage: {ee}' ) continue - + # these things need to be considered res['obj_id'] = obj_id res['n_epoch_model'] = len(stamp.gal_vign_list) final_res.append(res) @@ -531,12 +539,16 @@ def process(self): # Save results self.save_results(res_dict) -def prepare_postage_stamps(vignet, tile_cat, backgroud_subtract=True): +def prepare_postage_stamps(vignet, tile_cat): i_tile = tile_cat.obj_id - 1 obj_id = tile_cat.obj_id # define per-object lists of individual exposures to go into ngmix stamp = Postage_stamp() - + if ( + (vignet.psf_vign_cat[str(obj_id)] == 'empty') + or (vignet.gal_vign_cat[str(obj_id)] == 'empty') + ): + raise AttributeError #identify exposure and ccd number from psf catalog psf_expccd_names = list(vignet.psf_vign_cat[str(obj_id)].keys()) for expccd_name in psf_expccd_names: @@ -578,7 +590,7 @@ def prepare_postage_stamps(vignet, tile_cat, backgroud_subtract=True): vignet.weight_vign_cat[str(obj_id)][expccd_name]['VIGNET'] ) - jacob = get_jacob( + jacob = get_galsim_jacobian( vignet.f_wcs_file[exp_name][int(ccd_n)]['WCS'], tile_cat.ra[i_tile], tile_cat.dec[i_tile] @@ -653,9 +665,9 @@ def rescale_epoch_fluxes(gal,weight,header): return gal_scaled, weight_scaled -def get_jacob(wcs, ra, dec): - """Get Jacobian. - Return the Jacobian of the WCS at the required position. +def get_galsim_jacobian(wcs, ra, dec): + """Get local wcs. + This produces a galsim jacobian at a point. We call it local_wcs because we convert to a ngmix object to create the jacobian later. TO DO: can we do this within ngmix? Parameters @@ -780,7 +792,7 @@ def prepare_ngmix_weights(gal,weight,flag): return gal_masked, weight_map, noise_img -def prepare_galaxy_data(gal,weight,flag,psf,wcs): +def make_ngmix_observation(gal,weight,flag,psf,wcs): """single galaxy and epoch to be passed to ngmix TO DO: pixel scale Parameters @@ -802,6 +814,7 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): """ # prepare psf + # WHY RECENTER psf_jacob = ngmix.Jacobian( row=(psf.shape[0] - 1) / 2, col=(psf.shape[1] - 1) / 2, @@ -816,7 +829,7 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): weight, flag ) - + # WHY RECENTER??? # Recenter jacobian if necessary gal_jacob = ngmix.Jacobian( row=(gal.shape[0] - 1) / 2, @@ -834,6 +847,8 @@ def prepare_galaxy_data(gal,weight,flag,psf,wcs): return gal_obs +def weighted_average(data): + def average_multiepoch_psf(obsdict,nepoch): """ averages psf information over multiple epochs we may need to do this for original psf as well @@ -926,7 +941,7 @@ def do_ngmix_metacal( # create list of ngmix observations for each galaxy for n_e in range(n_epoch): - gal_obs = prepare_galaxy_data( + gal_obs = make_ngmix_observation( stamp.gals[n_e], stamp.weights[n_e], stamp.flags[n_e], From a98063a08e4aa3a7fec91761811ae723e9642637 Mon Sep 17 00:00:00 2001 From: lbaumo Date: Wed, 18 Oct 2023 09:57:37 +0200 Subject: [PATCH 12/80] ngmix update --- shapepipe/modules/ngmix_package/ngmix.py | 60 +++++++++++++++++++----- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/shapepipe/modules/ngmix_package/ngmix.py b/shapepipe/modules/ngmix_package/ngmix.py index abc520214..2d7380d8b 100644 --- a/shapepipe/modules/ngmix_package/ngmix.py +++ b/shapepipe/modules/ngmix_package/ngmix.py @@ -17,7 +17,8 @@ from shapepipe.pipeline import file_io -class Tile_cat(object): +# I still don't know how to handle this +class Tile_cat(): """Tile_cat. catalog measured on a tile @@ -35,9 +36,9 @@ def __init__( # sextractor detection catalog for the tile self.tile_vignet - dtype = [('obj_id','i8'),('ra',''),('dec',''),('flux','')] - self.tile_data = np.recarray(()) - + dtype = [('obj_id','i4'),('ra','>f8'),('dec','>f8'),('flux','>f4'),('VIGNET', '>f4', (51, 51))] + #self.tile_data = np.recarray(()) + @classmethod def get_data(self, cat_path): tile_cat = file_io.FITSCatalogue( @@ -52,9 +53,13 @@ def get_data(self, cat_path): self.ra = np.copy(tile_cat.get_data()['XWIN_WORLD']) self.dec = np.copy(tile_cat.get_data()['YWIN_WORLD']) self.flux = np.copy(tile_cat.get_data()['FLUX_AUTO']) + self.size = np.copy(tile_cat.get_data()['FWHM_WORLD']) + self.e = np.copy(tile_cat.get_data()['ELLIPTICITY']) + self.theta = np.copy(tile_cat.get_data()['THETA_WIN_WORLD']) + tile_cat.close() -class Postage_stamp(object): +class Postage_stamp(): """Galaxy Postage Stamp. Class to hold catalog of postage stamps for a single galaxy @@ -81,7 +86,7 @@ def __init__( self.bkg_sub = bkg_sub self.megacam_flip = megacam_flip -class Vignet(object): +class Vignet(): """Vignet. Class to hold catalog of postage stamps @@ -650,6 +655,7 @@ def rescale_epoch_fluxes(gal,weight,header): weight : numpy.ndarray weight image header : + image header Returns ------- @@ -742,7 +748,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): return sig_noise -def prepare_ngmix_weights(gal,weight,flag): +def prepare_ngmix_weights(gal,weight,flag,tile_cat): """bookkeeping for ngmix weights. runs on a single galaxy and epoch pixel scale and galaxy guess TO DO: decide if we want galaxy guess stuff @@ -783,11 +789,13 @@ def prepare_ngmix_weights(gal,weight,flag): noise_img = np.random.randn(*gal.shape) * sig_noise noise_img_gal = np.random.randn(*gal.shape) * sig_noise - + + # fill in galaxy image masked regions with noise gal_masked = np.copy(gal) if (len(np.where(weight_map == 0)[0]) != 0): gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] + # convert weight map to variance map weight_map *= 1 / sig_noise ** 2 return gal_masked, weight_map, noise_img @@ -847,8 +855,6 @@ def make_ngmix_observation(gal,weight,flag,psf,wcs): return gal_obs -def weighted_average(data): - def average_multiepoch_psf(obsdict,nepoch): """ averages psf information over multiple epochs we may need to do this for original psf as well @@ -886,7 +892,7 @@ def average_multiepoch_psf(obsdict,nepoch): g_psf_err_sum += g_psf_err * ne_wsum T_psf_sum += T_psf * ne_wsum T_psf_err_sum += T_psf_err * ne_wsum - #npsf += 1 + if wsum == 0: raise ZeroDivisionError('Sum of weights = 0, division by zero') @@ -961,7 +967,7 @@ def do_ngmix_metacal( # psf fitting a gaussian psf_fitter = ngmix.fitting.Fitter(model=psf_model, prior=prior) - # TO DO! update flux to sextractor flux + # TO DO! what do we do about size? psf_guesser = ngmix.guessers.TFluxGuesser( rng=rng, T=0.25, @@ -1001,3 +1007,33 @@ def do_ngmix_metacal( # compile results to include psf information psf_res = average_multiepoch_psf(obsdict) return resdict, psf_res + +# Define the SExtractor parameters for a galaxy +def sextractor_e1e2(e,theta): + """sextractor_e1e2 + + computes ellipticity from sextrator quantities + Parameters + ---------- + stamp : Postage_stamp + List of the galaxy vignets. List indices run over epochs + prior : ngmix.priors + Priors for the fitting parameters + flux_guess : np.ndarray + guess for flux + pixel_scale : float + pixel scale in arcsec + rng : numpy.random.RandomState + Random state for guesses and priors + + Returns + ------- + np.ndarray + ellipticity + + """ + # Convert the position angle from degrees to radians + phi = np.radians(theta) - np.pi/2 + # Calculate the ellipticity vector + e_vec = e * np.array([np.cos(2*phi), np.sin(2*phi)]) + return e_vec From 886e0dbd3cb685a980f8c4718a89b9ae8d038700 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Mon, 2 Feb 2026 10:01:06 +0100 Subject: [PATCH 13/80] Added ngmix test fitting script --- scripts/python/fitting.py | 265 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 scripts/python/fitting.py diff --git a/scripts/python/fitting.py b/scripts/python/fitting.py new file mode 100644 index 000000000..84c48a8d5 --- /dev/null +++ b/scripts/python/fitting.py @@ -0,0 +1,265 @@ +""" +example fitting an exponential model. The psf is fit +using a set of coelliptical gaussians + +Despite this being a fit to single image we use the full fitting framework. We +use a generic bootstrapper to "bootstrap" the process, first fitting the psf +and then a psf flux for the object. Then the object is fit including the PSF, +so the inferred parameters are "pre-psf". The guess for the fit is made +based on the psf flux fit and a generic rough guess for size. + +To faciliate this bootstrapping process we define the fitters for psf and +object as well as objects to provide guesses. + +Bootstrappers are especially useful when you will perform the same fit on many +objects. + +A run of the code should produce output something like thid + + > python fitting_bd_empsf.py + + S/N: 920.5078121454815 + true flux: 100 meas flux: 95.3763 +/- 0.653535 (99.7% conf) + true g1: 0.05 meas g1: 0.0508 +/- 0.00960346 (99.7% conf) + true g2: -0.02 meas g2: -0.0261123 +/- 0.0095837 (99.7% conf) + true fracdev: 0.5 meas fracdev: 0.514028 +/- 0.011873 (99.7% conf) +""" +import numpy as np +import galsim +import ngmix + + +def main(): + + args = get_args() + rng = np.random.RandomState(args.seed) + + nepoch=3 + obs, obj_pars = make_data(rng=rng, noise=args.noise) + gal_obs_list = ngmix.observation.ObsList() + for i in np.arange(nepoch): + gal_obs_list.append(obs) + print(len(gal_obs_list)) + # fit the object to an exponential disk + prior = get_prior(rng=rng, scale=obs.jacobian.scale) + # fit using the levenberg marquards algorithm + fitter = ngmix.fitting.Fitter(model='gauss', prior=prior) + # make parameter guesses based on a psf flux and a rough T + guesser = ngmix.guessers.TPSFFluxAndPriorGuesser( + rng=rng, + T=0.25, + prior=prior, + ) + + # psf fitting with coelliptical gaussians + psf_ngauss = 1 + psf_fitter = fitter + # special guesser for coelliptical gaussians + psf_guesser = ngmix.guessers.TFluxGuesser(rng=rng, + T=0.25, + prior=prior, + flux=100, + + ) + # this runs the fitter. We set ntry=2 to retry the fit if it fails + psf_runner = ngmix.runners.PSFRunner( + fitter=psf_fitter, guesser=psf_guesser, + ntry=2, + ) + runner = ngmix.runners.Runner( + fitter=fitter, guesser=guesser, + ntry=2, + ) + + metacal_pars = { + 'types': ['noshear', '1p', '1m', '2p', '2m'], + 'step': 0.01, + 'psf': 'fitgauss', + 'fixnoise': True, + 'use_noise_image': False + } + # this bootstraps the process, first fitting psfs then the object + boot = ngmix.metacal.MetacalBootstrapper( + runner=runner, + psf_runner=psf_runner, + rng=rng + ) + + res, obsdict = boot.go(gal_obs_list) + print(res.keys()) + print(res['noshear'].keys()) + print('psf_options',obsdict['noshear'][0].weight.sum()) + #print('psf_options',obsdict['noshear'].psf.meta['result'].keys()) + print('S/N:', res['noshear']['s2n']) + print('true flux: %g meas flux: %g +/- %g (99.7%% conf)' % ( + obj_pars['flux'], res['noshear']['flux'], res['noshear']['flux_err']*3, + )) + print('true g1: %g meas g1: %g +/- %g (99.7%% conf)' % ( + obj_pars['g1'], res['noshear']['g'][0], res['noshear']['g_err'][0]*3, + )) + print('true g2: %g meas g2: %g +/- %g (99.7%% conf)' % ( + obj_pars['g2'], res['noshear']['g'][1], res['noshear']['g_err'][1]*3, + )) + print( + "Delta g / sig g = (" + + f"{(obj_pars['g1'] - res['noshear']['g'][0]) / res['noshear']['g_err'][0]:.2f}," + + f" {(obj_pars['g2'] - res['noshear']['g'][1]) / res['noshear']['g_err'][1]:.2f})" + ) + + if args.show: + try: + import images + except ImportError: + from espy import images + + imfit = res.make_image() + + images.compare_images(obs.image, imfit) + + +def get_prior(*, rng, scale, T_range=None, F_range=None, nband=None): + """ + get a prior for use with the maximum likelihood fitter + + Parameters + ---------- + rng: np.random.RandomState + The random number generator + scale: float + Pixel scale + T_range: (float, float), optional + The range for the prior on T + F_range: (float, float), optional + Fhe range for the prior on flux + nband: int, optional + number of bands + """ + if T_range is None: + T_range = [-1.0, 1.e3] + if F_range is None: + F_range = [-100.0, 1.e9] + + g_prior = ngmix.priors.GPriorBA(sigma=0.1, rng=rng) + cen_prior = ngmix.priors.CenPrior( + cen1=0, cen2=0, sigma1=scale, sigma2=scale, rng=rng, + ) + T_prior = ngmix.priors.FlatPrior(minval=T_range[0], maxval=T_range[1], rng=rng) + F_prior = ngmix.priors.FlatPrior(minval=F_range[0], maxval=F_range[1], rng=rng) + + if nband is not None: + F_prior = [F_prior]*nband + + prior = ngmix.joint_prior.PriorSimpleSep( + cen_prior=cen_prior, + g_prior=g_prior, + T_prior=T_prior, + F_prior=F_prior, + ) + + return prior + + +def make_data(rng, noise, g1=0.05, g2=-0.02, flux=100.0): + """ + simulate an exponential object with moffat psf + + Parameters + ---------- + rng: np.random.RandomState + The random number generator + noise: float + Noise for the image + g1: float + object g1, default 0.05 + g2: float + object g2, default -0.02 + flux: float, optional + default 100 + + Returns + ------- + ngmix.Observation, pars dict + """ + + psf_noise = 1.0e-6 + + scale = 0.263 + + psf_fwhm = 0.9 + gal_hlr = 0.5 + dy, dx = rng.uniform(low=-scale/2, high=scale/2, size=2) + + psf = galsim.Moffat( + beta=2.5, fwhm=psf_fwhm, + ).shear( + g1=-0.01, + g2=-0.01, + ) + + obj0 = galsim.Exponential( + half_light_radius=gal_hlr, + flux=flux, + ).shear( + g1=g1, + g2=g2, + ).shift( + dx=dx, + dy=dy, + ) + + obj = galsim.Convolve(psf, obj0) + + psf_im = psf.drawImage(scale=scale).array + im = obj.drawImage(scale=scale).array + + psf_im += rng.normal(scale=psf_noise, size=psf_im.shape) + im += rng.normal(scale=noise, size=im.shape) + + cen = (np.array(im.shape)-1.0)/2.0 + psf_cen = (np.array(psf_im.shape)-1.0)/2.0 + + jacobian = ngmix.DiagonalJacobian( + row=cen[0] + dy/scale, col=cen[1] + dx/scale, scale=scale, + ) + psf_jacobian = ngmix.DiagonalJacobian( + row=psf_cen[0], col=psf_cen[1], scale=scale, + ) + + wt = im*0 + 1.0/noise**2 + psf_wt = psf_im*0 + 1.0/psf_noise**2 + + psf_obs = ngmix.Observation( + psf_im, + weight=psf_wt, + jacobian=psf_jacobian, + ) + + obs = ngmix.Observation( + im, + weight=wt, + jacobian=jacobian, + psf=psf_obs, + ) + + obj_pars = { + 'g1': g1, + 'g2': g2, + 'flux': flux, + } + return obs, obj_pars + + +def get_args(): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--seed', type=int, default=42, + help='seed for rng') + parser.add_argument('--show', action='store_true', + help='show plot comparing model and data') + parser.add_argument('--noise', type=float, default=0.01, + help='noise for images') + return parser.parse_args() + + +if __name__ == '__main__': + main() From 8fd590cb52b8d0df901358063b5e3a3dae3b1ead Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sat, 25 Apr 2026 09:46:10 +0200 Subject: [PATCH 14/80] docker install: leave extensions --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 805458117..caa11afee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,7 @@ RUN chown -R root:root /app && chmod -R u+rwX /app RUN pip install --no-cache-dir -e ".[fitsio]" && \ for ext in .py .sh .bash; do \ for script in /app/scripts/*/*$ext; do \ - link_name=$(basename $script $ext); \ + link_name=$(basename $script); \ ln -s $script /usr/local/bin/$link_name; \ done; \ done From 621bab1abdc9f23c30b41d47d02c39cb0794fb75 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 26 Apr 2026 14:47:35 +0200 Subject: [PATCH 15/80] remove obsolete scripts --- pyproject.toml | 14 +- scripts/python/check_nobj_ngmix_mc.py | 113 ----- scripts/python/count_shdu_special.py | 57 --- scripts/python/create_sample_results.py | 323 --------------- scripts/sh/job_sp.bash | 524 ------------------------ scripts/sh/untar_results.bash | 92 ----- 6 files changed, 8 insertions(+), 1115 deletions(-) delete mode 100755 scripts/python/check_nobj_ngmix_mc.py delete mode 100755 scripts/python/count_shdu_special.py delete mode 100755 scripts/python/create_sample_results.py delete mode 100755 scripts/sh/job_sp.bash delete mode 100755 scripts/sh/untar_results.bash diff --git a/pyproject.toml b/pyproject.toml index db7d4521e..090808c5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,12 +66,14 @@ test = [ fitsio = ["fitsio"] dev = ["shapepipe[doc,lint,release,test]"] -[project.scripts] -shapepipe_run = "shapepipe.shapepipe_run:main" -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" +[tool.setuptools] +script-files = [ + "bin/shapepipe_run.py", + "bin/summary_run.py", + "bin/canfar_submit_job.py", + "bin/canfar_monitor.py", + "bin/canfar_monitor_log.py", +] [tool.pytest.ini_options] addopts = "--verbose --cov=shapepipe" diff --git a/scripts/python/check_nobj_ngmix_mc.py b/scripts/python/check_nobj_ngmix_mc.py deleted file mode 100755 index cfe11d1f8..000000000 --- a/scripts/python/check_nobj_ngmix_mc.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python - -import os -import glob -from astropy.io import fits - -# Base directory for the tile runs -tile_runs_dir = "tile_runs" - - -def get_naxis2_value(file_path): - """Get the NAXIS2 value from HDU #1 of a FITS file.""" - try: - hdu_list = fits.open(file_path) - except: - raise OSError(f"problem with opening {file_path}") - - try: - return hdu_list[1].header.get("NAXIS2", None) - except: - raise IndexError(f"problem with {file_path} header") - - hdu_list.close() - - -# Iterate through directories -for ID in os.listdir(tile_runs_dir): - id_path = os.path.join(tile_runs_dir, ID, "output") - - # Check if ID has the correct format - if not os.path.isdir(id_path): # or not ID.replace(".", "", 1).isdigit(): - print(f"{ID} -- path {id_path} not found") - continue - - IDx = ID.replace(".", "-") - - # Define file paths - ngmix_file_pattern = os.path.join( - id_path, - f"run_sp_Ms_202*/merge_sep_cats_runner/output/ngmix-{IDx}.fits", - ) - mc_file_pattern = os.path.join( - id_path, - f"run_sp_Mc_202*/make_cat_runner/output/final_cat-{IDx}.fits", - ) - - # Resolve file names using glob to handle wildcard - ngmix_files = [f for f in glob.glob(ngmix_file_pattern)] - mc_files = [f for f in glob.glob(mc_file_pattern)] - - if not ngmix_files: - print(f"{ID} -- ngmix cats {ngmix_file_pattern} not found") - continue - if not mc_files: - print(f"{ID} -- mc cats not found {mc_file_pattern}") - continue - - # Get newest files - ngmix_file = max(ngmix_files, key=lambda f: os.path.getmtime(f)) - mc_file = max(mc_files, key=lambda f: os.path.getmtime(f)) - - # Get NAXIS2 values - ngmix_naxis2 = get_naxis2_value(ngmix_file) - mc_naxis2 = get_naxis2_value(mc_file) - - # Look at separate ngmix cats - sep_ngmix_file_pattern = os.path.join( - id_path, - f"run_sp_tile_ngmix_Ng*/ngmix_runner/output/ngmix-{IDx}.fits", - ) - sep_ngmix_files = [f for f in glob.glob(sep_ngmix_file_pattern)] - sep_ngmix_file = max(sep_ngmix_files, key=lambda f: os.path.getmtime(f)) - - if not sep_ngmix_files: - raise ValueError(f"No separate ngmix files found for ID {ID}") - sep_naxis2 = 0 - for sep_ngmix_file in sep_ngmix_files: - sep_naxis2 += get_naxis2_value(sep_ngmix_file) - - if ngmix_naxis2 is None: - print(f"{ID} -- no NAXIS2 keyword found in ngmix cat {ngmix_file}") - continue - if mc_naxis2 is None: - print(f"{ID} -- no NAXIS2 keyword found in mc cat") - continue - - # Check run time of 128, 256, and 512 jobs - if ( - os.path.getmtime(ngmix_file) > os.path.getmtime(mc_file) - or os.path.getmtime(mc_file) < os.path.getmtime(sep_ngmix_file) - ): - print(f"{ID} -- mc, sep, and/or merged ngmix file out of order") - - # Compare values and output ID if they differ - elif ngmix_naxis2 != sep_naxis2: - print( - f"{ID} -- ngmix differ between sep {sep_naxis2} and merged" - + f" {ngmix_naxis2}" - ) - - elif ngmix_naxis2 != mc_naxis2: - ratio = float(ngmix_naxis2) / float(mc_naxis2) - if ratio < 0.75: - - print( - f"{ID} -- NAXIS2 differ {ngmix_naxis2} / {mc_naxis2} =" - + f" {ratio:.3f} (sum {len(sep_ngmix_files)} sep_ng =" - + f" {sep_naxis2})" - ) - else: - print(f"{ID} -- similar NAXIS2, {ngmix_naxis2} / {mc_naxis2} = {ratio:.3f}") - else: - print(f"{ID} -- identcal NAXIS2 {ngmix_naxis2} {mc_naxis2}") diff --git a/scripts/python/count_shdu_special.py b/scripts/python/count_shdu_special.py deleted file mode 100755 index dfe9f7d17..000000000 --- a/scripts/python/count_shdu_special.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python - -import os -import re -import sys - -ID = sys.argv[1] -print(f"Checking tile ID {ID}...") - -# Base directory for the tile runs -tile_runs_dir = "tile_runs" - -sum_dir = "summary" -specials = ["special_job_32_setools_runner_0", "special_job_32_setools_runner_1"] - - -shdu_IDs = [] -print("Going through links...") -#3for ID in os.listdir(tile_runs_dir): -if True: - id_path = os.path.join(tile_runs_dir, ID, "output") - - for filename in os.listdir(id_path): - if ( - filename.startswith("run_sp_exp_SxSePsf") - and os.path.islink(os.path.join(id_path, filename)) - ): - symlink_path = os.path.join(id_path, filename) - target_path = os.readlink(symlink_path) - - pattern = r"/exp_runs/([^/]+)/output/" - - match = re.search(pattern, target_path) - if match: - shdu_IDs.append(match.group(1)) - else: - print(f"{filename} -> No match in target path: {target_path}") - -if len(shdu_IDs) == 0: - print("No shdus found, exiting") - sys.exit(1) -else: - print(f"Found {len(shdu_IDs)} links in dir {id_path}") - - -print("Going through special files...") -count = 0 -for special in specials: - print(f"special {special}...") - path = f"{sum_dir}/{special}.txt" - with open(path, "r") as f: - content = f.read() - for shdu_ID in shdu_IDs: - if re.search(shdu_ID, content): - count += 1 - -print(f"Found {count} / {len(shdu_IDs)} = {count / len(shdu_IDs):.2%}") diff --git a/scripts/python/create_sample_results.py b/scripts/python/create_sample_results.py deleted file mode 100755 index 0e0223ffe..000000000 --- a/scripts/python/create_sample_results.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python - -"""Script create_sample_results.py - -Create directory with links to results for a given (sub-)sample. - -:Author: Martin Kilbinger - -:Date: 07/2020 -""" - -import re -import os -import sys -import glob -import copy -import io -from contextlib import redirect_stdout -from optparse import OptionParser -from shapepipe.utilities import cfis -from shapepipe.utilities.file_system import mkdir - - -def params_default(): - """Set default parameter values. - - Parameters - ---------- - None - - Returns - ------- - p_def: class cfis.param - parameter values - """ - - p_def = cfis.param(psf="mccd") - - return p_def - - -def parse_options(p_def): - """Parse command line options. - - Parameters - ---------- - p_def: class cfis.param - parameter values - - Returns - ------- - options: tuple - Command line options - args: str - Command line string - """ - - usage = "%prog [OPTIONS]" - parser = OptionParser(usage=usage) - - # I/O - parser.add_option( - "", - "--input_IDs", - dest="input_IDs", - type="string", - help="input tile ID file specifying sample", - ) - parser.add_option( - "-i", - "--input_dir", - dest="input_dir", - type="string", - help="input directory name", - ) - parser.add_option( - "-o", - "--output_dir", - dest="output_dir", - type="string", - help="output directory name", - ) - - # Misc - parser.add_option( - "-p", - "--psf", - dest="psf", - type="string", - default=p_def.psf, - help=f"PSF model, one in ['psfex'|'mccd'], default='{p_def.psf}'", - ) - - # Control - parser.add_option( - "-v", - "--verbose", - dest="verbose", - action="store_true", - help="verbose output", - ) - - options, args = parser.parse_args() - - return options, args - - -def check_options(options): - """Check command line options. - - Parameters - ---------- - options: tuple - Command line options - - Returns - ------- - erg: bool - Result of option check. False if invalid option value. - """ - - if options.input_IDs is None: - print("No input ID file list given (option '--input_IDs')") - return False - if options.input_dir is None: - print("No input directory name given (option '--input_dir')") - return False - if options.output_dir is None: - print("No output directory name given (option '--output_dir')") - return False - - return True - - -def update_param(p_def, options): - """Return default parameter, updated and complemented according to options. - - Parameters - ---------- - p_def: class param - parameter values - optiosn: tuple - command line options - - Returns - ------- - param: class param - updated paramter values - """ - - param = copy.copy(p_def) - - # Update keys in param according to options values - for key in vars(param): - if key in vars(options): - setattr(param, key, getattr(options, key)) - - # Add remaining keys from options to param - for key in vars(options): - if not key in vars(param): - setattr(param, key, getattr(options, key)) - - # Do extra stuff if necessary - - return param - - -def read_ID_list(input_ID_path, verbose=False): - """Return input ID list from file. - - Parameters - ---------- - input_ID_path: str - input ID file path - verbose: bool, optional, default=False - verbose output if True - - Returns - ------- - input_IDs: list of str - input ID list - """ - - if verbose: - print("Reading input ID list...") - - input_IDs = [] - with open(input_ID_path) as f: - for line in f: - input_IDs.append(line.rstrip()) - - if verbose: - print("{} IDs found in input file".format(len(input_IDs))) - - return input_IDs - - -def create_links( - input_dir, output_dir, input_IDs, result_base_names, verbose=False -): - """Create symbolic links to result files corresponding to (sub-)sample. - - Parameters - ---------- - input_dir: str - input directory - output_dir: str - output directory - input_IDs: list of str - tile ID list - results_base_names: list of str - file base names - verbose: bool, optional, default=False - verbose output if True - """ - - if verbose: - print("Creating links...") - - n_total = {} - n_created = 0 - n_existed = 0 - for ID in input_IDs: - n_total[ID] = 0 - for base in result_base_names: - name = "{}_{}.tgz".format(base, ID) - src = "{}/{}".format(os.path.abspath(input_dir), name) - link_name = "{}/{}".format(output_dir, name) - - # if verbose: - # print('Creating link {} <- {}'.format(src, link_name)) - - if not os.path.exists(src): - # raise IOError('Source file \'{}\' does not exist'.format(src)) - print("Source file '{}' does not exist, skipping".format(src)) - elif not os.path.exists(link_name): - os.symlink(src, link_name) - n_created = n_created + 1 - else: - n_existed = n_existed + 1 - - n_total[ID] = n_total[ID] + 1 - - n_expected = len(input_IDs) * len(result_base_names) - if verbose: - print("{:5d} links created".format(n_created)) - print("{:5d} links existed already".format(n_existed)) - print( - "{:5d}/{} links available now".format( - n_created + n_existed, n_expected - ) - ) - n_tot = sum(n_total.values()) - print("{:5d} as cross-check".format(n_tot)) - - -def main(argv=None): - - # Set default parameters - p_def = params_default() - - # Command line options - options, args = parse_options(p_def) - # Without option parsing, this would be: args = argv[1:] - - if check_options(options) is False: - return 1 - - param = update_param(p_def, options) - - # Save calling command - cfis.log_command(argv) - if param.verbose: - cfis.log_command(argv, name="sys.stdout") - - ### Start main program ### - - if param.verbose: - print("Start of program {}".format(os.path.basename(argv[0]))) - - input_IDs = read_ID_list(param.input_IDs, verbose=param.verbose) - - result_base_names = [ - "final_cat", - "logs", - "setools_mask", - "setools_stat", - "setools_plot", - "pipeline_flag", - ] - if param.psf == "psfex": - result_base_names.append("psfex_interp_exp") - elif param.psf == "mccd": - result_base_names.append("mccd_fit_val_runner") - - if os.path.isdir(param.output_dir): - if param.verbose: - print( - "Directory {} already exists, continuing...".format( - param.output_dir - ) - ) - else: - mkdir(param.output_dir) - - create_links( - param.input_dir, - param.output_dir, - input_IDs, - result_base_names, - verbose=param.verbose, - ) - - ### End main program - - if param.verbose: - print("End of program {}".format(os.path.basename(argv[0]))) - - return 0 - - -if __name__ == "__main__": - sys.exit(main(sys.argv)) diff --git a/scripts/sh/job_sp.bash b/scripts/sh/job_sp.bash deleted file mode 100755 index 2a207d807..000000000 --- a/scripts/sh/job_sp.bash +++ /dev/null @@ -1,524 +0,0 @@ -#!/usr/bin/env bash - -# Name: job_sp.bash -# Description: General script to process one or more tiles -# with all contributing exposures. -# This works as job submission script for -# the canfar batch system. -# called in interactive mode on a virtual -# machine. -# Author: Martin Kilbinger - - -# VM home, required for canfar run. -## On other machines set to $HOME -export VM_HOME=/home/ubuntu -if [ ! -d "$VM_HOME" ]; then - export VM_HOME=$HOME -fi - -# Command line arguments -## Default values -job=255 -#config_dir='vos:cfis/cosmostat/kilbinger/cfis' -config_dir=$VM_HOME/shapepipe/example/cfis -psf='mccd' -retrieve='vos' -star_cat_for_mask='onthefly' -results='cosmostat/kilbinger/results_v1' -n_smp=-1 -nsh_step=-1 -nsh_max=-1 -nsh_jobs=8 - -## Help string -usage="Usage: $(basename "$0") [OPTIONS] TILE_ID_1 [TILE_ID_2 [...]] -\n\nOptions:\n - -h\tthis message\n - -j, --job JOB\tRunning JOB, bit-coded\n - \t 1: retrieve images (online if method=vos)\n - \t 2: prepare images (offline)\n - \t 4: mask (online)\n - \t 8: detection of galaxies on tiles; processing of stars on exposures (offline)\n - \t 16: galaxy selection on tiles (offline)\n - \t 32: shapes and morphology (offline)\n - \t 64: paste catalogues (offline)\n - \t 128: upload results (online)\n - -c, --config_dir DIR\n - \t config file directory, default='$config_dir'\n - -p, --psf MODEL\n - \tPSF model, one in ['psfex'|'mccd'], default='$psf'\n - -r, --retrieve METHOD\n - \tmethod to retrieve images, allowed are 'vos', 'symlink', default='$retrieve'\n - -s, --star_cat_for_mask\n - \tcatalogue for masking bright stars, allowed are 'onthefly', 'save',\n - \tdefault is '${star_cat_for_mask}'\n - -o, --output_dir\n - \toutput (upload) directory on vos:cfis, default='$results'\n - -n, --n_smp\n - \tnumber of jobs (SMP mode only), default from original config files\n - --nsh_step NSTEP\n - --nsh_jobs NJOB\n - \tnumber of shape measurement parallel jobs, default=$nsh_jobs\n - \tnumber of objects per parallel shape module call, \n - \tdefault: optimal number is computed\n - --nsh_max NMAX\n - \tmax number of objects per parallel shape module call, \n - \tdefault: unlimited; has precedent over --nsh_step\n - TILE_ID_i\n - \ttile ID(s), e.g. 283.247 214.242\n -" - -## Help if no arguments -if [ -z $1 ]; then - echo -ne $usage - exit 1 -fi - -## Parse command line -TILE_ARR=() -while [ $# -gt 0 ]; do - case "$1" in - -h) - echo -ne $usage - exit 0 - ;; - -j|--job) - job="$2" - shift - ;; - -c|--config_dir) - config_dir="$2" - shift - ;; - -p|--psf) - psf="$2" - shift - ;; - -r|--retrieve) - retrieve="$2" - shift - ;; - -s|--star_cat_for_mask) - star_cat_for_mask="$2" - shift - ;; - -o|--output_dir) - results="$2" - shift - ;; - -n|--n_smp) - n_smp="$2" - shift - ;; - --nsh_max) - nsh_max="$2" - shift - ;; - --nsh_step) - nsh_step="$2" - shift - ;; - --nsh_jobs) - nsh_jobs="$2" - shift - ;; - *) - TILE_ARR+=("$1") - ;; - esac - shift -done - -## Check options -if [ "$psf" != "psfex" ] && [ "$psf" != "mccd" ]; then - echo "PSF (option -p) needs to be 'psfex' or 'mccd'" - exit 2 -fi - -if [ "$star_cat_for_mask" != "onthefly" ] && [ "$star_cat_for_mask" != "save" ]; then - echo "Star cat for mask (option -s) needs to be 'onthefly' or 'save'" - exit 4 -fi - -if [ "$retrieve" != "vos" ] && [ "$retrieve" != "symlink" ]; then - echo "method to retrieve images (option -r) needs to be 'vos' or 'symlink'" - exit 5 -fi - -n_tile=${#TILE_ARR[@]} -if [ "$n_tile" == "0" ]; then - echo "No tile ID given" - exit 3 -fi - -if [ $nsh_max != -1 ]; then - nsh_step=$nsh_max -fi - -# For tar archives. Should be unique to each job -export ID=`echo ${TILE_ARR[@]} | tr ' ' '_'` - -## Paths - -# SExtractor library bug work-around -export PATH="$PATH:$VM_HOME/bin" - -## Path variables used in shapepipe config files - -# Run path and location of input image directories -export SP_RUN=`pwd` - -# Config file path -export SP_CONFIG=$SP_RUN/cfis -export SP_CONFIG_MOD=$SP_RUN/cfis_mod - -## Other variables - -# Input tile numbers ASCII file -export TILE_NUMBERS_PATH=tile_numbers.txt - -# Output -OUTPUT=$SP_RUN/output - -# For tar archives -output_rel=`realpath --relative-to=. $OUTPUT` - -# Stop on error, default=1 -STOP=1 - -# Verbose mode (1: verbose, 0: quiet) -VERBOSE=1 - -# VCP options -export CERTFILE=$VM_HOME/.ssl/cadcproxy.pem -export VCP="vcp --certfile=$CERTFILE" - - -## Functions - -# Print string, executes command, and prints return value. -function command () { - cmd=$1 - str=$2 - - RED='\033[0;31m' - GREEN='\033[0;32m' - NC='\033[0m' # No Color - # Color escape characters show up in log files - #RED='' - #GREEN='' - #NC='' - - - if [ $# == 2 ]; then - if [ $VERBOSE == 1 ]; then - echo "$str: running '$cmd'" - fi - $cmd - else - if [ $VERBOSE == 1 ]; then - echo "$str: running '$cmd $4 \"$5 $6\"'" - fi - $cmd $4 "$5 $6" - fi - res=$? - - if [ $VERBOSE == 1 ]; then - if [ $res == 0 ]; then - echo -e "${GREEN}success, return value = $res${NC}" - else - echo -e "${RED}error, return value = $res${NC}" - if [ $STOP == 1 ]; then - echo "${RED}exiting 'canfar_sp.bash', error in command '$cmd'${NC}" - exit $res - else - echo "${RED}continuing 'canfar_sp.bash', error in command '$cmd'${NC}" - fi - fi - fi -} - -# Run shapepipe command. If error occurs, upload sp log files before stopping script. -function command_sp() { - local cmd=$1 - local str=$2 - - command "$1" "$2" -} - -# Set up config file and call shapepipe_run -function command_cfg_shapepipe() { - local config_name=$1 - local str=$2 - local _n_smp=$3 - - config_upd=$(set_config_n_smp $config_name $_n_smp) - local cmd="shapepipe_run -c $config_upd" - command_sp "$cmd" "$str" -} - -# Tar and upload files to vos -function upload() { - base=$1 - shift - ID=$1 - shift - verbose=$1 - shift - upl=("$@") - - echo "Counting upload files" - n_upl=(`ls -l ${upl[@]} | wc`) - if [ $n_upl == 0 ]; then - if [ $STOP == 1 ]; then - echo "Exiting script, no file found for '$base' tar ball" - exit 3 - fi - fi - tar czf ${base}_${ID}.tgz ${upl[@]} - command "$VCP ${base}_${ID}.tgz vos:cfis/$results" "Upload tar ball" -} - -# Upload log files -function upload_logs() { - id=$1 - verbose=$2 - - upl="$output_rel/*/*/logs $output_rel/*/logs" - upload "logs" "$id" "$verbose" "${upl[@]}" -} - -function set_config_n_smp() { - local config_name=$1 - local _n_smp=$2 - - local config_orig="$SP_CONFIG/$config_name" - - if [[ $_n_smp != -1 ]]; then - # Update SMP batch size - local config_upd="$SP_CONFIG_MOD/$config_name" - update_config $config_orig $config_upd "SMP_BATCH_SIZE" $_n_smp - else - # Keep original config file - local config_upd=$config_orig - fi - - # Set "return" value (stdout) - echo "$config_upd" -} - -# Update config file -function update_config() { - local config_orig=$1 - local config_upd=$2 - local key=$3 - local val_upd=$4 - - cat $config_orig \ - | perl -ane 's/'$key'\s+=.+/'$key' = '$val_upd'/; print' > $config_upd -} - -### Start ### - -echo "Start" - -echo "Processing $n_tile tile(s)" - -# Create input and output directories -mkdir -p $SP_RUN -cd $SP_RUN -mkdir -p $OUTPUT -mkdir -p $SP_CONFIG_MOD - -# Processing - -## Retrieve config files and images (online if retrieve=vos) -## Retrieve and save star catalogues for masking (if star_cat_for_mask=save) -(( do_job= $job & 1 )) -if [[ $do_job != 0 ]]; then - - # Write tile numbers to ASCII input file - rm -rf $TILE_NUMBERS_PATH - for TILE in ${TILE_ARR[@]}; do - echo $TILE >> $TILE_NUMBERS_PATH - done - - ### Retrieve config files - if [[ $config_dir == *"vos:"* ]]; then - command_sp "$VCP $config_dir ." "Retrieve shapepipe config files" - else - if [[ ! -L cfis ]]; then - command_sp "ln -s $config_dir cfis" "Retrieve shapepipe config files" - fi - fi - - ### Retrieve files - command_sp "shapepipe_run -c $SP_CONFIG/config_GitFeGie_$retrieve.ini" "Retrieve images" - - ### Retrieve and save star catalogues for masking - if [ "$star_cat_for_mask" == "save" ]; then - #### For tiles - mkdir $SP_RUN/star_cat_tiles - command_sp "create_star_cat $SP_RUN/output/run_sp_GitFeGie_*/get_images_runner_run_1/output $SP_RUN/star_cat_tiles" "Save star cats for masking (tile)" - - #### For single-exposures - mkdir $SP_RUN/star_cat_exp - command_sp "create_star_cat $SP_RUN/output/run_sp_GitFeGie_*/get_images_runner_run_2/output $SP_RUN/star_cat_exp exp" "Save star cats for masking (exp)" - fi - -fi - -## Prepare images (offline) -(( do_job= $job & 2 )) -if [[ $do_job != 0 ]]; then - - ### Uncompress tile weights - command_cfg_shapepipe "config_tile_Uz.ini" "Run shapepipe (uncompress tile weights)" $n_smp - - ### Split images into single-HDU files, merge headers for WCS info - command_cfg_shapepipe "config_exp_SpMh.ini" "Run shapepipe (split images, merge headers)" $n_smp - -fi - -## Mask tiles and exposures: add star, halo, and Messier object masks (online if "star_cat_for_mask" is "onthefly") -(( do_job= $job & 4 )) -if [[ $do_job != 0 ]]; then - - ### Mask tiles and exposures - command_cfg_shapepipe "config_MaMa_$star_cat_for_mask.ini" "Run shapepipe (mask)" $n_smp - -fi - - -## Remaining exposure processing (offline) -(( do_job= $job & 8 )) -if [[ $do_job != 0 ]]; then - - ### Star detection, selection, PSF model. setools can exit with an error for CCD with insufficient stars, - ### the script should continue - STOP=0 - command_cfg_shapepipe "config_tile_Sx_exp_${psf}.ini" "Run shapepipe (tile detection, exp $psf)" $n_smp - STOP=1 - -fi - -## Process tiles up to shape measurement -(( do_job= $job & 16 )) -if [[ $do_job != 0 ]]; then - - ### PSF model letter: 'P' (psfex) or 'M' (mccd) - letter=${psf:0:1} - Letter=${letter^} - command_sp "shapepipe_run -c $SP_CONFIG/config_tile_${Letter}iViSmVi.ini" "Run shapepipe (tile PsfInterp=$Letter}: up to ngmix+galsim)" - -fi - -## Shape measurement (offline) -(( do_job= $job & 32 )) -if [[ $do_job != 0 ]]; then - - ### Prepare config files - n_min=0 - if [[ $nsh_step == -1 ]]; then - n_obj=`get_number_objects.py` - nsh_step=`echo "$(($n_obj/$nsh_jobs))"` - fi - - n_max=$((nsh_step - 1)) - for k in $(seq 1 $nsh_jobs); do - cat $SP_CONFIG/config_tile_Ng_template.ini | \ - perl -ane \ - 's/(ID_OBJ_MIN =) X/$1 '$n_min'/; s/(ID_OBJ_MAX =) X/$1 '$n_max'/; s/NgXu/Ng'$k'u/; s/X_interp/'$psf'_interp/g; print' \ - > $SP_CONFIG_MOD/config_tile_Ng${k}u.ini - n_min=$((n_min + nsh_step)) - if [ "$k" == $((nsh_jobs - 1)) ] && [ $nsh_max == -1 ]; then - n_max=-1 - else - n_max=$((n_min + nsh_step - 1)) - fi - done - - ### Shapes, run $nsh_jobs parallel processes - VERBOSE=0 - for k in $(seq 1 $nsh_jobs); do - command_sp "shapepipe_run -c $SP_CONFIG_MOD/config_tile_Ng${k}u.ini" "Run shapepipe (tile: ngmix+galsim $k)" & - done - wait - VERBOSE=1 - -fi - -## Create final catalogues (offline) -(( do_job= $job & 64 )) -if [[ $do_job != 0 ]]; then - - cat $SP_CONFIG/config_merge_sep_cats_template.ini | \ - perl -ane \ - 's/(N_SPLIT_MAX =) X/$1 '$nsh_jobs'/; print' \ - > $SP_CONFIG_MOD/config_merge_sep_cats.ini - - ### Merge separated shapes catalogues - command_sp "shapepipe_run -c $SP_CONFIG_MOD/config_merge_sep_cats.ini" "Run shapepipe (tile: merge sep cats)" "$VERBOSE" "$ID" - - ### Merge all relevant information into final catalogue - command_sp "shapepipe_run -c $SP_CONFIG/config_make_cat_$psf.ini" "Run shapepipe (tile: create final cat $psf)" "$VERBOSE" "$ID" - -fi - -## Upload results (online) -(( do_job= $job & 128 )) -if [[ $do_job != 0 ]]; then - - ### module and pipeline log files - upload_logs "$ID" "$VERBOSE" - - ### Final shape catalog - ### pipeline_flags are the tile masks, for random cats - ### SETools masks (selection), stats and plots - ### ${psf}_interp_exp for diagnostics, validation with leakage, - ### validation with residuals, rho stats - - NAMES=( - "final_cat" - "pipeline_flag" - "setools_mask" - "setools_stat" - "setools_plot" - ) - DIRS=( - "*/make_cat_runner/output" - "*/mask_runner_run_1/output" - "*/setools_runner/output/mask" - "*/setools_runner/output/stat" - "*/setools_runner/output/plot" - ) - PATTERNS=( - "final_cat-*" - "pipeline_flag-???-???*" - "*" - "*" - "*" - ) - - # PSF validation - pattern="validation_psf-*" - if [ "$psf" == "psfex" ]; then - name="psfex_interp_exp" - dir="*/psfex_interp_runner/output" - else - name="mccd_fit_val_runner" - dir="*/mccd_fit_val_runner/output" - fi - upl=$output_rel/$dir/$pattern - upload "$name" "$ID" "$VERBOSE" "${upl[@]}" - - for n in "${!NAMES[@]}"; do - name=${NAMES[$n]} - dir=${DIRS[$n]} - pattern=${PATTERNS[$n]} - upl=$output_rel/$dir/$pattern - upload "$name" "$ID" "$VERBOSE" "${upl[@]}" - done - -fi diff --git a/scripts/sh/untar_results.bash b/scripts/sh/untar_results.bash deleted file mode 100755 index 9db70404e..000000000 --- a/scripts/sh/untar_results.bash +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env bash - -# Name: untar_results.bash -# Description: Untar .tgz files = results of ShapePipe runs -# Author: Martin Kilbinger -# Date: 05/2020 -# Package: shapepipe - -# Command line arguments - -## Default values -psf="mccd" -output="" - -## Help string -usage="Usage: $(basename "$0") [OPTIONS] -\n\nOptions:\n - -h\tthis message\n - -p, --psf MODEL\n - \tPSF model, one in ['psfex'|'mccd'], default='$psf'\n - -o, --output FILE\n - \tOutput file to list untared archives, default None\n -" - -## Parse command line -while [ $# -gt 0 ]; do - case "$1" in - -h) - echo -ne $usage - exit 0 - ;; - -p|--psf) - psf="$2" - shift - ;; - -o|--output) - output="$2" - shift - ;; - *) - echo -ne usage - exit 1 - ;; - esac - shift -done - - -NAMES=( - "final_cat" - "setools_mask" - "setools_stat" - "setools_plot" - "pipeline_flag" - ) - -if [ "$psf" == "psfex" ]; then - NAMES+=( - "psfex_interp_exp" - ) -elif [ "$psf" == "mccd" ]; then - NAMES+=( - "mccd_fit_val_runner" - ) -fi - -if [[ "$output" != "" ]]; then - echo "Resetting output file $output" - rm -f $output -fi - -# Check number of files -for out in ${NAMES[@]}; do - echo "$out" - FILES=${out}_*.tgz - n_files=${#FILES[@]} - n_ok=0 - n_fail=0 - for file in $FILES; do - tar xf $file - res=$? - if [ $res == 0 ]; then - ((n_ok=n_ok+1)) - if [[ "$output" != "" ]]; then - echo $file >> $output - fi - else - ((n_fail=n_fail+1)) - fi - done - echo " $n_files tgz files, $n_ok successful untar commands, $n_fail failures" -done From 06934c985fe839d45934318b42f90fe479aab205 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 26 Apr 2026 14:52:23 +0200 Subject: [PATCH 16/80] keeping extension for shapepipe_run.py, updated workflow --- .github/workflows/deploy-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-image.yml b/.github/workflows/deploy-image.yml index 75c89e4ad..6bb4fc387 100644 --- a/.github/workflows/deploy-image.yml +++ b/.github/workflows/deploy-image.yml @@ -40,7 +40,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} - name: Test - run: docker run --rm ${{ steps.meta.outputs.tags }} shapepipe_run -c /app/example/config.ini + run: docker run --rm ${{ steps.meta.outputs.tags }} shapepipe_run.py -c /app/example/config.ini - name: Push uses: docker/build-push-action@v6 From 883c4d6ad9d996b48f7da8c57fbd46b6b9f050f5 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 26 Apr 2026 15:56:41 +0200 Subject: [PATCH 17/80] Fixed Dockerfile --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 090808c5c..6406defa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,9 @@ script-files = [ "bin/canfar_monitor_log.py", ] +[tool.setuptools.packages.find] +where = ["src"] + [tool.pytest.ini_options] addopts = "--verbose --cov=shapepipe" testpaths = ["shapepipe"] From a662554dccea5dbe60f2ce04b2bf06a54ff1b093 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 26 Apr 2026 16:09:48 +0200 Subject: [PATCH 18/80] fix: add bin/ wrappers to git and fix pyproject.toml license format - bin/ scripts were untracked, causing Docker build to fail - Fix license field to use SPDX string format (MIT) to resolve SetuptoolsDeprecationWarning Co-Authored-By: Claude Sonnet 4.6 --- bin/canfar_monitor.py | 3 +++ bin/canfar_monitor_log.py | 3 +++ bin/canfar_submit_job.py | 3 +++ bin/shapepipe_run.py | 3 +++ bin/summary_run.py | 3 +++ pyproject.toml | 3 ++- 6 files changed, 17 insertions(+), 1 deletion(-) create mode 100755 bin/canfar_monitor.py create mode 100755 bin/canfar_monitor_log.py create mode 100755 bin/canfar_submit_job.py create mode 100755 bin/shapepipe_run.py create mode 100755 bin/summary_run.py diff --git a/bin/canfar_monitor.py b/bin/canfar_monitor.py new file mode 100755 index 000000000..e01872201 --- /dev/null +++ b/bin/canfar_monitor.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from shapepipe.canfar_run import run_log +run_log() diff --git a/bin/canfar_monitor_log.py b/bin/canfar_monitor_log.py new file mode 100755 index 000000000..1ef99aa04 --- /dev/null +++ b/bin/canfar_monitor_log.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from shapepipe.canfar_run import run_monitor_log +run_monitor_log() diff --git a/bin/canfar_submit_job.py b/bin/canfar_submit_job.py new file mode 100755 index 000000000..202e9d274 --- /dev/null +++ b/bin/canfar_submit_job.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from shapepipe.canfar_run import run_job +run_job() diff --git a/bin/shapepipe_run.py b/bin/shapepipe_run.py new file mode 100755 index 000000000..dedfc5093 --- /dev/null +++ b/bin/shapepipe_run.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from shapepipe.shapepipe_run import main +main() diff --git a/bin/summary_run.py b/bin/summary_run.py new file mode 100755 index 000000000..a59689e41 --- /dev/null +++ b/bin/summary_run.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from shapepipe.summary_run import main +main() diff --git a/pyproject.toml b/pyproject.toml index 6406defa6..a7def169c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,8 @@ authors = [ { name = "Axel Guinot", email = "axel.guinot@cea.fr" }, { name = "Martin Kilbinger", email = "martin.kilbinger@cea.fr" } ] -license = { "file" = "LICENSE" } +license = "MIT" +license-files = ["LICENSE"] requires-python = ">=3.12" dependencies = [ "astropy>=5.2", From ce6804d64199d6d86d38dda41ccf88a7e121c43f Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 26 Apr 2026 16:31:31 +0200 Subject: [PATCH 19/80] fix: use ENV key=value format in Dockerfile Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index caa11afee..9ef424ada 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN cd /tmp/weightwatcher-${WW_VERSION} && \ make install # Ensure astroml:latest conda Python 3.12 is used (Docker RUN does not source conda init) -ENV PATH /opt/conda/bin:$PATH +ENV PATH=/opt/conda/bin:$PATH # Upgrade pip and install tools not part of the ShapePipe package RUN pip install --no-cache-dir --upgrade pip && \ From 88f3fe3fa7eb4c466bfb78faff5e2e6bae59b3a8 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 26 Apr 2026 18:16:48 +0200 Subject: [PATCH 20/80] fixed extensions of scripts --- pyproject.toml | 4 ++++ scripts/sh/job_sp_canfar_v2.0.bash | 5 ++--- scripts/sh/run_job_sp_canfar_v2.0.bash | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a7def169c..c55be2531 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,10 @@ script-files = [ "bin/canfar_submit_job.py", "bin/canfar_monitor.py", "bin/canfar_monitor_log.py", + "scripts/python/update_runs_log_file.py", + "scripts/sh/run_job_sp_canfar_v2.0.bash", + "scripts/sh/job_sp_canfar_v2.0.bash", + "scripts/sh/init_run_v2.0.sh", ] [tool.setuptools.packages.find] diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index 3fbe7b684..f30699641 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -253,8 +253,7 @@ function command_cfg_shapepipe() { fi config_upd=$(set_config_n_smp $config_name $_n_smp) - #local cmd="/arc/home/kilbinger/.conda/envs/shapepipe/bin/shapepipe_run -c $config_upd $exclusive_flag" - local cmd="shapepipe_run -c $config_upd $exclusive_flag" + local cmd="shapepipe_run.py -c $config_upd $exclusive_flag" command "$cmd" "$str" } @@ -489,7 +488,7 @@ if [[ $do_job != 0 ]]; then ### Merge separated shapes catalogues command \ - "shapepipe_run -c $SP_CONFIG_MOD/config_tile_merge_sep_cats.ini" \ + "shapepipe_run.py -c $SP_CONFIG_MOD/config_tile_merge_sep_cats.ini" \ "Run shapepipe (tile: merge sep cats)" fi diff --git a/scripts/sh/run_job_sp_canfar_v2.0.bash b/scripts/sh/run_job_sp_canfar_v2.0.bash index c59dafca9..0ab846d5c 100755 --- a/scripts/sh/run_job_sp_canfar_v2.0.bash +++ b/scripts/sh/run_job_sp_canfar_v2.0.bash @@ -14,7 +14,7 @@ version="2.0" job=-1 ID=-1 psf='psfex' -tile_det='sx' +tile_det='uc' tile_mask=0 N_SMP=1 dry_run=0 @@ -286,8 +286,8 @@ function run_exp_job() { echo "$(basename "$0") -j $exp_job -e $exp_id" > "$exp_log_file" echo "pwd=`pwd`" - command "job_sp_canfar_v2.0 -p $psf --tile_det $tile_det --tile_mask $tile_mask -j $exp_job --n_smp $N_SMP --nsh_jobs $N_SMP $debug_flag" $dry_run 2>&1 | tee -a "$exp_log_file" - echo "Done with job_sp_canfar_v2.0" + command "job_sp_canfar_v2.0.bash -p $psf --tile_det $tile_det --tile_mask $tile_mask -j $exp_job --n_smp $N_SMP --nsh_jobs $N_SMP $debug_flag" $dry_run 2>&1 | tee -a "$exp_log_file" + echo "Done with job_sp_canfar_v2.0.bash" #cd "$dir" @@ -383,7 +383,7 @@ function run_tile_job() { fi # Run job script - command "job_sp_canfar_v2.0 -p $psf --tile_det $tile_det --tile_mask $tile_mask -j $tile_job --n_smp $N_SMP --nsh_jobs $N_SMP $debug_flag" $dry_run 2>&1 | tee -a "$log_file" + command "job_sp_canfar_v2.0.bash -p $psf --tile_det $tile_det --tile_mask $tile_mask -j $tile_job --n_smp $N_SMP --nsh_jobs $N_SMP $debug_flag" $dry_run 2>&1 | tee -a "$log_file" } From 25d9238b7227a2c70c313d0d7fecbd23d42418fe Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Mon, 27 Apr 2026 14:38:32 +0000 Subject: [PATCH 21/80] added missing Git_cat cfg file --- example/cfis/config_tile_Git_cat_vos.ini | 90 ++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 example/cfis/config_tile_Git_cat_vos.ini diff --git a/example/cfis/config_tile_Git_cat_vos.ini b/example/cfis/config_tile_Git_cat_vos.ini new file mode 100644 index 000000000..29008c194 --- /dev/null +++ b/example/cfis/config_tile_Git_cat_vos.ini @@ -0,0 +1,90 @@ +# ShapePipe configuration file for: get external tile catalogue + + +## Default ShapePipe options +[DEFAULT] + +# verbose mode (optional), default: True, print messages on terminal +VERBOSE = False + +# Name of run (optional) default: shapepipe_run +RUN_NAME = run_sp_Gic + +# Add date and time to RUN_NAME, optional, default: False +RUN_DATETIME = True + + +## ShapePipe execution options +[EXECUTION] + +# Module name, single string or comma-separated list of valid module runner names +MODULE = get_images_runner + +# Parallel processing mode, SMP or MPI +MODE = SMP + + +## ShapePipe file handling options +[FILE] + +# Log file master name, optional, default: shapepipe +LOG_NAME = log_sp + +# Runner log file name, optional, default: shapepipe_runs +RUN_LOG_NAME = log_run_sp + +# Input directory, containing input files, single string or list of names +INPUT_DIR = $SP_RUN + +# Output directory +OUTPUT_DIR = $SP_RUN/output + + +## ShapePipe job handling options +[JOB] + +# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial +SMP_BATCH_SIZE = 1 + +# Timeout value (optional), default is None, i.e. no timeout limit applied +TIMEOUT = 96:00:00 + + +## Module options + +# Get external tile catalogue +[GET_IMAGES_RUNNER] + +FILE_PATTERN = tile_numbers + +FILE_EXT = .txt + +# NUMBERING_SCHEME (optional) string with numbering pattern for input files +NUMBERING_SCHEME = + +# Paths + +# Input path where catalogues are stored +INPUT_PATH = vos:cfis/tiles_DR6 + +# Input file pattern including tile number as dummy template +INPUT_FILE_PATTERN = CFIS.000.000.r + +# Input file extensions +INPUT_FILE_EXT = .cat + +# Input numbering scheme, python regexp +INPUT_NUMBERING = \d{3}\.\d{3} + +# Output file pattern without number +OUTPUT_FILE_PATTERN = CFIS_cat- + +# Copy/download method, one in 'vos', 'symlink' +RETRIEVE = vos + +# If RETRIEVE=vos, number of attempts to download +# Optional, default=3 +N_TRY = 3 + +# Copy command options, optional +RETRIEVE_OPTIONS = --certfile=$HOME/.ssl/cadcproxy.pem From 8bcfe1f3521eda4a4af8ba96cf16d24be819dcb8 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Mon, 27 Apr 2026 21:16:01 +0200 Subject: [PATCH 22/80] modified Mh runner acfg --- example/cfis/{config_exp_Mh.ini => config_tile_Mh_exp.ini} | 2 +- scripts/sh/job_sp_canfar_v2.0.bash | 2 +- scripts/sh/run_job_sp_canfar_v2.0.bash | 2 +- .../modules/merge_headers_package/merge_headers.py | 6 ++++-- src/shapepipe/modules/merge_headers_runner.py | 4 ++-- 5 files changed, 9 insertions(+), 7 deletions(-) rename example/cfis/{config_exp_Mh.ini => config_tile_Mh_exp.ini} (98%) diff --git a/example/cfis/config_exp_Mh.ini b/example/cfis/config_tile_Mh_exp.ini similarity index 98% rename from example/cfis/config_exp_Mh.ini rename to example/cfis/config_tile_Mh_exp.ini index f99f01562..0d3f9f8b3 100644 --- a/example/cfis/config_exp_Mh.ini +++ b/example/cfis/config_tile_Mh_exp.ini @@ -11,7 +11,7 @@ VERBOSE = True # Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_exp_Mh +RUN_NAME = run_sp_tile_Mh_exp # Add date and time to RUN_NAME, optional, default: True RUN_DATETIME = False diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index f30699641..284e35441 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -403,7 +403,7 @@ if [[ $do_job != 0 ]]; then ### Merge single-exposure WCS headers into tile-level sqlite log. ### Must run before object detection/selection (make_post_process needs it). command_cfg_shapepipe \ - "config_exp_Mh.ini" \ + "config_tile_Mh_exp.ini" \ "Run shapepipe (merge exp headers)" \ $n_smp \ $exclusive diff --git a/scripts/sh/run_job_sp_canfar_v2.0.bash b/scripts/sh/run_job_sp_canfar_v2.0.bash index 0ab846d5c..0436e0c33 100755 --- a/scripts/sh/run_job_sp_canfar_v2.0.bash +++ b/scripts/sh/run_job_sp_canfar_v2.0.bash @@ -520,7 +520,7 @@ fi (( do_job = job & 128 )) if [[ $do_job != 0 ]]; then # Job 128: merge exposure WCS headers into tile-level sqlite log - run_tile_job 128 "exp_Mh" "merge_headers_runner:1" + run_tile_job 128 "Mh_exp" "merge_headers_runner:1" fi (( do_job = job & 256 )) diff --git a/src/shapepipe/modules/merge_headers_package/merge_headers.py b/src/shapepipe/modules/merge_headers_package/merge_headers.py index 5c0a2213e..47df44fec 100644 --- a/src/shapepipe/modules/merge_headers_package/merge_headers.py +++ b/src/shapepipe/modules/merge_headers_package/merge_headers.py @@ -15,7 +15,7 @@ from sqlitedict import SqliteDict -def merge_headers(input_file_list, output_dir): +def merge_headers(input_file_list, output_dir, file_number_string=""): """Merge Headers. This function opens the files in the input file list and merges them into @@ -27,6 +27,8 @@ def merge_headers(input_file_list, output_dir): List of input files output_dir : str Output path + file_number_string : str, optional + SP tile number string, e.g. ``-301-279`` Raises ------ @@ -41,7 +43,7 @@ def merge_headers(input_file_list, output_dir): ) # Open SqliteDict file - final_file = SqliteDict(f"{output_dir}/log_exp_headers.sqlite") + final_file = SqliteDict(f"{output_dir}/log_exp_headers{file_number_string}.sqlite") # Set matching pattern pattern = "headers-" diff --git a/src/shapepipe/modules/merge_headers_runner.py b/src/shapepipe/modules/merge_headers_runner.py index a176a493b..c984c18e9 100644 --- a/src/shapepipe/modules/merge_headers_runner.py +++ b/src/shapepipe/modules/merge_headers_runner.py @@ -48,11 +48,11 @@ def merge_headers_runner( ".npy", w_log=w_log, ) - merge_headers(headers_file_list, output_dir) + merge_headers(headers_file_list, output_dir, file_number_string) w_log.info(f"Merged {len(headers_file_list)} exposure header files") else: # Per-exposure mode: input_file_list already contains the header files. - merge_headers(input_file_list, output_dir) + merge_headers(input_file_list, output_dir, file_number_string) w_log.info(f"Merged {len(input_file_list)} input file headers") # No return objects From a9e70c632ff391f15f85d0b140266f14a4ac2751 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Tue, 28 Apr 2026 13:27:13 +0200 Subject: [PATCH 23/80] going to job 512 --- scripts/sh/job_sp_canfar_v2.0.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index 284e35441..184833832 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -463,7 +463,7 @@ if [[ $do_job != 0 ]]; then fi ## Process tiles up to shape measurement -(( do_job = $job & 1024 )) +(( do_job = $job & 512 )) if [[ $do_job != 0 ]]; then ### PSF model letter: 'P' (psfex) or 'M' (mccd) From a3eec86cc7314dda6ef081b203b92a9a7864efab Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Mon, 4 May 2026 13:55:58 +0200 Subject: [PATCH 24/80] added ID to Mh output --- .../modules/merge_headers_package/merge_headers.py | 8 +++++++- src/shapepipe/modules/merge_headers_runner.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/shapepipe/modules/merge_headers_package/merge_headers.py b/src/shapepipe/modules/merge_headers_package/merge_headers.py index 5c0a2213e..5e46daa02 100644 --- a/src/shapepipe/modules/merge_headers_package/merge_headers.py +++ b/src/shapepipe/modules/merge_headers_package/merge_headers.py @@ -15,7 +15,7 @@ from sqlitedict import SqliteDict -def merge_headers(input_file_list, output_dir): +def merge_headers(input_file_list, output_dir, tile_number=None): """Merge Headers. This function opens the files in the input file list and merges them into @@ -27,6 +27,9 @@ def merge_headers(input_file_list, output_dir): List of input files output_dir : str Output path + tile_number : str, optional + Tile number ID to store in the output file under the key + ``"TILE_ID"``, default is ``None`` Raises ------ @@ -45,6 +48,9 @@ def merge_headers(input_file_list, output_dir): # Set matching pattern pattern = "headers-" + if tile_number is not None: + final_file["TILE_ID"] = tile_number + for file_path in input_file_list: # Extract file path file_path_scalar = file_path[0] diff --git a/src/shapepipe/modules/merge_headers_runner.py b/src/shapepipe/modules/merge_headers_runner.py index a176a493b..75eefc084 100644 --- a/src/shapepipe/modules/merge_headers_runner.py +++ b/src/shapepipe/modules/merge_headers_runner.py @@ -48,11 +48,11 @@ def merge_headers_runner( ".npy", w_log=w_log, ) - merge_headers(headers_file_list, output_dir) + merge_headers(headers_file_list, output_dir, tile_number=file_number_string) w_log.info(f"Merged {len(headers_file_list)} exposure header files") else: # Per-exposure mode: input_file_list already contains the header files. - merge_headers(input_file_list, output_dir) + merge_headers(input_file_list, output_dir, tile_number=file_number_string) w_log.info(f"Merged {len(input_file_list)} input file headers") # No return objects From 5856b9f1e4f33eaabe7ed1202abd96b478dfd9d8 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Mon, 4 May 2026 15:36:47 +0200 Subject: [PATCH 25/80] v2.0 running up to job 256 --- example/cfis/config_exp_mccd.ini | 2 - example/cfis/config_tile_Git_cat_vos.ini | 4 +- example/cfis/config_tile_MiViSmVi.ini | 16 +- example/cfis/config_tile_Ng_template.ini | 9 +- .../cfis/config_tile_Ng_template_batch.ini | 9 +- example/cfis/config_tile_PiViSmVi.ini | 16 +- example/cfis/config_tile_PiViSmVi_canfar.ini | 16 +- example/cfis/config_tile_PiViVi_canfar.ini | 16 +- example/cfis/config_tile_Sx.ini | 9 +- example/cfis/config_tile_Sx_nomask.ini | 9 +- example/cfis/config_tile_Uc.ini | 11 +- pyproject.toml | 2 +- scripts/sh/job_list_help.bash | 6 +- scripts/sh/job_sp_canfar_v2.0.bash | 3 +- scripts/sh/run_job_sp_canfar_v2.0.bash | 140 ++++++++++++------ src/shapepipe/modules/mccd_interp_runner.py | 3 +- .../merge_headers_package/merge_headers.py | 13 +- src/shapepipe/modules/merge_headers_runner.py | 11 +- src/shapepipe/modules/ngmix_runner.py | 6 +- src/shapepipe/modules/psfex_interp_runner.py | 3 +- .../modules/read_ext_sexcat_runner.py | 2 +- src/shapepipe/modules/sextractor_runner.py | 7 +- src/shapepipe/modules/vignetmaker_runner.py | 2 +- 23 files changed, 173 insertions(+), 142 deletions(-) diff --git a/example/cfis/config_exp_mccd.ini b/example/cfis/config_exp_mccd.ini index 470c1a6b2..8307a6aeb 100644 --- a/example/cfis/config_exp_mccd.ini +++ b/example/cfis/config_exp_mccd.ini @@ -236,5 +236,3 @@ PSF_MODEL_PATTERN = fitted_model # PSF model separator PSF_MODEL_SEPARATOR = - -# For multi-epoch purposes -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite diff --git a/example/cfis/config_tile_Git_cat_vos.ini b/example/cfis/config_tile_Git_cat_vos.ini index 29008c194..8254066b1 100644 --- a/example/cfis/config_tile_Git_cat_vos.ini +++ b/example/cfis/config_tile_Git_cat_vos.ini @@ -8,10 +8,10 @@ VERBOSE = False # Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_Gic +RUN_NAME = run_sp_tile_Gic # Add date and time to RUN_NAME, optional, default: False -RUN_DATETIME = True +RUN_DATETIME = False ## ShapePipe execution options diff --git a/example/cfis/config_tile_MiViSmVi.ini b/example/cfis/config_tile_MiViSmVi.ini index 7b5989894..4a6eb79cc 100644 --- a/example/cfis/config_tile_MiViSmVi.ini +++ b/example/cfis/config_tile_MiViSmVi.ini @@ -57,11 +57,11 @@ TIMEOUT = 96:00:00 [MCCD_INTERP_RUNNER] -INPUT_DIR = last:sextractor_runner_run_1 +INPUT_DIR = last:sextractor_runner_run_1, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -84,9 +84,6 @@ PSF_MODEL_PATTERN = fitted_model PSF_MODEL_SEPARATOR = - -# Multi-epoch mode: Path to file with single-exposure WCS header information -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - [VIGNETMAKER_RUNNER_RUN_1] @@ -146,11 +143,11 @@ OUTPUT_MODE = new # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = last:sextractor_runner_run_1 +INPUT_DIR = last:sextractor_runner_run_1, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -178,4 +175,3 @@ PREFIX = # run outputs ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2 ME_IMAGE_PATTERN = flag, image, weight, background -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite diff --git a/example/cfis/config_tile_Ng_template.ini b/example/cfis/config_tile_Ng_template.ini index 8799042cf..70cf91de7 100644 --- a/example/cfis/config_tile_Ng_template.ini +++ b/example/cfis/config_tile_Ng_template.ini @@ -55,18 +55,15 @@ TIMEOUT = 96:00:00 # Model-fitting shapes with ngmix [NGMIX_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2 +INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2,run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet +FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers -FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite +FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # Magnitude zero-point MAG_ZP = 30.0 diff --git a/example/cfis/config_tile_Ng_template_batch.ini b/example/cfis/config_tile_Ng_template_batch.ini index 38146cc9d..1ddb00d44 100644 --- a/example/cfis/config_tile_Ng_template_batch.ini +++ b/example/cfis/config_tile_Ng_template_batch.ini @@ -55,18 +55,15 @@ TIMEOUT = 96:00:00 # Model-fitting shapes with ngmix [NGMIX_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2 +INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2,run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet +FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers -FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite +FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # Directory of previous run, optional. CHECK_EXISTING_DIR = $SP_RUN/output/run_sp_tile_ngmix_Ng1u_prev/ngmix_runner/output diff --git a/example/cfis/config_tile_PiViSmVi.ini b/example/cfis/config_tile_PiViSmVi.ini index 6c6f0f868..03ebf5fbb 100644 --- a/example/cfis/config_tile_PiViSmVi.ini +++ b/example/cfis/config_tile_PiViSmVi.ini @@ -58,11 +58,11 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = last:sextractor_runner_run_1 +INPUT_DIR = last:sextractor_runner_run_1, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -92,9 +92,6 @@ ME_DOT_PSF_DIR = psfex_runner # Input psf file pattern ME_DOT_PSF_PATTERN = star_split_ratio_80 -# Multi-epoch mode: Path to file with single-exposure WCS header information -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # Create vignets for tiles weights [VIGNETMAKER_RUNNER_RUN_1] @@ -153,11 +150,11 @@ OUTPUT_MODE = new # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = last:sextractor_runner_run_1 +INPUT_DIR = last:sextractor_runner_run_1, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -185,4 +182,3 @@ PREFIX = # run outputs ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2 ME_IMAGE_PATTERN = flag, image, weight, background -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite diff --git a/example/cfis/config_tile_PiViSmVi_canfar.ini b/example/cfis/config_tile_PiViSmVi_canfar.ini index d9244ded0..ceb356855 100644 --- a/example/cfis/config_tile_PiViSmVi_canfar.ini +++ b/example/cfis/config_tile_PiViSmVi_canfar.ini @@ -58,11 +58,11 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -92,9 +92,6 @@ ME_DOT_PSF_DIR = all:psfex_runner # Input psf file pattern ME_DOT_PSF_PATTERN = star_split_ratio_80 -# Multi-epoch mode: Path to file with single-exposure WCS header information -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # Create vignets for tiles weights [VIGNETMAKER_RUNNER_RUN_1] @@ -153,11 +150,11 @@ OUTPUT_MODE = new # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = run_sp_tile_Sx:sextractor_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -185,4 +182,3 @@ PREFIX = # run outputs ME_IMAGE_DIR = all:split_exp_runner, all:split_exp_runner, all:split_exp_runner, all:sextractor_runner ME_IMAGE_PATTERN = flag, image, weight, background -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite diff --git a/example/cfis/config_tile_PiViVi_canfar.ini b/example/cfis/config_tile_PiViVi_canfar.ini index 0f94ba15a..c841e207b 100644 --- a/example/cfis/config_tile_PiViVi_canfar.ini +++ b/example/cfis/config_tile_PiViVi_canfar.ini @@ -57,11 +57,11 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -91,9 +91,6 @@ ME_DOT_PSF_DIR = all:psfex_runner # Input psf file pattern ME_DOT_PSF_PATTERN = star_split_ratio_80 -# Multi-epoch mode: Path to file with single-exposure WCS header information -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # Create vignets for tiles weights [VIGNETMAKER_RUNNER_RUN_1] @@ -132,11 +129,11 @@ PREFIX = weight # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = run_sp_tile_Sx:sextractor_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -164,4 +161,3 @@ PREFIX = # run outputs ME_IMAGE_DIR = all:split_exp_runner, all:split_exp_runner, all:split_exp_runner, all:sextractor_runner ME_IMAGE_PATTERN = flag, image, weight, background -ME_LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite diff --git a/example/cfis/config_tile_Sx.ini b/example/cfis/config_tile_Sx.ini index 6ac5c5440..085b4fb5a 100644 --- a/example/cfis/config_tile_Sx.ini +++ b/example/cfis/config_tile_Sx.ini @@ -55,11 +55,11 @@ TIMEOUT = 96:00:00 [SEXTRACTOR_RUNNER] -INPUT_DIR = run_sp_Git:get_images_runner, last:uncompress_fits_runner, run_sp_Ma_tile:mask_runner +INPUT_DIR = run_sp_Git:get_images_runner, last:uncompress_fits_runner, run_sp_Ma_tile:mask_runner, run_sp_tile_Mh_exp:merge_headers_runner -FILE_PATTERN = CFIS_image, CFIS_weight, pipeline_flag +FILE_PATTERN = CFIS_image, CFIS_weight, pipeline_flag, log_exp_headers -FILE_EXT = .fits, .fits, .fits +FILE_EXT = .fits, .fits, .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -107,9 +107,6 @@ SUFFIX = sexcat # Necessary for tiles, to enable multi-exposure processing MAKE_POST_PROCESS = True -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y WORLD_POSITION = XWIN_WORLD,YWIN_WORLD diff --git a/example/cfis/config_tile_Sx_nomask.ini b/example/cfis/config_tile_Sx_nomask.ini index d9e4e94ab..81bb89c61 100644 --- a/example/cfis/config_tile_Sx_nomask.ini +++ b/example/cfis/config_tile_Sx_nomask.ini @@ -55,11 +55,11 @@ TIMEOUT = 96:00:00 [SEXTRACTOR_RUNNER] -INPUT_DIR = run_sp_Git:get_images_runner, last:uncompress_fits_runner +INPUT_DIR = run_sp_Git:get_images_runner, last:uncompress_fits_runner, run_sp_exp_Mh:merge_headers_runner -FILE_PATTERN = CFIS_image, CFIS_weight +FILE_PATTERN = CFIS_image, CFIS_weight, log_exp_headers -FILE_EXT = .fits, .fits +FILE_EXT = .fits, .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -107,9 +107,6 @@ SUFFIX = sexcat # Necessary for tiles, to enable multi-exposure processing MAKE_POST_PROCESS = True -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y WORLD_POSITION = XWIN_WORLD,YWIN_WORLD diff --git a/example/cfis/config_tile_Uc.ini b/example/cfis/config_tile_Uc.ini index da7535909..628e81a0c 100644 --- a/example/cfis/config_tile_Uc.ini +++ b/example/cfis/config_tile_Uc.ini @@ -54,13 +54,13 @@ TIMEOUT = 96:00:00 ## Module options -[READ_EXT_CAT_RUNNER] +[READ_EXT_SEXCAT_RUNNER] -INPUT_DIR = run_sp_Gic:get_images_runner, run_sp_Git:get_images_runner +INPUT_DIR = run_sp_tile_Gic:get_images_runner, run_sp_tile_Git:get_images_runner, run_sp_tile_Mh_exp:merge_headers_runner -FILE_PATTERN = CFIS_cat, CFIS_image +FILE_PATTERN = CFIS_cat, CFIS_image, log_exp_headers -FILE_EXT = .cat, .fits +FILE_EXT = .cat, .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -77,9 +77,6 @@ VIGNET_SIZE = 51 # Necessary for tiles, to enable multi-exposure processing MAKE_POST_PROCESS = True -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/run_sp_exp_Mh/merge_headers_runner/output/log_exp_headers.sqlite - # World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y WORLD_POSITION = ALPHA_J2000,DELTA_J2000 diff --git a/pyproject.toml b/pyproject.toml index 89a1fae5f..35f542dfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,9 +74,9 @@ script-files = [ "bin/canfar_monitor.py", "bin/canfar_monitor_log.py", "scripts/python/update_runs_log_file.py", + "scripts/sh/init_run_v2.0.sh", "scripts/sh/run_job_sp_canfar_v2.0.bash", "scripts/sh/job_sp_canfar_v2.0.bash", - "scripts/sh/init_run_v2.0.sh", ] [tool.setuptools.packages.find] diff --git a/scripts/sh/job_list_help.bash b/scripts/sh/job_list_help.bash index 3213e762e..52430a434 100644 --- a/scripts/sh/job_list_help.bash +++ b/scripts/sh/job_list_help.bash @@ -14,5 +14,7 @@ JOB_LIST_HELP="\ \t 64: process stars on exposures, PSF model (offline)\n\ \t 128: merge exposure WCS headers into tile-level sqlite log\n\ \t 256: object selection on tiles (online if UNIONS catalogue or tar_cat_for_mask=onthefly)\n\ - \t 512: create final catalogue\n\ - \t1024: process tiles (PSF interp, vignet, shape measurement)\n" + \t 512: postage stamp creation\n\ + \t1024: multi-epoch shape measurement\n\ + \t2048: create final catalogue\n\ +" diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index 184833832..ae28647b9 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -401,7 +401,6 @@ fi if [[ $do_job != 0 ]]; then ### Merge single-exposure WCS headers into tile-level sqlite log. - ### Must run before object detection/selection (make_post_process needs it). command_cfg_shapepipe \ "config_tile_Mh_exp.ini" \ "Run shapepipe (merge exp headers)" \ @@ -462,7 +461,7 @@ if [[ $do_job != 0 ]]; then fi -## Process tiles up to shape measurement +## Process tiles up to shape measurement: postage stamp creation (( do_job = $job & 512 )) if [[ $do_job != 0 ]]; then diff --git a/scripts/sh/run_job_sp_canfar_v2.0.bash b/scripts/sh/run_job_sp_canfar_v2.0.bash index 0436e0c33..fcb4a0963 100755 --- a/scripts/sh/run_job_sp_canfar_v2.0.bash +++ b/scripts/sh/run_job_sp_canfar_v2.0.bash @@ -164,13 +164,20 @@ function init_exp_work_dir() { # Run a per-exposure job (e.g. job 8, 16). -# Args: $1 = job number, $2 = run_sp output dir prefix (e.g. "Gie") -# $3 = space-separated list of "runner_subdir:N" completeness checks -# all pairs must pass for an exposure to be considered complete +# Args: $1 = job number +# $2 = space-separated list of run_sp_exp output dir prefixes (e.g. "Gie") +# first prefix is the main one; all are force-removed when --force is set +# $3 = space-separated list of completeness checks, each in one of two forms: +# "runner_subdir:N[:subpath[:warn]]" +# check runner_subdir/output in the main (first) prefix run dir +# "run_prefix:runner_subdir:N[:subpath[:warn]]" +# check runner_subdir/output in run_sp_exp_run_prefix* dir +# all non-warn checks must pass for an exposure to be considered complete function run_exp_job() { local exp_job=$1 - local run_prefix=$2 + local run_prefixes=$2 local complete_checks=$3 + local main_prefix="${run_prefixes%% *}" exp_numbers_file=$(ls -t "$work_dir/output/run_sp_tile_Fe"*/find_exposures_runner/output/"exp_numbers-${IDra}-${IDdec}.txt" 2>/dev/null | head -1) @@ -203,25 +210,43 @@ function run_exp_job() { # Create exp_numbers-000-000.txt and cfis link if not existent init_exp_work_dir "$exp_id" "$exp_work_dir" - # --force: remove all existing run directories for this prefix before running + # force: remove all existing run directories for each prefix before running if [ "$force" == "1" ]; then - local dirs_to_remove - dirs_to_remove=$(ls -d "$exp_work_dir/output/run_sp_exp_${run_prefix}"* 2>/dev/null) - if [ -n "$dirs_to_remove" ]; then - for d in $dirs_to_remove; do - message "Force-removing $d" "$debug_out" -1 - command "rm -rf $d" $dry_run - done - fi + local run_prefix + for run_prefix in $run_prefixes; do + local dirs_to_remove + dirs_to_remove=$(ls -d "$exp_work_dir/output/run_sp_exp_${run_prefix}"* 2>/dev/null) + if [ -n "$dirs_to_remove" ]; then + for d in $dirs_to_remove; do + message "Force-removing $d" "$debug_out" -1 + command "rm -rf $d" $dry_run + done + fi + done fi - # Check completeness of existing run output - local run_dir=$(ls -dt "$exp_work_dir/output/run_sp_exp_${run_prefix}"* 2>/dev/null | head -1) + # Check completeness of existing run output (main prefix) + local run_dir=$(ls -dt "$exp_work_dir/output/run_sp_exp_${main_prefix}"* 2>/dev/null | head -1) local is_complete=1 local check_desc="" for check_pair in $complete_checks; do - local subdir="${check_pair%%:*}" - local rest="${check_pair#*:}" + local field1="${check_pair%%:*}" + local rest1="${check_pair#*:}" + local field2="${rest1%%:*}" + + local check_run_dir subdir rest + if [[ "$field2" =~ ^[0-9]+$ ]]; then + # "subdir:N[...]" — check in main run dir + check_run_dir="$run_dir" + subdir="$field1" + rest="$rest1" + else + # "run_prefix:subdir:N[...]" — check in that prefix's run dir + check_run_dir=$(ls -dt "$exp_work_dir/output/run_sp_exp_${field1}"* 2>/dev/null | head -1) + subdir="$field2" + rest="${rest1#*:}" + fi + local n_threshold="${rest%%:*}" local rest2="${rest#*:}" local subpath="" @@ -234,10 +259,11 @@ function run_exp_job() { fi local n_out=0 + local out_dir if [ -n "$subpath" ]; then - out_dir="$run_dir/${subdir}/output/${subpath}" + out_dir="${check_run_dir}/${subdir}/output/${subpath}" else - out_dir="$run_dir/${subdir}/output" + out_dir="${check_run_dir}/${subdir}/output" fi # Remove broken symlinks in module output dir @@ -248,7 +274,7 @@ function run_exp_job() { fi done - [ -n "$run_dir" ] && n_out=$(ls "$out_dir/" 2>/dev/null | wc -l) + [ -n "$check_run_dir" ] && n_out=$(ls "$out_dir/" 2>/dev/null | wc -l) check_desc+="${subdir}:${n_out}/${n_threshold} " if [ "$n_out" -lt "$n_threshold" ]; then if [ "$warn_only" == "1" ]; then @@ -261,7 +287,7 @@ function run_exp_job() { done if [ "$is_complete" == "1" ]; then - message "Complete $exp_id_disp: run_sp_exp_${run_prefix} ( $check_desc)" "$debug_out" -1 + message "Complete $exp_id_disp: run_sp_exp_${main_prefix} ( $check_desc)" "$debug_out" -1 (( n_complete++ )) continue fi @@ -304,37 +330,61 @@ function run_exp_job() { # Run a tile-level job (jobs 1, 2, 4, 128, 256, ...). # Args: $1 = job number -# $2 = run_sp output dir prefix (e.g. "Fe", "tile_Sx", "exp_Mh") -# $3 = space-separated completeness checks "runner_subdir:N[:subpath[:warn]]" +# $2 = space-separated list of run_sp_tile output dir prefixes (e.g. "Fe") +# first prefix is the main one; all are force-removed when --force is set +# $3 = space-separated completeness checks, each in one of two forms: +# "runner_subdir:N[:subpath[:warn]]" +# check runner_subdir/output in the main (first) prefix run dir +# "run_prefix:runner_subdir:N[:subpath[:warn]]" +# check runner_subdir/output in run_sp_tile_run_prefix* dir # omit or pass "" to skip the completeness check and always run function run_tile_job() { local tile_job=$1 - local run_prefix=$2 + local run_prefixes=$2 local complete_checks=$3 + local main_prefix="${run_prefixes%% *}" - # --force: remove all existing run directories for this prefix before running + # force: remove all existing run directories for each prefix before running if [ "$force" == "1" ]; then - local dirs_to_remove - dirs_to_remove=$(ls -d "$work_dir/output/run_sp_tile_${run_prefix}"* 2>/dev/null) - if [ -n "$dirs_to_remove" ]; then - for d in $dirs_to_remove; do - message "Force-removing $d" "$debug_out" -1 - command "rm -rf $d" $dry_run - done - fi + local run_prefix + for run_prefix in $run_prefixes; do + local dirs_to_remove + dirs_to_remove=$(ls -d "$work_dir/output/run_sp_tile_${run_prefix}"* 2>/dev/null) + if [ -n "$dirs_to_remove" ]; then + for d in $dirs_to_remove; do + message "Force-removing $d" "$debug_out" -1 + command "rm -rf $d" $dry_run + done + fi + done fi - # Locate most recent existing run directory for this prefix + # Locate most recent existing run directory for the main prefix local run_dir - run_dir=$(ls -dt "$work_dir/output/run_sp_tile_${run_prefix}"* 2>/dev/null | head -1) + run_dir=$(ls -dt "$work_dir/output/run_sp_tile_${main_prefix}"* 2>/dev/null | head -1) local is_complete=1 local check_desc="" if [ -n "$complete_checks" ]; then for check_pair in $complete_checks; do - local subdir="${check_pair%%:*}" - local rest="${check_pair#*:}" + local field1="${check_pair%%:*}" + local rest1="${check_pair#*:}" + local field2="${rest1%%:*}" + + local check_run_dir subdir rest + if [[ "$field2" =~ ^[0-9]+$ ]]; then + # "subdir:N[...]" — check in main run dir + check_run_dir="$run_dir" + subdir="$field1" + rest="$rest1" + else + # "run_prefix:subdir:N[...]" — check in that prefix's run dir + check_run_dir=$(ls -dt "$work_dir/output/run_sp_tile_${field1}"* 2>/dev/null | head -1) + subdir="$field2" + rest="${rest1#*:}" + fi + local n_threshold="${rest%%:*}" local rest2="${rest#*:}" local subpath="" @@ -346,13 +396,13 @@ function run_tile_job() { local out_dir if [ -n "$subpath" ]; then - out_dir="$run_dir/${subdir}/output/${subpath}" + out_dir="${check_run_dir}/${subdir}/output/${subpath}" else - out_dir="$run_dir/${subdir}/output" + out_dir="${check_run_dir}/${subdir}/output" fi local n_out=0 - [ -n "$run_dir" ] && n_out=$(ls "$out_dir/" 2>/dev/null | wc -l) + [ -n "$check_run_dir" ] && n_out=$(ls "$out_dir/" 2>/dev/null | wc -l) check_desc+="${subdir}:${n_out}/${n_threshold} " if [ "$n_out" -lt "$n_threshold" ]; then [ "$warn_only" != "1" ] && is_complete=0 @@ -361,15 +411,15 @@ function run_tile_job() { fi if [ "$is_complete" == "1" ] && [ -n "$complete_checks" ]; then - message "Complete: run_sp_tile_${run_prefix} ( $check_desc)" "$debug_out" -1 + message "Complete: run_sp_tile_${main_prefix} ( $check_desc)" "$debug_out" -1 return 0 fi if [ "$check" == "1" ]; then if [ -n "$run_dir" ]; then - message "Incomplete: run_sp_tile_${run_prefix} ($check_desc)" "$debug_out" -1 + message "Incomplete: run_sp_tile_${main_prefix} ($check_desc)" "$debug_out" -1 else - message "Missing: run_sp_tile_${run_prefix}" "$debug_out" -1 + message "Missing: run_sp_tile_${main_prefix}" "$debug_out" -1 fi return 0 fi @@ -527,7 +577,7 @@ fi if [[ $do_job != 0 ]]; then # Job 256: object selection on tiles if [ "$tile_det" == "uc" ]; then - run_tile_job 256 "Uc" "read_ext_sexcat_runner:1" + run_tile_job 256 "Gic Uc" "Gic_cat_vos:get_images_runner:1 read_ext_sexcat_runner:1" else run_tile_job 256 "Sx" "sextractor_runner:1" fi @@ -542,7 +592,7 @@ fi (( do_job = job & 1024 )) if [[ $do_job != 0 ]]; then # Job 1024: process tiles (PSF interp, vignet, shape measurement) - run_tile_job 1024 "tile_${Letter}sViSmVi" "" + run_tile_job 1024 "tile_${Letter}iVi_canfar" fi if [ -n "$scratch" ]; then diff --git a/src/shapepipe/modules/mccd_interp_runner.py b/src/shapepipe/modules/mccd_interp_runner.py index 1428e44d6..1e3a28d50 100644 --- a/src/shapepipe/modules/mccd_interp_runner.py +++ b/src/shapepipe/modules/mccd_interp_runner.py @@ -90,9 +90,8 @@ def mccd_interp_runner( module = config.getexpanded(module_config_sec, "PSF_MODEL_DIR") psf_model_dir = get_last_dir(run_dirs["run_log"], module) psf_model_pattern = config.get(module_config_sec, "PSF_MODEL_PATTERN") - f_wcs_path = config.getexpanded(module_config_sec, "ME_LOG_WCS") - galcat_path = input_file_list[0] + f_wcs_path = input_file_list[1] inst = mccd_interp.MCCDinterpolator( None, diff --git a/src/shapepipe/modules/merge_headers_package/merge_headers.py b/src/shapepipe/modules/merge_headers_package/merge_headers.py index 8a03dc665..1b15a1a5f 100644 --- a/src/shapepipe/modules/merge_headers_package/merge_headers.py +++ b/src/shapepipe/modules/merge_headers_package/merge_headers.py @@ -15,7 +15,7 @@ from sqlitedict import SqliteDict -def merge_headers(input_file_list, output_dir, file_number_string=""): +def merge_headers(input_file_list, output_dir, tile_number=None): """Merge Headers. This function opens the files in the input file list and merges them into @@ -27,8 +27,8 @@ def merge_headers(input_file_list, output_dir, file_number_string=""): List of input files output_dir : str Output path - file_number_string : str, optional - SP tile number string, e.g. ``-301-279`` + tile_number : str, optional + Tile number ID stored under the key ``"TILE_ID"``, default is ``None`` Raises ------ @@ -43,12 +43,13 @@ def merge_headers(input_file_list, output_dir, file_number_string=""): ) # Open SqliteDict file - final_file = SqliteDict(f"{output_dir}/log_exp_headers{file_number_string}.sqlite") + suffix = tile_number if tile_number else "" + final_file = SqliteDict(f"{output_dir}/log_exp_headers{suffix}.sqlite") # Set matching pattern pattern = "headers-" - if file_number_string is not None: - final_file["TILE_ID"] = file_number_string + if tile_number: + final_file["TILE_ID"] = tile_number for file_path in input_file_list: # Extract file path diff --git a/src/shapepipe/modules/merge_headers_runner.py b/src/shapepipe/modules/merge_headers_runner.py index 15c3fe6be..9bee35159 100644 --- a/src/shapepipe/modules/merge_headers_runner.py +++ b/src/shapepipe/modules/merge_headers_runner.py @@ -6,6 +6,9 @@ """ +import os +import re + from shapepipe.modules.merge_headers_package.merge_headers import merge_headers from shapepipe.modules.module_decorator import module_runner from shapepipe.pipeline.exp_utils import get_exp_output_files @@ -48,11 +51,15 @@ def merge_headers_runner( ".npy", w_log=w_log, ) - merge_headers(headers_file_list, output_dir, file_number_string) + # Extract tile number from the exp_numbers filename, e.g. + # "exp_numbers-284.272-1.000.txt" -> "-284.272-1.000" + base = os.path.splitext(os.path.basename(exp_numbers_file))[0] + tile_number = re.sub(r"^exp_numbers", "", base) + merge_headers(headers_file_list, output_dir, tile_number) w_log.info(f"Merged {len(headers_file_list)} exposure header files") else: # Per-exposure mode: input_file_list already contains the header files. - merge_headers(input_file_list, output_dir, file_number_string) + merge_headers(input_file_list, output_dir) w_log.info(f"Merged {len(input_file_list)} input file headers") # No return objects diff --git a/src/shapepipe/modules/ngmix_runner.py b/src/shapepipe/modules/ngmix_runner.py index fbdc533c7..08e92a605 100644 --- a/src/shapepipe/modules/ngmix_runner.py +++ b/src/shapepipe/modules/ngmix_runner.py @@ -16,6 +16,7 @@ "sextractor_runner", "psfex_interp_runner", "vignetmaker_runner", + "merge_headers_runner", ], file_pattern=[ "tile_sexcat", @@ -24,8 +25,9 @@ "galaxy_psf", "weight", "flag", + "log_exp_headers", ], - file_ext=[".fits", ".sqlite", ".sqlite", ".sqlite", ".sqlite", ".sqlite"], + file_ext=[".fits", ".sqlite", ".sqlite", ".sqlite", ".sqlite", ".sqlite", ".sqlite"], depends=["numpy", "ngmix", "galsim", "sqlitedict", "astropy"], ) def ngmix_runner( @@ -46,7 +48,7 @@ def ngmix_runner( pixel_scale = config.getfloat(module_config_sec, "PIXEL_SCALE") # Path to merged single-exposure single-HDU headers - f_wcs_path = config.getexpanded(module_config_sec, "LOG_WCS") + f_wcs_path = input_file_list[6] # Input directory to check for already retrieved files if config.has_option(module_config_sec, "CHECK_EXISTING_DIR"): diff --git a/src/shapepipe/modules/psfex_interp_runner.py b/src/shapepipe/modules/psfex_interp_runner.py index 55d8d73c1..cfa968bc7 100644 --- a/src/shapepipe/modules/psfex_interp_runner.py +++ b/src/shapepipe/modules/psfex_interp_runner.py @@ -81,10 +81,9 @@ def psfex_interp_runner( module_config_sec, "ME_DOT_PSF_PATTERN", ) - f_wcs_path = config.getexpanded(module_config_sec, "ME_LOG_WCS") - # Set input paths galcat_path = input_file_list[0] + f_wcs_path = input_file_list[1] # Create instance of PSFExInterpolator psfex_interp_inst = psfex_interp.PSFExInterpolator( diff --git a/src/shapepipe/modules/read_ext_sexcat_runner.py b/src/shapepipe/modules/read_ext_sexcat_runner.py index 949f5a7c7..57035a9b6 100644 --- a/src/shapepipe/modules/read_ext_sexcat_runner.py +++ b/src/shapepipe/modules/read_ext_sexcat_runner.py @@ -61,7 +61,7 @@ def read_ext_sexcat_runner( ) if config.getboolean(module_config_sec, "MAKE_POST_PROCESS"): - f_wcs_path = config.getexpanded(module_config_sec, "LOG_WCS") + f_wcs_path = input_file_list[2] pos_params = config.getlist(module_config_sec, "WORLD_POSITION") ccd_size = config.getlist(module_config_sec, "CCD_SIZE") w_log.info("Running post-processing") diff --git a/src/shapepipe/modules/sextractor_runner.py b/src/shapepipe/modules/sextractor_runner.py index 87bc02c2c..dfc28c8f9 100644 --- a/src/shapepipe/modules/sextractor_runner.py +++ b/src/shapepipe/modules/sextractor_runner.py @@ -68,6 +68,12 @@ def sextractor_runner( else: prefix = None + # When post-processing is enabled the sqlite WCS file is the last input; + # remove it before passing the image files to SExtractorCaller. + if config.getboolean(module_config_sec, "MAKE_POST_PROCESS"): + f_wcs_path = input_file_list[-1] + input_file_list = list(input_file_list[:-1]) + # Create sextractor caller class instance ss_inst = ss.SExtractorCaller( input_file_list, @@ -101,7 +107,6 @@ def sextractor_runner( # Run sextractor post processing if config.getboolean(module_config_sec, "MAKE_POST_PROCESS"): - f_wcs_path = config.getexpanded(module_config_sec, "LOG_WCS") pos_params = config.getlist(module_config_sec, "WORLD_POSITION") ccd_size = config.getlist(module_config_sec, "CCD_SIZE") ss.make_post_process(ss_inst.path_output_file, f_wcs_path, pos_params, ccd_size) diff --git a/src/shapepipe/modules/vignetmaker_runner.py b/src/shapepipe/modules/vignetmaker_runner.py index 5b9663e71..86dc6b100 100644 --- a/src/shapepipe/modules/vignetmaker_runner.py +++ b/src/shapepipe/modules/vignetmaker_runner.py @@ -126,7 +126,7 @@ def vignetmaker_runner( ) # Get WCS log file path - f_wcs_path = config.getexpanded(module_config_sec, "ME_LOG_WCS") + f_wcs_path = input_file_list[1] # Process inputs vm_inst.process_me(image_dirs, image_pattern, f_wcs_path, radius) From adc2684d4f85cee2a49cb819d0d47596f4803862 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Tue, 5 May 2026 13:56:55 +0000 Subject: [PATCH 26/80] fixing log headers; weightwatcher exe --- example/cfis/config_onthefly.mask | 2 +- example/cfis/config_save.mask | 2 +- example/cfis/config_tile_PiViVi_canfar.ini | 11 ++- example/cfis/config_tile_onthefly.mask | 2 +- example/cfis/config_tile_save.mask | 2 +- scripts/sh/job_sp_canfar_v2.0.bash | 7 +- src/shapepipe/modules/mask_runner.py | 2 +- src/shapepipe/modules/psfex_interp_runner.py | 35 +++++--- src/shapepipe/pipeline/exp_utils.py | 92 ++++++++++++++++++++ 9 files changed, 133 insertions(+), 22 deletions(-) diff --git a/example/cfis/config_onthefly.mask b/example/cfis/config_onthefly.mask index 9a147bdcd..7c185c602 100644 --- a/example/cfis/config_onthefly.mask +++ b/example/cfis/config_onthefly.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/example/cfis/config_save.mask b/example/cfis/config_save.mask index 887110ff5..497dedda9 100644 --- a/example/cfis/config_save.mask +++ b/example/cfis/config_save.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/example/cfis/config_tile_PiViVi_canfar.ini b/example/cfis/config_tile_PiViVi_canfar.ini index c841e207b..e9c78135c 100644 --- a/example/cfis/config_tile_PiViVi_canfar.ini +++ b/example/cfis/config_tile_PiViVi_canfar.ini @@ -57,11 +57,11 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner -FILE_PATTERN = sexcat, log_exp_headers +FILE_PATTERN = sexcat, log_exp_headers, exp_numbers -FILE_EXT = .fits, .sqlite +FILE_EXT = .fits, .sqlite, .txt # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -86,7 +86,10 @@ CHI2_THRESH = 2 # Multi-epoch mode parameters -ME_DOT_PSF_DIR = all:psfex_runner +# Root directory of per-exposure work directories; replaces ME_DOT_PSF_DIR +# for v2.0 per-exposure pipeline. psfex_runner/output/ dirs are discovered +# by scanning $SP_EXP for the exposures listed in the exp_numbers input file. +ME_DOT_PSF_EXP_DIR = $SP_EXP # Input psf file pattern ME_DOT_PSF_PATTERN = star_split_ratio_80 diff --git a/example/cfis/config_tile_onthefly.mask b/example/cfis/config_tile_onthefly.mask index bcd7663ad..18cf5db2d 100644 --- a/example/cfis/config_tile_onthefly.mask +++ b/example/cfis/config_tile_onthefly.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/example/cfis/config_tile_save.mask b/example/cfis/config_tile_save.mask index 4b5fbb7f8..1de738301 100644 --- a/example/cfis/config_tile_save.mask +++ b/example/cfis/config_tile_save.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index ae28647b9..be5a54038 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -171,11 +171,16 @@ export SP_CONFIG_MOD=$SP_RUN/cfis_mod # conventional layout (SP_RUN = .../v2.0/tiles/IDra/ID, three levels up + exp). if [ -z "${SP_EXP}" ]; then export SP_EXP=$(realpath "$SP_RUN/../../../exp") - echo "SP_EXP not set, using computed path: $SP_EXP" + echo "Setting SP_EXP to $SP_EXP" fi ## Other variables +# Override OMP_NUM_THREADS if the CANFAR provisioning template was not expanded +if [[ "${OMP_NUM_THREADS}" == *'${'* ]] || [[ "${OMP_NUM_THREADS}" == *'.'* ]]; then + export OMP_NUM_THREADS=1 +fi + # Output OUTPUT=$SP_RUN/output diff --git a/src/shapepipe/modules/mask_runner.py b/src/shapepipe/modules/mask_runner.py index 8cfd9375a..13fbf9b6c 100644 --- a/src/shapepipe/modules/mask_runner.py +++ b/src/shapepipe/modules/mask_runner.py @@ -15,7 +15,7 @@ file_pattern=["image", "weight", "flag"], file_ext=[".fits", ".fits", ".fits"], depends=["numpy", "astropy"], - executes=["ww"], + executes=["weightwatcher"], numbering_scheme="_0", ) def mask_runner( diff --git a/src/shapepipe/modules/psfex_interp_runner.py b/src/shapepipe/modules/psfex_interp_runner.py index cfa968bc7..c382184f1 100644 --- a/src/shapepipe/modules/psfex_interp_runner.py +++ b/src/shapepipe/modules/psfex_interp_runner.py @@ -9,6 +9,7 @@ from shapepipe.modules.module_decorator import module_runner from shapepipe.modules.psfex_interp_package import psfex_interp +from shapepipe.pipeline.exp_utils import get_exp_output_dirs from shapepipe.pipeline.run_log import get_last_dir, get_all_dirs @@ -62,20 +63,30 @@ def psfex_interp_runner( elif mode == "MULTI-EPOCH": # Fetch multi-epoch parameters - module = config.getexpanded( - module_config_sec, - "ME_DOT_PSF_DIR", - ) - module_name = module.split(":")[-1] - if "last" in module: - dot_psf_dirs = [get_last_dir(run_dirs["run_log"], module_name)] - elif "all" in module: - dot_psf_dirs = get_all_dirs(run_dirs["run_log"], module_name) + if config.has_option(module_config_sec, "ME_DOT_PSF_EXP_DIR"): + # v2.0: locate psfex_runner output dirs via the $SP_EXP tree + exp_base_dir = config.getexpanded( + module_config_sec, "ME_DOT_PSF_EXP_DIR" + ) + exp_numbers_file = input_file_list[2] + dot_psf_dirs = get_exp_output_dirs( + exp_base_dir, exp_numbers_file, "psfex_runner", w_log + ) else: - raise ValueError( - "Expected qualifier 'last:' or 'all' before module" - + f" '{module}' in config entry 'ME_DOT_PSF_DIR'" + module = config.getexpanded( + module_config_sec, + "ME_DOT_PSF_DIR", ) + module_name = module.split(":")[-1] + if "last" in module: + dot_psf_dirs = [get_last_dir(run_dirs["run_log"], module_name)] + elif "all" in module: + dot_psf_dirs = get_all_dirs(run_dirs["run_log"], module_name) + else: + raise ValueError( + "Expected qualifier 'last:' or 'all' before module" + + f" '{module}' in config entry 'ME_DOT_PSF_DIR'" + ) dot_psf_pattern = config.get( module_config_sec, diff --git a/src/shapepipe/pipeline/exp_utils.py b/src/shapepipe/pipeline/exp_utils.py index 56987a025..8f0df784a 100644 --- a/src/shapepipe/pipeline/exp_utils.py +++ b/src/shapepipe/pipeline/exp_utils.py @@ -124,3 +124,95 @@ def get_exp_output_files( w_log.info(f"Found {len(file_list)} exposure output files") return file_list + + +def get_exp_output_dirs( + exp_base_dir, + exp_numbers_file, + runner_name, + w_log=None, +): + """Return the most-recent output directory of runner_name for each exposure. + + For each exposure ID listed in ``exp_numbers_file``, the most recent + runner output directory is located at:: + + ///output/run_sp_*//output/ + + Parameters + ---------- + exp_base_dir : str + Root directory containing all per-exposure work directories. + exp_numbers_file : str + Path to a text file listing one exposure ID per line. + runner_name : str + Module runner directory name whose output directory is needed, + e.g. ``psfex_runner``. + w_log : logging.Logger, optional + Pipeline logger. + + Returns + ------- + list of str + One output directory path per exposure, in the order they appear + in ``exp_numbers_file``. + + Raises + ------ + FileNotFoundError + If ``exp_numbers_file`` does not exist or a runner output directory + is absent for one or more exposures. + + """ + if not os.path.exists(exp_numbers_file): + raise FileNotFoundError( + f"Exposure numbers file not found: {exp_numbers_file}" + ) + + with open(exp_numbers_file) as fh: + exp_ids = [line.strip() for line in fh if line.strip()] + + if w_log: + w_log.info( + f"Collecting {runner_name}/output dirs " + f"for {len(exp_ids)} exposures from {exp_base_dir}" + ) + + dir_list = [] + missing = [] + + for exp_id in exp_ids: + exp_prefix = exp_id[:2] + exp_base = exp_id[:-1] + + pattern = os.path.join( + exp_base_dir, + exp_prefix, + exp_base, + "output", + "run_sp_*", + runner_name, + "output", + ) + matches = sorted(glob.glob(pattern)) + + if matches: + chosen = matches[-1] # most recent run (lexicographic sort on datetime) + dir_list.append(chosen) + if w_log: + w_log.debug(f" {exp_id}: {chosen}") + else: + missing.append(exp_id) + if w_log: + w_log.warning(f" {exp_id}: no match for {pattern}") + + if missing: + raise FileNotFoundError( + f"No {runner_name} output directory found for " + f"{len(missing)} exposure(s): {missing}" + ) + + if w_log: + w_log.info(f"Found {len(dir_list)} exposure output directories") + + return dir_list From b0c6f314b4234e4b713ac20c2c47bbc682e12074 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Wed, 6 May 2026 15:28:38 +0200 Subject: [PATCH 27/80] improved job checks --- example/cfis/config_tile_Git_cat_vos.ini | 2 +- ...r.ini => config_tile_PiViVi_canfar_sx.ini} | 4 +- example/cfis/config_tile_PiViVi_canfar_uc.ini | 161 ++++++++++++++++++ scripts/python/update_runs_log_file.py | 13 +- scripts/sh/job_sp_canfar_v2.0.bash | 18 +- scripts/sh/run_job_sp_canfar_v2.0.bash | 39 +++-- 6 files changed, 193 insertions(+), 44 deletions(-) rename example/cfis/{config_tile_PiViVi_canfar.ini => config_tile_PiViVi_canfar_sx.ini} (95%) create mode 100644 example/cfis/config_tile_PiViVi_canfar_uc.ini diff --git a/example/cfis/config_tile_Git_cat_vos.ini b/example/cfis/config_tile_Git_cat_vos.ini index 8254066b1..d2a18d816 100644 --- a/example/cfis/config_tile_Git_cat_vos.ini +++ b/example/cfis/config_tile_Git_cat_vos.ini @@ -11,7 +11,7 @@ VERBOSE = False RUN_NAME = run_sp_tile_Gic # Add date and time to RUN_NAME, optional, default: False -RUN_DATETIME = False +RUN_DATETIME = True ## ShapePipe execution options diff --git a/example/cfis/config_tile_PiViVi_canfar.ini b/example/cfis/config_tile_PiViVi_canfar_sx.ini similarity index 95% rename from example/cfis/config_tile_PiViVi_canfar.ini rename to example/cfis/config_tile_PiViVi_canfar_sx.ini index c841e207b..33f6c8d7f 100644 --- a/example/cfis/config_tile_PiViVi_canfar.ini +++ b/example/cfis/config_tile_PiViVi_canfar_sx.ini @@ -57,7 +57,7 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = sexcat, log_exp_headers @@ -129,7 +129,7 @@ PREFIX = weight # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = sexcat, log_exp_headers diff --git a/example/cfis/config_tile_PiViVi_canfar_uc.ini b/example/cfis/config_tile_PiViVi_canfar_uc.ini new file mode 100644 index 000000000..6e13c681a --- /dev/null +++ b/example/cfis/config_tile_PiViVi_canfar_uc.ini @@ -0,0 +1,161 @@ +# ShapePipe configuration file for tile, from detection up to shape measurement. +# PSFEx PSF model. + + +## Default ShapePipe options +[DEFAULT] + +# verbose mode (optional), default: True, print messages on terminal +VERBOSE = True + +# Name of run (optional) default: shapepipe_run +RUN_NAME = run_sp_tile_PsViSmVi + +# Add date and time to RUN_NAME, optional, default: False +; RUN_DATETIME = False + + +## ShapePipe execution options +[EXECUTION] + +# Module name, single string or comma-separated list of valid module runner names +MODULE = psfex_interp_runner, vignetmaker_runner, vignetmaker_runner + +# Parallel processing mode, SMP or MPI +MODE = SMP + + +## ShapePipe file handling options +[FILE] + +# Log file master name, optional, default: shapepipe +LOG_NAME = log_sp + +# Runner log file name, optional, default: shapepipe_runs +RUN_LOG_NAME = log_run_sp + +# Input directory, containing input files, single string or list of names +INPUT_DIR = . + +# Output directory +OUTPUT_DIR = $SP_RUN/output + + +## ShapePipe job handling options +[JOB] + +# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial +SMP_BATCH_SIZE = 16 + +# Timeout value (optional), default is None, i.e. no timeout limit applied +TIMEOUT = 96:00:00 + + +## Module options + +[PSFEX_INTERP_RUNNER] + +INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner + +FILE_PATTERN = sexcat, log_exp_headers + +FILE_EXT = .fits, .sqlite + +# NUMBERING_SCHEME (optional) string with numbering pattern for input files +NUMBERING_SCHEME = -000-000 + +# Run mode for psfex interpolation: +# CLASSIC: 'classical' run, interpolate to object positions +# MULTI-EPOCH: interpolate for multi-epoch images +# VALIDATION: validation for single-epoch images +MODE = MULTI-EPOCH + +# Column names of position parameters +POSITION_PARAMS = XWIN_WORLD,YWIN_WORLD + +# If True, measure and store ellipticity of the PSF +GET_SHAPES = True + +# Number of stars threshold +STAR_THRESH = 20 + +# chi^2 threshold +CHI2_THRESH = 2 + +# Multi-epoch mode parameters + +ME_DOT_PSF_DIR = all:psfex_runner + +# Input psf file pattern +ME_DOT_PSF_PATTERN = star_split_ratio_80 + + +# Create vignets for tiles weights +[VIGNETMAKER_RUNNER_RUN_1] + +INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner, last:uncompress_fits_runner + +FILE_PATTERN = sexcat, CFIS_weight + +FILE_EXT = .fits, .fits + +# NUMBERING_SCHEME (optional) string with numbering pattern for input files +NUMBERING_SCHEME = -000-000 + +MASKING = False +MASK_VALUE = 0 + +# Run mode for psfex interpolation: +# CLASSIC: 'classical' run, interpolate to object positions +# MULTI-EPOCH: interpolate for multi-epoch images +# VALIDATION: validation for single-epoch images +MODE = CLASSIC + +# Coordinate frame type, one in PIX (pixel frame), SPHE (spherical coordinates) +COORD = PIX +POSITION_PARAMS = XWIN_IMAGE,YWIN_IMAGE + +# Vignet size in pixels +STAMP_SIZE = 51 + +# Output file name prefix, file name is _vignet.fits +PREFIX = weight + + +[VIGNETMAKER_RUNNER_RUN_2] + +# Create multi-epoch vignets for tiles corresponding to +# positions on single-exposures + +INPUT_DIR = run_sp_tile_Sx:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner + +FILE_PATTERN = sexcat, log_exp_headers + +FILE_EXT = .fits, .sqlite + +# NUMBERING_SCHEME (optional) string with numbering pattern for input files +NUMBERING_SCHEME = -000-000 + +MASKING = False +MASK_VALUE = 0 + +# Run mode for psfex interpolation: +# CLASSIC: 'classical' run, interpolate to object positions +# MULTI-EPOCH: interpolate for multi-epoch images +# VALIDATION: validation for single-epoch images +MODE = MULTI-EPOCH + +# Coordinate frame type, one in PIX (pixel frame), SPHE (spherical coordinates) +COORD = SPHE +POSITION_PARAMS = XWIN_WORLD,YWIN_WORLD + +# Vignet size in pixels +STAMP_SIZE = 51 + +# Output file name prefix, file name is vignet.fits +PREFIX = + +# Additional parameters for path and file pattern corresponding to single-exposure +# run outputs +ME_IMAGE_DIR = all:split_exp_runner, all:split_exp_runner, all:split_exp_runner, all:sextractor_runner +ME_IMAGE_PATTERN = flag, image, weight, background diff --git a/scripts/python/update_runs_log_file.py b/scripts/python/update_runs_log_file.py index debbe9c85..42721ef31 100755 --- a/scripts/python/update_runs_log_file.py +++ b/scripts/python/update_runs_log_file.py @@ -53,23 +53,12 @@ def update_log_file(module_runs, log_name): with open(log_name, "w") as f_out: for key in module_runs: - print(key, file=f_out, end=" ") + print(os.path.abspath(key), file=f_out, end=" ") print(",".join(module_runs[key]), file=f_out) def main(argv=None): - # Set default parameters - # p_def = params_default() - - # Command line options - # options, args = parse_options(p_def) - - # if check_options(options) is False: - # return 1 - - # param = update_param(p_def, options) - base_dir = "./output" pattern = "run_sp_" log_name = f"{base_dir}/log_run_sp.txt" diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index ae28647b9..d120f129a 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -469,8 +469,8 @@ if [[ $do_job != 0 ]]; then letter=${psf:0:1} Letter=${letter^} command_cfg_shapepipe \ - "config_tile_${Letter}iViVi_canfar.ini" \ - "Run shapepipe (tile PsfInterp=${Letter}: up to ngmix+galsim)" \ + "config_tile_${Letter}iViVi_canfar_${tile_det}.ini" \ + "Run shapepipe (tile PsfInterp=${Letter}: postage stamp creation)" \ $n_smp \ $exclusive @@ -478,20 +478,6 @@ fi ## Create final catalogues (offline) (( do_job = $job & 1024 )) -if [[ $do_job != 0 ]]; then - - cat $SP_CONFIG/config_merge_sep_cats_template.ini | \ - perl -ane \ - 's/(N_SPLIT_MAX =) X/$1 '$nsh_jobs'/; print' \ - > $SP_CONFIG_MOD/config_merge_sep_cats.ini - - ### Merge separated shapes catalogues - command \ - "shapepipe_run.py -c $SP_CONFIG_MOD/config_tile_merge_sep_cats.ini" \ - "Run shapepipe (tile: merge sep cats)" -fi - -(( do_job = $job & 512 )) if [[ $do_job != 0 ]]; then suff_sm="_nosm" diff --git a/scripts/sh/run_job_sp_canfar_v2.0.bash b/scripts/sh/run_job_sp_canfar_v2.0.bash index fcb4a0963..6ad9a59d5 100755 --- a/scripts/sh/run_job_sp_canfar_v2.0.bash +++ b/scripts/sh/run_job_sp_canfar_v2.0.bash @@ -315,8 +315,6 @@ function run_exp_job() { command "job_sp_canfar_v2.0.bash -p $psf --tile_det $tile_det --tile_mask $tile_mask -j $exp_job --n_smp $N_SMP --nsh_jobs $N_SMP $debug_flag" $dry_run 2>&1 | tee -a "$exp_log_file" echo "Done with job_sp_canfar_v2.0.bash" - #cd "$dir" - done < "$exp_numbers_file" local t_loop_end=$(date +%s) @@ -338,6 +336,7 @@ function run_exp_job() { # "run_prefix:runner_subdir:N[:subpath[:warn]]" # check runner_subdir/output in run_sp_tile_run_prefix* dir # omit or pass "" to skip the completeness check and always run + function run_tile_job() { local tile_job=$1 local run_prefixes=$2 @@ -367,20 +366,32 @@ function run_tile_job() { local check_desc="" if [ -n "$complete_checks" ]; then - for check_pair in $complete_checks; do + + # turn inputs into arrays + read -r -a run_prefix_array <<< "$run_prefixes" + read -r -a check_array <<< "$complete_checks" + + # sanity check (optional but strongly recommended) + if [ "${#run_prefix_array[@]}" -ne "${#check_array[@]}" ]; then + echo "Error: run_prefixes and complete_checks must have same length" + return 1 + fi + + # zip loop + for i in "${!run_prefix_array[@]}"; do + run_prefix="${run_prefix_array[$i]}" + check_pair="${check_array[$i]}" + local field1="${check_pair%%:*}" local rest1="${check_pair#*:}" local field2="${rest1%%:*}" local check_run_dir subdir rest + check_run_dir=$(ls -dt "$work_dir/output/run_sp_tile_${run_prefix}"* 2>/dev/null | head -1) if [[ "$field2" =~ ^[0-9]+$ ]]; then - # "subdir:N[...]" — check in main run dir - check_run_dir="$run_dir" subdir="$field1" rest="$rest1" else - # "run_prefix:subdir:N[...]" — check in that prefix's run dir - check_run_dir=$(ls -dt "$work_dir/output/run_sp_tile_${field1}"* 2>/dev/null | head -1) subdir="$field2" rest="${rest1#*:}" fi @@ -403,7 +414,7 @@ function run_tile_job() { local n_out=0 [ -n "$check_run_dir" ] && n_out=$(ls "$out_dir/" 2>/dev/null | wc -l) - check_desc+="${subdir}:${n_out}/${n_threshold} " + check_desc+="${run_prefix}/${subdir}:${n_out}/${n_threshold} " if [ "$n_out" -lt "$n_threshold" ]; then [ "$warn_only" != "1" ] && is_complete=0 fi @@ -411,15 +422,15 @@ function run_tile_job() { fi if [ "$is_complete" == "1" ] && [ -n "$complete_checks" ]; then - message "Complete: run_sp_tile_${main_prefix} ( $check_desc)" "$debug_out" -1 + message "Complete: ( $check_desc)" "$debug_out" -1 return 0 fi if [ "$check" == "1" ]; then if [ -n "$run_dir" ]; then - message "Incomplete: run_sp_tile_${main_prefix} ($check_desc)" "$debug_out" -1 + message "Incomplete: ($check_desc)" "$debug_out" -1 else - message "Missing: run_sp_tile_${main_prefix}" "$debug_out" -1 + message "Missing: ($check_desc)" "$debug_out" -1 fi return 0 fi @@ -432,11 +443,12 @@ function run_tile_job() { ln -sf ~/shapepipe/example/cfis "cfis" fi + command "update_runs_log_file.py" $dry_run + # Run job script command "job_sp_canfar_v2.0.bash -p $psf --tile_det $tile_det --tile_mask $tile_mask -j $tile_job --n_smp $N_SMP --nsh_jobs $N_SMP $debug_flag" $dry_run 2>&1 | tee -a "$log_file" } - # Init message if [ "$test_only" == "1" ]; then message "$(basename "$0") test mode, exiting." "$debug_out" 0 @@ -504,6 +516,7 @@ fi echo -n "pwd: "; pwd + # Avoid Qt error with setools export DISPLAY=:1.0 @@ -577,7 +590,7 @@ fi if [[ $do_job != 0 ]]; then # Job 256: object selection on tiles if [ "$tile_det" == "uc" ]; then - run_tile_job 256 "Gic Uc" "Gic_cat_vos:get_images_runner:1 read_ext_sexcat_runner:1" + run_tile_job 256 "Gic Uc" "get_images_runner:2 read_ext_sexcat_runner:1" else run_tile_job 256 "Sx" "sextractor_runner:1" fi From 8bd99d69704333a81e243209bc8835a6f6f2b186 Mon Sep 17 00:00:00 2001 From: "martin.kilbinger" Date: Thu, 7 May 2026 14:41:38 +0200 Subject: [PATCH 28/80] dell changes --- .github/workflows/deploy-image.yml | 83 ++++++++- Dockerfile | 157 +++++++++++++++--- Dockerfile.jupyter | 2 +- docs/source/installation.md | 10 +- docs/source/toc.rst | 1 + example/cfis/config_onthefly.mask | 2 +- example/cfis/config_save.mask | 2 +- example/cfis/config_tile_PiViVi_canfar_sx.ini | 11 +- example/cfis/config_tile_PiViVi_canfar_uc.ini | 6 +- example/cfis/config_tile_onthefly.mask | 2 +- example/cfis/config_tile_save.mask | 2 +- pyproject.toml | 56 ++++--- scripts/sh/job_sp_canfar_v2.0.bash | 7 +- src/shapepipe/modules/mask_runner.py | 2 +- src/shapepipe/modules/psfex_interp_runner.py | 35 ++-- src/shapepipe/pipeline/exp_utils.py | 92 ++++++++++ 16 files changed, 384 insertions(+), 86 deletions(-) diff --git a/.github/workflows/deploy-image.yml b/.github/workflows/deploy-image.yml index 6bb4fc387..dcf8b5412 100644 --- a/.github/workflows/deploy-image.yml +++ b/.github/workflows/deploy-image.yml @@ -3,7 +3,6 @@ on: [push, workflow_dispatch] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} - BRANCH: ${{ github.ref }} jobs: build-and-push-image: runs-on: ubuntu-latest @@ -26,25 +25,91 @@ jobs: with: driver-opts: network=host - - name: Extract metadata (tags, labels) for Docker - id: meta + # Two parallel tag sets. `dev` is the default (no suffix, e.g. `:latest`, + # `:develop`); `runtime` carries a `-runtime` suffix. + - name: Tags — dev (default) + id: meta-dev uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Build and export to Docker + - name: Tags — runtime + id: meta-runtime + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-runtime,onlatest=true + + # Build runtime first (smaller, used to smoke-test pipeline binaries) + - name: Build runtime (load) uses: docker/build-push-action@v6 with: + context: . + target: runtime load: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta-runtime.outputs.tags }} + labels: ${{ steps.meta-runtime.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max +<<<<<<< HEAD - name: Test run: docker run --rm ${{ steps.meta.outputs.tags }} shapepipe_run.py -c /app/example/config.ini +======= + # Smoke-test the binaries baked into the runtime image. Catches the + # class of regression where the image builds but a runtime tool + # (sextractor, weightwatcher) is missing or unrunnable. + - name: Test runtime — binaries + run: | + IMAGE=$(echo "${{ steps.meta-runtime.outputs.tags }}" | head -n1) + docker run --rm "$IMAGE" source-extractor --version + docker run --rm "$IMAGE" weightwatcher --version + docker run --rm "$IMAGE" psfex --version +>>>>>>> mkdocker + + - name: Test runtime — shapepipe entry point + run: | + IMAGE=$(echo "${{ steps.meta-runtime.outputs.tags }}" | head -n1) + docker run --rm "$IMAGE" shapepipe_run -c /app/example/config.ini + + # Build dev (reuses cached `base` layer) + - name: Build dev (load) + uses: docker/build-push-action@v6 + with: + context: . + target: dev + load: true + tags: ${{ steps.meta-dev.outputs.tags }} + labels: ${{ steps.meta-dev.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Verify the dev-only additions are present and runnable. + - name: Test dev — interactive tools and test extras + run: | + IMAGE=$(echo "${{ steps.meta-dev.outputs.tags }}" | head -n1) + docker run --rm "$IMAGE" vim --version | head -n1 + docker run --rm "$IMAGE" rg --version | head -n1 + docker run --rm "$IMAGE" pytest --version + + # Push both targets + - name: Push runtime + uses: docker/build-push-action@v6 + with: + context: . + target: runtime + push: true + tags: ${{ steps.meta-runtime.outputs.tags }} + labels: ${{ steps.meta-runtime.outputs.labels }} + cache-from: type=gha - - name: Push + - name: Push dev uses: docker/build-push-action@v6 with: + context: . + target: dev push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta-dev.outputs.tags }} + labels: ${{ steps.meta-dev.outputs.labels }} + cache-from: type=gha diff --git a/Dockerfile b/Dockerfile index 9ef424ada..9e497f786 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,67 @@ -FROM images.canfar.net/skaha/astroml:latest +# syntax=docker/dockerfile:1.7 +# +# Two-target image: +# --target runtime → minimal, for canfar batch jobs and downstream stacks +# --target dev → runtime + everyday CLI tools + all extras (test, +# lint, doc, …); default if --target is omitted +# +# Both share the `base` stage (system deps + uv + lockfile copy), so the +# heavy apt + wheel-resolution work is cached once. + +# ---------------------------------------------------------------------- +# base — system deps shared by every target +# ---------------------------------------------------------------------- +FROM python:3.12-slim-bookworm AS base -# Metadata LABEL maintainer="martin.kilbinger@cea.fr" -LABEL description="ShapePipe base image with common dependencies" -# Install system dependencies needed for ShapePipe and WeightWatcher -RUN apt-get update -o Acquire::ForceIPv4=true -y --quiet && \ +ENV SHELL=/bin/bash \ + QT_QPA_PLATFORM=offscreen \ + PIP_NO_CACHE_DIR=1 \ + DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + # Make the image well-behaved on read-only filesystems (apptainer/SIF): + # UV_NO_SYNC — `uv run` skips the auto-sync that would otherwise + # mutate /app/.venv. Run with what's baked in. + # UV_CACHE_DIR — uv's package cache. /tmp is tmpfs, writable in + # read-only SIFs and writable sandboxes alike. + # COVERAGE_FILE — pytest-cov writes its data file here instead of + # /app/.coverage; needed because pyproject sets + # `addopts = --cov=shapepipe` and pytest-cov erases + # the file at startup. + UV_NO_SYNC=1 \ + UV_CACHE_DIR=/tmp/uv-cache \ + COVERAGE_FILE=/tmp/.coverage + +# System dependencies — three categories: +# - astromatic binaries (psfex, source-extractor, weightwatcher) ship as +# Debian packages on bookworm; preferred over building from source. +# - compilers and dev libs needed to build the heavier wheels (galsim, +# mpi4py, python-pysap, fitsio). +# - libgl1, proj, fftw at runtime for skyproj/PyQt5/galsim. +RUN apt-get update -y --quiet && \ apt-get install -y --no-install-recommends \ - psfex source-extractor \ - libproj-dev proj-bin && \ + build-essential \ + cmake \ + gfortran \ + git \ + wget \ + pkg-config \ + libfftw3-dev libfftw3-bin \ + libgsl-dev \ + libcfitsio-dev \ + libopenmpi-dev openmpi-bin \ + libproj-dev proj-bin \ + libgl1-mesa-glx \ + psfex source-extractor weightwatcher && \ apt-get clean && rm -rf /var/lib/apt/lists/* -# Build and install WeightWatcher from source -ARG WW_VERSION=1.12 -RUN cd /tmp && \ - wget --no-check-certificate https://github.com/astromatic/weightwatcher/archive/refs/tags/${WW_VERSION}.tar.gz && \ - tar -xzf ${WW_VERSION}.tar.gz && \ - rm ${WW_VERSION}.tar.gz -RUN cd /tmp/weightwatcher-${WW_VERSION} && \ - sed -i 's/^ prefstruct\tprefs;/extern prefstruct\tprefs;/' src/prefs.h && \ - sed -i 's/^char\t\tgstr\[MAXCHAR\];/extern char\t\tgstr[MAXCHAR];/' src/globals.h && \ - sed -i 's/^int\t\tbswapflag;/extern int\t\tbswapflag;/' src/fits/fitscat.h && \ - sed -i '/preflist\.h/a prefstruct\tprefs;' src/prefs.c && \ - sed -i '/xml\.h/a char\t\tgstr[MAXCHAR];' src/main.c && \ - sed -i '/fitscat\.h/a int\t\tbswapflag;' src/fits/fitscat.c && \ - ./configure --quiet && \ - make --quiet && \ - make install +# uv — fast reproducible Python deps installer. pyproject.toml + uv.lock +# are the SSOT; `uv sync --frozen` installs exactly what uv.lock specifies, +# so upstream changes only land when we deliberately regenerate the lockfile. +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv +<<<<<<< HEAD # Ensure astroml:latest conda Python 3.12 is used (Docker RUN does not source conda init) ENV PATH=/opt/conda/bin:$PATH @@ -39,15 +73,84 @@ RUN pip install --no-cache-dir --upgrade pip && \ snakemake==8.27.1 # Set working directory and copy source code +======= +>>>>>>> mkdocker WORKDIR /app -COPY . /app/. -RUN chown -R root:root /app && chmod -R u+rwX /app +COPY pyproject.toml uv.lock /app/ + +# ---------------------------------------------------------------------- +# runtime — minimal target for batch jobs and downstream FROM clauses +# ---------------------------------------------------------------------- +FROM base AS runtime +LABEL description="ShapePipe runtime — slim Python + uv-frozen deps" + +# Lockfile-frozen Python deps + jupyter + fitsio. Test/lint/doc extras +# are intentionally left out here; they live in the dev target. +RUN uv sync --frozen --no-install-project --extra jupyter --extra fitsio -# Install ShapePipe and its dependencies (including fitsio optional extra) -RUN pip install --no-cache-dir -e ".[fitsio]" && \ +# Copy the source and install shapepipe into the same venv. +COPY . /app/. +# go+rwX so non-root users on canfar/skaha can read/traverse /app and +# write into the venv when they need to (e.g. uv add for ad-hoc deps). +RUN chmod -R go+rwX /app && \ + uv pip install --no-deps -e . && \ for ext in .py .sh .bash; do \ for script in /app/scripts/*/*$ext; do \ link_name=$(basename $script); \ ln -s $script /usr/local/bin/$link_name; \ done; \ done + +# Activate the uv-managed venv on container start so shapepipe_run etc +# resolve against it without explicit activation. +ENV PATH="/app/.venv/bin:${PATH}" \ + VIRTUAL_ENV=/app/.venv + +# ---------------------------------------------------------------------- +# dev — everyday working environment (default target) +# ---------------------------------------------------------------------- +FROM base AS dev +LABEL description="ShapePipe dev — runtime + interactive CLI tools + all extras" + +# Interactive tools for working inside the container. Curated subset of +# cailmdaley/containers focused on the search/edit/process loop; heavier +# tooling (neovim, polspice, quarto, zellij) is intentionally not here. +RUN apt-get update -y --quiet && \ + apt-get install -y --no-install-recommends \ + vim \ + less \ + tmux \ + htop \ + procps \ + ripgrep \ + fd-find \ + jq \ + bat \ + curl \ + ca-certificates \ + git-lfs \ + rsync \ + unzip zip \ + openssh-client \ + locales && \ + if command -v batcat >/dev/null; then ln -sf "$(command -v batcat)" /usr/local/bin/bat; fi && \ + if command -v fdfind >/dev/null; then ln -sf "$(command -v fdfind)" /usr/local/bin/fd; fi && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# All extras pre-installed (dev = doc + jupyter + lint + release + test + +# fitsio). Pre-installing avoids the read-only-fs failure Martin hit when +# trying to live `uv sync --extra test` inside the runtime image on canfar. +RUN uv sync --frozen --no-install-project --extra dev + +COPY . /app/. +RUN chmod -R go+rwX /app && \ + uv pip install --no-deps -e . && \ + for ext in .py .sh .bash; do \ + for script in /app/scripts/*/*$ext; do \ + link_name=$(basename $script $ext); \ + ln -s $script /usr/local/bin/$link_name; \ + done; \ + done + +ENV PATH="/app/.venv/bin:${PATH}" \ + VIRTUAL_ENV=/app/.venv diff --git a/Dockerfile.jupyter b/Dockerfile.jupyter index fc24a75f0..828c1bfac 100644 --- a/Dockerfile.jupyter +++ b/Dockerfile.jupyter @@ -1,7 +1,7 @@ # Dockerfile for ShapePipe Jupyter Lab # Uses the shared base image -FROM ghcr.io/cosmostat/shapepipe:v2.0 +FROM ghcr.io/cosmostat/shapepipe:develop # Default command starts Jupyter Lab ENTRYPOINT ["jupyter","lab","--ip=0.0.0.0","--port=8888","--no-browser","--allow-root"] diff --git a/docs/source/installation.md b/docs/source/installation.md index 831f12cc9..7858dc2ba 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -8,7 +8,7 @@ data should be processed. ## Container Installation (Recommended) -The easiest way to install ShapePipe is via a container. Docker images are automatically built and pushed to the [Github Container Registry (GHCR)](ghcr.io/cosmostat/shapepipe) for each release. This images can be installed and run on most systems (including clusters) with just a few lines of code. +The easiest way to install ShapePipe is via a container. Docker images are automatically built and pushed to the [Github Container Registry (GHCR)](ghcr.io/cosmostat/shapepipe) on every push. Two image targets are published per branch — the default tag is the rich `dev` image; `-runtime` is a slim variant for batch jobs. See [Container Workflow](container.md) for the full rationale and the relationship between `pyproject.toml`, `uv.lock`, and the `Dockerfile`. We recommend running the image with **Apptainer** (formerly Singularity) which is installed on most HPC clusters. To simply run the image, use the following command: @@ -25,7 +25,13 @@ cd /app && shapepipe_run -c /app/example/config.ini You can also run the image with **Docker**: ```bash -docker run --rm -it ghcr.io/cosmostat/shapepipe:develop shapepipe_run -c /app/example/config.ini +docker run --rm -it ghcr.io/cosmostat/shapepipe:develop shapepipe_run -c /app/example/config.ini +``` + +For canfar batch jobs or downstream images, the slim runtime tag is preferred: + +```bash +docker pull ghcr.io/cosmostat/shapepipe:develop-runtime ``` ```{attention} diff --git a/docs/source/toc.rst b/docs/source/toc.rst index da8f06f25..95b1eaa3c 100644 --- a/docs/source/toc.rst +++ b/docs/source/toc.rst @@ -15,6 +15,7 @@ dependencies installation + container .. toctree:: :hidden: diff --git a/example/cfis/config_onthefly.mask b/example/cfis/config_onthefly.mask index 9a147bdcd..7c185c602 100644 --- a/example/cfis/config_onthefly.mask +++ b/example/cfis/config_onthefly.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/example/cfis/config_save.mask b/example/cfis/config_save.mask index 887110ff5..497dedda9 100644 --- a/example/cfis/config_save.mask +++ b/example/cfis/config_save.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/example/cfis/config_tile_PiViVi_canfar_sx.ini b/example/cfis/config_tile_PiViVi_canfar_sx.ini index 33f6c8d7f..eb822a232 100644 --- a/example/cfis/config_tile_PiViVi_canfar_sx.ini +++ b/example/cfis/config_tile_PiViVi_canfar_sx.ini @@ -57,11 +57,11 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_tile_Mh_exp:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner -FILE_PATTERN = sexcat, log_exp_headers +FILE_PATTERN = sexcat, log_exp_headers, exp_numbers -FILE_EXT = .fits, .sqlite +FILE_EXT = .fits, .sqlite, .txt # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -86,7 +86,10 @@ CHI2_THRESH = 2 # Multi-epoch mode parameters -ME_DOT_PSF_DIR = all:psfex_runner +# Root directory of per-exposure work directories; replaces ME_DOT_PSF_DIR +# for v2.0 per-exposure pipeline. psfex_runner/output/ dirs are discovered +# by scanning $SP_EXP for the exposures listed in the exp_numbers input file. +ME_DOT_PSF_EXP_DIR = $SP_EXP # Input psf file pattern ME_DOT_PSF_PATTERN = star_split_ratio_80 diff --git a/example/cfis/config_tile_PiViVi_canfar_uc.ini b/example/cfis/config_tile_PiViVi_canfar_uc.ini index 6e13c681a..ca8ab808e 100644 --- a/example/cfis/config_tile_PiViVi_canfar_uc.ini +++ b/example/cfis/config_tile_PiViVi_canfar_uc.ini @@ -127,11 +127,11 @@ PREFIX = weight # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = run_sp_tile_Sx:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner -FILE_PATTERN = sexcat, log_exp_headers +FILE_PATTERN = sexcat, log_exp_headers, exp_numbers -FILE_EXT = .fits, .sqlite +FILE_EXT = .fits, .sqlite, .txt # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 diff --git a/example/cfis/config_tile_onthefly.mask b/example/cfis/config_tile_onthefly.mask index bcd7663ad..18cf5db2d 100644 --- a/example/cfis/config_tile_onthefly.mask +++ b/example/cfis/config_tile_onthefly.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/example/cfis/config_tile_save.mask b/example/cfis/config_tile_save.mask index 4b5fbb7f8..1de738301 100644 --- a/example/cfis/config_tile_save.mask +++ b/example/cfis/config_tile_save.mask @@ -3,7 +3,7 @@ ## Paths to executables [PROGRAM_PATH] -WW_PATH = ww +WW_PATH = weightwatcher WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # Indicate cds client executable if no external star catalogue is available diff --git a/pyproject.toml b/pyproject.toml index 35f542dfa..aaee93d94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,32 +11,39 @@ authors = [ license = "MIT" license-files = ["LICENSE"] requires-python = ">=3.12" +# Abstract runtime constraints. Exact versions live in `uv.lock` — pyproject +# is the single source of truth for *what* shapepipe needs, the lockfile for +# *which versions*. Minimums signal the major-version target; uv.lock pins +# the actual installed versions. Bump minimums only when crossing a major +# version line or when a feature actually requires a specific release. dependencies = [ - "astropy>=5.2", + "astropy>=7.0", # major 6 → 7 "astroquery", "canfar", - "cs_util>=0.1", - "galsim>=2.5.3", + "cs_util>=0.1.9", + "galsim>=2.8", "h5py", - "joblib>=0.13", - "matplotlib>=3.8.4", + "joblib>=1.4", + "matplotlib>=3.10", "mccd>=1.2.4", - "modopt>=1.2", - "mpi4py", - "numba>=0.58.1", - "numpy>=1.14", - "pandas", + "modopt>=1.6", + "mpi4py>=4.0", + "numba>=0.59", # numpy 2 support + "numpy>=2.0", # major 1 → 2 + "pandas>=3.0", # major 2 → 3 "python-dateutil", - "python-pysap>=0.2.1", + "python-pysap>=0.3", "PyQt5", "pyqtgraph", - "reproject", - "sf_tools", - "skaha", - "sqlitedict", + "reproject>=0.19", + "sf_tools>=2.0.4", + "skaha>=1.7", + "sqlitedict>=2.0", "termcolor", - "tqdm", - "vos", + "tqdm>=4.63", + "vos>=3.6", + # ngmix is pinned to Axel's stable_version branch until upstream ngmix + # absorbs the fixes — tracked separately, do not modernize this line. "ngmix @ git+https://github.com/aguinot/ngmix@stable_version", ] @@ -49,6 +56,11 @@ doc = [ "sphinxcontrib-bibtex", "sphinx-book-theme" ] +jupyter = [ + "ipython>=8.18", + "jupyterlab>=4.3", + "snakemake>=8.27", +] lint = [ "black", "isort" @@ -58,13 +70,13 @@ release = [ "twine", ] test = [ - "pytest", - "pytest-cov", - "pytest-pycodestyle", - "pytest-pydocstyle" + "pytest>=8.3", + "pytest-cov>=5.0", + "pytest-pycodestyle>=2.4", + "pytest-pydocstyle>=2.4", ] fitsio = ["fitsio"] -dev = ["shapepipe[doc,lint,release,test]"] +dev = ["shapepipe[doc,jupyter,lint,release,test,fitsio]"] [tool.setuptools] script-files = [ diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index d120f129a..25a80212b 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -171,11 +171,16 @@ export SP_CONFIG_MOD=$SP_RUN/cfis_mod # conventional layout (SP_RUN = .../v2.0/tiles/IDra/ID, three levels up + exp). if [ -z "${SP_EXP}" ]; then export SP_EXP=$(realpath "$SP_RUN/../../../exp") - echo "SP_EXP not set, using computed path: $SP_EXP" + echo "Setting SP_EXP to $SP_EXP" fi ## Other variables +# Override OMP_NUM_THREADS if the CANFAR provisioning template was not expanded +if [[ "${OMP_NUM_THREADS}" == *'${'* ]] || [[ "${OMP_NUM_THREADS}" == *'.'* ]]; then + export OMP_NUM_THREADS=1 +fi + # Output OUTPUT=$SP_RUN/output diff --git a/src/shapepipe/modules/mask_runner.py b/src/shapepipe/modules/mask_runner.py index 8cfd9375a..13fbf9b6c 100644 --- a/src/shapepipe/modules/mask_runner.py +++ b/src/shapepipe/modules/mask_runner.py @@ -15,7 +15,7 @@ file_pattern=["image", "weight", "flag"], file_ext=[".fits", ".fits", ".fits"], depends=["numpy", "astropy"], - executes=["ww"], + executes=["weightwatcher"], numbering_scheme="_0", ) def mask_runner( diff --git a/src/shapepipe/modules/psfex_interp_runner.py b/src/shapepipe/modules/psfex_interp_runner.py index cfa968bc7..c382184f1 100644 --- a/src/shapepipe/modules/psfex_interp_runner.py +++ b/src/shapepipe/modules/psfex_interp_runner.py @@ -9,6 +9,7 @@ from shapepipe.modules.module_decorator import module_runner from shapepipe.modules.psfex_interp_package import psfex_interp +from shapepipe.pipeline.exp_utils import get_exp_output_dirs from shapepipe.pipeline.run_log import get_last_dir, get_all_dirs @@ -62,20 +63,30 @@ def psfex_interp_runner( elif mode == "MULTI-EPOCH": # Fetch multi-epoch parameters - module = config.getexpanded( - module_config_sec, - "ME_DOT_PSF_DIR", - ) - module_name = module.split(":")[-1] - if "last" in module: - dot_psf_dirs = [get_last_dir(run_dirs["run_log"], module_name)] - elif "all" in module: - dot_psf_dirs = get_all_dirs(run_dirs["run_log"], module_name) + if config.has_option(module_config_sec, "ME_DOT_PSF_EXP_DIR"): + # v2.0: locate psfex_runner output dirs via the $SP_EXP tree + exp_base_dir = config.getexpanded( + module_config_sec, "ME_DOT_PSF_EXP_DIR" + ) + exp_numbers_file = input_file_list[2] + dot_psf_dirs = get_exp_output_dirs( + exp_base_dir, exp_numbers_file, "psfex_runner", w_log + ) else: - raise ValueError( - "Expected qualifier 'last:' or 'all' before module" - + f" '{module}' in config entry 'ME_DOT_PSF_DIR'" + module = config.getexpanded( + module_config_sec, + "ME_DOT_PSF_DIR", ) + module_name = module.split(":")[-1] + if "last" in module: + dot_psf_dirs = [get_last_dir(run_dirs["run_log"], module_name)] + elif "all" in module: + dot_psf_dirs = get_all_dirs(run_dirs["run_log"], module_name) + else: + raise ValueError( + "Expected qualifier 'last:' or 'all' before module" + + f" '{module}' in config entry 'ME_DOT_PSF_DIR'" + ) dot_psf_pattern = config.get( module_config_sec, diff --git a/src/shapepipe/pipeline/exp_utils.py b/src/shapepipe/pipeline/exp_utils.py index 56987a025..8f0df784a 100644 --- a/src/shapepipe/pipeline/exp_utils.py +++ b/src/shapepipe/pipeline/exp_utils.py @@ -124,3 +124,95 @@ def get_exp_output_files( w_log.info(f"Found {len(file_list)} exposure output files") return file_list + + +def get_exp_output_dirs( + exp_base_dir, + exp_numbers_file, + runner_name, + w_log=None, +): + """Return the most-recent output directory of runner_name for each exposure. + + For each exposure ID listed in ``exp_numbers_file``, the most recent + runner output directory is located at:: + + ///output/run_sp_*//output/ + + Parameters + ---------- + exp_base_dir : str + Root directory containing all per-exposure work directories. + exp_numbers_file : str + Path to a text file listing one exposure ID per line. + runner_name : str + Module runner directory name whose output directory is needed, + e.g. ``psfex_runner``. + w_log : logging.Logger, optional + Pipeline logger. + + Returns + ------- + list of str + One output directory path per exposure, in the order they appear + in ``exp_numbers_file``. + + Raises + ------ + FileNotFoundError + If ``exp_numbers_file`` does not exist or a runner output directory + is absent for one or more exposures. + + """ + if not os.path.exists(exp_numbers_file): + raise FileNotFoundError( + f"Exposure numbers file not found: {exp_numbers_file}" + ) + + with open(exp_numbers_file) as fh: + exp_ids = [line.strip() for line in fh if line.strip()] + + if w_log: + w_log.info( + f"Collecting {runner_name}/output dirs " + f"for {len(exp_ids)} exposures from {exp_base_dir}" + ) + + dir_list = [] + missing = [] + + for exp_id in exp_ids: + exp_prefix = exp_id[:2] + exp_base = exp_id[:-1] + + pattern = os.path.join( + exp_base_dir, + exp_prefix, + exp_base, + "output", + "run_sp_*", + runner_name, + "output", + ) + matches = sorted(glob.glob(pattern)) + + if matches: + chosen = matches[-1] # most recent run (lexicographic sort on datetime) + dir_list.append(chosen) + if w_log: + w_log.debug(f" {exp_id}: {chosen}") + else: + missing.append(exp_id) + if w_log: + w_log.warning(f" {exp_id}: no match for {pattern}") + + if missing: + raise FileNotFoundError( + f"No {runner_name} output directory found for " + f"{len(missing)} exposure(s): {missing}" + ) + + if w_log: + w_log.info(f"Found {len(dir_list)} exposure output directories") + + return dir_list From 12254cd1601ec0e02d33de5df07dd6e814c5572f Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Thu, 7 May 2026 15:49:10 +0000 Subject: [PATCH 29/80] cleaned up cfg files; job 512 seems to be working using ; fixed some bugs when exp list is empty --- example/cfis/config_tile_PiViVi_canfar_sx.ini | 14 +++--- example/cfis/config_tile_PiViVi_canfar_uc.ini | 21 +++++---- .../{ => defunct}/config_tile_PiViSmVi.ini | 0 .../config_tile_PiViSmVi_canfar.ini | 0 scripts/sh/run_job_sp_canfar_v2.0.bash | 8 +--- src/shapepipe/modules/mask_package/mask.py | 2 +- .../psfex_interp_package/psfex_interp.py | 12 +++-- .../vignetmaker_package/vignetmaker.py | 8 +++- src/shapepipe/modules/vignetmaker_runner.py | 45 +++++++++++++------ src/shapepipe/pipeline/file_io.py | 2 +- 10 files changed, 71 insertions(+), 41 deletions(-) rename example/cfis/{ => defunct}/config_tile_PiViSmVi.ini (100%) rename example/cfis/{ => defunct}/config_tile_PiViSmVi_canfar.ini (100%) diff --git a/example/cfis/config_tile_PiViVi_canfar_sx.ini b/example/cfis/config_tile_PiViVi_canfar_sx.ini index eb822a232..b116de9b6 100644 --- a/example/cfis/config_tile_PiViVi_canfar_sx.ini +++ b/example/cfis/config_tile_PiViVi_canfar_sx.ini @@ -9,7 +9,7 @@ VERBOSE = True # Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_PsViSmVi +RUN_NAME = run_sp_tile_PsViVi # Add date and time to RUN_NAME, optional, default: False ; RUN_DATETIME = False @@ -132,11 +132,11 @@ PREFIX = weight # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_tile_Mh_exp:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner -FILE_PATTERN = sexcat, log_exp_headers +FILE_PATTERN = sexcat, log_exp_headers, exp_numbers -FILE_EXT = .fits, .sqlite +FILE_EXT = .fits, .sqlite, .txt # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -161,6 +161,8 @@ STAMP_SIZE = 51 PREFIX = # Additional parameters for path and file pattern corresponding to single-exposure -# run outputs -ME_IMAGE_DIR = all:split_exp_runner, all:split_exp_runner, all:split_exp_runner, all:sextractor_runner +# run outputs. ME_IMAGE_EXP_DIR/ME_IMAGE_EXP_RUNNERS replace ME_IMAGE_DIR for +# the v2.0 per-exposure pipeline; output dirs are discovered by scanning $SP_EXP. +ME_IMAGE_EXP_DIR = $SP_EXP +ME_IMAGE_EXP_RUNNERS = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner ME_IMAGE_PATTERN = flag, image, weight, background diff --git a/example/cfis/config_tile_PiViVi_canfar_uc.ini b/example/cfis/config_tile_PiViVi_canfar_uc.ini index ca8ab808e..e2f955909 100644 --- a/example/cfis/config_tile_PiViVi_canfar_uc.ini +++ b/example/cfis/config_tile_PiViVi_canfar_uc.ini @@ -9,7 +9,7 @@ VERBOSE = True # Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_PsViSmVi +RUN_NAME = run_sp_tile_PsViVi # Add date and time to RUN_NAME, optional, default: False ; RUN_DATETIME = False @@ -55,11 +55,11 @@ TIMEOUT = 96:00:00 [PSFEX_INTERP_RUNNER] -INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner +INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner -FILE_PATTERN = sexcat, log_exp_headers +FILE_PATTERN = sexcat, log_exp_headers, exp_numbers -FILE_EXT = .fits, .sqlite +FILE_EXT = .fits, .sqlite, .txt # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -84,7 +84,10 @@ CHI2_THRESH = 2 # Multi-epoch mode parameters -ME_DOT_PSF_DIR = all:psfex_runner +# Root directory of per-exposure work directories; replaces ME_DOT_PSF_DIR +# for v2.0 per-exposure pipeline. psfex_runner/output/ dirs are discovered +# by scanning $SP_EXP for the exposures listed in the exp_numbers input file. +ME_DOT_PSF_EXP_DIR = $SP_EXP # Input psf file pattern ME_DOT_PSF_PATTERN = star_split_ratio_80 @@ -127,7 +130,7 @@ PREFIX = weight # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = run_sp_tile_Sx:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner +INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner, run_sp_tile_Mh_exp:merge_headers_runner, last:find_exposures_runner FILE_PATTERN = sexcat, log_exp_headers, exp_numbers @@ -156,6 +159,8 @@ STAMP_SIZE = 51 PREFIX = # Additional parameters for path and file pattern corresponding to single-exposure -# run outputs -ME_IMAGE_DIR = all:split_exp_runner, all:split_exp_runner, all:split_exp_runner, all:sextractor_runner +# run outputs. ME_IMAGE_EXP_DIR/ME_IMAGE_EXP_RUNNERS replace ME_IMAGE_DIR for +# the v2.0 per-exposure pipeline; output dirs are discovered by scanning $SP_EXP. +ME_IMAGE_EXP_DIR = $SP_EXP +ME_IMAGE_EXP_RUNNERS = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner ME_IMAGE_PATTERN = flag, image, weight, background diff --git a/example/cfis/config_tile_PiViSmVi.ini b/example/cfis/defunct/config_tile_PiViSmVi.ini similarity index 100% rename from example/cfis/config_tile_PiViSmVi.ini rename to example/cfis/defunct/config_tile_PiViSmVi.ini diff --git a/example/cfis/config_tile_PiViSmVi_canfar.ini b/example/cfis/defunct/config_tile_PiViSmVi_canfar.ini similarity index 100% rename from example/cfis/config_tile_PiViSmVi_canfar.ini rename to example/cfis/defunct/config_tile_PiViSmVi_canfar.ini diff --git a/scripts/sh/run_job_sp_canfar_v2.0.bash b/scripts/sh/run_job_sp_canfar_v2.0.bash index 6ad9a59d5..03a1e9614 100755 --- a/scripts/sh/run_job_sp_canfar_v2.0.bash +++ b/scripts/sh/run_job_sp_canfar_v2.0.bash @@ -597,15 +597,9 @@ if [[ $do_job != 0 ]]; then fi (( do_job = job & 512 )) -if [[ $do_job != 0 ]]; then - # Job 512: create final catalogue - run_tile_job 512 "Mc" "make_cat_runner:1" -fi - -(( do_job = job & 1024 )) if [[ $do_job != 0 ]]; then # Job 1024: process tiles (PSF interp, vignet, shape measurement) - run_tile_job 1024 "tile_${Letter}iVi_canfar" + run_tile_job 512 "${Letter}iVi ${Letter}iVi ${Letter}iVi" "psfex_runner:1 vignetmaker_runner_run_1:1 vignetmaker_runner_run_2:1" fi if [ -n "$scratch" ]; then diff --git a/src/shapepipe/modules/mask_package/mask.py b/src/shapepipe/modules/mask_package/mask.py index d4282bc60..17e052a6e 100644 --- a/src/shapepipe/modules/mask_package/mask.py +++ b/src/shapepipe/modules/mask_package/mask.py @@ -168,7 +168,7 @@ def _get_config(self): "PROGRAM_PATH", "WW_PATH" ) else: - self._config["PATH"]["WW"] = "ww" + self._config["PATH"]["WW"] = "weightwatcher" self._config["PATH"]["WW_configfile"] = conf.getexpanded( "PROGRAM_PATH", "WW_CONFIG_FILE" ) diff --git a/src/shapepipe/modules/psfex_interp_package/psfex_interp.py b/src/shapepipe/modules/psfex_interp_package/psfex_interp.py index da12f2582..3fb5ce264 100644 --- a/src/shapepipe/modules/psfex_interp_package/psfex_interp.py +++ b/src/shapepipe/modules/psfex_interp_package/psfex_interp.py @@ -718,9 +718,15 @@ def _interpolate_me(self): else: array_exp_name = np.concatenate((array_exp_name, exp_name_tmp)) - final_list.append( - [array_id, array_psf, array_shape, array_exp_name] - ) + if array_id is not None: + final_list.append( + [array_id, array_psf, array_shape, array_exp_name] + ) + else: + self._w_log.info( + f"All CCDs skipped for exposure {exp_name} " + + "(no valid PSF model); epoch excluded from output" + ) self._f_wcs_file.close() cat.close() diff --git a/src/shapepipe/modules/vignetmaker_package/vignetmaker.py b/src/shapepipe/modules/vignetmaker_package/vignetmaker.py index f599ab677..74e26915b 100644 --- a/src/shapepipe/modules/vignetmaker_package/vignetmaker.py +++ b/src/shapepipe/modules/vignetmaker_package/vignetmaker.py @@ -318,7 +318,13 @@ def _get_stamp_me(self, image_dirs, image_pattern): else: array_exp_name = np.concatenate((array_exp_name, exp_name_tmp)) - final_list.append([array_id, array_vign, array_exp_name]) + if array_id is not None: + final_list.append([array_id, array_vign, array_exp_name]) + else: + self._w_log.info( + f"All CCDs skipped for exposure {exp_name} " + + "(ccd == -1 for all); epoch excluded from output" + ) cat.close() diff --git a/src/shapepipe/modules/vignetmaker_runner.py b/src/shapepipe/modules/vignetmaker_runner.py index 86dc6b100..d3061ec8f 100644 --- a/src/shapepipe/modules/vignetmaker_runner.py +++ b/src/shapepipe/modules/vignetmaker_runner.py @@ -9,6 +9,7 @@ from shapepipe.modules.module_decorator import module_runner from shapepipe.modules.vignetmaker_package import vignetmaker as vm +from shapepipe.pipeline.exp_utils import get_exp_output_dirs from shapepipe.pipeline.run_log import get_last_dir, get_all_dirs @@ -104,21 +105,37 @@ def vignetmaker_runner( elif mode == "MULTI-EPOCH": # Multi-epoch exposures - # Get input image directory and file patterns - modules = config.getlist(module_config_sec, "ME_IMAGE_DIR") - image_dirs = [] - for module in modules: - module_name = module.split(":")[-1] - if "last" in module: - dirs = [get_last_dir(run_dirs["run_log"], module_name)] - elif "all" in module: - dirs = get_all_dirs(run_dirs["run_log"], module_name) - else: - raise ValueError( - "Expected qualifier 'last:' or 'all' before module" - + f" '{module}' in config entry 'ME_IMAGE_DIR'" + if config.has_option(module_config_sec, "ME_IMAGE_EXP_DIR"): + # v2.0: locate runner output dirs via the $SP_EXP tree + exp_base_dir = config.getexpanded( + module_config_sec, "ME_IMAGE_EXP_DIR" + ) + exp_numbers_file = input_file_list[2] + exp_runner_names = config.getlist( + module_config_sec, "ME_IMAGE_EXP_RUNNERS" + ) + image_dirs = [] + for runner_name in exp_runner_names: + dirs = get_exp_output_dirs( + exp_base_dir, exp_numbers_file, runner_name, w_log ) - image_dirs.append(dirs) + image_dirs.append(dirs) + else: + # v1: run-log based lookup via symlinked exposure run dirs + modules = config.getlist(module_config_sec, "ME_IMAGE_DIR") + image_dirs = [] + for module in modules: + module_name = module.split(":")[-1] + if "last" in module: + dirs = [get_last_dir(run_dirs["run_log"], module_name)] + elif "all" in module: + dirs = get_all_dirs(run_dirs["run_log"], module_name) + else: + raise ValueError( + "Expected qualifier 'last:' or 'all' before module" + + f" '{module}' in config entry 'ME_IMAGE_DIR'" + ) + image_dirs.append(dirs) image_pattern = config.getlist( module_config_sec, diff --git a/src/shapepipe/pipeline/file_io.py b/src/shapepipe/pipeline/file_io.py index d7a442d06..0bb8fb763 100644 --- a/src/shapepipe/pipeline/file_io.py +++ b/src/shapepipe/pipeline/file_io.py @@ -1570,7 +1570,7 @@ def _get_fits_col_type(self, col_data): col_type = "D" elif type(col_data[0]) is bool: col_type = "L" - elif type(col_data[0]) in [str, np.str_, np.str0]: + elif type(col_data[0]) in [str, np.str_]: col_type = "A" else: col_type = "D" From bed3a50ce1deb80cd5f89be345d7b1f7e7b26bea Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sat, 9 May 2026 06:34:39 +0000 Subject: [PATCH 30/80] running until ngmix call --- .../cfis/config_tile_Ng_batch_psfex_uc.ini | 74 ++++++++++++++++++ example/cfis/config_tile_PiViVi_canfar_sx.ini | 2 +- example/cfis/config_tile_PiViVi_canfar_uc.ini | 2 +- scripts/sh/job_sp_canfar_v2.0.bash | 75 +++++++------------ scripts/sh/run_job_sp_canfar_v2.0.bash | 12 ++- .../psfex_interp_package/psfex_interp.py | 9 +++ src/shapepipe/pipeline/args.py | 9 +++ src/shapepipe/run.py | 2 + 8 files changed, 133 insertions(+), 52 deletions(-) create mode 100644 example/cfis/config_tile_Ng_batch_psfex_uc.ini diff --git a/example/cfis/config_tile_Ng_batch_psfex_uc.ini b/example/cfis/config_tile_Ng_batch_psfex_uc.ini new file mode 100644 index 000000000..3d8e37140 --- /dev/null +++ b/example/cfis/config_tile_Ng_batch_psfex_uc.ini @@ -0,0 +1,74 @@ +# ShapePipe configuration file for tiles: ngmix + + +## Default ShapePipe options +[DEFAULT] + +# verbose mode (optional), default: True, print messages on terminal +VERBOSE = True + +# Name of run (optional) default: shapepipe_run +RUN_NAME = run_sp_tile_Ng + +# Add date and time to RUN_NAME, optional, default: False +RUN_DATETIME = False + + +## ShapePipe execution options +[EXECUTION] + +# Module name, single string or comma-separated list of valid module runner names +MODULE = ngmix_runner + +# Parallel processing mode, SMP or MPI +MODE = SMP + + +## ShapePipe file handling options +[FILE] + +# Log file master name, optional, default: shapepipe +LOG_NAME = log_sp + +# Runner log file name, optional, default: shapepipe_runs +RUN_LOG_NAME = log_run_sp + +# Input directory, containing input files, single string or list of names +INPUT_DIR = . + +# Output directory +OUTPUT_DIR = $SP_RUN/output + + +## ShapePipe job handling options +[JOB] + +# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial +SMP_BATCH_SIZE = 24 + +# Timeout value (optional), default is None, i.e. no timeout limit applied +TIMEOUT = 96:00:00 + + +## Module options + +# Model-fitting shapes with ngmix +[NGMIX_RUNNER] + +INPUT_DIR = run_sp_tile_Uc:read_ext_sexcat_runner,last:psfex_interp_runner,last:vignetmaker_runner_run_2,run_sp_tile_Mh_exp:merge_headers_runner + +FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers + +FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite + +# NUMBERING_SCHEME (optional) string with numbering pattern for input files +NUMBERING_SCHEME = -000-000 + +# Magnitude zero-point +MAG_ZP = 30.0 + +# Pixel scale in arcsec +PIXEL_SCALE = 0.186 + +ID_OBJ_MIN = -1 +ID_OBJ_MAX = -1 diff --git a/example/cfis/config_tile_PiViVi_canfar_sx.ini b/example/cfis/config_tile_PiViVi_canfar_sx.ini index b116de9b6..dcd8a48cc 100644 --- a/example/cfis/config_tile_PiViVi_canfar_sx.ini +++ b/example/cfis/config_tile_PiViVi_canfar_sx.ini @@ -9,7 +9,7 @@ VERBOSE = True # Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_PsViVi +RUN_NAME = run_sp_tile_PiViVi # Add date and time to RUN_NAME, optional, default: False ; RUN_DATETIME = False diff --git a/example/cfis/config_tile_PiViVi_canfar_uc.ini b/example/cfis/config_tile_PiViVi_canfar_uc.ini index e2f955909..32c530788 100644 --- a/example/cfis/config_tile_PiViVi_canfar_uc.ini +++ b/example/cfis/config_tile_PiViVi_canfar_uc.ini @@ -9,7 +9,7 @@ VERBOSE = True # Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_PsViVi +RUN_NAME = run_sp_tile_PiViVi # Add date and time to RUN_NAME, optional, default: False ; RUN_DATETIME = False diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index 25a80212b..c1fa6cd29 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -164,7 +164,6 @@ export SP_RUN=`pwd` # Config file path export SP_CONFIG=$SP_RUN/cfis -export SP_CONFIG_MOD=$SP_RUN/cfis_mod # Root directory for per-exposure work directories. # Set SP_EXP in the environment to override; otherwise falls back to the @@ -244,11 +243,12 @@ function command () { fi } -# Set up config file and call shapepipe_run +# Set up config file and call shapepipe_run. +# Batch size is passed via --batch_size flag; no config editing needed. function command_cfg_shapepipe() { local config_name=$1 local str=$2 - local _n_smp=$3 + local _n_smp=$3 local _exclusive=$4 if [ "$exclusive" != "" ]; then @@ -257,39 +257,14 @@ function command_cfg_shapepipe() { exclusive_flag="" fi - config_upd=$(set_config_n_smp $config_name $_n_smp) - local cmd="shapepipe_run.py -c $config_upd $exclusive_flag" - command "$cmd" "$str" -} - -function set_config_n_smp() { - local config_name=$1 - local _n_smp=$2 - - local config_orig="$SP_CONFIG/$config_name" - - if [[ $_n_smp != -1 ]]; then - # Update SMP batch size - local config_upd="$SP_CONFIG_MOD/$config_name" - update_config $config_orig $config_upd "SMP_BATCH_SIZE" $_n_smp - else - # Keep original config file - local config_upd=$config_orig - fi - - # Set "return" value (stdout) - echo "$config_upd" -} - -# Update config file -function update_config() { - local config_orig=$1 - local config_upd=$2 - local key=$3 - local val_upd=$4 + local batch_flag="" + if [[ $_n_smp != -1 ]]; then + batch_flag="--batch_size $_n_smp" + fi - cat $config_orig \ - | perl -ane 's/'$key'\s+=.+/'$key' = '$val_upd'/; print' > $config_upd + local config="$SP_CONFIG/$config_name" + local cmd="shapepipe_run.py -c $config $exclusive_flag $batch_flag" + command "$cmd" "$str" } ### Start ### @@ -300,7 +275,6 @@ echo "Start processing" mkdir -p $SP_RUN cd $SP_RUN mkdir -p $OUTPUT -mkdir -p $SP_CONFIG_MOD # Processing @@ -324,18 +298,14 @@ if [[ $do_job != 0 ]]; then command \ "create_star_cat $SP_RUN/output/run_sp_tile_Git_*/get_images_runner_run/output $SP_RUN/star_cat_tiles" \ "Save star cats for masking (tile)" - - #### For single-exposures - # TODO fi fi -## Prepare images (offline) +## Uncompress tile weights (( do_job = $job & 2 )) if [[ $do_job != 0 ]]; then - ### Uncompress tile weights command_cfg_shapepipe "config_tile_Uz.ini" "Run shapepipe (uncompress tile weights)" $n_smp $exclusive fi @@ -344,7 +314,6 @@ fi (( do_job = $job & 4 )) if [[ $do_job != 0 ]]; then - ### Uncompress tile weights command_cfg_shapepipe "config_tile_Fe.ini" "Run shapepipe (find exposures)" $n_smp $exclusive fi @@ -353,7 +322,6 @@ fi (( do_job = $job & 8 )) if [[ $do_job != 0 ]]; then - ### Retrieve exposure images command_cfg_shapepipe \ "config_exp_Gie_vos.ini" \ "Run shapepipe (get exposure images)" \ @@ -362,10 +330,10 @@ if [[ $do_job != 0 ]]; then fi +## Split images into single-HDU files, merge headers for WCS info (( do_job = $job & 16 )) if [[ $do_job != 0 ]]; then - ### Split images into single-HDU files, merge headers for WCS info command_cfg_shapepipe \ "config_exp_Sp.ini" \ "Run shapepipe (split images, merge headers)" \ @@ -374,10 +342,10 @@ if [[ $do_job != 0 ]]; then fi +## Mask exposures (( do_job = $job & 32 )) if [[ $do_job != 0 ]]; then - ### Mask exposures command_cfg_shapepipe \ "config_exp_Ma_${star_cat_for_mask}.ini" \ "Run shapepipe (mask exposures)" \ @@ -402,10 +370,10 @@ if [[ $do_job != 0 ]]; then fi +## Merge single-exposure WCS headers into tile-level sqlite log. (( do_job = $job & 128 )) if [[ $do_job != 0 ]]; then - ### Merge single-exposure WCS headers into tile-level sqlite log. command_cfg_shapepipe \ "config_tile_Mh_exp.ini" \ "Run shapepipe (merge exp headers)" \ @@ -414,6 +382,7 @@ if [[ $do_job != 0 ]]; then fi +## Select objects on tiles (( do_job = $job & 256 )) if [[ $do_job != 0 ]]; then @@ -481,8 +450,20 @@ if [[ $do_job != 0 ]]; then fi -## Create final catalogues (offline) +## Process tiles up to shape measurement: postage stamp creation (( do_job = $job & 1024 )) +if [[ $do_job != 0 ]]; then + + command_cfg_shapepipe \ + "config_tile_Ng_batch_${psf}_${tile_det}.ini" \ + "Run shapepipe (tile): shape measurement" \ + $n_smp \ + $exclusive + +fi + +## Create final catalogues (offline) +(( do_job = $job & 2048 )) if [[ $do_job != 0 ]]; then suff_sm="_nosm" diff --git a/scripts/sh/run_job_sp_canfar_v2.0.bash b/scripts/sh/run_job_sp_canfar_v2.0.bash index 03a1e9614..9da08f598 100755 --- a/scripts/sh/run_job_sp_canfar_v2.0.bash +++ b/scripts/sh/run_job_sp_canfar_v2.0.bash @@ -43,7 +43,7 @@ ${JOB_LIST_HELP} -e, --exclusive ID\timage ID\n -N, --N_SMP N_SMP\tnumber of SMP jobs, default from original config files\n -d, --directory DIR\trun directory, default is pwd ($dir)\n -S, --scratch DIR\tprocessing scratch directory, default=none\n - -n, --dry_run\t\tdry run, no actual processing; default is $dry_run\n + -n, --dry_run\t\tDRY RUN, no actual processing; default is $dry_run\n --debug_out PATH\tdebug output file PATH, default=none\n --test\t\t\ttest mode, no processing\n --check\t\tcheck download completeness only (job 8), no processing\n @@ -598,8 +598,14 @@ fi (( do_job = job & 512 )) if [[ $do_job != 0 ]]; then - # Job 1024: process tiles (PSF interp, vignet, shape measurement) - run_tile_job 512 "${Letter}iVi ${Letter}iVi ${Letter}iVi" "psfex_runner:1 vignetmaker_runner_run_1:1 vignetmaker_runner_run_2:1" + # Job 512: process tiles (PSF interp, vignet) + run_tile_job 512 "${Letter}iViVi ${Letter}iViVi ${Letter}iViVi" "psfex_interp_runner:1 vignetmaker_runner_run_1:1 vignetmaker_runner_run_2:4" +fi + +(( do_job = job & 1024 )) +if [[ $do_job != 0 ]]; then + # Job 1024: shape measurement + run_tile_job 1024 "Ng" "ngmix_interp_runner:1" fi if [ -n "$scratch" ]; then diff --git a/src/shapepipe/modules/psfex_interp_package/psfex_interp.py b/src/shapepipe/modules/psfex_interp_package/psfex_interp.py index 3fb5ce264..956092f2e 100644 --- a/src/shapepipe/modules/psfex_interp_package/psfex_interp.py +++ b/src/shapepipe/modules/psfex_interp_package/psfex_interp.py @@ -625,6 +625,8 @@ def _interpolate_me(self): array_id = None array_shape = None array_exp_name = None + n_ccd_valid = 0 + n_ccd_total = sum(1 for c in ccd_list if c != -1) for ccd in ccd_list: if ccd == -1: continue @@ -718,7 +720,14 @@ def _interpolate_me(self): else: array_exp_name = np.concatenate((array_exp_name, exp_name_tmp)) + n_ccd_valid += 1 + if array_id is not None: + n_psf = len(array_id) + self._w_log.info( + f"Exposure {exp_name}: {n_psf} PSFs from" + + f" {n_ccd_valid}/{n_ccd_total} CCDs" + ) final_list.append( [array_id, array_psf, array_shape, array_exp_name] ) diff --git a/src/shapepipe/pipeline/args.py b/src/shapepipe/pipeline/args.py index 8d52875f0..d402132f2 100644 --- a/src/shapepipe/pipeline/args.py +++ b/src/shapepipe/pipeline/args.py @@ -140,5 +140,14 @@ def create_arg_parser(): "--exclusive", help="exclusive input file number string", ) + + optional.add_argument( + "-b", + "--batch_size", + type=int, + default=None, + help="SMP batch size; overrides SMP_BATCH_SIZE in the config file", + ) + # Return parser return parser.parse_args() diff --git a/src/shapepipe/run.py b/src/shapepipe/run.py index fe2093a0d..e64a44b2d 100644 --- a/src/shapepipe/run.py +++ b/src/shapepipe/run.py @@ -341,6 +341,7 @@ def run_smp(pipe): job_type=pipe.run_method[module], exclusive=pipe.exclusive, verbose=pipe.verbose, + batch_size=pipe._args.batch_size, ) # Submit jobs @@ -400,6 +401,7 @@ def run_mpi(pipe, comm): parallel_mode="mpi", exclusive=pipe.exclusive, verbose=verbose, + batch_size=pipe._args.batch_size, ) # Get job type From d01503d0675da97b889359c2c44a201459725c5a Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sun, 10 May 2026 13:40:13 +0000 Subject: [PATCH 31/80] Update to ngmix v2.4; Fix check on zero-valued pixels (most did not pass), now all vignets with at least one != 0 pixel pass. --- pyproject.toml | 4 +- src/shapepipe/modules/ngmix_package/ngmix.py | 76 +++++++++++--------- src/shapepipe/modules/ngmix_runner.py | 3 +- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aaee93d94..a541fdc26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,9 +42,7 @@ dependencies = [ "termcolor", "tqdm>=4.63", "vos>=3.6", - # ngmix is pinned to Axel's stable_version branch until upstream ngmix - # absorbs the fixes — tracked separately, do not modernize this line. - "ngmix @ git+https://github.com/aguinot/ngmix@stable_version", + "ngmix>=2.4", ] diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 55eb83b2e..16aa55357 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -6,6 +6,7 @@ """ +import os import re import ngmix import galsim @@ -20,7 +21,7 @@ # I still don't know how to handle this class Tile_cat(): - """Tile_cat. + """Tile_cat. catalog measured on a tile @@ -30,33 +31,32 @@ class Tile_cat(): """ def __init__( - self, + self, cat_path ): self.cat_path = cat_path - - # sextractor detection catalog for the tile - self.tile_vignet - dtype = [('obj_id','i4'),('ra','>f8'),('dec','>f8'),('flux','>f4'),('VIGNET', '>f4', (51, 51))] - #self.tile_data = np.recarray(()) - - @classmethod + if cat_path: + self.get_data(cat_path) + def get_data(self, cat_path): tile_cat = file_io.FITSCatalogue( cat_path, SEx_catalogue=True, ) tile_cat.open() - # I would like to make this into an object cat - self.vign = np.copy(tile_cat.get_data()['VIGNET']) + data = tile_cat.get_data() + cols = data.dtype.names - self.obj_id = np.copy(tile_cat.get_data()['NUMBER']) - self.ra = np.copy(tile_cat.get_data()['XWIN_WORLD']) - self.dec = np.copy(tile_cat.get_data()['YWIN_WORLD']) - self.flux = np.copy(tile_cat.get_data()['FLUX_AUTO']) - self.size = np.copy(tile_cat.get_data()['FWHM_WORLD']) - self.e = np.copy(tile_cat.get_data()['ELLIPTICITY']) - self.theta = np.copy(tile_cat.get_data()['THETA_WIN_WORLD']) + self.obj_id = np.copy(data['NUMBER']) + self.ra = np.copy(data['XWIN_WORLD']) + self.dec = np.copy(data['YWIN_WORLD']) + + # Optional columns — may be absent in external (non-SExtractor) catalogs + self.flux = np.copy(data['FLUX_AUTO']) if 'FLUX_AUTO' in cols else None + self.vign = np.copy(data['VIGNET']) if 'VIGNET' in cols else None + self.size = np.copy(data['FWHM_WORLD']) if 'FWHM_WORLD' in cols else None + self.e = np.copy(data['ELLIPTICITY']) if 'ELLIPTICITY' in cols else None + self.theta = np.copy(data['THETA_WIN_WORLD']) if 'THETA_WIN_WORLD' in cols else None tile_cat.close() @@ -278,8 +278,8 @@ def get_prior(self, T_range=None, F_range=None): cen_prior = ngmix.priors.CenPrior( cen1=0.0, cen2=0.0, - sigma1=self.scale, - sigma2=self.scale, + sigma1=self._pixel_scale, + sigma2=self._pixel_scale, rng=self._rng ) @@ -613,11 +613,12 @@ def process(self): Dictionary containing the NGMIX metacal results """ - tile_cat = Tile_cat('') + tile_cat = Tile_cat(self._tile_cat_path) # i would like to make this into an object vignet vignet_cat = self._vignet_cat final_res = [] + psf_res = None prior = self.get_prior() count = 0 @@ -638,7 +639,7 @@ def process(self): # make postage stamp, skip if not observed try: - stamp = prepare_postage_stamps(vignet_cat) + stamp = prepare_postage_stamps(vignet_cat, obj_id, i_tile, tile_cat) except AttributeError: continue @@ -647,10 +648,15 @@ def process(self): if len(stamp.gals) == 0: continue try: + flux_guess = ( + tile_cat.flux[i_tile] + if tile_cat.flux is not None + else 1.0 + ) res, psf_res = do_ngmix_metacal( stamp, prior, - tile_cat.flux[i_tile], + flux_guess, self._pixel_scale, self._rng ) @@ -692,14 +698,12 @@ def process(self): vignet_cat.close # Put all results together - res_dict = self.compile_results(final_res,psf_res) + res_dict = self.compile_results(final_res) # Save results self.save_results(res_dict) -def prepare_postage_stamps(vignet, tile_cat): - i_tile = tile_cat.obj_id - 1 - obj_id = tile_cat.obj_id +def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): # define per-object lists of individual exposures to go into ngmix stamp = Postage_stamp() if ( @@ -716,7 +720,7 @@ def prepare_postage_stamps(vignet, tile_cat): vignet.gal_vign_cat[str(obj_id)][expccd_name]['VIGNET'] ) - if len(np.where(gal_vign.ravel() == 0)[0]) != 0: + if np.all(gal_vign == 0): continue if stamp.bkg_sub: @@ -730,15 +734,19 @@ def prepare_postage_stamps(vignet, tile_cat): else: gal_vign_sub_bkg = gal_vign - if stamp.megacam_flip: - tile_vign = ( - Ngmix.MegaCamFlip(np.copy(tile_vign[i_tile]), int(ccd_n)) - ) + tile_vign = ( + np.copy(tile_cat.vign[i_tile]) + if tile_cat.vign is not None + else None + ) + if stamp.megacam_flip and tile_vign is not None: + tile_vign = Ngmix.MegaCamFlip(tile_vign, int(ccd_n)) flag_vign = ( vignet.flag_vign_cat[str(obj_id)][expccd_name]['VIGNET'] ) - flag_vign[np.where(tile_vign == -1e30)] = 2**10 + if tile_vign is not None: + flag_vign[np.where(tile_vign == -1e30)] = 2**10 v_flag_tmp = flag_vign.ravel() # remove objects that are more than 1/3 masked if len(np.where(v_flag_tmp != 0)[0]) / (51 * 51) > 1 / 3.0: @@ -874,7 +882,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): Pixel scale of the galaxy image thresh : float, optional Threshold to cut the window function, - cut = ``thresh`` * :math:`\sigma_{\rm noise}`; the default is ``1.2`` + cut = ``thresh`` * sigma_noise; the default is ``1.2`` Returns ------- diff --git a/src/shapepipe/modules/ngmix_runner.py b/src/shapepipe/modules/ngmix_runner.py index 08e92a605..b0e91a236 100644 --- a/src/shapepipe/modules/ngmix_runner.py +++ b/src/shapepipe/modules/ngmix_runner.py @@ -74,8 +74,9 @@ def ngmix_runner( id_obj_max = config.getint(module_config_sec, "ID_OBJ_MAX") # Initialise class instance + # input_file_list[6] is log_exp_headers, already extracted as f_wcs_path ngmix_inst = Ngmix( - input_file_list, + input_file_list[:6], run_dirs["output"], file_number_string, zero_point, From fd88094e2496159ad793b805d67f8fa89d9c2518 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Tue, 12 May 2026 13:37:11 +0200 Subject: [PATCH 32/80] Added Axel's centroid shift test scripy (py) --- scripts/jupyter/test_centroid_shift.py | 686 +++++++++++++++++++++++++ 1 file changed, 686 insertions(+) create mode 100644 scripts/jupyter/test_centroid_shift.py diff --git a/scripts/jupyter/test_centroid_shift.py b/scripts/jupyter/test_centroid_shift.py new file mode 100644 index 000000000..d5ed45385 --- /dev/null +++ b/scripts/jupyter/test_centroid_shift.py @@ -0,0 +1,686 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[14]: + + +import numpy as np +import galsim + +import ngmix + +from shapepipe.modules.ngmix_package.ngmix import ( + get_noise, + get_guess, +) + +#print("Use shapepipe version of metacal") +#from shapepipe.modules.ngmix_package.ngmix import do_ngmix_metacal + +print("Use this version of metacal") + +from modopt.math.stats import sigma_mad +from ngmix import Observation, ObsList +from numpy.random import uniform as urand + + +# ### Simu + +# In[15]: + + +def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): + """ + simulate an exponential object with moffat psf + + Parameters + ---------- + rng: np.random.RandomState + The random number generator + noise: float + Noise for the image + shear: (g1, g2) + The shear in each component + + Returns + ------- + Lists + """ + + psf_noise = 1.0e-6 + + img_size = 201 + scale = 0.1857 + wcs = galsim.PixelScale(scale) + + psf_fwhm = 0.55 + gal_hlr = 0.3 + + if share_shift: + dy, dx = rng.uniform(low=-scale/2, high=scale/2, size=2) + + + gals = [] + psfs = [] + psfs_sigmas = [] + weights = [] + flags = [] + jacob_lists = [] + for epoch in range(n_epochs): + if not share_shift: + dy, dx = rng.uniform(low=-scale/2, high=scale/2, size=2) + psf = galsim.Moffat( + beta=2.5, fwhm=psf_fwhm, + ) + + obj0 = galsim.Exponential( + half_light_radius=gal_hlr, + flux=1000, + ).shear( + g1=shear[0], + g2=shear[1], + ) + + obj = galsim.Convolve(psf, obj0) + obj = obj.shift(dx, dy) + + psf_im_ = psf.drawImage(nx=img_size, ny=img_size, wcs=wcs) + s = galsim.hsm.FindAdaptiveMom(psf_im_) + psfs_sigmas.append(s.moments_sigma) + psf_im = psf_im_.array.astype(np.float64) + im = obj.drawImage(nx=img_size, ny=img_size, wcs=wcs).array.astype(np.float64) + + psf_im += rng.normal(scale=psf_noise, size=psf_im.shape) + im += rng.normal(scale=noise, size=im.shape) + + + wt = im*0 + 1.0/noise**2 + flag = im*0 + + weights.append(wt) + psfs.append(psf_im) + flags.append(flag) + gals.append(im) + jacob_lists.append(wcs.jacobian()) + + return gals, psfs, psfs_sigmas, weights, flags, jacob_lists + + +# ### From shapepipe + +# In[16]: + + +def get_prior(pixel_scale): + """Get Prior. + + Return prior for the different parameters. + + Returns + ------- + ngmix.priors + Priors for the different parameters + + """ + # Prior on ellipticity. Details do not matter, as long + # as it regularizes the fit. From Bernstein & Armstrong 2014 + g_sigma = 0.4 + g_prior = ngmix.priors.GPriorBA(g_sigma) + + # 2-d Gaussian prior on the center row and column center + # (relative to the center of the jacobian, which + # would be zero) and the sigma of the Gaussians. + # Units same as jacobian, probably arcsec + row, col = 0.0, 0.0 + row_sigma, col_sigma = pixel_scale, pixel_scale + cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma) + + # Size prior. Instead of flat, two-sided error function (TwoSidedErf) + # could be used + Tminval = -10.0 # arcsec squared + Tmaxval = 1.0e6 + T_prior = ngmix.priors.FlatPrior(Tminval, Tmaxval) + + # Flux prior. Bounds need to make sense for + # images in question + Fminval = -1.0e4 + Fmaxval = 1.0e9 + F_prior = ngmix.priors.FlatPrior(Fminval, Fmaxval) + + # Joint prior, combine all individual priors + prior = ngmix.joint_prior.PriorSimpleSep( + cen_prior, g_prior, T_prior, F_prior + ) + + return prior + + +# In[17]: + + +def make_galsimfit(obs, model, guess0, prior=None, ntry=5): + """Make GalSim Fit. + + Fit image using simple GalSim model. + + Parameters + ---------- + obs : ngmix.observation.Observation + Image to fit + model : str + Model for fit + guess0 : numpy.ndarray + Parameters of first model guess + prior : ngmix.prior, optional + Prior for fit paraemeters + ntry : int, optional + Number of tries for fit, the default is ``5`` + + Returns + ------- + dict + Results + + Raises + ------ + ngmix.BootGalFailure + Failure to bootstrap galaxy + + """ + limit = 0.1 + + guess = np.copy(guess0) + fres = {} + for it in range(ntry): + guess[0:5] += urand(low=-limit, high=limit) + guess[5:] *= 1 + urand(low=-limit, high=limit) + fres["flags"] = 1 + try: + fitter = ngmix.galsimfit.GalsimSimple( + obs, + model, + prior=prior, + ) + fitter.go(guess) + fres = fitter.get_result() + except: + continue + + if fres["flags"] == 0: + break + + if fres["flags"] != 0: + raise ngmix.gexceptions.BootGalFailure( + "Failed to fit galaxy image with galsimfit" + ) + + fres["ntry"] = it + 1 + + return fres + + +# In[18]: + + +def do_ngmix_metacal( + gals, psfs, psfs_sigma, weights, flags, jacob_list, prior, pixel_scale, sig_noise=None, +): + """Do Ngmix Metacal. + + Perform the metacalibration on a multi-epoch object and return the joint + shape measurement with NGMIX. + + Parameters + ---------- + gals : list + List of the galaxy vignets + psfs : list + List of the PSF vignets + psfs_sigma : list + List of the sigma PSFs + weights : list + List of the weight vignets + flags : list + List of the flag vignets + jacob_list : list + List of the Jacobians + prior : ngmix.priors + Priors for the fitting parameters + pixel_scale : float + pixel scale in arcsec + + Returns + ------- + dict + Dictionary containing the results of NGMIX metacal + + """ + n_epoch = len(gals) + + if n_epoch == 0: + raise ValueError("0 epoch to process") + + # Make observation + gal_obs_list = ObsList() + T_guess_psf = [] + psf_res_gT = { + "g_PSFo": np.array([0.0, 0.0]), + "g_err_PSFo": np.array([0.0, 0.0]), + "T_PSFo": 0.0, + "T_err_PSFo": 0.0, + } + gal_guess = [] + gal_guess_flag = True + wsum = 0 + for n_e in range(n_epoch): + + psf_jacob = ngmix.Jacobian( + row=(psfs[0].shape[0] - 1) / 2, + col=(psfs[0].shape[1] - 1) / 2, + wcs=jacob_list[n_e], + ) + + psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) + + psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale + + weight_map = np.copy(weights[n_e]) + weight_map[np.where(flags[n_e] != 0)] = 0.0 + weight_map[weight_map != 0] = 1 + + psf_guess = np.array([0.0, 0.0, 0.0, 0.0, psf_T, 1.0]) + try: + psf_res = make_galsimfit(psf_obs, "gauss", psf_guess) + except Exception: + continue + + # Gal guess + try: + gal_guess_tmp = get_guess( + gals[n_e], pixel_scale, guess_size_type="T", guess_centroid_unit="img" + ) + except Exception: + gal_guess_flag = False + gal_guess_tmp = np.array([0.0, 0.0, 0.0, 0.0, 1, 100]) + + # Recenter jacobian if necessary + gal_jacob = ngmix.Jacobian( + row=(gals[n_e].shape[0] - 1) / 2 + gal_guess_tmp[1], + col=(gals[n_e].shape[1] - 1) / 2 + gal_guess_tmp[0], + wcs=jacob_list[n_e], + ) + + # Noise handling + if sig_noise is None: + if gal_guess_flag: + sig_noise = get_noise( + gals[n_e], + weight_map, + gal_guess_tmp, + pixel_scale, + ) + else: + sig_noise = sigma_mad(gals[n_e]) + + noise_img = np.random.randn(*gals[n_e].shape) * sig_noise + noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise + + gal_masked = np.copy(gals[n_e]) + if len(np.where(weight_map == 0)[0]) != 0: + gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] + + weight_map *= 1 / sig_noise**2 + + # Original PSF fit + w_tmp = np.sum(weight_map) + psf_res_gT["g_PSFo"] += psf_res["g"] * w_tmp + psf_res_gT["g_err_PSFo"] += ( + np.array([psf_res["pars_err"][2], psf_res["pars_err"][3]]) * w_tmp + ) + psf_res_gT["T_PSFo"] += psf_res["T"] * w_tmp + psf_res_gT["T_err_PSFo"] += psf_res["T_err"] * w_tmp + wsum += w_tmp + + gal_obs = Observation( + gal_masked, + weight=weight_map, + jacobian=gal_jacob, + psf=psf_obs, + noise=noise_img, + ) + + if gal_guess_flag: + # gal_guess_tmp[:2] = 0 + final_gal_guess = np.copy(gal_guess_tmp) + final_gal_guess[:2] = 0 + gal_guess.append(final_gal_guess) + + gal_obs_list.append(gal_obs) + T_guess_psf.append(psf_T) + gal_guess_flag = True + + if wsum == 0: + raise ZeroDivisionError("Sum of weights = 0, division by zero") + + # Normalize PSF fit output + for key in psf_res_gT.keys(): + psf_res_gT[key] /= wsum + + # Gal guess handling + fail_get_guess = False + if len(gal_guess) == 0: + fail_get_guess = True + gal_pars = [0.0, 0.0, 0.0, 0.0, 1, 100] + else: + gal_pars = np.mean(gal_guess, 0) + + psf_model = "gauss" + gal_model = "gauss" + + # metacal specific parameters + metacal_pars = { + "types": ["noshear", "1p", "1m"], + "step": 0.01, + "psf": "gauss", + "fixnoise": True, + "cheatnoise": False, + "symmetrize_psf": False, + "use_noise_image": True, + } + + Tguess = np.mean(T_guess_psf) + + # retry the fit twice + obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) + res = {"mcal_flags": 0} + + ntry = 5 + + for key in sorted(obs_dict_mcal): + + fres = make_galsimfit( + obs_dict_mcal[key], gal_model, gal_pars, prior=prior + ) + # print(fres["pars"][0:2]) + # fres = make_fit( + # obs_dict_mcal[key], gal_model, gal_pars, prior=prior + # ) + + res["mcal_flags"] |= fres["flags"] + tres = {} + + for name in fres.keys(): + tres[name] = fres[name] + tres["flags"] = fres["flags"] + + wsum = 0 + Tpsf_sum = 0 + gpsf_sum = np.zeros(2) + npsf = 0 + for obs in obs_dict_mcal[key]: + + if hasattr(obs, "psf_nopix"): + try: + psf_res = make_galsimfit( + obs.psf_nopix, + psf_model, + np.array([0.0, 0.0, 0.0, 0.0, Tguess, 1.0]), + ntry=ntry, + ) + except Exception: + continue + g1, g2 = psf_res["g"] + T = psf_res["T"] + else: + try: + psf_res = make_galsimfit( + obs.psf, + psf_model, + np.array([0.0, 0.0, 0.0, 0.0, Tguess, 1.0]), + ) + except Exception: + continue + g1, g2 = psf_res["g"] + T = psf_res["T"] + + # TODO we sometimes use other weights + twsum = obs.weight.sum() + + wsum += twsum + gpsf_sum[0] += g1 * twsum + gpsf_sum[1] += g2 * twsum + Tpsf_sum += T * twsum + npsf += 1 + + tres["gpsf"] = gpsf_sum / wsum + tres["Tpsf"] = Tpsf_sum / wsum + + res[key] = tres + + # result dictionary, keyed by the types in metacal_pars above + metacal_res = res + + metacal_res.update(psf_res_gT) + metacal_res["moments_fail"] = fail_get_guess + + return metacal_res + + +# ### Extra func modified from: [metacal example](https://github.com/esheldon/ngmix/blob/master/examples/metacal/metacal.py) + +# In[19]: + + +def progress(total, miniters=1): + last_print_n = 0 + last_printed_len = 0 + sl = str(len(str(total))) + mf = '%'+sl+'d/%'+sl+'d %3d%%' + for i in range(total): + yield i + + num = i+1 + if i == 0 or num == total or num - last_print_n >= miniters: + meter = mf % (num, total, 100*float(num) / total) + nspace = max(last_printed_len-len(meter), 0) + + print('\r'+meter+' '*nspace, flush=True, end='') + last_printed_len = len(meter) + if i > 0: + last_print_n = num + + print(flush=True) + + +# In[20]: + + +def make_struct(res, shear_type): + """ + make the data structure + + Parameters + ---------- + res: dict + With keys 's2n', 'e', and 'T' + obs: ngmix.Observation + The observation for this shear type + shear_type: str + The shear type + + Returns + ------- + 1-element array with fields + """ + dt = [ + ('flags', 'i4'), + ('shear_type', 'U7'), + ('s2n', 'f8'), + ('g', 'f8', 2), + ('T', 'f8'), + ('Tpsf', 'f8'), + ] + data = np.zeros(1, dtype=dt) + data['shear_type'] = shear_type + data['flags'] = res['flags'] + if res['flags'] == 0: + s2n = res["flux"]/res["flux_err"] + data['s2n'] = s2n + # for moments we are actually measureing e, the elliptity + data['g'] = res['g'] + data['T'] = res['T'] + else: + data['s2n'] = np.nan + data['g'] = np.nan + data['T'] = np.nan + data['Tpsf'] = np.nan + + # we only have one epoch and band, so we can get the psf T from the + # observation rather than averaging over epochs/bands + data['Tpsf'] = res["Tpsf"] + + return data + + +# In[21]: + + +def select(data, shear_type): + """ + select the data by shear type and size + + Parameters + ---------- + data: array + The array with fields shear_type and T + shear_type: str + e.g. 'noshear', '1p', etc. + + Returns + ------- + array of indices + """ + + w, = np.where( + (data['flags'] == 0) & (data['shear_type'] == shear_type) + ) + return w + + +# In[22]: + + +gals, psfs, psfs_sigmas, weights, flags, jacob_lists = make_data(rng=np.random.RandomState(10), noise=1.e-10, shear=(0.02, 0.0), n_epochs=3, share_shift=False) + + +# In[23]: + + +pixel_scale = 0.1857 +prior = get_prior(pixel_scale) + + +# In[24]: + + +res = do_ngmix_metacal(gals, psfs, psfs_sigmas, weights, flags, jacob_lists, prior=prior, pixel_scale=pixel_scale, sig_noise=1e-10) + + +# In[25]: + + +sig_noise = 1e-10 +ntrial = 50 +seed = 42 +shear_types = ["noshear", "1p", "1m"] +rng = np.random.RandomState(seed) +seeds = rng.randint(0, 2**30, size=ntrial) + +dlist_p_ = [] +dlist_m_ = [] +for i in progress(ntrial, miniters=10): + + d_ = [] + for shear_true in [(0.02, 0.00), (-0.02, 0.00)]: + + gals, psfs, psfs_sigmas, weights, flags, jacob_lists = make_data(rng=np.random.RandomState(seeds[i]), noise=sig_noise, shear=shear_true, n_epochs=3, share_shift=False) + try: + resdict = do_ngmix_metacal(gals, psfs, psfs_sigmas, weights, flags, jacob_lists, prior=prior, pixel_scale=pixel_scale, sig_noise=sig_noise) + stt = [] + for stype in shear_types: + st = make_struct(res=resdict[stype], shear_type=stype) + stt.append(st) + except: + continue + d_.append(np.hstack(stt)) + if len(d_) != 2: + continue + dlist_p_.extend(d_[0]) + dlist_m_.extend(d_[1]) + +print() + +data_p = np.hstack(dlist_p_) +data_m = np.hstack(dlist_m_) + +# P shear +w = select(data=data_p, shear_type='noshear') +w_1p = select(data=data_p, shear_type='1p') +w_1m = select(data=data_p, shear_type='1m') + +g_p = data_p['g'][w] +g1_1p_p = data_p['g'][w_1p, 0] +g1_1m_p = data_p['g'][w_1m, 0] +R11_p = np.atleast_2d((g1_1p_p - g1_1m_p)/0.02).T + +# M shear +w = select(data=data_m, shear_type='noshear') +w_1p = select(data=data_m, shear_type='1p') +w_1m = select(data=data_m, shear_type='1m') + +g_m = data_m['g'][w] +g1_1p_m = data_m['g'][w_1p, 0] +g1_1m_m = data_m['g'][w_1m, 0] +R11_m = np.atleast_2d((g1_1p_m - g1_1m_m)/0.02).T + +shear_ = (g_p - g_m) / (R11_p + R11_m) +shear = np.mean(shear_, axis=0) +shear_err = np.std(shear_, axis=0)/np.sqrt(len(shear_)) + +m = shear[0]/0.02-1 +merr = shear_err[0]/0.02 + +s2n = data_p['s2n'][w].mean() + +print('S/N: %g' % s2n) +print('R11: %g %g' % (np.mean(R11_p), np.mean(R11_m))) +print('m[1e-3, 3sigmas]: %g +/- %g (99.7%% conf)' % (m/1e-3, merr*3/1e-3)) +print('c[1e-5, 3sigmas]: %g +/- %g (99.7%% conf)' % (shear[1]/1e-5, shear_err[1]*3/1e-5)) + + +# # Below is from Axel + +# ## Before the fix +# +# S/N: 342.343 +# +# R11: 0.926435 0.926423 +# +# m[1e-3, 3sigmas]: -27.8983 +/- 6.50568 (99.7% conf) +# +# c[1e-5, 3sigmas]: 0.0068871 +/- 0.18067 (99.7% conf) + +# ## After the fix +# +# 50/50 100% +# +# S/N: 1006.71 +# +# R11: 0.922675 0.922739 +# +# m[1e-3, 3sigmas]: 0.308435 +/- 0.597766 (99.7% conf) +# +# c[1e-5, 3sigmas]: 0.0981643 +/- 0.435279 (99.7% conf) +# + +# From 7e00cfd871c2813bebe46010f59ddf76615a3412 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Tue, 12 May 2026 11:38:19 +0000 Subject: [PATCH 33/80] debugging ngmix and small nb of processed objects --- src/shapepipe/modules/ngmix_package/ngmix.py | 87 ++++++++++---------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 16aa55357..6589478c6 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -614,19 +614,23 @@ def process(self): """ tile_cat = Tile_cat(self._tile_cat_path) - # i would like to make this into an object vignet - vignet_cat = self._vignet_cat + vignet_cat = self._vignet_cat final_res = [] psf_res = None prior = self.get_prior() count = 0 + n_empty_cat = 0 + n_no_epoch = 0 + n_ngmix_fail = 0 + n_fitted = 0 id_first = -1 id_last = -1 + count_batch = 0 + saved_batch_cumul = 0 for i_tile, obj_id in enumerate(tile_cat.obj_id): - # only run on objects in config file if they are specified (-1 means not set) if self._id_obj_min > 0 and obj_id < self._id_obj_min: continue if self._id_obj_max > 0 and obj_id > self._id_obj_max: @@ -634,19 +638,20 @@ def process(self): if id_first == -1: id_first = obj_id id_last = obj_id + count += 1 - count = count + 1 - - # make postage stamp, skip if not observed - try: - stamp = prepare_postage_stamps(vignet_cat, obj_id, i_tile, tile_cat) - except AttributeError: + # Skip objects with no multi-epoch PSF or vignet data + if (vignet_cat.psf_vign_cat[str(obj_id)] == 'empty' + or vignet_cat.gal_vign_cat[str(obj_id)] == 'empty'): + n_empty_cat += 1 continue - - #if object is observed, carry out metacal operations and run ngmix - + + stamp = prepare_postage_stamps(vignet_cat, obj_id, i_tile, tile_cat) + if len(stamp.gals) == 0: + n_no_epoch += 1 continue + try: flux_guess = ( tile_cat.flux[i_tile] @@ -657,46 +662,50 @@ def process(self): stamp, prior, flux_guess, - self._pixel_scale, self._rng ) - except Exception as ee: self._w_log.info( f'ngmix failed for object ID={obj_id}.\nMessage: {ee}' ) + n_ngmix_fail += 1 continue - # these things need to be considered + res['obj_id'] = obj_id - res['n_epoch_model'] = len(stamp.gal_vign_list) + res['n_epoch_model'] = len(stamp.gals) final_res.append(res) + n_fitted += 1 + count_batch += 1 - if count_batch == self._save_batch: - - # Put batch results together + if self._save_batch > 0 and count_batch == self._save_batch: res_dict = self.compile_results(final_res) - - # Save batch to disk self.save_results(res_dict) - saved_batch_cumul += count_batch - self._w_log.info( - f"Batch-saved {count_batch} ({len(res_dict)} valid) objects to" - + f" file, cumul={saved_batch_cumul}" + f"Batch-saved {count_batch} ({len(res_dict)} valid) objects," + + f" cumul={saved_batch_cumul}" ) - - # Reset for next batch final_res = [] count_batch = 0 + if count % 1000 == 0: + self._w_log.info( + f"Progress: {count} iterated, {n_empty_cat} empty catalog," + + f" {n_no_epoch} no valid epoch, {n_ngmix_fail} fit failed," + + f" {n_fitted} fitted" + ) + self._w_log.info( - f"ngmix loop over objects finished, measured {count} " - + f"objects, id first/last={id_first}/{id_last}" + f"ngmix loop finished: {count} iterated" + + f" (id {id_first}/{id_last})," + + f" {n_empty_cat} empty catalog," + + f" {n_no_epoch} no valid epoch," + + f" {n_ngmix_fail} fit failed," + + f" {n_fitted} fitted" ) - vignet_cat.close - + vignet_cat.close() + # Put all results together res_dict = self.compile_results(final_res) @@ -706,11 +715,6 @@ def process(self): def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): # define per-object lists of individual exposures to go into ngmix stamp = Postage_stamp() - if ( - (vignet.psf_vign_cat[str(obj_id)] == 'empty') - or (vignet.gal_vign_cat[str(obj_id)] == 'empty') - ): - raise AttributeError #identify exposure and ccd number from psf catalog psf_expccd_names = list(vignet.psf_vign_cat[str(obj_id)].keys()) for expccd_name in psf_expccd_names: @@ -774,14 +778,13 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): ) # gather postage stamps in all of the epochs - stamp.gal_vign_list.append(gal_vign_scaled) - stamp.psf_vign_list.append( + stamp.gals.append(gal_vign_scaled) + stamp.psfs.append( vignet.psf_vign_cat[str(obj_id)][expccd_name]['VIGNET'] ) - - stamp.weight_vign_list.append(weight_vign_scaled) - stamp.flag_vign_list.append(flag_vign) - stamp.jacob_list.append(jacob) + stamp.weights.append(weight_vign_scaled) + stamp.flags.append(flag_vign) + stamp.jacobs.append(jacob) return stamp From b729eb8a92182d8f85e13ad26fa0e86ad4ef63e1 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Wed, 13 May 2026 09:01:32 +0200 Subject: [PATCH 34/80] adapting centroid shift script to v2.0 --- scripts/jupyter/test_centroid_shift.py | 42 ++++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/scripts/jupyter/test_centroid_shift.py b/scripts/jupyter/test_centroid_shift.py index d5ed45385..1940509e3 100644 --- a/scripts/jupyter/test_centroid_shift.py +++ b/scripts/jupyter/test_centroid_shift.py @@ -11,7 +11,6 @@ from shapepipe.modules.ngmix_package.ngmix import ( get_noise, - get_guess, ) #print("Use shapepipe version of metacal") @@ -111,7 +110,7 @@ def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): # In[16]: -def get_prior(pixel_scale): +def get_prior(pixel_scale, rng=None): """Get Prior. Return prior for the different parameters. @@ -122,10 +121,13 @@ def get_prior(pixel_scale): Priors for the different parameters """ + if rng is None: + rng = np.random.default_rng() + # Prior on ellipticity. Details do not matter, as long # as it regularizes the fit. From Bernstein & Armstrong 2014 g_sigma = 0.4 - g_prior = ngmix.priors.GPriorBA(g_sigma) + g_prior = ngmix.priors.GPriorBA(g_sigma, rng=rng) # 2-d Gaussian prior on the center row and column center # (relative to the center of the jacobian, which @@ -133,19 +135,19 @@ def get_prior(pixel_scale): # Units same as jacobian, probably arcsec row, col = 0.0, 0.0 row_sigma, col_sigma = pixel_scale, pixel_scale - cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma) + cen_prior = ngmix.priors.CenPrior(row, col, row_sigma, col_sigma, rng=rng) # Size prior. Instead of flat, two-sided error function (TwoSidedErf) # could be used Tminval = -10.0 # arcsec squared Tmaxval = 1.0e6 - T_prior = ngmix.priors.FlatPrior(Tminval, Tmaxval) + T_prior = ngmix.priors.FlatPrior(Tminval, Tmaxval, rng=rng) # Flux prior. Bounds need to make sense for # images in question Fminval = -1.0e4 Fmaxval = 1.0e9 - F_prior = ngmix.priors.FlatPrior(Fminval, Fmaxval) + F_prior = ngmix.priors.FlatPrior(Fminval, Fmaxval, rng=rng) # Joint prior, combine all individual priors prior = ngmix.joint_prior.PriorSimpleSep( @@ -196,13 +198,11 @@ def make_galsimfit(obs, model, guess0, prior=None, ntry=5): guess[5:] *= 1 + urand(low=-limit, high=limit) fres["flags"] = 1 try: - fitter = ngmix.galsimfit.GalsimSimple( - obs, - model, - prior=prior, - ) - fitter.go(guess) - fres = fitter.get_result() + fitter = ngmix.fitting.GalsimFitter(model, prior=prior) + fres = fitter.go(obs, guess) + if fres["flags"] == 0 and "T" not in fres: + fres["T"] = fres["pars"][4] + fres["T_err"] = np.sqrt(fres["pars_cov"][4, 4]) except: continue @@ -294,11 +294,15 @@ def do_ngmix_metacal( except Exception: continue - # Gal guess + # Gal guess via adaptive moments try: - gal_guess_tmp = get_guess( - gals[n_e], pixel_scale, guess_size_type="T", guess_centroid_unit="img" - ) + _gim = galsim.Image(gals[n_e], scale=pixel_scale) + _hsm = galsim.hsm.FindAdaptiveMom(_gim, strict=False) + if _hsm.error_message != "": + raise galsim.hsm.GalSimHSMError(_hsm.error_message) + _cen = _hsm.moments_centroid - _gim.center # pixels (centroid_unit="img") + _size = 2 * (_hsm.moments_sigma * pixel_scale) ** 2 # T in arcsec^2 + gal_guess_tmp = np.array([_cen.x, _cen.y, 0.0, 0.0, _size, _hsm.moments_amp]) except Exception: gal_guess_flag = False gal_guess_tmp = np.array([0.0, 0.0, 0.0, 0.0, 1, 100]) @@ -383,8 +387,6 @@ def do_ngmix_metacal( "step": 0.01, "psf": "gauss", "fixnoise": True, - "cheatnoise": False, - "symmetrize_psf": False, "use_noise_image": True, } @@ -577,7 +579,7 @@ def select(data, shear_type): pixel_scale = 0.1857 -prior = get_prior(pixel_scale) +prior = get_prior(pixel_scale, rng=np.random.default_rng(42)) # In[24]: From 9f4170d24d059bc5483d1269f7d0cd06aa53fe93 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Wed, 13 May 2026 16:43:16 +0200 Subject: [PATCH 35/80] Add centroid bias validation infrastructure from test_centroid_bug --- scripts/validation/centroid/centroid_bias.py | 397 ++++++++++++++++++ .../centroid/configs/centroid_bug.yaml | 6 + .../centroid/configs/centroid_fix.yaml | 6 + .../validation/centroid/envs/centroid_bug.yml | 19 + .../validation/centroid/envs/centroid_fix.yml | 19 + scripts/validation/centroid/run_bias_test.sh | 97 +++++ src/shapepipe/testing/__init__.py | 1 + src/shapepipe/testing/simulate.py | 75 ++++ 8 files changed, 620 insertions(+) create mode 100644 scripts/validation/centroid/centroid_bias.py create mode 100644 scripts/validation/centroid/configs/centroid_bug.yaml create mode 100644 scripts/validation/centroid/configs/centroid_fix.yaml create mode 100644 scripts/validation/centroid/envs/centroid_bug.yml create mode 100644 scripts/validation/centroid/envs/centroid_fix.yml create mode 100755 scripts/validation/centroid/run_bias_test.sh create mode 100644 src/shapepipe/testing/__init__.py create mode 100644 src/shapepipe/testing/simulate.py diff --git a/scripts/validation/centroid/centroid_bias.py b/scripts/validation/centroid/centroid_bias.py new file mode 100644 index 000000000..2ba7fadea --- /dev/null +++ b/scripts/validation/centroid/centroid_bias.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python +"""centroid_bias.py + +Metacal multiplicative-bias validation for ShapePipe/ngmix. + +Measures the multiplicative shear bias m by running metacalibration on +simulated exponential galaxies with a Moffat PSF and comparing recovered +shear to the true input shear. + +Simulation data (make_data) is imported from shapepipe.testing.simulate so +it stays stable across branches. All processing code here is +version-specific and changes with each shapepipe/ngmix branch. + +Usage +----- + python centroid_bias.py [--ntrial N] [--seed S] [--noise SIGMA] + +Expected output (centroid_bug branch, ngmix stable_version): + m ~ -28e-3 (large negative bias, the bug) + +Expected output (centroid_fix branch, ngmix fix_jac_centroid): + m ~ 0 (bias consistent with zero) +""" + +import argparse + +import numpy as np +import ngmix +from ngmix import Observation, ObsList +from numpy.random import uniform as urand + +from shapepipe.modules.ngmix_package.ngmix import get_guess, get_noise +from shapepipe.testing.simulate import make_data +from modopt.math.stats import sigma_mad + + +# --------------------------------------------------------------------------- +# Priors (ngmix-version-specific) +# --------------------------------------------------------------------------- + +def get_prior(pixel_scale): + """Build ngmix joint prior for 6-parameter galaxy model.""" + g_prior = ngmix.priors.GPriorBA(0.4) + cen_prior = ngmix.priors.CenPrior(0.0, 0.0, pixel_scale, pixel_scale) + T_prior = ngmix.priors.FlatPrior(-10.0, 1.0e6) + F_prior = ngmix.priors.FlatPrior(-1.0e4, 1.0e9) + return ngmix.joint_prior.PriorSimpleSep(cen_prior, g_prior, T_prior, F_prior) + + +# --------------------------------------------------------------------------- +# Fitting (ngmix-version-specific) +# --------------------------------------------------------------------------- + +def make_galsimfit(obs, model, guess0, prior=None, ntry=5): + """Fit an observation with GalsimSimple, retrying with perturbed guesses. + + Parameters + ---------- + obs : ngmix.Observation or ObsList + model : str + guess0 : numpy.ndarray shape (6,) + prior : ngmix prior, optional + ntry : int, optional + + Returns + ------- + dict + Fit result with flags, g, T, T_err, pars, pars_err, etc. + + Raises + ------ + ngmix.gexceptions.BootGalFailure + """ + limit = 0.1 + guess = np.copy(guess0) + fres = {"flags": 1} + + for it in range(ntry): + guess[0:5] += urand(low=-limit, high=limit) + guess[5:] *= 1 + urand(low=-limit, high=limit) + try: + fitter = ngmix.galsimfit.GalsimSimple(obs, model, prior=prior) + fitter.go(guess) + fres = fitter.get_result() + except Exception: + continue + if fres["flags"] == 0: + break + + if fres["flags"] != 0: + raise ngmix.gexceptions.BootGalFailure( + "Failed to fit galaxy image with galsimfit" + ) + fres["ntry"] = it + 1 + return fres + + +# --------------------------------------------------------------------------- +# Metacal pipeline (ngmix-version-specific) +# --------------------------------------------------------------------------- + +def do_ngmix_metacal( + gals, psfs, psfs_sigma, weights, flags, jacob_list, + prior, pixel_scale, sig_noise=None, +): + """Run metacalibration on a multi-epoch object. + + Parameters + ---------- + gals, psfs, psfs_sigma, weights, flags : lists + Per-epoch image arrays and metadata. + jacob_list : list of galsim Jacobians + prior : ngmix joint prior + pixel_scale : float [arcsec] + sig_noise : float or None + If None, estimated from the first epoch. + + Returns + ------- + dict + Metacal result dict keyed by shear type ('noshear', '1p', '1m'). + """ + n_epoch = len(gals) + if n_epoch == 0: + raise ValueError("0 epochs to process") + + gal_obs_list = ObsList() + T_guess_psf = [] + psf_res_gT = { + "g_PSFo": np.array([0.0, 0.0]), + "g_err_PSFo": np.array([0.0, 0.0]), + "T_PSFo": 0.0, + "T_err_PSFo": 0.0, + } + gal_guess = [] + gal_guess_flag = True + wsum = 0 + + for n_e in range(n_epoch): + psf_jacob = ngmix.Jacobian( + row=(psfs[0].shape[0] - 1) / 2, + col=(psfs[0].shape[1] - 1) / 2, + wcs=jacob_list[n_e], + ) + psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) + psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale + + weight_map = np.copy(weights[n_e]) + weight_map[flags[n_e] != 0] = 0.0 + weight_map[weight_map != 0] = 1 + + psf_guess = np.array([0.0, 0.0, 0.0, 0.0, psf_T, 1.0]) + try: + psf_res = make_galsimfit(psf_obs, "gauss", psf_guess) + except Exception: + continue + + try: + gal_guess_tmp = get_guess( + gals[n_e], pixel_scale, + guess_size_type="T", guess_centroid_unit="img", + ) + except Exception: + gal_guess_flag = False + gal_guess_tmp = np.array([0.0, 0.0, 0.0, 0.0, 1, 100]) + + gal_jacob = ngmix.Jacobian( + row=(gals[n_e].shape[0] - 1) / 2 + gal_guess_tmp[1], + col=(gals[n_e].shape[1] - 1) / 2 + gal_guess_tmp[0], + wcs=jacob_list[n_e], + ) + + if sig_noise is None: + sig_noise = ( + get_noise(gals[n_e], weight_map, gal_guess_tmp, pixel_scale) + if gal_guess_flag + else sigma_mad(gals[n_e]) + ) + + noise_img = np.random.randn(*gals[n_e].shape) * sig_noise + noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise + + gal_masked = np.copy(gals[n_e]) + if (weight_map == 0).any(): + gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] + weight_map *= 1 / sig_noise ** 2 + + w_tmp = np.sum(weight_map) + psf_res_gT["g_PSFo"] += psf_res["g"] * w_tmp + psf_res_gT["g_err_PSFo"] += ( + np.array([psf_res["pars_err"][2], psf_res["pars_err"][3]]) * w_tmp + ) + psf_res_gT["T_PSFo"] += psf_res["T"] * w_tmp + psf_res_gT["T_err_PSFo"] += psf_res["T_err"] * w_tmp + wsum += w_tmp + + gal_obs = Observation( + gal_masked, weight=weight_map, + jacobian=gal_jacob, psf=psf_obs, noise=noise_img, + ) + if gal_guess_flag: + final_gal_guess = np.copy(gal_guess_tmp) + final_gal_guess[:2] = 0 + gal_guess.append(final_gal_guess) + + gal_obs_list.append(gal_obs) + T_guess_psf.append(psf_T) + gal_guess_flag = True + + if wsum == 0: + raise ZeroDivisionError("Sum of weights = 0") + + for key in psf_res_gT: + psf_res_gT[key] /= wsum + + fail_get_guess = len(gal_guess) == 0 + gal_pars = [0.0, 0.0, 0.0, 0.0, 1, 100] if fail_get_guess else np.mean(gal_guess, 0) + + metacal_pars = { + "types": ["noshear", "1p", "1m"], + "step": 0.01, + "psf": "gauss", + "fixnoise": True, + "cheatnoise": False, + "symmetrize_psf": False, + "use_noise_image": True, + } + Tguess = np.mean(T_guess_psf) + + obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) + res = {"mcal_flags": 0} + + for key in sorted(obs_dict_mcal): + fres = make_galsimfit(obs_dict_mcal[key], "gauss", gal_pars, prior=prior) + res["mcal_flags"] |= fres["flags"] + tres = dict(fres) + + wsum_psf = 0 + Tpsf_sum = 0 + gpsf_sum = np.zeros(2) + for obs in obs_dict_mcal[key]: + psf_obs_fit = getattr(obs, "psf_nopix", obs.psf) + try: + psf_res = make_galsimfit( + psf_obs_fit, "gauss", + np.array([0.0, 0.0, 0.0, 0.0, Tguess, 1.0]), + ) + except Exception: + continue + tw = obs.weight.sum() + wsum_psf += tw + gpsf_sum += np.array(psf_res["g"]) * tw + Tpsf_sum += psf_res["T"] * tw + + tres["gpsf"] = gpsf_sum / wsum_psf if wsum_psf > 0 else np.zeros(2) + tres["Tpsf"] = Tpsf_sum / wsum_psf if wsum_psf > 0 else 0.0 + res[key] = tres + + res.update(psf_res_gT) + res["moments_fail"] = fail_get_guess + return res + + +# --------------------------------------------------------------------------- +# Analysis utilities (stable) +# --------------------------------------------------------------------------- + +def progress(total, miniters=1): + """Minimal progress printer.""" + sl = str(len(str(total))) + fmt = "%" + sl + "d/%" + sl + "d %3d%%" + last_print_n = 0 + last_len = 0 + for i in range(total): + yield i + num = i + 1 + if i == 0 or num == total or num - last_print_n >= miniters: + meter = fmt % (num, total, 100 * num // total) + print("\r" + meter + " " * max(last_len - len(meter), 0), + end="", flush=True) + last_len = len(meter) + last_print_n = num + print(flush=True) + + +def make_struct(res, shear_type): + """Pack a metacal result into a structured array row.""" + dt = [ + ("flags", "i4"), ("shear_type", "U7"), + ("s2n", "f8"), ("g", "f8", 2), ("T", "f8"), ("Tpsf", "f8"), + ] + data = np.zeros(1, dtype=dt) + data["shear_type"] = shear_type + data["flags"] = res["flags"] + if res["flags"] == 0: + data["s2n"] = res["flux"] / res["flux_err"] + data["g"] = res["g"] + data["T"] = res["T"] + data["Tpsf"] = res["Tpsf"] + else: + data["s2n"] = data["g"] = data["T"] = data["Tpsf"] = np.nan + return data + + +def select(data, shear_type): + """Return indices where flags==0 and shear_type matches.""" + return np.where( + (data["flags"] == 0) & (data["shear_type"] == shear_type) + )[0] + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): + rng_prior = np.random.RandomState(seed) + prior = get_prior(pixel_scale) + shear_types = ["noshear", "1p", "1m"] + + rng = np.random.RandomState(seed) + seeds = rng.randint(0, 2 ** 30, size=ntrial) + + dlist_p, dlist_m = [], [] + + for i in progress(ntrial, miniters=10): + d_ = [] + for shear_true in [(0.02, 0.00), (-0.02, 0.00)]: + gals, psfs, psfs_sigmas, weights, flags, jacob_lists = make_data( + rng=np.random.RandomState(seeds[i]), + noise=sig_noise, + shear=shear_true, + n_epochs=n_epochs, + share_shift=False, + ) + try: + resdict = do_ngmix_metacal( + gals, psfs, psfs_sigmas, weights, flags, jacob_lists, + prior=prior, pixel_scale=pixel_scale, sig_noise=sig_noise, + ) + stt = [make_struct(resdict[st], st) for st in shear_types] + except Exception: + continue + d_.append(np.hstack(stt)) + + if len(d_) != 2: + continue + dlist_p.extend(d_[0]) + dlist_m.extend(d_[1]) + + print() + + data_p = np.hstack(dlist_p) + data_m = np.hstack(dlist_m) + + w = select(data_p, "noshear") + w_1p = select(data_p, "1p") + w_1m = select(data_p, "1m") + R11_p = np.atleast_2d((data_p["g"][w_1p, 0] - data_p["g"][w_1m, 0]) / 0.02).T + + w = select(data_m, "noshear") + w_1p = select(data_m, "1p") + w_1m = select(data_m, "1m") + R11_m = np.atleast_2d((data_m["g"][w_1p, 0] - data_m["g"][w_1m, 0]) / 0.02).T + + g_p = data_p["g"][select(data_p, "noshear")] + g_m = data_m["g"][select(data_m, "noshear")] + shear_ = (g_p - g_m) / (R11_p + R11_m) + shear = np.mean(shear_, axis=0) + shear_err = np.std(shear_, axis=0) / np.sqrt(len(shear_)) + + m = shear[0] / 0.02 - 1 + merr = shear_err[0] / 0.02 + s2n = data_p["s2n"][select(data_p, "noshear")].mean() + + print("S/N: %g" % s2n) + print("R11: %g %g" % (np.mean(R11_p), np.mean(R11_m))) + print("m[1e-3, 3sigmas]: %g +/- %g (99.7%% conf)" % (m / 1e-3, merr * 3 / 1e-3)) + print("c[1e-5, 3sigmas]: %g +/- %g (99.7%% conf)" % (shear[1] / 1e-5, shear_err[1] * 3 / 1e-5)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Metacal centroid bias validation") + parser.add_argument("--ntrial", type=int, default=50, help="number of trials") + parser.add_argument("--seed", type=int, default=42, help="random seed") + parser.add_argument("--noise", type=float, default=1e-10, help="per-pixel noise sigma") + parser.add_argument("--n-epochs", type=int, default=3, help="epochs per galaxy") + parser.add_argument("--pixel-scale", type=float, default=0.1857, help="pixel scale [arcsec]") + args = parser.parse_args() + + main( + ntrial=args.ntrial, + seed=args.seed, + sig_noise=args.noise, + n_epochs=args.n_epochs, + pixel_scale=args.pixel_scale, + ) diff --git a/scripts/validation/centroid/configs/centroid_bug.yaml b/scripts/validation/centroid/configs/centroid_bug.yaml new file mode 100644 index 000000000..e1d1c71c8 --- /dev/null +++ b/scripts/validation/centroid/configs/centroid_bug.yaml @@ -0,0 +1,6 @@ +conda_env: sp_centroid_bug +description: test_centroid_bug branch + aguinot/ngmix@stable_version (1.3.6) — shows + large m bias +name: centroid_bug +shapepipe_branch: test_centroid_bug +test_script: scripts/validation/centroid/centroid_bias.py diff --git a/scripts/validation/centroid/configs/centroid_fix.yaml b/scripts/validation/centroid/configs/centroid_fix.yaml new file mode 100644 index 000000000..bcd2f86f2 --- /dev/null +++ b/scripts/validation/centroid/configs/centroid_fix.yaml @@ -0,0 +1,6 @@ +conda_env: sp_centroid_fix +description: test_centroid_bug branch + aguinot/ngmix@fix_jac_centroid — preliminary + centroid fix +name: centroid_fix +shapepipe_branch: test_centroid_bug +test_script: scripts/validation/centroid/centroid_bias.py diff --git a/scripts/validation/centroid/envs/centroid_bug.yml b/scripts/validation/centroid/envs/centroid_bug.yml new file mode 100644 index 000000000..e26234fe5 --- /dev/null +++ b/scripts/validation/centroid/envs/centroid_bug.yml @@ -0,0 +1,19 @@ +name: sp_centroid_bug +# Minimal environment for the centroid bias test script. +# Only packages directly needed by test_centroid_shift.py and the +# shapepipe ngmix module it imports. +channels: + - conda-forge + - nodefaults +dependencies: + - python=3.10 + - astropy + - galsim + - numba + - numpy + - tqdm + - pip: + - cs_util==0.1 + - modopt + - sqlitedict + - git+https://github.com/aguinot/ngmix@stable_version diff --git a/scripts/validation/centroid/envs/centroid_fix.yml b/scripts/validation/centroid/envs/centroid_fix.yml new file mode 100644 index 000000000..e6fe329bf --- /dev/null +++ b/scripts/validation/centroid/envs/centroid_fix.yml @@ -0,0 +1,19 @@ +name: sp_centroid_fix +# Minimal environment for the centroid bias test script. +# Only packages directly needed by test_centroid_shift.py and the +# shapepipe ngmix module it imports. +channels: + - conda-forge + - nodefaults +dependencies: + - python=3.10 + - astropy + - galsim + - numba + - numpy + - tqdm + - pip: + - cs_util==0.1 + - modopt + - sqlitedict + - git+https://github.com/aguinot/ngmix@fix_jac_centroid diff --git a/scripts/validation/centroid/run_bias_test.sh b/scripts/validation/centroid/run_bias_test.sh new file mode 100755 index 000000000..9e44428f0 --- /dev/null +++ b/scripts/validation/centroid/run_bias_test.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# run_bias_test.sh +# +# Run a centroid-bias test under the conda environment and shapepipe branch +# defined in configs/.yaml. +# +# The shapepipe repo root is derived automatically from this script's +# location (scripts/validation/centroid/ -> root). +# +# Results are written to ${SP_RESULTS_DIR}// where +# SP_RESULTS_DIR defaults to ~/astro/Runs/shapepipe/CFIS/centroid_bug/results +# and can be overridden by setting the environment variable. +# +# Conda envs are looked for at ${SCRIPT_DIR}/envs/. +# They are not committed; create them with: +# mamba env create -f envs/.yml --prefix envs/ +# i.e.: +# mamba env create -f envs/centroid_bug.yml --prefix envs/sp_centroid_bug +# mamba env create -f envs/centroid_fix.yml --prefix envs/sp_centroid_fix +# +# Usage: +# ./run_bias_test.sh centroid_bug +# ./run_bias_test.sh centroid_fix + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Repo root is 3 levels up from scripts/validation/centroid/ +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + +CONFIG_NAME="${1:?Usage: $0 }" +CONFIG_FILE="${SCRIPT_DIR}/configs/${CONFIG_NAME}.yaml" + +if [[ ! -f "$CONFIG_FILE" ]]; then + echo "ERROR: config not found: $CONFIG_FILE" + exit 1 +fi + +# Parse YAML config with Python (avoids yq dependency) +eval "$(python3 - < config value > default +DEFAULT_RESULTS="${HOME}/astro/Runs/shapepipe/CFIS/centroid_bug/results/${CONFIG_NAME}" +RESULTS_DIR="${SP_RESULTS_DIR:-${RESULTS_DIR:-${DEFAULT_RESULTS}}}" + +echo "=== Bias test: ${NAME} ===" +echo " ${DESCRIPTION}" +echo " conda env : ${CONDA_ENV}" +echo " branch : ${SHAPEPIPE_BRANCH}" +echo " shapepipe : ${SHAPEPIPE_PATH}" +echo " test script : ${TEST_SCRIPT}" +echo " results : ${RESULTS_DIR}" +echo + +# Verify branch +CURRENT_BRANCH=$(git -C "${SHAPEPIPE_PATH}" branch --show-current 2>/dev/null || echo "unknown") +if [[ "${CURRENT_BRANCH}" != "${SHAPEPIPE_BRANCH}" ]]; then + echo "ERROR: ${SHAPEPIPE_PATH} is on branch '${CURRENT_BRANCH}', expected '${SHAPEPIPE_BRANCH}'." + echo " Check out the correct branch or add a git worktree." + exit 1 +fi + +mkdir -p "${RESULTS_DIR}" +LOG="${RESULTS_DIR}/run_$(date +%Y%m%d_%H%M%S).log" +echo "Logging to ${LOG}" +echo + +# Conda envs live next to this script under envs/ +ENV_PREFIX="${SCRIPT_DIR}/envs/${CONDA_ENV}" +if [[ ! -d "${ENV_PREFIX}" ]]; then + echo "ERROR: conda env not found at ${ENV_PREFIX}. Create it with:" + echo " mamba env create -f ${SCRIPT_DIR}/envs/${CONFIG_NAME}.yml --prefix ${ENV_PREFIX}" + exit 1 +fi +echo " env prefix : ${ENV_PREFIX}" +echo + +# PYTHONNOUSERSITE=1 prevents ~/.local site-packages from overriding the +# conda env packages (e.g. a system-wide ngmix install). +mamba run --prefix "${ENV_PREFIX}" \ + env PYTHONNOUSERSITE=1 \ + PYTHONPATH="${SHAPEPIPE_PATH}/src:${PYTHONPATH:-}" \ + python "${SHAPEPIPE_PATH}/${TEST_SCRIPT}" \ + 2>&1 | tee "${LOG}" + +echo +echo "Done. Results in ${RESULTS_DIR}" diff --git a/src/shapepipe/testing/__init__.py b/src/shapepipe/testing/__init__.py new file mode 100644 index 000000000..188000214 --- /dev/null +++ b/src/shapepipe/testing/__init__.py @@ -0,0 +1 @@ +"""ShapePipe testing utilities.""" diff --git a/src/shapepipe/testing/simulate.py b/src/shapepipe/testing/simulate.py new file mode 100644 index 000000000..00c971dfa --- /dev/null +++ b/src/shapepipe/testing/simulate.py @@ -0,0 +1,75 @@ +"""Simulation utilities for ShapePipe validation tests. + +Stable across shapepipe branches and ngmix versions — contains only galsim +and numpy, no shapepipe processing or ngmix fitting code. +""" + +import numpy as np +import galsim + + +def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): + """Simulate an exponential galaxy with Moffat PSF. + + Parameters + ---------- + rng : numpy.random.RandomState + Random number generator. + shear : tuple of float + True shear (g1, g2). + noise : float, optional + Per-pixel noise sigma. Default 1e-5. + n_epochs : int, optional + Number of epochs. Default 1. + share_shift : bool, optional + If True, all epochs share the same sub-pixel shift. + If False, each epoch draws an independent shift. + + Returns + ------- + gals : list of numpy.ndarray + psfs : list of numpy.ndarray + psfs_sigmas : list of float + weights : list of numpy.ndarray + flags : list of numpy.ndarray + jacob_lists : list of galsim.BaseWCS + """ + psf_noise = 1.0e-6 + img_size = 201 + scale = 0.1857 + wcs = galsim.PixelScale(scale) + psf_fwhm = 0.55 + gal_hlr = 0.3 + + if share_shift: + dy, dx = rng.uniform(low=-scale / 2, high=scale / 2, size=2) + + gals, psfs, psfs_sigmas, weights, flags, jacob_lists = [], [], [], [], [], [] + + for epoch in range(n_epochs): + if not share_shift: + dy, dx = rng.uniform(low=-scale / 2, high=scale / 2, size=2) + + psf = galsim.Moffat(beta=2.5, fwhm=psf_fwhm) + obj = galsim.Convolve( + psf, + galsim.Exponential(half_light_radius=gal_hlr, flux=1000).shear( + g1=shear[0], g2=shear[1] + ), + ).shift(dx, dy) + + psf_im_ = psf.drawImage(nx=img_size, ny=img_size, wcs=wcs) + psfs_sigmas.append(galsim.hsm.FindAdaptiveMom(psf_im_).moments_sigma) + psf_im = psf_im_.array.astype(np.float64) + im = obj.drawImage(nx=img_size, ny=img_size, wcs=wcs).array.astype(np.float64) + + psf_im += rng.normal(scale=psf_noise, size=psf_im.shape) + im += rng.normal(scale=noise, size=im.shape) + + gals.append(im) + psfs.append(psf_im) + weights.append(im * 0 + 1.0 / noise ** 2) + flags.append(im * 0) + jacob_lists.append(wcs.jacobian()) + + return gals, psfs, psfs_sigmas, weights, flags, jacob_lists From bd1288052af949b41f16bcc0c78aa1bc952cd88a Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Fri, 15 May 2026 14:50:40 +0200 Subject: [PATCH 36/80] testing centroid bug and fix with three different setting (bug, fix, v2.0) --- scripts/validation/centroid/centroid_bias.py | 5 + .../validation/centroid/centroid_bias_v2.py | 477 ++++++++++++++++++ .../centroid/configs/centroid_bug.yaml | 1 + .../centroid/configs/centroid_fix.yaml | 1 + .../centroid/configs/centroid_v2.yaml | 5 + .../{envs => envs_config}/centroid_bug.yml | 0 .../{envs => envs_config}/centroid_fix.yml | 0 .../centroid/envs_config/centroid_v2.yml | 18 + scripts/validation/centroid/run_all.sh | 67 +++ scripts/validation/centroid/run_bias_test.sh | 25 +- src/shapepipe/testing/simulate.py | 2 + 11 files changed, 590 insertions(+), 11 deletions(-) create mode 100644 scripts/validation/centroid/centroid_bias_v2.py create mode 100644 scripts/validation/centroid/configs/centroid_v2.yaml rename scripts/validation/centroid/{envs => envs_config}/centroid_bug.yml (100%) rename scripts/validation/centroid/{envs => envs_config}/centroid_fix.yml (100%) create mode 100644 scripts/validation/centroid/envs_config/centroid_v2.yml create mode 100755 scripts/validation/centroid/run_all.sh diff --git a/scripts/validation/centroid/centroid_bias.py b/scripts/validation/centroid/centroid_bias.py index 2ba7fadea..d9c0e0fbd 100644 --- a/scripts/validation/centroid/centroid_bias.py +++ b/scripts/validation/centroid/centroid_bias.py @@ -20,6 +20,11 @@ Expected output (centroid_fix branch, ngmix fix_jac_centroid): m ~ 0 (bias consistent with zero) + +Autors +------ + Axel Guinot + Martin Kilbinger """ import argparse diff --git a/scripts/validation/centroid/centroid_bias_v2.py b/scripts/validation/centroid/centroid_bias_v2.py new file mode 100644 index 000000000..ca8a39904 --- /dev/null +++ b/scripts/validation/centroid/centroid_bias_v2.py @@ -0,0 +1,477 @@ +#!/usr/bin/env python +"""centroid_bias_v2.py + +Metacal multiplicative-bias validation for ShapePipe ngmix_v2.0 interface. + +Uses the updated ngmix v2.4+ API: + - ngmix.fitting.GalsimFitter (replaces GalsimSimple) + - priors require rng argument + - gal centroid via inline galsim HSM (replaces get_guess) + - get_all_metacal without cheatnoise/symmetrize_psf + +Simulation data is imported from shapepipe.testing.simulate (stable, +shared with centroid_bias.py). + +Usage +----- + python centroid_bias_v2.py [--ntrial N] [--seed S] [--noise SIGMA] +""" + +import argparse + +import galsim +import numpy as np +import ngmix +from ngmix import Observation, ObsList +from ngmix.fitting.galsim_results import GalsimFitModel +from ngmix.gexceptions import GMixRangeError +from numpy.random import uniform as urand + +from shapepipe.modules.ngmix_package.ngmix import get_noise +from shapepipe.testing.simulate import make_data +from modopt.math.stats import sigma_mad + + +# --------------------------------------------------------------------------- +# Jacobian-shift fix for new ngmix GalsimFitModel +# +# GalsimFitModel.make_model applies shift = pars[0:2] only. When the ngmix +# Jacobian center is recentered to the galaxy centroid (row0, col0 ≠ canonical), +# get_galsim_wcs() drops the center information, so the k-space +# InterpolatedImage still places the galaxy at its pixel offset from the +# canonical center. The model shift must include this Jacobian center offset +# (in sky units) so that pars[0:2] = 0 at the solution and the centroid prior +# does not bias the shape. This replicates Axel Guinot's _set_jacobian_shift +# fix from the aguinot/ngmix fork. +# --------------------------------------------------------------------------- + +class FixedGalsimFitModel(GalsimFitModel): + """GalsimFitModel with Jacobian-shift fix applied in make_model. + + The parent's _fill_models calls make_model(band_pars) without band/ep, + so we override _fill_models to pass them explicitly. + """ + + def _set_jacobian_shift(self, obs_in): + """Compute sky offset of Jacobian center from canonical image center.""" + + def get_shift(obs): + nrow, ncol = obs.image.shape + canonical = (np.array((ncol, nrow)) - 1.0) / 2.0 + jrow, jcol = obs.jacobian.get_cen() + offset = np.array((jcol, jrow)) - canonical + offset *= obs.jacobian.get_scale() + return offset + + if isinstance(obs_in, Observation): + self._jac_shift = [[get_shift(obs_in)]] + elif isinstance(obs_in, ObsList): + self._jac_shift = [[get_shift(o) for o in obs_in]] + else: # MultiBandObsList + self._jac_shift = [ + [get_shift(o) for o in obs_list] for obs_list in obs_in + ] + + def __init__(self, obs, model, guess, prior=None): + # Must set _jac_shift before super().__init__() because the parent + # calls make_model() (via _check_guess) during initialisation. + self._set_jacobian_shift(obs) + super().__init__(obs, model, guess, prior=prior) + + def make_model(self, pars, band=0, ep=0): + model = self.make_round_model(pars) + shift = pars[0:2] + self._jac_shift[band][ep] + try: + model = model.shear(g1=pars[2], g2=pars[3]) + except ValueError as err: + raise GMixRangeError(str(err)) + model = model.shift(shift) + return model + + def _fill_models(self, pars): + """Override to pass band and ep to make_model.""" + try: + for band, kobs_list in enumerate(self.mb_kobs): + band_pars = self.get_band_pars(pars, band) + for ep, kobs in enumerate(kobs_list): + gal = self.make_model(band_pars, band=band, ep=ep) + meta = kobs.meta + kmodel = meta["kmodel"] + gal._drawKImage(kmodel) + if kobs.has_psf(): + kmodel *= kobs.psf.kimage + except RuntimeError as err: + raise GMixRangeError(str(err)) + + +class FixedGalsimFitter(ngmix.fitting.GalsimFitter): + """GalsimFitter that uses FixedGalsimFitModel (Jacobian-shift fix).""" + + def _make_fit_model(self, obs, guess): + return FixedGalsimFitModel( + obs=obs, model=self.model, guess=guess, prior=self.prior, + ) + + +# --------------------------------------------------------------------------- +# Priors (ngmix v2.4+ API: all priors require rng) +# --------------------------------------------------------------------------- + +def get_prior(pixel_scale, rng=None): + """Build ngmix joint prior for 6-parameter galaxy model.""" + if rng is None: + rng = np.random.default_rng() + g_prior = ngmix.priors.GPriorBA(0.4, rng=rng) + cen_prior = ngmix.priors.CenPrior(0.0, 0.0, pixel_scale, pixel_scale, rng=rng) + T_prior = ngmix.priors.FlatPrior(-10.0, 1.0e6, rng=rng) + F_prior = ngmix.priors.FlatPrior(-1.0e4, 1.0e9, rng=rng) + return ngmix.joint_prior.PriorSimpleSep(cen_prior, g_prior, T_prior, F_prior) + + +# --------------------------------------------------------------------------- +# Fitting (ngmix v2.4+ API: GalsimFitter, obs passed to go()) +# --------------------------------------------------------------------------- + +def make_galsimfit(obs, model, guess0, prior=None, ntry=5): + """Fit an observation with GalsimFitter, retrying with perturbed guesses. + + Parameters + ---------- + obs : ngmix.Observation or ObsList + model : str + guess0 : numpy.ndarray shape (6,) + prior : ngmix prior, optional + ntry : int, optional + + Returns + ------- + dict-like + Fit result with flags, g, T, T_err, pars, pars_err, etc. + + Raises + ------ + ngmix.gexceptions.BootGalFailure + """ + limit = 0.1 + guess = np.copy(guess0) + fres = {"flags": 1} + + for it in range(ntry): + guess[0:5] += urand(low=-limit, high=limit) + guess[5:] *= 1 + urand(low=-limit, high=limit) + try: + fitter = FixedGalsimFitter(model, prior=prior) + fres = fitter.go(obs, guess) + if fres["flags"] == 0 and "T" not in fres: + fres["T"] = fres["pars"][4] + fres["T_err"] = np.sqrt(fres["pars_cov"][4, 4]) + except Exception: + continue + if fres["flags"] == 0: + break + + if fres["flags"] != 0: + raise ngmix.gexceptions.BootGalFailure( + "Failed to fit galaxy image with GalsimFitter" + ) + fres["ntry"] = it + 1 + return fres + + +# --------------------------------------------------------------------------- +# Metacal pipeline (ngmix v2.4+ API) +# --------------------------------------------------------------------------- + +def do_ngmix_metacal( + gals, psfs, psfs_sigma, weights, flags, jacob_list, + prior, pixel_scale, sig_noise=None, +): + """Run metacalibration on a multi-epoch object. + + Parameters + ---------- + gals, psfs, psfs_sigma, weights, flags : lists + Per-epoch image arrays and metadata. + jacob_list : list of galsim Jacobians + prior : ngmix joint prior + pixel_scale : float [arcsec] + sig_noise : float or None + + Returns + ------- + dict + Metacal result dict keyed by shear type ('noshear', '1p', '1m'). + """ + n_epoch = len(gals) + if n_epoch == 0: + raise ValueError("0 epochs to process") + + gal_obs_list = ObsList() + T_guess_psf = [] + psf_res_gT = { + "g_PSFo": np.array([0.0, 0.0]), + "g_err_PSFo": np.array([0.0, 0.0]), + "T_PSFo": 0.0, + "T_err_PSFo": 0.0, + } + gal_guess = [] + gal_guess_flag = True + wsum = 0 + + for n_e in range(n_epoch): + psf_jacob = ngmix.Jacobian( + row=(psfs[0].shape[0] - 1) / 2, + col=(psfs[0].shape[1] - 1) / 2, + wcs=jacob_list[n_e], + ) + psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) + psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale + + weight_map = np.copy(weights[n_e]) + weight_map[flags[n_e] != 0] = 0.0 + weight_map[weight_map != 0] = 1 + + psf_guess = np.array([0.0, 0.0, 0.0, 0.0, psf_T, 1.0]) + try: + psf_res = make_galsimfit(psf_obs, "gauss", psf_guess) + except Exception: + continue + + # Gal centroid + size guess via adaptive moments (inline HSM) + try: + _gim = galsim.Image(gals[n_e], scale=pixel_scale) + _hsm = galsim.hsm.FindAdaptiveMom(_gim, strict=False) + if _hsm.error_message != "": + raise galsim.hsm.GalSimHSMError(_hsm.error_message) + _cen = _hsm.moments_centroid - _gim.center + _size = 2 * (_hsm.moments_sigma * pixel_scale) ** 2 + gal_guess_tmp = np.array([_cen.x, _cen.y, 0.0, 0.0, _size, _hsm.moments_amp]) + except Exception: + gal_guess_flag = False + gal_guess_tmp = np.array([0.0, 0.0, 0.0, 0.0, 1, 100]) + + gal_jacob = ngmix.Jacobian( + row=(gals[n_e].shape[0] - 1) / 2 + gal_guess_tmp[1], + col=(gals[n_e].shape[1] - 1) / 2 + gal_guess_tmp[0], + wcs=jacob_list[n_e], + ) + + if sig_noise is None: + sig_noise = ( + get_noise(gals[n_e], weight_map, gal_guess_tmp, pixel_scale) + if gal_guess_flag + else sigma_mad(gals[n_e]) + ) + + noise_img = np.random.randn(*gals[n_e].shape) * sig_noise + noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise + + gal_masked = np.copy(gals[n_e]) + if (weight_map == 0).any(): + gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] + weight_map *= 1 / sig_noise ** 2 + + w_tmp = np.sum(weight_map) + psf_res_gT["g_PSFo"] += psf_res["g"] * w_tmp + psf_res_gT["g_err_PSFo"] += ( + np.array([psf_res["pars_err"][2], psf_res["pars_err"][3]]) * w_tmp + ) + psf_res_gT["T_PSFo"] += psf_res["T"] * w_tmp + psf_res_gT["T_err_PSFo"] += psf_res["T_err"] * w_tmp + wsum += w_tmp + + gal_obs = Observation( + gal_masked, weight=weight_map, + jacobian=gal_jacob, psf=psf_obs, noise=noise_img, + ) + if gal_guess_flag: + final_gal_guess = np.copy(gal_guess_tmp) + final_gal_guess[:2] = 0 + gal_guess.append(final_gal_guess) + + gal_obs_list.append(gal_obs) + T_guess_psf.append(psf_T) + gal_guess_flag = True + + if wsum == 0: + raise ZeroDivisionError("Sum of weights = 0") + + for key in psf_res_gT: + psf_res_gT[key] /= wsum + + fail_get_guess = len(gal_guess) == 0 + gal_pars = [0.0, 0.0, 0.0, 0.0, 1, 100] if fail_get_guess else np.mean(gal_guess, 0) + + metacal_pars = { + "types": ["noshear", "1p", "1m"], + "step": 0.01, + "psf": "gauss", + "fixnoise": True, + "use_noise_image": True, + } + Tguess = np.mean(T_guess_psf) + + obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) + res = {"mcal_flags": 0} + + for key in sorted(obs_dict_mcal): + fres = make_galsimfit(obs_dict_mcal[key], "gauss", gal_pars, prior=prior) + res["mcal_flags"] |= fres["flags"] + tres = dict(fres) + + wsum_psf = 0 + Tpsf_sum = 0 + gpsf_sum = np.zeros(2) + for obs in obs_dict_mcal[key]: + psf_obs_fit = getattr(obs, "psf_nopix", obs.psf) + try: + psf_res = make_galsimfit( + psf_obs_fit, "gauss", + np.array([0.0, 0.0, 0.0, 0.0, Tguess, 1.0]), + ) + except Exception: + continue + tw = obs.weight.sum() + wsum_psf += tw + gpsf_sum += np.array(psf_res["g"]) * tw + Tpsf_sum += psf_res["T"] * tw + + tres["gpsf"] = gpsf_sum / wsum_psf if wsum_psf > 0 else np.zeros(2) + tres["Tpsf"] = Tpsf_sum / wsum_psf if wsum_psf > 0 else 0.0 + res[key] = tres + + res.update(psf_res_gT) + res["moments_fail"] = fail_get_guess + return res + + +# --------------------------------------------------------------------------- +# Analysis utilities (identical to centroid_bias.py — ngmix-version agnostic) +# --------------------------------------------------------------------------- + +def progress(total, miniters=1): + """Minimal progress printer.""" + sl = str(len(str(total))) + fmt = "%" + sl + "d/%" + sl + "d %3d%%" + last_print_n = 0 + last_len = 0 + for i in range(total): + yield i + num = i + 1 + if i == 0 or num == total or num - last_print_n >= miniters: + meter = fmt % (num, total, 100 * num // total) + print("\r" + meter + " " * max(last_len - len(meter), 0), + end="", flush=True) + last_len = len(meter) + last_print_n = num + print(flush=True) + + +def make_struct(res, shear_type): + """Pack a metacal result into a structured array row.""" + dt = [ + ("flags", "i4"), ("shear_type", "U7"), + ("s2n", "f8"), ("g", "f8", 2), ("T", "f8"), ("Tpsf", "f8"), + ] + data = np.zeros(1, dtype=dt) + data["shear_type"] = shear_type + data["flags"] = res["flags"] + if res["flags"] == 0: + data["s2n"] = res["flux"] / res["flux_err"] + data["g"] = res["g"] + data["T"] = res["T"] + data["Tpsf"] = res["Tpsf"] + else: + data["s2n"] = data["g"] = data["T"] = data["Tpsf"] = np.nan + return data + + +def select(data, shear_type): + """Return indices where flags==0 and shear_type matches.""" + return np.where( + (data["flags"] == 0) & (data["shear_type"] == shear_type) + )[0] + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): + prior = get_prior(pixel_scale, rng=np.random.default_rng(seed)) + shear_types = ["noshear", "1p", "1m"] + + rng = np.random.RandomState(seed) + seeds = rng.randint(0, 2 ** 30, size=ntrial) + + dlist_p, dlist_m = [], [] + + for i in progress(ntrial, miniters=10): + d_ = [] + for shear_true in [(0.02, 0.00), (-0.02, 0.00)]: + gals, psfs, psfs_sigmas, weights, flags, jacob_lists = make_data( + rng=np.random.RandomState(seeds[i]), + noise=sig_noise, + shear=shear_true, + n_epochs=n_epochs, + share_shift=False, + ) + try: + resdict = do_ngmix_metacal( + gals, psfs, psfs_sigmas, weights, flags, jacob_lists, + prior=prior, pixel_scale=pixel_scale, sig_noise=sig_noise, + ) + stt = [make_struct(resdict[st], st) for st in shear_types] + except Exception: + continue + d_.append(np.hstack(stt)) + + if len(d_) != 2: + continue + dlist_p.extend(d_[0]) + dlist_m.extend(d_[1]) + + print() + + data_p = np.hstack(dlist_p) + data_m = np.hstack(dlist_m) + + w_p = select(data_p, "noshear") + R11_p = np.atleast_2d( + (data_p["g"][select(data_p, "1p"), 0] - data_p["g"][select(data_p, "1m"), 0]) / 0.02 + ).T + w_m = select(data_m, "noshear") + R11_m = np.atleast_2d( + (data_m["g"][select(data_m, "1p"), 0] - data_m["g"][select(data_m, "1m"), 0]) / 0.02 + ).T + + shear_ = (data_p["g"][w_p] - data_m["g"][w_m]) / (R11_p + R11_m) + shear = np.mean(shear_, axis=0) + shear_err = np.std(shear_, axis=0) / np.sqrt(len(shear_)) + + m = shear[0] / 0.02 - 1 + merr = shear_err[0] / 0.02 + s2n = data_p["s2n"][w_p].mean() + + print("S/N: %g" % s2n) + print("R11: %g %g" % (np.mean(R11_p), np.mean(R11_m))) + print("m[1e-3, 3sigmas]: %g +/- %g (99.7%% conf)" % (m / 1e-3, merr * 3 / 1e-3)) + print("c[1e-5, 3sigmas]: %g +/- %g (99.7%% conf)" % (shear[1] / 1e-5, shear_err[1] * 3 / 1e-5)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Metacal centroid bias validation (v2 interface)") + parser.add_argument("--ntrial", type=int, default=50) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--noise", type=float, default=1e-10) + parser.add_argument("--n-epochs", type=int, default=3) + parser.add_argument("--pixel-scale", type=float, default=0.1857) + args = parser.parse_args() + + main( + ntrial=args.ntrial, + seed=args.seed, + sig_noise=args.noise, + n_epochs=args.n_epochs, + pixel_scale=args.pixel_scale, + ) diff --git a/scripts/validation/centroid/configs/centroid_bug.yaml b/scripts/validation/centroid/configs/centroid_bug.yaml index e1d1c71c8..a73cd2afe 100644 --- a/scripts/validation/centroid/configs/centroid_bug.yaml +++ b/scripts/validation/centroid/configs/centroid_bug.yaml @@ -3,4 +3,5 @@ description: test_centroid_bug branch + aguinot/ngmix@stable_version (1.3.6) — large m bias name: centroid_bug shapepipe_branch: test_centroid_bug +shapepipe_path: /automnt/n17data/mkilbing/astro/repositories/github/shapepipe_test_centroid_bug test_script: scripts/validation/centroid/centroid_bias.py diff --git a/scripts/validation/centroid/configs/centroid_fix.yaml b/scripts/validation/centroid/configs/centroid_fix.yaml index bcd2f86f2..6cd997799 100644 --- a/scripts/validation/centroid/configs/centroid_fix.yaml +++ b/scripts/validation/centroid/configs/centroid_fix.yaml @@ -3,4 +3,5 @@ description: test_centroid_bug branch + aguinot/ngmix@fix_jac_centroid — preli centroid fix name: centroid_fix shapepipe_branch: test_centroid_bug +shapepipe_path: /automnt/n17data/mkilbing/astro/repositories/github/shapepipe_test_centroid_bug test_script: scripts/validation/centroid/centroid_bias.py diff --git a/scripts/validation/centroid/configs/centroid_v2.yaml b/scripts/validation/centroid/configs/centroid_v2.yaml new file mode 100644 index 000000000..8e72b6e67 --- /dev/null +++ b/scripts/validation/centroid/configs/centroid_v2.yaml @@ -0,0 +1,5 @@ +conda_env: sp_centroid_v2 +description: ngmix_v2.0 branch + ngmix v2.4+ (GalsimFitter, new prior API) +name: centroid_v2 +shapepipe_branch: ngmix_v2.0 +test_script: scripts/validation/centroid/centroid_bias_v2.py diff --git a/scripts/validation/centroid/envs/centroid_bug.yml b/scripts/validation/centroid/envs_config/centroid_bug.yml similarity index 100% rename from scripts/validation/centroid/envs/centroid_bug.yml rename to scripts/validation/centroid/envs_config/centroid_bug.yml diff --git a/scripts/validation/centroid/envs/centroid_fix.yml b/scripts/validation/centroid/envs_config/centroid_fix.yml similarity index 100% rename from scripts/validation/centroid/envs/centroid_fix.yml rename to scripts/validation/centroid/envs_config/centroid_fix.yml diff --git a/scripts/validation/centroid/envs_config/centroid_v2.yml b/scripts/validation/centroid/envs_config/centroid_v2.yml new file mode 100644 index 000000000..6e6555017 --- /dev/null +++ b/scripts/validation/centroid/envs_config/centroid_v2.yml @@ -0,0 +1,18 @@ +name: sp_centroid_v2 +# Minimal environment for centroid_bias_v2.py (ngmix_v2.0 branch). +# Uses ngmix v2.4+ (GalsimFitter, new prior API). +channels: + - conda-forge + - nodefaults +dependencies: + - python=3.10 + - astropy + - galsim + - numba + - numpy + - tqdm + - pip: + - cs_util + - modopt + - sqlitedict + - git+https://github.com/aguinot/ngmix diff --git a/scripts/validation/centroid/run_all.sh b/scripts/validation/centroid/run_all.sh new file mode 100755 index 000000000..e3e002116 --- /dev/null +++ b/scripts/validation/centroid/run_all.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# run_all.sh +# Run all centroid bias validation tests. +# One-time setup steps are skipped if already done. + +path_to_val_cen=$HOME/astro/repositories/github/shapepipe/scripts/validation/centroid +path_to_envs=$path_to_val_cen/envs_config +script=$path_to_val_cen/run_bias_test.sh + +RUNDIR="$(pwd)" +SP_WORKTREE=/automnt/n17data/mkilbing/astro/repositories/github/shapepipe_test_centroid_bug +SP_MAIN=/automnt/n17data/mkilbing/astro/repositories/github/shapepipe + +export SP_ENV_DIR="${RUNDIR}/envs" + +# --------------------------------------------------------------------------- +# One-time setup (each step is skipped if already done) +# --------------------------------------------------------------------------- + +## Worktree for test_centroid_bug branch +if [[ ! -d "${SP_WORKTREE}" ]]; then + echo "Creating worktree for test_centroid_bug..." + git -C "${SP_MAIN}" worktree add "${SP_WORKTREE}" test_centroid_bug +else + echo "Worktree already exists: ${SP_WORKTREE}" +fi + +## Conda envs +for cfg in centroid_bug centroid_fix centroid_v2; do + env_name="sp_${cfg}" + if [[ ! -d "${SP_ENV_DIR}/${env_name}" ]]; then + echo "Creating conda env ${env_name}..." + CONDARC=~/.condarc mamba env create -y \ + -f "${path_to_envs}/${cfg}.yml" \ + --prefix "${SP_ENV_DIR}/${env_name}" + else + echo "Conda env already exists: ${env_name}" + fi +done + +## Install shapepipe into each env (editable, no deps) +declare -A ENV_SP_PATH=( + [sp_centroid_bug]="${SP_WORKTREE}" + [sp_centroid_fix]="${SP_WORKTREE}" + [sp_centroid_v2]="${SP_MAIN}" +) +for env_name in sp_centroid_bug sp_centroid_fix sp_centroid_v2; do + pip_bin="${SP_ENV_DIR}/${env_name}/bin/pip" + sp_path="${ENV_SP_PATH[$env_name]}" + if [[ -f "${pip_bin}" ]]; then + if ! "${pip_bin}" show shapepipe &>/dev/null; then + echo "Installing shapepipe into ${env_name}..." + "${pip_bin}" install -e "${sp_path}" --no-deps -q + else + echo "shapepipe already installed in ${env_name}" + fi + fi +done + +# --------------------------------------------------------------------------- +# Run tests +# --------------------------------------------------------------------------- + +cd "${RUNDIR}" +"${script}" centroid_bug +"${script}" centroid_fix +"${script}" centroid_v2 diff --git a/scripts/validation/centroid/run_bias_test.sh b/scripts/validation/centroid/run_bias_test.sh index 9e44428f0..1ff4da20b 100755 --- a/scripts/validation/centroid/run_bias_test.sh +++ b/scripts/validation/centroid/run_bias_test.sh @@ -11,12 +11,12 @@ # SP_RESULTS_DIR defaults to ~/astro/Runs/shapepipe/CFIS/centroid_bug/results # and can be overridden by setting the environment variable. # -# Conda envs are looked for at ${SCRIPT_DIR}/envs/. +# Conda envs are looked for at ${SCRIPT_DIR}/envs_config/. # They are not committed; create them with: -# mamba env create -f envs/.yml --prefix envs/ +# mamba env create -f envs_config/.yml --prefix envs/ # i.e.: -# mamba env create -f envs/centroid_bug.yml --prefix envs/sp_centroid_bug -# mamba env create -f envs/centroid_fix.yml --prefix envs/sp_centroid_fix +# mamba env create -f envs_config/centroid_bug.yml --prefix envs/sp_centroid_bug +# mamba env create -f envs_config/centroid_fix.yml --prefix envs/sp_centroid_fix # # Usage: # ./run_bias_test.sh centroid_bug @@ -75,22 +75,25 @@ LOG="${RESULTS_DIR}/run_$(date +%Y%m%d_%H%M%S).log" echo "Logging to ${LOG}" echo -# Conda envs live next to this script under envs/ -ENV_PREFIX="${SCRIPT_DIR}/envs/${CONDA_ENV}" +# Conda envs: SP_ENV_DIR overrides the default (next to this script) +ENV_DIR="${SP_ENV_DIR:-${SCRIPT_DIR}/envs}" +ENV_PREFIX="${ENV_DIR}/${CONDA_ENV}" if [[ ! -d "${ENV_PREFIX}" ]]; then echo "ERROR: conda env not found at ${ENV_PREFIX}. Create it with:" - echo " mamba env create -f ${SCRIPT_DIR}/envs/${CONFIG_NAME}.yml --prefix ${ENV_PREFIX}" + echo " CONDARC=~/.condarc mamba env create -f ${SCRIPT_DIR}/envs_config/${CONFIG_NAME}.yml --prefix ${ENV_PREFIX}" + echo " (set SP_ENV_DIR to use a custom env location)" exit 1 fi echo " env prefix : ${ENV_PREFIX}" echo +# Call the env's Python directly to avoid mamba run lockfile issues. # PYTHONNOUSERSITE=1 prevents ~/.local site-packages from overriding the # conda env packages (e.g. a system-wide ngmix install). -mamba run --prefix "${ENV_PREFIX}" \ - env PYTHONNOUSERSITE=1 \ - PYTHONPATH="${SHAPEPIPE_PATH}/src:${PYTHONPATH:-}" \ - python "${SHAPEPIPE_PATH}/${TEST_SCRIPT}" \ +PYTHONNOUSERSITE=1 \ +CONDARC="${HOME}/.condarc" \ +PYTHONPATH="${SHAPEPIPE_PATH}/src:${PYTHONPATH:-}" \ + "${ENV_PREFIX}/bin/python" "${SHAPEPIPE_PATH}/${TEST_SCRIPT}" \ 2>&1 | tee "${LOG}" echo diff --git a/src/shapepipe/testing/simulate.py b/src/shapepipe/testing/simulate.py index 00c971dfa..17a8f4635 100644 --- a/src/shapepipe/testing/simulate.py +++ b/src/shapepipe/testing/simulate.py @@ -2,6 +2,8 @@ Stable across shapepipe branches and ngmix versions — contains only galsim and numpy, no shapepipe processing or ngmix fitting code. + +Author: Axel Guinot """ import numpy as np From 440f822d74a0e13acf7f83789ff2435e708b9972 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sat, 16 May 2026 08:34:16 +0200 Subject: [PATCH 37/80] centroid bug fixed for v2.0 --- .../validation/centroid/centroid_bias_v2.py | 383 ++---------------- .../centroid/envs_config/centroid_v2.yml | 8 +- scripts/validation/centroid/run_all.sh | 57 ++- src/shapepipe/modules/ngmix_package/ngmix.py | 292 ++++++------- 4 files changed, 213 insertions(+), 527 deletions(-) diff --git a/scripts/validation/centroid/centroid_bias_v2.py b/scripts/validation/centroid/centroid_bias_v2.py index ca8a39904..d5ac59dc2 100644 --- a/scripts/validation/centroid/centroid_bias_v2.py +++ b/scripts/validation/centroid/centroid_bias_v2.py @@ -1,16 +1,10 @@ #!/usr/bin/env python """centroid_bias_v2.py -Metacal multiplicative-bias validation for ShapePipe ngmix_v2.0 interface. +Metacal multiplicative-bias validation using the ShapePipe ngmix v2.0 interface. -Uses the updated ngmix v2.4+ API: - - ngmix.fitting.GalsimFitter (replaces GalsimSimple) - - priors require rng argument - - gal centroid via inline galsim HSM (replaces get_guess) - - get_all_metacal without cheatnoise/symmetrize_psf - -Simulation data is imported from shapepipe.testing.simulate (stable, -shared with centroid_bias.py). +Simulation from shapepipe.testing.simulate; fitting via +shapepipe.modules.ngmix_package.ngmix (Postage_stamp, do_ngmix_metacal, get_prior). Usage ----- @@ -19,334 +13,18 @@ import argparse -import galsim import numpy as np -import ngmix -from ngmix import Observation, ObsList -from ngmix.fitting.galsim_results import GalsimFitModel -from ngmix.gexceptions import GMixRangeError -from numpy.random import uniform as urand -from shapepipe.modules.ngmix_package.ngmix import get_noise +from shapepipe.modules.ngmix_package.ngmix import ( + Postage_stamp, + do_ngmix_metacal, + get_prior, +) from shapepipe.testing.simulate import make_data -from modopt.math.stats import sigma_mad - - -# --------------------------------------------------------------------------- -# Jacobian-shift fix for new ngmix GalsimFitModel -# -# GalsimFitModel.make_model applies shift = pars[0:2] only. When the ngmix -# Jacobian center is recentered to the galaxy centroid (row0, col0 ≠ canonical), -# get_galsim_wcs() drops the center information, so the k-space -# InterpolatedImage still places the galaxy at its pixel offset from the -# canonical center. The model shift must include this Jacobian center offset -# (in sky units) so that pars[0:2] = 0 at the solution and the centroid prior -# does not bias the shape. This replicates Axel Guinot's _set_jacobian_shift -# fix from the aguinot/ngmix fork. -# --------------------------------------------------------------------------- - -class FixedGalsimFitModel(GalsimFitModel): - """GalsimFitModel with Jacobian-shift fix applied in make_model. - - The parent's _fill_models calls make_model(band_pars) without band/ep, - so we override _fill_models to pass them explicitly. - """ - - def _set_jacobian_shift(self, obs_in): - """Compute sky offset of Jacobian center from canonical image center.""" - - def get_shift(obs): - nrow, ncol = obs.image.shape - canonical = (np.array((ncol, nrow)) - 1.0) / 2.0 - jrow, jcol = obs.jacobian.get_cen() - offset = np.array((jcol, jrow)) - canonical - offset *= obs.jacobian.get_scale() - return offset - - if isinstance(obs_in, Observation): - self._jac_shift = [[get_shift(obs_in)]] - elif isinstance(obs_in, ObsList): - self._jac_shift = [[get_shift(o) for o in obs_in]] - else: # MultiBandObsList - self._jac_shift = [ - [get_shift(o) for o in obs_list] for obs_list in obs_in - ] - - def __init__(self, obs, model, guess, prior=None): - # Must set _jac_shift before super().__init__() because the parent - # calls make_model() (via _check_guess) during initialisation. - self._set_jacobian_shift(obs) - super().__init__(obs, model, guess, prior=prior) - - def make_model(self, pars, band=0, ep=0): - model = self.make_round_model(pars) - shift = pars[0:2] + self._jac_shift[band][ep] - try: - model = model.shear(g1=pars[2], g2=pars[3]) - except ValueError as err: - raise GMixRangeError(str(err)) - model = model.shift(shift) - return model - - def _fill_models(self, pars): - """Override to pass band and ep to make_model.""" - try: - for band, kobs_list in enumerate(self.mb_kobs): - band_pars = self.get_band_pars(pars, band) - for ep, kobs in enumerate(kobs_list): - gal = self.make_model(band_pars, band=band, ep=ep) - meta = kobs.meta - kmodel = meta["kmodel"] - gal._drawKImage(kmodel) - if kobs.has_psf(): - kmodel *= kobs.psf.kimage - except RuntimeError as err: - raise GMixRangeError(str(err)) - - -class FixedGalsimFitter(ngmix.fitting.GalsimFitter): - """GalsimFitter that uses FixedGalsimFitModel (Jacobian-shift fix).""" - - def _make_fit_model(self, obs, guess): - return FixedGalsimFitModel( - obs=obs, model=self.model, guess=guess, prior=self.prior, - ) - - -# --------------------------------------------------------------------------- -# Priors (ngmix v2.4+ API: all priors require rng) -# --------------------------------------------------------------------------- - -def get_prior(pixel_scale, rng=None): - """Build ngmix joint prior for 6-parameter galaxy model.""" - if rng is None: - rng = np.random.default_rng() - g_prior = ngmix.priors.GPriorBA(0.4, rng=rng) - cen_prior = ngmix.priors.CenPrior(0.0, 0.0, pixel_scale, pixel_scale, rng=rng) - T_prior = ngmix.priors.FlatPrior(-10.0, 1.0e6, rng=rng) - F_prior = ngmix.priors.FlatPrior(-1.0e4, 1.0e9, rng=rng) - return ngmix.joint_prior.PriorSimpleSep(cen_prior, g_prior, T_prior, F_prior) - - -# --------------------------------------------------------------------------- -# Fitting (ngmix v2.4+ API: GalsimFitter, obs passed to go()) -# --------------------------------------------------------------------------- - -def make_galsimfit(obs, model, guess0, prior=None, ntry=5): - """Fit an observation with GalsimFitter, retrying with perturbed guesses. - - Parameters - ---------- - obs : ngmix.Observation or ObsList - model : str - guess0 : numpy.ndarray shape (6,) - prior : ngmix prior, optional - ntry : int, optional - - Returns - ------- - dict-like - Fit result with flags, g, T, T_err, pars, pars_err, etc. - - Raises - ------ - ngmix.gexceptions.BootGalFailure - """ - limit = 0.1 - guess = np.copy(guess0) - fres = {"flags": 1} - - for it in range(ntry): - guess[0:5] += urand(low=-limit, high=limit) - guess[5:] *= 1 + urand(low=-limit, high=limit) - try: - fitter = FixedGalsimFitter(model, prior=prior) - fres = fitter.go(obs, guess) - if fres["flags"] == 0 and "T" not in fres: - fres["T"] = fres["pars"][4] - fres["T_err"] = np.sqrt(fres["pars_cov"][4, 4]) - except Exception: - continue - if fres["flags"] == 0: - break - - if fres["flags"] != 0: - raise ngmix.gexceptions.BootGalFailure( - "Failed to fit galaxy image with GalsimFitter" - ) - fres["ntry"] = it + 1 - return fres # --------------------------------------------------------------------------- -# Metacal pipeline (ngmix v2.4+ API) -# --------------------------------------------------------------------------- - -def do_ngmix_metacal( - gals, psfs, psfs_sigma, weights, flags, jacob_list, - prior, pixel_scale, sig_noise=None, -): - """Run metacalibration on a multi-epoch object. - - Parameters - ---------- - gals, psfs, psfs_sigma, weights, flags : lists - Per-epoch image arrays and metadata. - jacob_list : list of galsim Jacobians - prior : ngmix joint prior - pixel_scale : float [arcsec] - sig_noise : float or None - - Returns - ------- - dict - Metacal result dict keyed by shear type ('noshear', '1p', '1m'). - """ - n_epoch = len(gals) - if n_epoch == 0: - raise ValueError("0 epochs to process") - - gal_obs_list = ObsList() - T_guess_psf = [] - psf_res_gT = { - "g_PSFo": np.array([0.0, 0.0]), - "g_err_PSFo": np.array([0.0, 0.0]), - "T_PSFo": 0.0, - "T_err_PSFo": 0.0, - } - gal_guess = [] - gal_guess_flag = True - wsum = 0 - - for n_e in range(n_epoch): - psf_jacob = ngmix.Jacobian( - row=(psfs[0].shape[0] - 1) / 2, - col=(psfs[0].shape[1] - 1) / 2, - wcs=jacob_list[n_e], - ) - psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) - psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale - - weight_map = np.copy(weights[n_e]) - weight_map[flags[n_e] != 0] = 0.0 - weight_map[weight_map != 0] = 1 - - psf_guess = np.array([0.0, 0.0, 0.0, 0.0, psf_T, 1.0]) - try: - psf_res = make_galsimfit(psf_obs, "gauss", psf_guess) - except Exception: - continue - - # Gal centroid + size guess via adaptive moments (inline HSM) - try: - _gim = galsim.Image(gals[n_e], scale=pixel_scale) - _hsm = galsim.hsm.FindAdaptiveMom(_gim, strict=False) - if _hsm.error_message != "": - raise galsim.hsm.GalSimHSMError(_hsm.error_message) - _cen = _hsm.moments_centroid - _gim.center - _size = 2 * (_hsm.moments_sigma * pixel_scale) ** 2 - gal_guess_tmp = np.array([_cen.x, _cen.y, 0.0, 0.0, _size, _hsm.moments_amp]) - except Exception: - gal_guess_flag = False - gal_guess_tmp = np.array([0.0, 0.0, 0.0, 0.0, 1, 100]) - - gal_jacob = ngmix.Jacobian( - row=(gals[n_e].shape[0] - 1) / 2 + gal_guess_tmp[1], - col=(gals[n_e].shape[1] - 1) / 2 + gal_guess_tmp[0], - wcs=jacob_list[n_e], - ) - - if sig_noise is None: - sig_noise = ( - get_noise(gals[n_e], weight_map, gal_guess_tmp, pixel_scale) - if gal_guess_flag - else sigma_mad(gals[n_e]) - ) - - noise_img = np.random.randn(*gals[n_e].shape) * sig_noise - noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise - - gal_masked = np.copy(gals[n_e]) - if (weight_map == 0).any(): - gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] - weight_map *= 1 / sig_noise ** 2 - - w_tmp = np.sum(weight_map) - psf_res_gT["g_PSFo"] += psf_res["g"] * w_tmp - psf_res_gT["g_err_PSFo"] += ( - np.array([psf_res["pars_err"][2], psf_res["pars_err"][3]]) * w_tmp - ) - psf_res_gT["T_PSFo"] += psf_res["T"] * w_tmp - psf_res_gT["T_err_PSFo"] += psf_res["T_err"] * w_tmp - wsum += w_tmp - - gal_obs = Observation( - gal_masked, weight=weight_map, - jacobian=gal_jacob, psf=psf_obs, noise=noise_img, - ) - if gal_guess_flag: - final_gal_guess = np.copy(gal_guess_tmp) - final_gal_guess[:2] = 0 - gal_guess.append(final_gal_guess) - - gal_obs_list.append(gal_obs) - T_guess_psf.append(psf_T) - gal_guess_flag = True - - if wsum == 0: - raise ZeroDivisionError("Sum of weights = 0") - - for key in psf_res_gT: - psf_res_gT[key] /= wsum - - fail_get_guess = len(gal_guess) == 0 - gal_pars = [0.0, 0.0, 0.0, 0.0, 1, 100] if fail_get_guess else np.mean(gal_guess, 0) - - metacal_pars = { - "types": ["noshear", "1p", "1m"], - "step": 0.01, - "psf": "gauss", - "fixnoise": True, - "use_noise_image": True, - } - Tguess = np.mean(T_guess_psf) - - obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) - res = {"mcal_flags": 0} - - for key in sorted(obs_dict_mcal): - fres = make_galsimfit(obs_dict_mcal[key], "gauss", gal_pars, prior=prior) - res["mcal_flags"] |= fres["flags"] - tres = dict(fres) - - wsum_psf = 0 - Tpsf_sum = 0 - gpsf_sum = np.zeros(2) - for obs in obs_dict_mcal[key]: - psf_obs_fit = getattr(obs, "psf_nopix", obs.psf) - try: - psf_res = make_galsimfit( - psf_obs_fit, "gauss", - np.array([0.0, 0.0, 0.0, 0.0, Tguess, 1.0]), - ) - except Exception: - continue - tw = obs.weight.sum() - wsum_psf += tw - gpsf_sum += np.array(psf_res["g"]) * tw - Tpsf_sum += psf_res["T"] * tw - - tres["gpsf"] = gpsf_sum / wsum_psf if wsum_psf > 0 else np.zeros(2) - tres["Tpsf"] = Tpsf_sum / wsum_psf if wsum_psf > 0 else 0.0 - res[key] = tres - - res.update(psf_res_gT) - res["moments_fail"] = fail_get_guess - return res - - -# --------------------------------------------------------------------------- -# Analysis utilities (identical to centroid_bias.py — ngmix-version agnostic) +# Analysis utilities # --------------------------------------------------------------------------- def progress(total, miniters=1): @@ -371,18 +49,17 @@ def make_struct(res, shear_type): """Pack a metacal result into a structured array row.""" dt = [ ("flags", "i4"), ("shear_type", "U7"), - ("s2n", "f8"), ("g", "f8", 2), ("T", "f8"), ("Tpsf", "f8"), + ("s2n", "f8"), ("g", "f8", 2), ("T", "f8"), ] data = np.zeros(1, dtype=dt) data["shear_type"] = shear_type data["flags"] = res["flags"] if res["flags"] == 0: - data["s2n"] = res["flux"] / res["flux_err"] + data["s2n"] = res.get("s2n", np.nan) data["g"] = res["g"] - data["T"] = res["T"] - data["Tpsf"] = res["Tpsf"] + data["T"] = res.get("T", np.nan) else: - data["s2n"] = data["g"] = data["T"] = data["Tpsf"] = np.nan + data["s2n"] = data["g"] = data["T"] = np.nan return data @@ -398,17 +75,20 @@ def select(data, shear_type): # --------------------------------------------------------------------------- def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): - prior = get_prior(pixel_scale, rng=np.random.default_rng(seed)) - shear_types = ["noshear", "1p", "1m"] + + dg = 0.02 rng = np.random.RandomState(seed) + prior = get_prior(pixel_scale, rng) + shear_types = ["noshear", "1p", "1m"] + seeds = rng.randint(0, 2 ** 30, size=ntrial) dlist_p, dlist_m = [], [] for i in progress(ntrial, miniters=10): d_ = [] - for shear_true in [(0.02, 0.00), (-0.02, 0.00)]: + for shear_true in [(dg, 0.00), (-dg, 0.00)]: gals, psfs, psfs_sigmas, weights, flags, jacob_lists = make_data( rng=np.random.RandomState(seeds[i]), noise=sig_noise, @@ -416,11 +96,16 @@ def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): n_epochs=n_epochs, share_shift=False, ) + + stamp = Postage_stamp(bkg_sub=False, megacam_flip=False) + stamp.gals = gals + stamp.psfs = psfs + stamp.weights = weights + stamp.flags = flags + stamp.jacobs = jacob_lists + try: - resdict = do_ngmix_metacal( - gals, psfs, psfs_sigmas, weights, flags, jacob_lists, - prior=prior, pixel_scale=pixel_scale, sig_noise=sig_noise, - ) + resdict, psf_res = do_ngmix_metacal(stamp, prior, 1.0, rng) stt = [make_struct(resdict[st], st) for st in shear_types] except Exception: continue @@ -438,19 +123,19 @@ def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): w_p = select(data_p, "noshear") R11_p = np.atleast_2d( - (data_p["g"][select(data_p, "1p"), 0] - data_p["g"][select(data_p, "1m"), 0]) / 0.02 + (data_p["g"][select(data_p, "1p"), 0] - data_p["g"][select(data_p, "1m"), 0]) / dg ).T w_m = select(data_m, "noshear") R11_m = np.atleast_2d( - (data_m["g"][select(data_m, "1p"), 0] - data_m["g"][select(data_m, "1m"), 0]) / 0.02 + (data_m["g"][select(data_m, "1p"), 0] - data_m["g"][select(data_m, "1m"), 0]) / dg ).T shear_ = (data_p["g"][w_p] - data_m["g"][w_m]) / (R11_p + R11_m) shear = np.mean(shear_, axis=0) shear_err = np.std(shear_, axis=0) / np.sqrt(len(shear_)) - m = shear[0] / 0.02 - 1 - merr = shear_err[0] / 0.02 + m = shear[0] / dg - 1 + merr = shear_err[0] / dg s2n = data_p["s2n"][w_p].mean() print("S/N: %g" % s2n) @@ -460,7 +145,9 @@ def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Metacal centroid bias validation (v2 interface)") + parser = argparse.ArgumentParser( + description="Metacal centroid bias validation (v2 interface)" + ) parser.add_argument("--ntrial", type=int, default=50) parser.add_argument("--seed", type=int, default=42) parser.add_argument("--noise", type=float, default=1e-10) diff --git a/scripts/validation/centroid/envs_config/centroid_v2.yml b/scripts/validation/centroid/envs_config/centroid_v2.yml index 6e6555017..6afb27939 100644 --- a/scripts/validation/centroid/envs_config/centroid_v2.yml +++ b/scripts/validation/centroid/envs_config/centroid_v2.yml @@ -5,14 +5,10 @@ channels: - conda-forge - nodefaults dependencies: - - python=3.10 + - python=3.12 - astropy - galsim + - ngmix>=2.4 - numba - numpy - tqdm - - pip: - - cs_util - - modopt - - sqlitedict - - git+https://github.com/aguinot/ngmix diff --git a/scripts/validation/centroid/run_all.sh b/scripts/validation/centroid/run_all.sh index e3e002116..d85dc8fda 100755 --- a/scripts/validation/centroid/run_all.sh +++ b/scripts/validation/centroid/run_all.sh @@ -1,7 +1,24 @@ #!/bin/bash # run_all.sh -# Run all centroid bias validation tests. +# Run centroid bias validation tests. # One-time setup steps are skipped if already done. +# +# Usage: run_all.sh [case] +# case One of: centroid_bug, centroid_fix, centroid_v2 (default: all three) + +ALL_CASES=(centroid_bug centroid_fix centroid_v2) + +if [[ $# -gt 0 ]]; then + case "$1" in + centroid_bug|centroid_fix|centroid_v2) + RUN_CASES=("$1") ;; + *) + echo "Unknown case '$1'. Valid options: ${ALL_CASES[*]}" >&2 + exit 1 ;; + esac +else + RUN_CASES=("${ALL_CASES[@]}") +fi path_to_val_cen=$HOME/astro/repositories/github/shapepipe/scripts/validation/centroid path_to_envs=$path_to_val_cen/envs_config @@ -26,7 +43,7 @@ else fi ## Conda envs -for cfg in centroid_bug centroid_fix centroid_v2; do +for cfg in "${ALL_CASES[@]}"; do env_name="sp_${cfg}" if [[ ! -d "${SP_ENV_DIR}/${env_name}" ]]; then echo "Creating conda env ${env_name}..." @@ -44,7 +61,7 @@ declare -A ENV_SP_PATH=( [sp_centroid_fix]="${SP_WORKTREE}" [sp_centroid_v2]="${SP_MAIN}" ) -for env_name in sp_centroid_bug sp_centroid_fix sp_centroid_v2; do +for env_name in sp_centroid_bug sp_centroid_fix; do pip_bin="${SP_ENV_DIR}/${env_name}/bin/pip" sp_path="${ENV_SP_PATH[$env_name]}" if [[ -f "${pip_bin}" ]]; then @@ -57,11 +74,39 @@ for env_name in sp_centroid_bug sp_centroid_fix sp_centroid_v2; do fi done +## sp_centroid_v2: shapepipe + pip-only deps +## pip install is unreliable here (user ~/.local shadows env, requires-python mismatch), +## so we use direct --target installs and a .pth file for shapepipe. +python_v2="${SP_ENV_DIR}/sp_centroid_v2/bin/python" +pip_bin="${SP_ENV_DIR}/sp_centroid_v2/bin/pip" +if [[ -f "${python_v2}" ]]; then + site_pkg=$(PYTHONNOUSERSITE=1 "${python_v2}" -c "import site; print(site.getsitepackages()[0])") + + # shapepipe: .pth file into env site-packages (bypasses requires-python check) + pth_file="${site_pkg}/shapepipe.pth" + if [[ ! -f "${pth_file}" ]]; then + echo "Adding shapepipe src to sp_centroid_v2 via .pth file..." + echo "${SP_MAIN}/src" > "${pth_file}" + else + echo "shapepipe.pth already present in sp_centroid_v2" + fi + + # pip-only deps: use --target to install into env site-packages regardless + # of what is visible in ~/.local (pip --prefix sees user packages as satisfied) + for pkg in cs_util modopt sqlitedict; do + import_name="${pkg//-/_}" + if ! PYTHONNOUSERSITE=1 "${python_v2}" -c "import ${import_name}" 2>/dev/null; then + echo "Installing ${pkg} into sp_centroid_v2..." + "${pip_bin}" install "${pkg}" -q --target "${site_pkg}" + fi + done +fi + # --------------------------------------------------------------------------- # Run tests # --------------------------------------------------------------------------- cd "${RUNDIR}" -"${script}" centroid_bug -"${script}" centroid_fix -"${script}" centroid_v2 +for case in "${RUN_CASES[@]}"; do + "${script}" "${case}" +done diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 16aa55357..05c4c61a3 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -19,6 +19,47 @@ from shapepipe.pipeline import file_io + +def get_prior(pixel_scale, rng, T_range=None, F_range=None): + """Build ngmix joint prior for a 6-parameter galaxy model. + + Parameters + ---------- + pixel_scale : float + Pixel scale in arcsec (sets centroid prior width). + rng : numpy.random.RandomState + Random state for all priors. + T_range : tuple of float, optional + (min, max) for flat size prior; default (-1, 1e3). + F_range : tuple of float, optional + (min, max) for flat flux prior; default (-100, 1e9). + + Returns + ------- + ngmix.joint_prior.PriorSimpleSep + """ + if T_range is None: + T_range = [-1.0, 1.0e3] + if F_range is None: + F_range = [-100.0, 1.0e9] + + cen_prior = ngmix.priors.CenPrior( + cen1=0.0, cen2=0.0, + sigma1=pixel_scale, sigma2=pixel_scale, + rng=rng, + ) + g_prior = ngmix.priors.GPriorBA(sigma=0.4, rng=rng) + T_prior = ngmix.priors.FlatPrior(minval=T_range[0], maxval=T_range[1], rng=rng) + F_prior = ngmix.priors.FlatPrior(minval=F_range[0], maxval=F_range[1], rng=rng) + + return ngmix.joint_prior.PriorSimpleSep( + cen_prior=cen_prior, + g_prior=g_prior, + T_prior=T_prior, + F_prior=F_prior, + ) + + # I still don't know how to handle this class Tile_cat(): """Tile_cat. @@ -258,66 +299,15 @@ def MegaCamFlip(self, vign, ccd_nb): def get_prior(self, T_range=None, F_range=None): """Get Prior. - get a prior for use with the maximum likelihood fitter - - Parameters - ---------- - T_range: (float, float), optional - The range for the prior on T - F_range: (float, float), optional - Fhe range for the prior on flux - Returns ------- - ngmix.priors - Priors for the different parameters (ellipticity,center, size, flux) + ngmix.joint_prior.PriorSimpleSep """ - # 2-d Gaussian prior on the object center - # centered with respect to jacobian center - # Units same as jacobian, probably arcsec - cen_prior = ngmix.priors.CenPrior( - cen1=0.0, - cen2=0.0, - sigma1=self._pixel_scale, - sigma2=self._pixel_scale, - rng=self._rng - ) - - # Prior on ellipticity. Details do not matter, as long - # as it regularizes the fit. From Bernstein & Armstrong 2014 - g_sigma = 0.4 - g_prior = ngmix.priors.GPriorBA(sigma=g_sigma,rng=self._rng) - - if T_range is None: - T_range = [-1.0, 1.e3] - if F_range is None: - F_range = [-100.0, 1.e9] - - # Flat Size prior in arcsec squared. Instead of flat, TwoSidedErf could be used - T_prior = ngmix.priors.FlatPrior( - minval=T_range[0], - maxval=T_range[1], - rng=self._rng + return get_prior( + self._pixel_scale, self._rng, + T_range=T_range, F_range=F_range, ) - # Flat Flux prior. Bounds need to make sense for - # images in question - F_prior = ngmix.priors.FlatPrior( - minval=F_range[0], - maxval=F_range[1], - rng=self._rng - ) - - # Joint prior, combine all individual priors - prior = ngmix.joint_prior.PriorSimpleSep( - cen_prior=cen_prior, - g_prior=g_prior, - T_prior=T_prior, - F_prior=F_prior, - ) - - return prior - def compile_results(self, results): """Compile Results. @@ -618,10 +608,11 @@ def process(self): vignet_cat = self._vignet_cat final_res = [] - psf_res = None prior = self.get_prior() count = 0 + count_batch = 0 + saved_batch_cumul = 0 id_first = -1 id_last = -1 @@ -657,8 +648,7 @@ def process(self): stamp, prior, flux_guess, - self._pixel_scale, - self._rng + self._rng, ) except Exception as ee: @@ -668,8 +658,9 @@ def process(self): continue # these things need to be considered res['obj_id'] = obj_id - res['n_epoch_model'] = len(stamp.gal_vign_list) + res['n_epoch_model'] = len(stamp.gals) final_res.append(res) + count_batch += 1 if count_batch == self._save_batch: @@ -695,7 +686,7 @@ def process(self): + f"objects, id first/last={id_first}/{id_last}" ) - vignet_cat.close + vignet_cat.close() # Put all results together res_dict = self.compile_results(final_res) @@ -773,15 +764,11 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): header ) - # gather postage stamps in all of the epochs - stamp.gal_vign_list.append(gal_vign_scaled) - stamp.psf_vign_list.append( - vignet.psf_vign_cat[str(obj_id)][expccd_name]['VIGNET'] - ) - - stamp.weight_vign_list.append(weight_vign_scaled) - stamp.flag_vign_list.append(flag_vign) - stamp.jacob_list.append(jacob) + stamp.gals.append(gal_vign_scaled) + stamp.psfs.append(vignet.psf_vign_cat[str(obj_id)][expccd_name]['VIGNET']) + stamp.weights.append(weight_vign_scaled) + stamp.flags.append(flag_vign) + stamp.jacobs.append(jacob) return stamp @@ -907,160 +894,133 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): return sig_noise -def prepare_ngmix_weights(gal,weight,flag,tile_cat): - """bookkeeping for ngmix weights. runs on a single galaxy and epoch - pixel scale and galaxy guess - TO DO: decide if we want galaxy guess stuff +def prepare_ngmix_weights(gal, weight, flag): + """Bookkeeping for ngmix weights on a single galaxy and epoch. Parameters ---------- gal : numpy.ndarray - galaxy image. List indices run over epochs weight : numpy.ndarray - weight image List indices run over epochs flag : numpy.ndarray - flag image. List indices run over epochs Returns ------- numpy.ndarray - galaxy image where noise replaces masked regions + Galaxy image with masked pixels replaced by noise. numpy.ndarray - variance map for NGMIX + Variance map for NGMIX. numpy.ndarray - noise image - - """ - # integrate flag info into weights + Noise image. + """ weight_map = np.copy(weight) - weight_map[np.where(flag != 0)] = 0. - # This code combines integrates flag information into the weights. - - #if gal_guess_flag: - # sig_noise = get_noise( - # gal, - # weight, - # gal_guess_tmp, - # pixel_scale, - # ) - #else: + weight_map[flag != 0] = 0.0 + sig_noise = sigma_mad(gal) noise_img = np.random.randn(*gal.shape) * sig_noise noise_img_gal = np.random.randn(*gal.shape) * sig_noise - - # fill in galaxy image masked regions with noise + gal_masked = np.copy(gal) - if (len(np.where(weight_map == 0)[0]) != 0): + if (weight_map == 0).any(): gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] - # convert weight map to variance map weight_map *= 1 / sig_noise ** 2 - + return gal_masked, weight_map, noise_img -def make_ngmix_observation(gal,weight,flag,psf,wcs): - """single galaxy and epoch to be passed to ngmix - TO DO: pixel scale +def make_ngmix_observation(gal, weight, flag, psf, wcs): + """Build an ngmix Observation for a single galaxy epoch. + + The galaxy Jacobian is re-centered on the HSM centroid so that the + centroid prior (centered at the Jacobian origin) does not bias the fit. + Parameters ---------- gal : numpy.ndarray - List of the galaxy vignets. List indices run over epochs weight : numpy.ndarray - List of the PSF vignets flag : numpy.ndarray - flag image psf : numpy.ndarray - psf vignett - wcs : numpy.ndarray - Jacobian + wcs : galsim.BaseWCS + Local WCS Jacobian at the object position. + Returns ------- ngmix.observation.Observation - observation to fit using ngmix - """ - # prepare psf - # WHY RECENTER psf_jacob = ngmix.Jacobian( row=(psf.shape[0] - 1) / 2, col=(psf.shape[1] - 1) / 2, - wcs=wcs + wcs=wcs, ) - psf_obs = Observation(psf, jacobian=psf_jacob) - # prepare weight map - gal_masked, weight_map, noise_img = prepare_ngmix_weights( - gal, - weight, - flag - ) - # WHY RECENTER??? - # Recenter jacobian if necessary + gal_masked, weight_map, noise_img = prepare_ngmix_weights(gal, weight, flag) + + # Re-center Jacobian on HSM centroid (pixel offset from stamp center). + # Fixes: centroid prior biases fit when galaxy is offset from stamp center. + try: + _hsm = galsim.hsm.FindAdaptiveMom( + galsim.Image(gal, scale=1.0), strict=False + ) + if _hsm.error_message != "": + raise galsim.hsm.GalSimHSMError(_hsm.error_message) + _cen = _hsm.moments_centroid - galsim.Image(gal, scale=1.0).center + cen_row, cen_col = _cen.y, _cen.x + except Exception: + cen_row, cen_col = 0.0, 0.0 + gal_jacob = ngmix.Jacobian( - row=(gal.shape[0] - 1) / 2, - col=(gal.shape[1] - 1) / 2, - wcs=wcs + row=(gal.shape[0] - 1) / 2 + cen_row, + col=(gal.shape[1] - 1) / 2 + cen_col, + wcs=wcs, ) - # define ngmix observation - gal_obs = Observation( + + return Observation( gal_masked, weight=weight_map, jacobian=gal_jacob, psf=psf_obs, - noise=noise_img + noise=noise_img, ) - - return gal_obs -def average_multiepoch_psf(obsdict,nepoch): - """ averages psf information over multiple epochs - we may need to do this for original psf as well +def average_multiepoch_psf(obsdict): + """Average PSF shape and size over epochs from the noshear metacal branch. + Parameters ---------- obsdict : dict - dictionary of metacal observations after fit + Observation dict returned by MetacalBootstrapper.go(). Returns ------- dict - Average psf size, shape over n_epochs - + Keys: 'g_psf', 'g_psf_err', 'T_psf', 'T_psf_err' (weighted averages). """ - # create dictionary - names = ['T_psf', 'T_psf_err', 'g_psf', 'g_psf_err'] - psf_dict = {k: [] for k in names} - # include relevant psf quantities- check how they are presented for multi-epoch observations wsum = 0 g_psf_sum = np.array([0., 0.]) g_psf_err_sum = np.array([0., 0.]) - T_psf_sum = 0 - T_psf_err_sum = 0 - for n_e in np.arange(nepoch): - T_psf=obsdict['noshear'][n_e].psf.meta['result']['T'] - T_psf_err=obsdict['noshear'][n_e].psf.meta['result']['T_err'] - g_psf=obsdict['noshear'][n_e].psf.meta['result']['g'] - g_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] - ne_wsum = obsdict['noshear'][0].weight.sum() - - # we probably want to handle cases when there is no psf - # how are we dealing with the error, what is npsf + T_psf_sum = 0.0 + T_psf_err_sum = 0.0 + + for obs in obsdict['noshear']: + result = obs.psf.meta['result'] + ne_wsum = obs.weight.sum() wsum += ne_wsum - g_psf_sum += g_psf * ne_wsum - g_psf_err_sum += g_psf_err * ne_wsum - T_psf_sum += T_psf * ne_wsum - T_psf_err_sum += T_psf_err * ne_wsum + g_psf_sum += np.array(result['g']) * ne_wsum + # ngmix Fitter stores g_cov, not g_err + g_psf_err_sum += np.sqrt(np.diag(result['g_cov'])) * ne_wsum + T_psf_sum += result['T'] * ne_wsum + T_psf_err_sum += result['T_err'] * ne_wsum if wsum == 0: - raise ZeroDivisionError('Sum of weights = 0, division by zero') + raise ZeroDivisionError('Sum of weights = 0') - psf_dict['g_psf'] = g_psf_sum / wsum - psf_dict['g_psf_err'] = g_psf_err_sum / wsum - psf_dict['T_psf'] = T_psf_sum / wsum - psf_dict['T_psf_err'] = T_psf_err_sum / wsum - - return psf_dict + return { + 'g_psf': g_psf_sum / wsum, + 'g_psf_err': g_psf_err_sum / wsum, + 'T_psf': T_psf_sum / wsum, + 'T_psf_err': T_psf_err_sum / wsum, + } def do_ngmix_metacal( stamp, @@ -1155,15 +1115,13 @@ def do_ngmix_metacal( # this "bootstrapper" runs the metacal image shearing as well as both psf # and object measurements boot = ngmix.metacal.MetacalBootstrapper( - metacal_pars, - runner=runner, - psf_runner=psf_runner, + runner, + psf_runner, ignore_failed_psf=True, - rng=rng + rng=rng, + **metacal_pars, ) - # this is the actual fit resdict, obsdict = boot.go(gal_obs_list) - # compile results to include psf information psf_res = average_multiepoch_psf(obsdict) return resdict, psf_res From 0f529214f596da632d3b84ae89efd6e45ed59917 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sat, 16 May 2026 19:00:24 +0200 Subject: [PATCH 38/80] fixed pyproject for Docker deployment --- Dockerfile | 2 +- pyproject.toml | 12 +++++++----- src/shapepipe/testing/simulate.py | 30 ++++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5b2393370..a98507a32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,7 +82,7 @@ RUN chmod -R go+rwX /app && \ uv pip install --no-deps -e . && \ for ext in .py .sh .bash; do \ for script in /app/scripts/*/*$ext; do \ - link_name=$(basename $script); \ + link_name=$(basename $script $ext); \ ln -s $script /usr/local/bin/$link_name; \ done; \ done diff --git a/pyproject.toml b/pyproject.toml index a541fdc26..9b64beac3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,13 +76,15 @@ test = [ fitsio = ["fitsio"] dev = ["shapepipe[doc,jupyter,lint,release,test,fitsio]"] +[project.scripts] +shapepipe_run = "shapepipe.shapepipe_run:main" +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" + [tool.setuptools] script-files = [ - "bin/shapepipe_run.py", - "bin/summary_run.py", - "bin/canfar_submit_job.py", - "bin/canfar_monitor.py", - "bin/canfar_monitor_log.py", "scripts/python/update_runs_log_file.py", "scripts/sh/init_run_v2.0.sh", "scripts/sh/run_job_sp_canfar_v2.0.bash", diff --git a/src/shapepipe/testing/simulate.py b/src/shapepipe/testing/simulate.py index 17a8f4635..1b3d65ca5 100644 --- a/src/shapepipe/testing/simulate.py +++ b/src/shapepipe/testing/simulate.py @@ -10,7 +10,18 @@ import galsim -def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): +def make_data( + rng, + shear, + noise=1e-5, + n_epochs=1, + share_shift=False, + gal_hlr=0.3, + gal_flux=1000.0, + psf_fwhm=0.55, + pixel_scale=0.1857, + img_size=201, +): """Simulate an exponential galaxy with Moffat PSF. Parameters @@ -26,6 +37,16 @@ def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): share_shift : bool, optional If True, all epochs share the same sub-pixel shift. If False, each epoch draws an independent shift. + gal_hlr : float, optional + Galaxy half-light radius in arcsec. Default 0.3. + gal_flux : float, optional + Galaxy flux. Default 1000. + psf_fwhm : float, optional + PSF FWHM in arcsec. Default 0.55. + pixel_scale : float, optional + Pixel scale in arcsec/pixel. Default 0.1857. + img_size : int, optional + Stamp size in pixels (square). Default 201. Returns ------- @@ -37,11 +58,8 @@ def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): jacob_lists : list of galsim.BaseWCS """ psf_noise = 1.0e-6 - img_size = 201 - scale = 0.1857 + scale = pixel_scale wcs = galsim.PixelScale(scale) - psf_fwhm = 0.55 - gal_hlr = 0.3 if share_shift: dy, dx = rng.uniform(low=-scale / 2, high=scale / 2, size=2) @@ -55,7 +73,7 @@ def make_data(rng, shear, noise=1e-5, n_epochs=1, share_shift=False): psf = galsim.Moffat(beta=2.5, fwhm=psf_fwhm) obj = galsim.Convolve( psf, - galsim.Exponential(half_light_radius=gal_hlr, flux=1000).shear( + galsim.Exponential(half_light_radius=gal_hlr, flux=gal_flux).shear( g1=shear[0], g2=shear[1] ), ).shift(dx, dy) From 3a70fdf3e4189f77a9e34820491e3d83e47942c0 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Sat, 16 May 2026 20:56:53 +0200 Subject: [PATCH 39/80] fixing versions in docker --- pyproject.toml | 7 + uv.lock | 1958 ++++++++++++++---------------------------------- 2 files changed, 584 insertions(+), 1381 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9b64beac3..4a817db0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,13 @@ canfar_submit_job = "shapepipe.canfar_run:run_job" canfar_monitor = "shapepipe.canfar_run:run_log" canfar_monitor_log = "shapepipe.canfar_run:run_monitor_log" +[tool.uv] +# shapepipe targets Linux only; skip Windows/macOS wheel resolution +environments = ["sys_platform == 'linux'"] + +[tool.uv.sources] +ngmix = { git = "https://github.com/esheldon/ngmix", tag = "v2.4.0" } + [tool.setuptools] script-files = [ "scripts/python/update_runs_log_file.py", diff --git a/uv.lock b/uv.lock index fc0ac0f1d..5ada34bbd 100644 --- a/uv.lock +++ b/uv.lock @@ -2,12 +2,11 @@ version = 1 revision = 3 requires-python = ">=3.12" resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version < '3.14' and sys_platform == 'linux'", +] +supported-markers = [ + "sys_platform == 'linux'", ] [[package]] @@ -15,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 = [ @@ -63,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 = [ @@ -80,21 +79,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, ] -[[package]] -name = "appnope" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, -] - [[package]] 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 = [ @@ -106,30 +96,18 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, - { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, - { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, - { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, - { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, - { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, ] [[package]] @@ -146,8 +124,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 = [ @@ -159,22 +137,17 @@ name = "astropy" version = "7.2.0" 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/7b/92/2dce2d48347efc3346d08ca7995b152d242ebd170c571f7c9346468d8427/astropy-7.2.0.tar.gz", hash = "sha256:ae48bc26b1feaeb603cd94bd1fa1aa39137a115fe931b7f13787ab420e8c3070", size = 7057774, upload-time = "2025-11-25T22:36:41.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6d/6330a844bad8dfc4875e0f2fa1db1fee87837ba9805aa8a8d048c071363a/astropy-7.2.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:efac04df4cc488efe630c2fff1992d6516dfb16a06e197fb68bc9e8e3b85def1", size = 6442332, upload-time = "2025-11-25T22:36:23.6Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:52e9a7d9c86b21f1af911a2930cd0c4a275fb302d455c89e11eedaffef6f2ad0", size = 6413656, upload-time = "2025-11-25T22:36:26.548Z" }, { url = "https://files.pythonhosted.org/packages/be/ba/05e43b5a7d738316a097fa78524d3eaaff5986294b4a052d4adb3c45e7c0/astropy-7.2.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97c370421b9bb13d4c762c7af06d172bad7c01bd5bcf88314f6913c3c235b770", size = 9758867, upload-time = "2025-11-25T22:36:28.661Z" }, { url = "https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f39ce2c80211fbceb005d377a5478cd0d66c42aa1498d252f2239fe5a025c24", size = 9789007, upload-time = "2025-11-25T22:36:31.063Z" }, { url = "https://files.pythonhosted.org/packages/f8/fb/e4d35194a5009d7a73333079481a4ef1380a255d67b9c1db578151a5fb50/astropy-7.2.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ad4d71db994d45f046a1a5449000cf0f88ab6367cb67658500654a0586d6ab19", size = 9748547, upload-time = "2025-11-25T22:36:33.154Z" }, - { url = "https://files.pythonhosted.org/packages/36/ea/f990730978ae0a7a34705f885d2f3806928c5f0bc22eefd6a1a23539cc32/astropy-7.2.0-cp311-abi3-win32.whl", hash = "sha256:95161f26602433176483e8bde8ab1a8ca09148f5b4bf5190569a26d381091598", size = 6237228, upload-time = "2025-11-25T22:36:35.236Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bc/f4378f586dd63902c37d16f68f35f7d555b3b32e08ac6b1d633eb0a48805/astropy-7.2.0-cp311-abi3-win_amd64.whl", hash = "sha256:dc7c340ba1713e55c93071b32033f3153470a0f663a4d539c03a7c9b44020790", size = 6362868, upload-time = "2025-11-25T22:36:37.784Z" }, - { url = "https://files.pythonhosted.org/packages/77/79/b6d4bf01913cfd4ce0cd4c1be5916beccdb92b2970bab8c827984231eae6/astropy-7.2.0-cp311-abi3-win_arm64.whl", hash = "sha256:0c428735a3f15b05c2095bc6ccb5f98a64bc99fb7015866af19ff8492420ddaf", size = 6221756, upload-time = "2025-11-25T22:36:39.852Z" }, ] [[package]] @@ -182,25 +155,17 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/37/2f/481aaeadf28fe7d182b9d17c3101e682565a48ad80fe2af3b04a21860c25/astropy_healpix-1.1.3-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:182b0b6a833afa9daa0c73c8394a2a067da7f42cd27c838e1ced7592979ae934", size = 85014, upload-time = "2026-01-19T23:34:00.498Z" }, - { url = "https://files.pythonhosted.org/packages/88/7a/3fd2ca70e37546cf6c3fd083b77b30d24578243063b95e82ae7489aef293/astropy_healpix-1.1.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c518d4a8af61097be139fa825ffeb391606207fc0c40e7c60fc078ff620d13a5", size = 82245, upload-time = "2026-01-19T23:34:02.134Z" }, { url = "https://files.pythonhosted.org/packages/35/5b/136b7ca67e104e9df0189571e123659decb06737017c212d069785e9769b/astropy_healpix-1.1.3-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:706d2f17bbc97d5348512482ad5e49620724334163a810f23efa54187bb6176a", size = 189061, upload-time = "2026-01-19T23:34:03.564Z" }, { url = "https://files.pythonhosted.org/packages/9c/da/baa3dc875e05ef28e7c3c4aa2154a9a965a9814c56ff4ffe201ce52adb85/astropy_healpix-1.1.3-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49dc32289c7fd98c8dbe6794326d9298920dd9212f99d3bcdef31a19a1cde8ea", size = 191032, upload-time = "2026-01-19T23:34:04.802Z" }, { url = "https://files.pythonhosted.org/packages/eb/d9/4af8bee6ae902b2cfaa87c46487b030be04b5e8e1a520d4e98d11132cf42/astropy_healpix-1.1.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0c851836c9327065608c73d67fd8c00462d365c368038cd285bc60570d470e1b", size = 186453, upload-time = "2026-01-19T23:34:06.313Z" }, - { url = "https://files.pythonhosted.org/packages/06/b5/98136f90a60c06709d41c2791bc909c341d2e49fc5ee8cdb41aea136fbef/astropy_healpix-1.1.3-cp310-abi3-win32.whl", hash = "sha256:e9c5a81d936120c8aa11e042da6529a2255708150de515364eba79a5dd094ca1", size = 52583, upload-time = "2026-01-19T23:34:07.782Z" }, - { url = "https://files.pythonhosted.org/packages/30/cc/0f1fdfc3fc7922387ce3285c459833da502ed3ec976ad30a0bc95632b05c/astropy_healpix-1.1.3-cp310-abi3-win_amd64.whl", hash = "sha256:14133113b137821c1ea1229dc9b1eb9d187ed227ea9c8de60d3eb81296dea67b", size = 54625, upload-time = "2026-01-19T23:34:08.674Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/fd5455946e3063db373ce297cb82a395acc5b8a80e2f2edd12f614e6e1db/astropy_healpix-1.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:da885033f7cf319906f58c61087e137f0e473e78503fe94bf951f569ebd3b033", size = 85274, upload-time = "2026-01-19T23:34:10.055Z" }, - { url = "https://files.pythonhosted.org/packages/f1/db/8907751a5f2a8a7142dc0017a80b27007d4156d912e74a3e2e28eb2b3db8/astropy_healpix-1.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0cd93955d0b30bcee07b09b8ddf6e45cc39895f257fab1469524990e3a5b3e9e", size = 82494, upload-time = "2026-01-19T23:34:11.338Z" }, { url = "https://files.pythonhosted.org/packages/0f/01/17b7dcafa8142204f3c945335da687d9a396038e6ace74501212ffb40ff0/astropy_healpix-1.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a798bfdf2176426cf4947c4905f8862c29032635ea513d281ea27cc3a0303e4", size = 196519, upload-time = "2026-01-19T23:34:12.288Z" }, { url = "https://files.pythonhosted.org/packages/87/04/7c1c2000ce680eccc180649a6e08785e5fd8e7121c6f4552a8833bf5f3ec/astropy_healpix-1.1.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe96eeee5b3dd19f4ce7decef4f95a63ff2564652ec62392fbcabc2bd18095d6", size = 198858, upload-time = "2026-01-19T23:34:13.946Z" }, { url = "https://files.pythonhosted.org/packages/07/4e/e1d08af1651cc05e860b72c54f7368dac9d9027b8b374ae8d7f03d9f3721/astropy_healpix-1.1.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:638796f0c532cb4c77b28594b032e5d7de7c88b53b80ded8190b6f32adebd2bd", size = 193812, upload-time = "2026-01-19T23:34:15.475Z" }, - { url = "https://files.pythonhosted.org/packages/99/c1/5d375ce739ba005d9c84cb1048bf3ee01afb6b098ed2f66c84c5d1e4e1eb/astropy_healpix-1.1.3-cp314-cp314t-win32.whl", hash = "sha256:e83e677f3d66a4d2e1705457915df7021d98936d6167d2780fe2fd2e83a94911", size = 53775, upload-time = "2026-01-19T23:34:16.499Z" }, - { url = "https://files.pythonhosted.org/packages/11/2a/1ef78203d226a29ec0011b57a559e9321ae33c8f17c403f5dff389c92f7c/astropy_healpix-1.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:22327a7d00d28e89b1f182a2ee62d27dc204d0a0bebacfa0d3eabbe9dc25bf8f", size = 55799, upload-time = "2026-01-19T23:34:17.938Z" }, ] [[package]] @@ -217,13 +182,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 = [ @@ -271,8 +236,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 = [ @@ -284,30 +249,18 @@ name = "black" version = "26.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, + { name = "click", marker = "sys_platform == 'linux'" }, + { name = "mypy-extensions", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pathspec", marker = "sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'linux'" }, + { name = "pytokens", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, - { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, - { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, - { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, - { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, - { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, ] @@ -316,7 +269,7 @@ name = "bleach" version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "webencodings" }, + { name = "webencodings", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } wheels = [ @@ -325,7 +278,7 @@ wheels = [ [package.optional-dependencies] css = [ - { name = "tinycss2" }, + { name = "tinycss2", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -333,9 +286,8 @@ name = "build" version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { 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/02/ec/bf5ae0a7e5ab57abe8aabdd0759c971883895d1a20c49ae99f8146840c3c/build-1.4.4.tar.gz", hash = "sha256:f832ae053061f3fb524af812dc94b8b84bac6880cd587630e3b5d91a6a9c1703", size = 89220, upload-time = "2026-04-22T20:53:44.807Z" } wheels = [ @@ -347,13 +299,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 = [ @@ -365,17 +317,15 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/cc/0c/94ab3416737f39a975c8c0744d217b5944d2e05e1e105dd10c13b52bb522/camb-1.6.6-py3-none-macosx_14_0_arm64.whl", hash = "sha256:5fded11be64fea84f62dd82be0d947fdc37b749cdbf14308fce5c127d9edcd54", size = 1330923, upload-time = "2026-03-11T16:46:43.312Z" }, { url = "https://files.pythonhosted.org/packages/c9/f6/9131f388386c50982b1b2992424deca857e7c019f4014dab0770de690a84/camb-1.6.6-py3-none-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eded2dd7f19226ed4717535c07a029fc958b7c581549250da33a768e12c87b13", size = 1592029, upload-time = "2026-03-11T16:46:44.925Z" }, { url = "https://files.pythonhosted.org/packages/41/53/9d0b3cf1a2a07a882adac9565074ac6974fb8b031c31da438d407db22c89/camb-1.6.6-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb8e0c85b3e33d78ca0676c84b0b60b54dfa72d4e520a805166cab0458e81094", size = 2258716, upload-time = "2026-03-11T16:46:46.458Z" }, - { url = "https://files.pythonhosted.org/packages/24/0c/c7b33bf752f1d39340fa0f12fd9c5dfda00cb7c1e5bc883e6909a3a99138/camb-1.6.6-py3-none-win_amd64.whl", hash = "sha256:0307b751b6b0b5d7d0636f6ce45165d03a4f6a3c7925fd56bf202858fa73babd", size = 1488070, upload-time = "2026-03-11T16:46:48.53Z" }, ] [[package]] @@ -383,18 +333,18 @@ name = "canfar" version = "1.3.4" 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/04/32/60a7722dc81c81fb949b160e222f0e46b96f443ee9efafda2e2a14636d60/canfar-1.3.4.tar.gz", hash = "sha256:f445ea8574fcdee9c937e733ba34844290de5e29833fa276c3894b0ec6384c80", size = 25689527, upload-time = "2026-03-31T21:06:44.333Z" } wheels = [ @@ -415,12 +365,10 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, @@ -428,11 +376,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, @@ -440,31 +383,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] @@ -473,7 +403,6 @@ version = "3.4.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, @@ -486,10 +415,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, @@ -502,10 +427,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, @@ -518,10 +439,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, @@ -534,9 +451,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] @@ -544,9 +458,6 @@ wheels = [ name = "click" version = "8.3.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, @@ -584,7 +495,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 = [ @@ -611,65 +522,40 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, ] [[package]] @@ -678,8 +564,6 @@ version = "7.13.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, @@ -690,11 +574,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, @@ -705,11 +584,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, @@ -720,11 +594,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, @@ -735,11 +604,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, @@ -750,9 +614,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] @@ -761,11 +622,10 @@ name = "cryptography" version = "47.0.0" 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/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/98/40dfe932134bdcae4f6ab5927c87488754bf9eb79297d7e0070b78dd58e9/cryptography-47.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0", size = 7912214, upload-time = "2026-04-24T19:53:03.864Z" }, { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, @@ -777,9 +637,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, - { url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" }, - { url = "https://files.pythonhosted.org/packages/14/88/7aa18ad9c11bc87689affa5ce4368d884b517502d75739d475fc6f4a03c7/cryptography-47.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0", size = 7904299, upload-time = "2026-04-24T19:53:35.003Z" }, { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, @@ -791,9 +648,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, - { url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" }, - { url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" }, - { url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4", size = 7904072, upload-time = "2026-04-24T19:54:06.411Z" }, { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, @@ -805,8 +659,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, - { url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" }, - { url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" }, ] [[package]] @@ -814,18 +666,18 @@ name = "cs-util" version = "0.2" source = { registry = "https://pypi.org/simple" } 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'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/1d/068d7522652fed361d80502c958f37080a9f0cfee5ad201b9dea0197b562/cs_util-0.2.tar.gz", hash = "sha256:4cbe01556fb47e832b5d517af812a6cf4a94856ad134be8c21e74874b4d56de6", size = 24932, upload-time = "2026-04-07T07:59:21.174Z" } wheels = [ @@ -846,13 +698,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 = [ @@ -861,12 +713,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]] @@ -874,12 +726,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 = [ @@ -891,8 +743,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 = [ @@ -905,18 +757,9 @@ version = "1.8.20" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, - { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, - { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, - { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, ] @@ -961,7 +804,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 = [ @@ -1000,24 +843,16 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/dc/3f/6ecf0a9b597bd61de2705a10ae3ad1a8ec27da5f948cf38101376a3c5084/fitsio-1.3.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:e81b837a5e802784a487f00ef87783aee13204591c0f8283d020a48f348fa290", size = 687297, upload-time = "2025-11-12T15:23:59.312Z" }, - { url = "https://files.pythonhosted.org/packages/33/35/e037ab8263507d922f5201afb69c10a32a3d45ea1571b2a08cdd6ce0fff7/fitsio-1.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ac477c4b8f24d8e6fffe448f74e814b29e2ff24043797d497b0cacc8aef395db", size = 676817, upload-time = "2025-11-12T15:24:00.34Z" }, { url = "https://files.pythonhosted.org/packages/dd/5c/5713b58b7a848ba0738ce70f461dbf937f94f22a194762ef679b66293884/fitsio-1.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6d0528947dd81bf0a50c8d0187edd05b2112bf890fc4dc825babb7f6e1d10", size = 804603, upload-time = "2025-11-12T15:24:01.685Z" }, { url = "https://files.pythonhosted.org/packages/1e/9a/399a1e59664a5945ffe51b09230e9973104b8e1ba2bc6a894e1266a2ed16/fitsio-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:91556f6140e60a77cda96727fb7cb11316fc0f530ea6ddce4bee8b356737468c", size = 824075, upload-time = "2025-11-12T15:24:02.826Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a4/9a07cff037542479a6d606b705b471bd878f477ebbec4a17a95e55899bbf/fitsio-1.3.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:4636c63228527028fe73b4ff5f7202be668dcd784c44bae4d4d2013a5e40902b", size = 687295, upload-time = "2025-11-12T15:24:04.37Z" }, - { url = "https://files.pythonhosted.org/packages/53/55/f57eb7813534b5783db8ca25f25a2cdbc19d005c088a6b132af37f9268a1/fitsio-1.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5c124fdc03318b66a5227a44428b7b161e07d4841b4a58b13b5db99383959322", size = 676817, upload-time = "2025-11-12T15:24:05.948Z" }, { url = "https://files.pythonhosted.org/packages/55/9b/a270a186b38280579a5cc4342ef619cb488521d28f3a1872d4d81840cd0c/fitsio-1.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:37568c7e8d3193e96ebbd5d9221bbb5e288e8e3e44d3780bc656680404dc5ee6", size = 804706, upload-time = "2025-11-12T15:24:07.22Z" }, { url = "https://files.pythonhosted.org/packages/f4/42/683fd9ed3b5069ac41b0469fd1121d5c4c4aafb88b41d383206afe688223/fitsio-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1017eea6a3924ce47e3aae6f7451147ffb8251729a14c5fffc24b655924c862", size = 824191, upload-time = "2025-11-12T15:24:08.406Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/dd701b5464c0a3ed19f8f0b28aae3999ca63683065a4ff3e9f5554e2159b/fitsio-1.3.0-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:f99292e606acb47122a2b985bfa9a528ac9d37326eb0ea0b597e26b92fbff22b", size = 687289, upload-time = "2025-11-12T15:24:09.439Z" }, - { url = "https://files.pythonhosted.org/packages/06/f6/fc1780842bf27a61182c2fa1765bb07e8d48fd56a1f10d8119f0ca91ef30/fitsio-1.3.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:65956a157758fc719d148362a7fcfb4c48b539fe2a27046ea494c90c7fc7013f", size = 676845, upload-time = "2025-11-12T15:24:10.708Z" }, { url = "https://files.pythonhosted.org/packages/89/24/f17e2a70eb0ba962da95b1f92c5c05b4299d61934c55cf78b7419cb93b7f/fitsio-1.3.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b49dbdd0c4727b119da8a40bbd2eb245277de3cce4935157b304396351a9a61c", size = 804731, upload-time = "2025-11-12T15:24:11.799Z" }, { url = "https://files.pythonhosted.org/packages/41/f2/5c0f93aa2c39e9f9ce8ea414519d47edf09e5894a0ad19b232f375c4b8d7/fitsio-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d38863806b20231227acddfe1501644596199566007e8fae8c4526a3ae369dae", size = 824055, upload-time = "2025-11-12T15:24:12.868Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c6/d6d89fad808297c68b608a57fd3c2d607c69b53514a1c967bdd669772865/fitsio-1.3.0-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:d7d8ff2896d30a18a1d6ec836b727399040907937d368293a76ba6fe17f02d5b", size = 688438, upload-time = "2025-11-12T15:24:14.296Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a9/5f83fb47dcc89da6477bed2607ab0c32d824b185196bc6d53083ea47c0ed/fitsio-1.3.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3261739383b890fe3eea6a2d3f97172e8d369020c9656d42c5fc843f0b977550", size = 678095, upload-time = "2025-11-12T15:24:15.885Z" }, { url = "https://files.pythonhosted.org/packages/89/41/c6a1eb665d58436768c1cc0a075f082b8c2957471ae351b1900c875e3805/fitsio-1.3.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:08569c6774598bbd8c07f6505e9aa047fc7860a5225152df24d0cfc2dbe6994f", size = 817955, upload-time = "2025-11-12T15:24:17.351Z" }, { url = "https://files.pythonhosted.org/packages/a5/1a/3667f55e05a4ba214cc5349c626770d120f52d1b0bb1d90b590fcc8eb157/fitsio-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2b1fce5227aa2f9cb478f67d61e037428bcd14eaf7a4be2a0a6662cdd32d5e", size = 837007, upload-time = "2025-11-12T15:24:18.436Z" }, ] @@ -1028,38 +863,22 @@ version = "4.62.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] @@ -1095,28 +914,20 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/0e/da/a8d051b4cf8355fbb24b87519c9428728b6340225c6576e27912f240657f/galsim-2.8.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8afacd66e07fb9bfcda7fdf5a4e6e134b13d16d607f43520c15229687b157400", size = 5885879, upload-time = "2026-02-22T23:23:47.597Z" }, - { url = "https://files.pythonhosted.org/packages/c8/5e/448762c87f25fc187f1e734021c6c65a3052239c1ea47abb203051250451/galsim-2.8.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a0e7240f0c95270e923eac3cb4db0b295b89b7d4375b415a3c3225220896af0e", size = 6893219, upload-time = "2026-02-22T23:23:49.243Z" }, { url = "https://files.pythonhosted.org/packages/42/09/f224c9279ccc9b9c53ad02c3f6d8bac41f8314fc61843aabebea6c54ab01/galsim-2.8.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccc89555c1cb4ad02b3706cb4d7c010a1822c1bf5ad6306545e4eddd81dc5eb0", size = 55572971, upload-time = "2026-02-22T23:23:51.287Z" }, { url = "https://files.pythonhosted.org/packages/fa/66/ec10112baeda2452e398c5d5c32331ee0432b1a6f59a04a57c497d54a10d/galsim-2.8.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:48554af6aca27b6e68d1ffa53d16ba39ebb526e87943257d34999c6d4f00c2af", size = 56334003, upload-time = "2026-02-22T23:23:54.402Z" }, - { url = "https://files.pythonhosted.org/packages/16/a5/b11ee291c76852f2a730931e0aaaccf90c8cb3041579f1ac13561fe0a3e6/galsim-2.8.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a6f4b45a20eb22bdb6d4261249d27d4ec0662c4b06278dda367ee9b40f30ecc0", size = 5885910, upload-time = "2026-02-22T23:23:57.125Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2a/67ad8336ca09a91d2517b125e757cea423347c99463ecf0e58b6e0c0b50b/galsim-2.8.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:dced6d77e18cfdab7a8578db343d7095653c8a9a76bfbcb8b233ca282f4813b3", size = 6893212, upload-time = "2026-02-22T23:23:59.098Z" }, { url = "https://files.pythonhosted.org/packages/9f/24/3f0ba8ac584787f840a3d2715a8e8e36a4d8e89845fdcbb8ace28f6025c0/galsim-2.8.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0845e6a7a666fe12c88b975e783314b8fae0dfa5d21fb9f8c04d2790feb08fd8", size = 55619993, upload-time = "2026-02-22T23:24:01.421Z" }, { url = "https://files.pythonhosted.org/packages/e6/e4/56aec9553fdd2977eaf3ef4b80ed4de25d5d71afc693520e66c604dc1388/galsim-2.8.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c27b3853ac59b1781a7f45c3c2e1abc25ca8558fd4d9fb62cb7f0471c4397e03", size = 56317797, upload-time = "2026-02-22T23:24:05.409Z" }, - { url = "https://files.pythonhosted.org/packages/a5/dd/d6d1312d5ab83927929e86ced46bf1e28620d223d73a34109502a72d1a8b/galsim-2.8.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1cccc2999622d8f931ca1da7664a712910d9ce3aaf7a04a0a19a402e3080a255", size = 5876491, upload-time = "2026-02-22T23:24:08.147Z" }, - { url = "https://files.pythonhosted.org/packages/08/3b/f66b4ba00bbc6895ce7d7943cc3e719f822a9775c3f127dd1f650004ad66/galsim-2.8.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ea125ef36f9a8b1a0d6fcddae1cdbf8ca9ccf986d10b7bbab4f78a689382fa25", size = 6893527, upload-time = "2026-02-22T23:24:09.983Z" }, { url = "https://files.pythonhosted.org/packages/0b/b7/d8437a1818fc8015c6bbe985297d39a5a0e33e7029aa02db90e9cf4faff3/galsim-2.8.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ade67ffb0b5b1a85d58efab5b6d83042b27eea9475b129bd9ea47d9b5b32ce75", size = 55573321, upload-time = "2026-02-22T23:24:12.34Z" }, { url = "https://files.pythonhosted.org/packages/68/a2/5f89db85856613d0a085d42b565296bc697b436fc0dab4f6ebbfbf35aa97/galsim-2.8.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57ebf2670090839a54776d0f8b0a195584bd0aada1e2256ae3a4db503e1e0c8c", size = 56279408, upload-time = "2026-02-22T23:24:15.697Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/06f5f9f2e3b5bbcb5c9a3d897974b0716be9483960430361b994934bc67f/galsim-2.8.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:addaccabfae01a077d8b9063aaeab35022e96cb24e3e05ac6e56a9623370edcd", size = 5919891, upload-time = "2026-02-22T23:24:18.391Z" }, - { url = "https://files.pythonhosted.org/packages/23/06/a84187ca12475d98f6996d312fca504d85ec4988f21f1cdc72123bd1e706/galsim-2.8.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2a70246183293149e9ee643ec06980a058f0b854bb9555ced54dd55c1128d7a0", size = 6914271, upload-time = "2026-02-22T23:24:20.417Z" }, { url = "https://files.pythonhosted.org/packages/00/f2/97c4c35ccb72c3f6e8aa651c2743e8ed4ab45362ade300126545cac427f3/galsim-2.8.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2bb7dd15217036384a29f3b6e3ce3a6524162a903a563c4bae465f35c68aae60", size = 56696189, upload-time = "2026-02-22T23:24:22.841Z" }, { url = "https://files.pythonhosted.org/packages/bb/5f/2c87d3e63d522e2ecb096995e71d4cfb5f320fc69f75285cfca957c910cd/galsim-2.8.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c0956a376a441a13064cc8f39c277351fa3bc852ed0756e540c7e815f005fb8", size = 57139509, upload-time = "2026-02-22T23:24:26.335Z" }, ] @@ -1126,7 +937,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 = [ @@ -1138,7 +949,7 @@ name = "gitpython" version = "3.1.47" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "gitdb" }, + { name = "gitdb", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c1/bd/50db468e9b1310529a19fce651b3b0e753b5c07954d486cba31bbee9a5d5/gitpython-3.1.47.tar.gz", hash = "sha256:dba27f922bd2b42cb54c87a8ab3cb6beb6bf07f3d564e21ac848913a05a8a3cd", size = 216978, upload-time = "2026-04-22T02:44:44.059Z" } wheels = [ @@ -1151,21 +962,12 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, - { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, - { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, - { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, - { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, - { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, - { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, ] [[package]] @@ -1174,37 +976,26 @@ version = "3.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/cb/cb/baa584cb00532126ffe12d9787db0a60c5a4f55c27bfe2666df5d4c30a32/greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2", size = 235615, upload-time = "2026-04-27T12:21:38.57Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, - { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, - { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, - { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" }, - { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, - { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, ] [[package]] @@ -1221,8 +1012,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 = [ @@ -1234,42 +1025,26 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, - { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, - { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, - { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, - { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, - { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, - { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, - { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, - { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, - { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, ] [[package]] @@ -1277,28 +1052,19 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/4d/bf/9a19efa26b48e962bcda0f97443ab62b9a058577665780ec2ce4d78e2705/healpy-1.19.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:31496bc2eb9f52c1ea3ea4c7e67771e245a49f1a93937ff73f9056b666cd1f30", size = 1728152, upload-time = "2025-12-02T08:26:48.759Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6a/b87c279184278ea75fcaa81e2f1207384b232c038a45abc4c3d6999f8f49/healpy-1.19.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:69987864e318b4e22e2407670bdb2d791fcd9c244bc6c25c7001e14fc34f15a2", size = 1864134, upload-time = "2025-12-02T08:26:50.642Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7c/9f92228da5c03a9f3c7610210cbc57646e85ee8a8479d7bd94a6fefe736d/healpy-1.19.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e8b304fced0dad0a0a41ddd33bb6b057525f9ddd4dada993bf3d6aa830a08e31", size = 1860200, upload-time = "2025-12-02T08:26:51.843Z" }, { url = "https://files.pythonhosted.org/packages/17/51/c07439b92bc05966006897762b7291d7d2c83fde8807b8d53a48e140a8b4/healpy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:730095eef4bbe3c94039edb330d1800285cf43c7e16a81ce5b87edb31a8ffa42", size = 8239086, upload-time = "2025-12-02T08:26:53.076Z" }, { url = "https://files.pythonhosted.org/packages/61/c6/42745aeff172755e55af2487491c7a7475fe1068f152850cc14d64e04f30/healpy-1.19.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:89dbe14b270b5479a9652d1ab9f28fc7fce34253549db665f2e91e6cea588883", size = 8255685, upload-time = "2025-12-02T08:26:54.361Z" }, { url = "https://files.pythonhosted.org/packages/9d/86/2a223bd92dde578de34f07095c71152c84b158644782c4057ca9b25e53b5/healpy-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:29bb5f7acde68ca5850c3a59ba33cacf4c3768c9e85f7013a99aa5fbd127ce84", size = 9042629, upload-time = "2025-12-02T08:26:55.773Z" }, { url = "https://files.pythonhosted.org/packages/18/6d/037a7bb889c92b3e3f792de6a692e636de0577ea62bdd0038b1eb68270af/healpy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:126bef3ae6594f461435ac85c6bcb32e427eca43f743881d00186bc3a55da1d8", size = 9376051, upload-time = "2025-12-02T08:26:57.516Z" }, - { url = "https://files.pythonhosted.org/packages/79/7d/769d1f59ba6491cd835ac143639fdc59e5ade735c5aa14fd4f5cd133a30f/healpy-1.19.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:97155fe60e8309caa610cee6a26028219f181314dc8ad33517070ec48ea316f7", size = 1724949, upload-time = "2025-12-02T08:26:59.536Z" }, - { url = "https://files.pythonhosted.org/packages/4d/99/5264236d706f22d24268ea55a049822870e15c52929c065556cb9e47d379/healpy-1.19.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:70171ba093c9d7740d7248560609f6d8c17e6e1b9543e7b732f092b2a03dda55", size = 2590717, upload-time = "2025-12-02T08:27:00.691Z" }, - { url = "https://files.pythonhosted.org/packages/97/c0/d3ce7ecbb821e4d2914bf9a0dfbf79cf2fcffe02e059d88b24b54693a943/healpy-1.19.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:82b1f40ce8a982bf209a06e0f48b389ff1cbcd2e1524f12a2a78e92e600907b2", size = 2590479, upload-time = "2025-12-02T08:27:01.78Z" }, { url = "https://files.pythonhosted.org/packages/de/bb/314bf8c493701b5842cdd74a1b5b34cea1522444042efad5b7cb17b8d159/healpy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:086bae2510ac60f7cf9bbb520e1d1b8864cc8fca74b46b16e24857e74d08f896", size = 8198878, upload-time = "2025-12-02T08:27:03.119Z" }, { url = "https://files.pythonhosted.org/packages/f6/d4/a60ed9a50768ff5e896dd94d878496ae16767925ea32c49d5a4189ab818a/healpy-1.19.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:920c0a1c6749c05c8ad9522a5a2630f7bc83124c5742ef50f91b9f5e6a1bdcc7", size = 8205835, upload-time = "2025-12-02T08:27:04.416Z" }, { url = "https://files.pythonhosted.org/packages/6c/fd/5f4c989b53423bbad07c91a4b9d52de5cde9f1154e3d2b145b397e1b8cd8/healpy-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:26e3e6c50f2d256c9218f0fb0624406a93c4f22bc66deebf51a0f31fd5594b89", size = 8991846, upload-time = "2025-12-02T08:27:06.123Z" }, { url = "https://files.pythonhosted.org/packages/d5/df/00636a5d2f1141b1fa9646069436c4fc68b35ac2c53ef520f8812b900f05/healpy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bc3c243985abf1f1b5d91da12a23ceed49181dd9d0e46ed362babdc92c814aa8", size = 9325719, upload-time = "2025-12-02T08:27:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/df/14/0ef2e671f75f209d7ac813ed04ba80be82ca30cf517d7c65782a495718be/healpy-1.19.0-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:75179a3681d2e2bfcfe7b33171cbb520be914453062217e6beac15f3c11f9a63", size = 1723861, upload-time = "2025-12-02T08:27:09.971Z" }, - { url = "https://files.pythonhosted.org/packages/0d/eb/2136bbbfca816308136eefe331c346187935402d0a2f306f0e2eab9559c8/healpy-1.19.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:52cbcaacdb1ff252ce8edd39d07d44e47666cc09c471bdd8eb1ee472773bafb4", size = 2591225, upload-time = "2025-12-02T08:27:11.026Z" }, - { url = "https://files.pythonhosted.org/packages/70/ad/e511e15fedc11f7d3529440fc90333fb03fbf93a6339b70c4b5992d849d0/healpy-1.19.0-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:6236f8b900ae7914c8c3c04d3ddd9a01c695e001798e334d2bffb989603bc46b", size = 2591177, upload-time = "2025-12-02T08:27:12.511Z" }, { url = "https://files.pythonhosted.org/packages/c5/ca/f583967e1395e41608225d0a6d29e582236f615d83023d730e63bac89baa/healpy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3cf9c627af77a3fc670d6240d8a83485f373c274945ca8ad156fc2a25d6eb61", size = 8172544, upload-time = "2025-12-02T08:27:13.684Z" }, { url = "https://files.pythonhosted.org/packages/85/ae/80a5e0d36ee51e72b27fb8e11d0d43dfb3f8e2ccc315a38865a2ccd5ed73/healpy-1.19.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:c22360ad7ec6f16bdbb650dececf52d21f7f0e03a943eb1a1046fdb9c51a3c5e", size = 8198134, upload-time = "2025-12-02T08:27:15.156Z" }, { url = "https://files.pythonhosted.org/packages/36/7b/34b9954fdf76b1f5ab0a76228650c2575b189fb6f9f7865c98477a46086d/healpy-1.19.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1a479826c1d6ae8312476e079a3f5c7aa3e40911d74f2d7069adcd62e98604ad", size = 8986426, upload-time = "2025-12-02T08:27:16.417Z" }, @@ -1310,9 +1076,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 = [ @@ -1333,25 +1099,16 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/ae/30/26739a3427cb659d71080bd25c4e1f413d7eb167fcf94adff44fbd809733/hpgeom-1.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f073fd6c011769f6562c36c27d1bf315e63a74b17f634646551ee30b1d26f750", size = 74780, upload-time = "2026-02-24T19:02:47.15Z" }, - { url = "https://files.pythonhosted.org/packages/6d/7c/d1ff34ccf981d8db798fbd88341b52f8b17d6df76d3097712d2525485df2/hpgeom-1.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afefda2cd8f8f1eece4dc6189dc36ab88db0d62f5636263f9a5f42ffc1f0eb06", size = 68846, upload-time = "2026-02-24T19:02:48.542Z" }, { url = "https://files.pythonhosted.org/packages/9a/7f/bae5e1a44d76fa7396c4a004e2e86acb5fe70d8aaaa7f91190c65eb09e97/hpgeom-1.5.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d544a9834477b0aedf61ee934ce4598170cf14f16742200cd1729103edb5f506", size = 192550, upload-time = "2026-02-24T19:02:49.454Z" }, { url = "https://files.pythonhosted.org/packages/68/4d/8b83dcbbd08bd7e713583490c14db848515a909515644037c80d20e2b80c/hpgeom-1.5.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7af0216ff1db0a49a771356562088c49185104a10502b68ab26c8ca2816d17b0", size = 197180, upload-time = "2026-02-24T19:02:50.453Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d3/3aeb5bb35d01f7e57ccf5f03dbc4b90804114145ab721dfd191453157594/hpgeom-1.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:f8dc2a1fcf186dc67ae71bd1ed9a9dbf72eb5909775f70386e7d64f632a2bdec", size = 72773, upload-time = "2026-02-24T19:02:51.378Z" }, - { url = "https://files.pythonhosted.org/packages/15/9d/3b1de694b1a1c2c5aa4182de63bf720c88cc0a2db946edadd7509c8cba98/hpgeom-1.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f213a62d0688371a6023d19abbec5ba504b2806f00caac91048193022dabf336", size = 74783, upload-time = "2026-02-24T19:02:52.237Z" }, - { url = "https://files.pythonhosted.org/packages/55/9f/558e8db5b0cbbcabf4064667ba30bb19c049ffef9cacbf3a68d267897e4a/hpgeom-1.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:65f32958ec93bfe7b5a8e82740f25cf60787b0831404766d02ff80c1c22171d0", size = 68843, upload-time = "2026-02-24T19:02:53.083Z" }, { url = "https://files.pythonhosted.org/packages/75/dc/4c0c6d7eba1d42be23794b03e6048af4de0d92c5805f4f2ed29bc1ca0f16/hpgeom-1.5.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7058b3396fb7deb8b8637ac7f2861bfaa213574c50b9e771bf80f4d1e9064f3", size = 192643, upload-time = "2026-02-24T19:02:54.045Z" }, { url = "https://files.pythonhosted.org/packages/8d/9a/41ba036857a5489d2c1e6f742903aae452d874a48c5b24889de79e02e668/hpgeom-1.5.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e160d40a18b8e80b7e233b4459adcbd4aea5d1fb48cefe7f07e8fc7dfbdcd9f3", size = 197257, upload-time = "2026-02-24T19:02:55.356Z" }, - { url = "https://files.pythonhosted.org/packages/5b/48/327d86569bc4947510ccf7689984ce47219c89d2128e5ab16fc5629ae1c8/hpgeom-1.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:e296ab7f88d4796cc2d0e23202746217e0fd863a29311a53f696659e7cab4cc2", size = 72771, upload-time = "2026-02-24T19:02:56.289Z" }, - { url = "https://files.pythonhosted.org/packages/84/0d/d9fc919a8830622d80341d4c03e749ad8898523e7346782d7ec7e48e9ee4/hpgeom-1.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:09290094b5c5a41ef529745e32df7809f1602aa3bf6e79106669f6250098472f", size = 74736, upload-time = "2026-02-24T19:02:57.139Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b2/09672c0f5956f3fabdd5ee2eae8e3bd11a10e78601d638b0507dfb1dfbe2/hpgeom-1.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29848f353ace002c29a697eb327bc803b8ae6b8e7b6c19114148be9fdc5b41a8", size = 68870, upload-time = "2026-02-24T19:02:58.009Z" }, { url = "https://files.pythonhosted.org/packages/a7/83/45d4208eb33432195df1998d5b5d75afc52d52cdec5ccdec80fa9d3fb2cc/hpgeom-1.5.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47fe1c0c334d9722110a0d6346d2e4222aaed4454138996f9b2638a78ec98b60", size = 192606, upload-time = "2026-02-24T19:02:58.915Z" }, { url = "https://files.pythonhosted.org/packages/1d/67/ab80b95519d453e72cd65ccc6cd411f23a4d997f492ed9ad0dbc51bce0fa/hpgeom-1.5.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fce969f1311a2f44c4f1c58fca1302b472ef798e702725a75bc16a342f924a66", size = 197071, upload-time = "2026-02-24T19:03:00.063Z" }, - { url = "https://files.pythonhosted.org/packages/f0/8b/99915a17b20d56a88e865d1b9e53ac0bb9a2e579c122dc8f3eab6f535a6b/hpgeom-1.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:2e56853bbfda7df904006e4f9eda4095bf56406122155eaa1bcb379f61021eb8", size = 73943, upload-time = "2026-02-24T19:03:01.034Z" }, ] [[package]] @@ -1368,8 +1125,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 = [ @@ -1381,8 +1138,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 = [ @@ -1394,10 +1151,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 = [ @@ -1406,16 +1163,13 @@ wheels = [ [package.optional-dependencies] http2 = [ - { name = "h2" }, + { name = "h2", marker = "sys_platform == 'linux'" }, ] [[package]] name = "humanfriendly" version = "10.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, @@ -1444,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 = [ @@ -1465,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 = [ @@ -1488,22 +1242,14 @@ version = "0.21" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/69/41/0ccaa6ef9943c0609ec5aa663a3b3e681c1712c1007147b84590cec706a0/immutables-0.21.tar.gz", hash = "sha256:b55ffaf0449790242feb4c56ab799ea7af92801a0a43f9e2f4f8af2ab24dfc4a", size = 89008, upload-time = "2024-10-10T00:55:01.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/f9/0c46f600702b815182212453f5514c0070ee168b817cdf7c3767554c8489/immutables-0.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1ed262094b755903122c3c3a83ad0e0d5c3ab7887cda12b2fe878769d1ee0d", size = 31885, upload-time = "2024-10-10T00:54:19.406Z" }, - { url = "https://files.pythonhosted.org/packages/29/34/7608d2eab6179aa47e8f59ab0fbd5b3eeb2333d78c9dc2da0de8de4ed322/immutables-0.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce604f81d9d8f26e60b52ebcb56bb5c0462c8ea50fb17868487d15f048a2f13e", size = 31537, upload-time = "2024-10-10T00:54:20.998Z" }, { url = "https://files.pythonhosted.org/packages/f7/52/cb9e2bb7a69338155ffabbd2f993c968c750dd2d5c6c6eaa6ebb7bfcbdfa/immutables-0.21-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b48b116aaca4500398058b5a87814857a60c4cb09417fecc12d7da0f5639b73d", size = 104270, upload-time = "2024-10-10T00:54:21.912Z" }, { url = "https://files.pythonhosted.org/packages/0f/a4/25df835a9b9b372a4a869a8a1ac30a32199f2b3f581ad0e249f7e3d19eed/immutables-0.21-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad7c0c74b285cc0e555ec0e97acbdc6f1862fcd16b99abd612df3243732e741", size = 104864, upload-time = "2024-10-10T00:54:22.956Z" }, { url = "https://files.pythonhosted.org/packages/4a/51/b548fbc657134d658e179ee8d201ae82d9049aba5c3cb2d858ed2ecb7e3f/immutables-0.21-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e44346e2221a5a676c880ca8e0e6429fa24d1a4ae562573f5c04d7f2e759b030", size = 99733, upload-time = "2024-10-10T00:54:23.99Z" }, { url = "https://files.pythonhosted.org/packages/47/db/d7b1e0e88faf07fe9a88579a86f58078a9a37fff871f4b3dbcf28cad9a12/immutables-0.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b10139b529a460e53fe8be699ebd848c54c8a33ebe67763bcfcc809a475a26f", size = 101698, upload-time = "2024-10-10T00:54:25.734Z" }, - { url = "https://files.pythonhosted.org/packages/69/2d/6fe42a1a053dd8cfb9f45e91d5246522637c7287dc6bd347f67aedf7aedb/immutables-0.21-cp312-cp312-win32.whl", hash = "sha256:fc512d808662614feb17d2d92e98f611d69669a98c7af15910acf1dc72737038", size = 30977, upload-time = "2024-10-10T00:54:27.436Z" }, - { url = "https://files.pythonhosted.org/packages/63/45/d062aca6971e99454ce3ae42a7430037227fee961644ed1f8b6c9b99e0a5/immutables-0.21-cp312-cp312-win_amd64.whl", hash = "sha256:461dcb0f58a131045155e52a2c43de6ec2fe5ba19bdced6858a3abb63cee5111", size = 35088, upload-time = "2024-10-10T00:54:28.388Z" }, - { url = "https://files.pythonhosted.org/packages/5e/db/60da6f5a3c3f64e0b3940c4ad86e1971d9d2eb8b13a179c26eda5ec6a298/immutables-0.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:79674b51aa8dd983f9ac55f7f67b433b1df84a6b4f28ab860588389a5659485b", size = 31922, upload-time = "2024-10-10T00:54:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/5420f1d16a652024fcccc9c07d46d4157fcaf33ff37c82412c83fc16ef36/immutables-0.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93c8350f8f7d0d9693f708229d9d0578e6f3b785ce6da4bced1da97137aacfad", size = 31552, upload-time = "2024-10-10T00:54:30.282Z" }, { url = "https://files.pythonhosted.org/packages/d2/d0/a5fb7c164ddb298ec37537e618b70dfa30c7cae9fac01de374c36489cbc9/immutables-0.21-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:583d2a63e444ce1538cc2bda56ae1f4a1a11473dbc0377c82b516bc7eec3b81e", size = 104334, upload-time = "2024-10-10T00:54:31.284Z" }, { url = "https://files.pythonhosted.org/packages/f3/a5/5fda0ee4a261a85124011ac0750fec678f00e1b2d4a5502b149a3b4d86d9/immutables-0.21-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b274a52da9b106db55eceb93fc1aea858c4e6f4740189e3548e38613eafc2021", size = 104898, upload-time = "2024-10-10T00:54:32.295Z" }, { url = "https://files.pythonhosted.org/packages/93/fa/d46bfe92f2c66d35916344176ff87fa839aac9c16849652947e722b7a15f/immutables-0.21-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:338bede057250b33716a3e4892e15df0bf5a5ddbf1d67ead996b3e680b49ef9e", size = 99966, upload-time = "2024-10-10T00:54:34.046Z" }, { url = "https://files.pythonhosted.org/packages/d7/f5/2a19e2e095f7a39d8d77dcc10669734d2d99773ce00c99bdcfeeb7d714e6/immutables-0.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8781c89583b68f604cf30f0978b722165824c3075888639fde771bf1a3e12dc0", size = 101773, upload-time = "2024-10-10T00:54:35.851Z" }, - { url = "https://files.pythonhosted.org/packages/86/80/5b6ee53f836cf2067ced997efbf2ce20890627f150c3089ea50cf607e783/immutables-0.21-cp313-cp313-win32.whl", hash = "sha256:e97ea83befad873712f283c0cccd630f70cba753e207b4868af28d5b85e9dc54", size = 30988, upload-time = "2024-10-10T00:54:37.618Z" }, - { url = "https://files.pythonhosted.org/packages/ff/07/f623e6da78368fc0b1772f4877afbf60f34c4cc93f1a8f1006507afa21ec/immutables-0.21-cp313-cp313-win_amd64.whl", hash = "sha256:cfcb23bd898f5a4ef88692b42c51f52ca7373a35ba4dcc215060a668639eb5da", size = 35147, upload-time = "2024-10-10T00:54:38.558Z" }, ] [[package]] @@ -1511,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 = [ @@ -1532,19 +1278,18 @@ name = "ipykernel" version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appnope", marker = "sys_platform == 'darwin'" }, - { 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 = [ @@ -1556,17 +1301,16 @@ name = "ipython" version = "9.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { 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/cd/c4/87cda5842cf5c31837c06ddb588e11c3c35d8ece89b7a0108c06b8c9b00a/ipython-9.13.0.tar.gz", hash = "sha256:7e834b6afc99f020e3f05966ced34792f40267d64cb1ea9043886dab0dde5967", size = 4430549, upload-time = "2026-04-24T12:24:55.221Z" } wheels = [ @@ -1578,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 = [ @@ -1590,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 = [ @@ -1611,7 +1355,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 = [ @@ -1632,7 +1376,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 = [ @@ -1644,7 +1388,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 = [ @@ -1665,7 +1409,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 = [ @@ -1704,10 +1448,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 = [ @@ -1716,15 +1460,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]] @@ -1732,7 +1476,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 = [ @@ -1744,11 +1488,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 = [ @@ -1760,8 +1504,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 = [ @@ -1773,14 +1517,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 = [ @@ -1792,7 +1536,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 = [ @@ -1804,24 +1548,23 @@ name = "jupyter-server" version = "2.17.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 = "pywinpty", marker = "os_name == 'nt'" }, - { 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/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } wheels = [ @@ -1833,8 +1576,7 @@ name = "jupyter-server-terminals" version = "0.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywinpty", marker = "os_name == 'nt'" }, - { 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 = [ @@ -1846,19 +1588,19 @@ name = "jupyterlab" version = "4.5.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "async-lru" }, - { name = "httpx" }, - { name = "ipykernel" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyter-lsp" }, - { name = "jupyter-server" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "packaging" }, - { name = "setuptools" }, - { 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-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 = "setuptools", marker = "sys_platform == 'linux'" }, + { name = "tornado", marker = "sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } wheels = [ @@ -1879,13 +1621,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 = [ @@ -1897,11 +1639,10 @@ name = "keyring" version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jaraco-classes" }, - { name = "jaraco-context" }, - { name = "jaraco-functools" }, + { 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 = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { 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" } @@ -1915,9 +1656,6 @@ version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, - { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, - { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, @@ -1928,11 +1666,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, - { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, - { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, @@ -1943,11 +1676,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, - { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, - { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, - { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, @@ -1958,10 +1686,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, @@ -1972,11 +1696,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, - { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, - { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, - { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, - { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, - { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, @@ -1987,12 +1706,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, - { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, - { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, ] [[package]] @@ -2018,7 +1732,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 = [ @@ -2031,22 +1745,14 @@ version = "0.47.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" }, { url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" }, { url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" }, - { url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" }, - { url = "https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec", size = 37232771, upload-time = "2026-03-31T18:29:01.324Z" }, { url = "https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb", size = 56275178, upload-time = "2026-03-31T18:29:05.748Z" }, { url = "https://files.pythonhosted.org/packages/d6/da/b32cafcb926fb0ce2aa25553bf32cb8764af31438f40e2481df08884c947/llvmlite-0.47.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c6951e2b29930227963e53ee152441f0e14be92e9d4231852102d986c761e40", size = 55128632, upload-time = "2026-03-31T18:29:11.235Z" }, - { url = "https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e", size = 38138695, upload-time = "2026-03-31T18:29:15.43Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:de966c626c35c9dff5ae7bf12db25637738d0df83fc370cf793bc94d43d92d14", size = 37232773, upload-time = "2026-03-31T18:29:19.453Z" }, { url = "https://files.pythonhosted.org/packages/64/1d/a760e993e0c0ba6db38d46b9f48f6c7dceb8ac838824997fb9e25f97bc04/llvmlite-0.47.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddbccff2aeaff8670368340a158abefc032fe9b3ccf7d9c496639263d00151aa", size = 56275176, upload-time = "2026-03-31T18:29:24.149Z" }, { url = "https://files.pythonhosted.org/packages/84/3b/e679bc3b29127182a7f4aa2d2e9e5bea42adb93fb840484147d59c236299/llvmlite-0.47.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a7b778a2e144fc64468fb9bf509ac1226c9813a00b4d7afea5d988c4e22fca", size = 55128631, upload-time = "2026-03-31T18:29:29.536Z" }, - { url = "https://files.pythonhosted.org/packages/be/f7/19e2a09c62809c9e63bbd14ce71fb92c6ff7b7b3045741bb00c781efc3c9/llvmlite-0.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:694e3c2cdc472ed2bd8bd4555ca002eec4310961dd58ef791d508f57b5cc4c94", size = 39153826, upload-time = "2026-03-31T18:29:33.681Z" }, - { url = "https://files.pythonhosted.org/packages/40/a1/581a8c707b5e80efdbbe1dd94527404d33fe50bceb71f39d5a7e11bd57b7/llvmlite-0.47.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:92ec8a169a20b473c1c54d4695e371bde36489fc1efa3688e11e99beba0abf9c", size = 37232772, upload-time = "2026-03-31T18:29:37.952Z" }, { url = "https://files.pythonhosted.org/packages/11/03/16090dd6f74ba2b8b922276047f15962fbeea0a75d5601607edb301ba945/llvmlite-0.47.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbd800edd3b20bc141521f7fd45a6185a5b84109aa6855134e81397ffe72b", size = 56275178, upload-time = "2026-03-31T18:29:42.58Z" }, { url = "https://files.pythonhosted.org/packages/f5/cb/0abf1dd4c5286a95ffe0c1d8c67aec06b515894a0dd2ac97f5e27b82ab0b/llvmlite-0.47.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6725179b89f03b17dabe236ff3422cb8291b4c1bf40af152826dfd34e350ae8", size = 55128632, upload-time = "2026-03-31T18:29:46.939Z" }, - { url = "https://files.pythonhosted.org/packages/4f/79/d3bbab197e86e0ff4f9c07122895b66a3e0d024247fcff7f12c473cb36d9/llvmlite-0.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6842cf6f707ec4be3d985a385ad03f72b2d724439e118fcbe99b2929964f0453", size = 39153839, upload-time = "2026-03-31T18:29:51.004Z" }, ] [[package]] @@ -2063,8 +1769,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 = [ @@ -2077,8 +1783,6 @@ version = "6.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, @@ -2092,11 +1796,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, - { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, - { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, @@ -2110,11 +1809,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, - { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, - { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, @@ -2128,11 +1822,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, - { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, - { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, - { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, - { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, @@ -2146,9 +1835,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, ] [[package]] @@ -2156,7 +1842,7 @@ name = "markdown-it-py" version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl" }, + { name = "mdurl", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ @@ -2169,61 +1855,36 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] @@ -2231,53 +1892,33 @@ name = "matplotlib" version = "3.10.9" 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/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, - { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, - { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, - { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, - { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, - { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, - { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, - { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, - { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, - { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, - { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, - { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, - { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, - { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, ] [[package]] @@ -2285,7 +1926,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 = [ @@ -2297,11 +1938,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 = [ @@ -2313,7 +1954,7 @@ name = "mdit-py-plugins" version = "0.5.0" 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/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } wheels = [ @@ -2343,10 +1984,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 = [ @@ -2368,36 +2009,18 @@ version = "4.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/62/74/28ea85b0b949cad827ea50720e00e814e88c8fd536c27c3c491e4f025724/mpi4py-4.1.1.tar.gz", hash = "sha256:eb2c8489bdbc47fdc6b26ca7576e927a11b070b6de196a443132766b3d0a2a22", size = 500518, upload-time = "2025-10-10T13:55:20.402Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/b3/2e7df40608f2188dca16e38f8030add1071f06b1cd94dd8a4e16b9acbd84/mpi4py-4.1.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1586f5d1557abed9cba7e984d18f32e787b353be0986e599974db177ae36329a", size = 1422849, upload-time = "2025-10-10T13:53:40.082Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ed/970bd3edc0e614eccc726fa406255b88f728a8bc059e81f96f28d6ede0af/mpi4py-4.1.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ba85e4778d63c750226de95115c92b709f38d7e661be660a275da4f0992ee197", size = 1326982, upload-time = "2025-10-10T13:53:42.32Z" }, { url = "https://files.pythonhosted.org/packages/5d/c3/f9a5d1f9ba52ac6386bf3d3550027f42a6b102b0432113cc43294420feb2/mpi4py-4.1.1-cp310-abi3-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0a8332884626994d9ef48da233dc7a0355f4868dd7ff59f078d5813a2935b930", size = 1373127, upload-time = "2025-10-10T13:53:43.957Z" }, { url = "https://files.pythonhosted.org/packages/84/d1/1fe75025df801d817ed49371c719559f742f3f263323442d34dbe3366af3/mpi4py-4.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e0352860f0b3e18bc0dcb47e42e583ccb9472f89752d711a6fca46a38670554", size = 1225134, upload-time = "2025-10-10T13:53:45.583Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/d653fec0e4ca8181645da4bfb2763017625e5b3f151b208fadd932cb1766/mpi4py-4.1.1-cp310-abi3-win_amd64.whl", hash = "sha256:0f46dfe666a599e4bd2641116b2b4852a3ed9d37915edf98fae471d666663128", size = 1478863, upload-time = "2025-10-10T13:53:47.178Z" }, - { url = "https://files.pythonhosted.org/packages/ff/2c/e201cd4828555f10306a5439875cbd0ecfba766ace01ff5c6df43f795650/mpi4py-4.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4403a7cec985be9963efc626193e6df3f63f5ada0c26373c28e640e623e56c3", size = 1669517, upload-time = "2025-10-10T13:54:08.404Z" }, - { url = "https://files.pythonhosted.org/packages/7b/53/18d978c3a19deecf38217ce54319e6c9162fec3569c4256c039b66eac2f4/mpi4py-4.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a2ffccc9f3a8c7c957403faad594d650c60234ac08cbedf45beaa96602debe9", size = 1454721, upload-time = "2025-10-10T13:54:09.977Z" }, { url = "https://files.pythonhosted.org/packages/ee/15/b908d1d23a4bd2bd7b2e98de5df23b26e43145119fe294728bf89211b935/mpi4py-4.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3d9b619bf197a290f7fd67eb61b1c2a5c204afd9621651a50dc0b1c1280d45", size = 1448977, upload-time = "2025-10-10T13:54:11.65Z" }, { url = "https://files.pythonhosted.org/packages/5d/19/088a2d37e80e0feb7851853b2a71cbe6f9b18bdf0eab680977864ea83aab/mpi4py-4.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0699c194db5d95fc2085711e4e0013083bd7ae9a88438e1fd64ddb67e9b0cf9e", size = 1318737, upload-time = "2025-10-10T13:54:13.075Z" }, - { url = "https://files.pythonhosted.org/packages/97/3a/526261f39bf096e5ff396d18b76740a58d872425612ff84113dd85c2c08e/mpi4py-4.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:0abf5490c3d49c30542b461bfc5ad88dd7d147a4bdb456b7163640577fdfef88", size = 1725676, upload-time = "2025-10-10T13:54:14.681Z" }, - { url = "https://files.pythonhosted.org/packages/30/75/2ffccd69360680a0216e71f90fd50dc8ff49711be54502d522a068196c68/mpi4py-4.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3dd973c509f2dbb6904c035a4a071509cde98decf0528fa21e2e7d5db5cc988", size = 1710002, upload-time = "2025-10-10T13:54:17.042Z" }, - { url = "https://files.pythonhosted.org/packages/3c/13/22fa9dcbc5e4ae6fd10cba6d49b7c879c30c5bea88f450f79b373d200f40/mpi4py-4.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c8c83a359e62dd7fdd030360f430e0e8986df029c0953ab216ff97a110038dc4", size = 1484623, upload-time = "2025-10-10T13:54:19.097Z" }, { url = "https://files.pythonhosted.org/packages/47/01/476f0f9dc96261d02214009f42e10338fc56f260f1f10b23ee89c515c8b7/mpi4py-4.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:323ba354ba951c7736c033c5f2ad07bb1276f9696f0312ea6ff0a28cd0ab3e3d", size = 1448403, upload-time = "2025-10-10T13:54:21.211Z" }, { url = "https://files.pythonhosted.org/packages/a2/20/dc990edb7b075ecdba4e02bcd03d1583faeb84f664d1585c4c00a0f9851a/mpi4py-4.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c4ef9fe5fb211b1c5b6afe521397e3feb01e104024d6bc37aa4289c370605e2", size = 1318018, upload-time = "2025-10-10T13:54:23.23Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bf/b0ab43a99ac2a1d6d5765cb7d2a4f093656090ce07528043057ecc3e87cb/mpi4py-4.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:e13a1ba26604514a12c95b7d76058ce800d5740d5f5f3b50c4b782cfa0dfaa1f", size = 1722939, upload-time = "2025-10-10T13:54:24.862Z" }, - { url = "https://files.pythonhosted.org/packages/84/26/3e00dc536311e758096414b4f33beb4c7f04dff875e87a6e88fbbe4fc2d8/mpi4py-4.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:28ce1f7412f5e99a6b9fe2547203633431d0ee45670413a475a07e6c785e63b1", size = 1798116, upload-time = "2025-10-10T13:54:26.378Z" }, - { url = "https://files.pythonhosted.org/packages/15/51/d06d2b126be5660aca8c00fe0d940a8658085038f61a9cfc834d3d5ffa80/mpi4py-4.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd1e49b84a0651018517e87daf68085719eca25e5c9a7cd05d98a73418c88836", size = 1586285, upload-time = "2025-10-10T13:54:27.838Z" }, { url = "https://files.pythonhosted.org/packages/51/63/eeb936e0e8cfd8160b6b297645c730b22d242595861cf6a2fa627a358175/mpi4py-4.1.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd869ea7758b591ffbb1483588a6fbf84952a5090e80a45ea89674d55cf25f3b", size = 1514102, upload-time = "2025-10-10T13:54:29.297Z" }, { url = "https://files.pythonhosted.org/packages/1a/c1/06967d4c107ea7169d2120c4fb86c404707e6de82e277dc9f0fa5a9c1bf1/mpi4py-4.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:475da0797442cba723c0ad37da6a1c51d9624e697dd8bf89f23d0fad81e73eda", size = 1395247, upload-time = "2025-10-10T13:54:30.881Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7c/5f0f32b39185f0a7074c165dc37cdd235bfd737928a2fe223e41b308fb4c/mpi4py-4.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8d3bfa074776d9507ee957f5230d11ecd03da23f601a85349a1a333eaf55e5fa", size = 1771515, upload-time = "2025-10-10T13:54:32.395Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e8/93ddde2b6ee7631b46bb79b851630b3527d9060b9b999844bcd882977539/mpi4py-4.1.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:1deb6f9df28ec6972305287cb2035c20d3f5af59f687f962080756374c16e48f", size = 1713353, upload-time = "2025-10-10T13:54:33.934Z" }, - { url = "https://files.pythonhosted.org/packages/b2/23/449562bd23fcfbd7d01006b39429972bfed5dfb8541355d06d2e17c16c27/mpi4py-4.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1bb1e3ad0b9047b0dbc7b4014160a7ab2a84f1627be665527c7445fc312f189b", size = 1496415, upload-time = "2025-10-10T13:54:35.927Z" }, { url = "https://files.pythonhosted.org/packages/51/33/9a5b9ae66cbb095b711f4ddae6d2d4b0f55202ac9e503fd588b101f04a22/mpi4py-4.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5f757e3089abf2c9db69fac1665fa99c52ed392fdf799159f25cba9ee3b64f5a", size = 1450750, upload-time = "2025-10-10T13:54:37.608Z" }, { url = "https://files.pythonhosted.org/packages/d2/88/6acf948f19cb59c0e8843fed4ab4c471b7644e8a16c2d5d9c7ab6d73d573/mpi4py-4.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:807c6f1ed3adbc12952db52127e34cfbd6c48a05c3b3dd59deee2d2f09d78888", size = 1325773, upload-time = "2025-10-10T13:54:39.136Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b4/3021e073772cd9e1062a810b7298e68ea40933fb91b1c1c0d07c968dce5c/mpi4py-4.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:2c85983d38d77e6302a242e32afd2a9a9b3adedd770e199a38e5b8957150e7ac", size = 1721603, upload-time = "2025-10-10T13:54:41.396Z" }, - { url = "https://files.pythonhosted.org/packages/ed/02/b6700c24fe28588a4e40adb23d02fe2aea82b33495fd6290235da5199383/mpi4py-4.1.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:729c4f625ad60e5cfb6c260608d249dc35a33cc16605faff01c6adbbd7e8ce0f", size = 1799551, upload-time = "2025-10-10T13:54:43.084Z" }, - { url = "https://files.pythonhosted.org/packages/5a/93/9c9870174183869bd5a50bbfe7bda91a52bf7ca2d0851de4009590e735a2/mpi4py-4.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3cca235d46009f54cb319c779c6ac53d41ce1eee3cf07f157995bc7739329b97", size = 1587583, upload-time = "2025-10-10T13:54:45.989Z" }, { url = "https://files.pythonhosted.org/packages/29/12/c46bec2311fc937ed3767312f9feb5f11bc70058c20bc53ae7369d759424/mpi4py-4.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2580fab891db492f32a6e02717e824f6fd5588be6560b08627c1e9322f7ccbfb", size = 1513437, upload-time = "2025-10-10T13:54:48.145Z" }, { url = "https://files.pythonhosted.org/packages/09/3e/e46629867204b22ce6804096e0b7d35bb5b473df1d12272021843af726c3/mpi4py-4.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6beec4841f9436d49ec9cabfd76a19df61c10b21ca14eddafa58fe7977802ee7", size = 1395082, upload-time = "2025-10-10T13:54:49.744Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ca/7e27edf78cd8ba68aacafc836004cd092a978f0d5ffc8a3eac9e904a3e0e/mpi4py-4.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:b4b3813da9a7a1fc37ffb8dad314cb396313a40cd3fe150854ab29e999a9eb8c", size = 1771707, upload-time = "2025-10-10T13:54:51.756Z" }, ] [[package]] @@ -2423,12 +2046,12 @@ name = "myst-parser" version = "5.0.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/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } wheels = [ @@ -2440,10 +2063,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 = [ @@ -2455,20 +2078,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 = [ @@ -2480,10 +2103,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 = [ @@ -2510,8 +2133,8 @@ wheels = [ [[package]] name = "ngmix" -version = "1.3.6" -source = { git = "https://github.com/aguinot/ngmix?rev=stable_version#9ced491947d4801f8a4848616958a5751234f757" } +version = "2.4.0" +source = { git = "https://github.com/esheldon/ngmix?tag=v2.4.0#d08b471f4c4d5887df9f9f2551efaf8f2e226150" } [[package]] name = "nh3" @@ -2519,17 +2142,12 @@ version = "0.3.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9c/5f/1d19bdc7d27238e37f3672cdc02cb77c56a4a86d140cd4f4f23c90df6e16/nh3-0.3.5.tar.gz", hash = "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8", size = 20743, upload-time = "2026-04-25T10:44:16.066Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/b0/8587ac42a9627ab88e7e221601f1dfccbf4db80b2a29222ea63266dc9abc/nh3-0.3.5-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a", size = 1420126, upload-time = "2026-04-25T10:43:39.834Z" }, { url = "https://files.pythonhosted.org/packages/c0/1b/1dbc4d0c43f12e8c1784ede17eaee6f061d4fbe5505757c65c49b2ceab95/nh3-0.3.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263", size = 793943, upload-time = "2026-04-25T10:43:41.363Z" }, { url = "https://files.pythonhosted.org/packages/47/9f/d6758d7a14ee964bf439cc35ae4fa24a763a93399c8ef6f22bd11d532d29/nh3-0.3.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9", size = 841150, upload-time = "2026-04-25T10:43:43.007Z" }, { url = "https://files.pythonhosted.org/packages/b6/36/d5d1ae8374612c98f390e1ea7c610fa6c9716259a03bbf4d15b269f40073/nh3-0.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588", size = 1008415, upload-time = "2026-04-25T10:43:44.324Z" }, { url = "https://files.pythonhosted.org/packages/ba/8f/d13a9c3fd2d9c131a2a281737380e9379eb0f8c33fea24c2b923aaafbb15/nh3-0.3.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd", size = 1092706, upload-time = "2026-04-25T10:43:45.653Z" }, { url = "https://files.pythonhosted.org/packages/bb/57/2f3add7f8680fcc896afa6a675cb2bab09982853ee8af40bad621f6b61c4/nh3-0.3.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17", size = 1048346, upload-time = "2026-04-25T10:43:46.974Z" }, { url = "https://files.pythonhosted.org/packages/c1/c3/2f9e4ffa82863074d1361bfe949bc46393d91b3411579dfbbd090b24cac5/nh3-0.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29", size = 1029038, upload-time = "2026-04-25T10:43:48.569Z" }, - { url = "https://files.pythonhosted.org/packages/e8/10/2804deb3f3315184c9cae41702e293c87524b5a21f766b07d7fe3ffbcfbb/nh3-0.3.5-cp314-cp314t-win32.whl", hash = "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88", size = 603263, upload-time = "2026-04-25T10:43:49.851Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/f6685248b49f7548fc9a8c335ab3a52f68610b72e8a61576447151e4e2e6/nh3-0.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f", size = 616866, upload-time = "2026-04-25T10:43:51.005Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b6/d8c9018635d4acfefde6b68470daa510eed715a350cbaa2f928ba0609f81/nh3-0.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79", size = 602566, upload-time = "2026-04-25T10:43:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/85/30/d162e99746a2fb1d98bb0ef23af3e201b156cf09f7de867c7390c8fe1c06/nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101", size = 1442393, upload-time = "2026-04-25T10:43:53.556Z" }, { url = "https://files.pythonhosted.org/packages/25/8c/072120d506978ab053e1732d0efa7c86cb478fee0ee098fda0ac0d31cb34/nh3-0.3.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878", size = 837722, upload-time = "2026-04-25T10:43:55.073Z" }, { url = "https://files.pythonhosted.org/packages/52/86/d4e06e28c5ad1c4b065f89737d02631bd49f1660b6ebcf17a87ffcd201da/nh3-0.3.5-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93", size = 822872, upload-time = "2026-04-25T10:43:56.581Z" }, { url = "https://files.pythonhosted.org/packages/0a/62/50659255213f241ec5797ae7427464c969397373e83b3659372b341ae869/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3", size = 1100031, upload-time = "2026-04-25T10:43:58.098Z" }, @@ -2542,9 +2160,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/aa/d9c59c1b49669fcb7bababa55df82385f029ad5c2651f583c3a1141cfdd1/nh3-0.3.5-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d", size = 1103530, upload-time = "2026-04-25T10:44:07.68Z" }, { url = "https://files.pythonhosted.org/packages/90/b0/cdd210bfb8d9d43fb02fc3c868336b9955934d8e15e66eb1d15a147b8af0/nh3-0.3.5-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad", size = 1061754, upload-time = "2026-04-25T10:44:09.362Z" }, { url = "https://files.pythonhosted.org/packages/ce/cb/7a39e72e668c8445bdd95e494b3e21cfdddc68329be8ea3522c8befb46c4/nh3-0.3.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9", size = 1040938, upload-time = "2026-04-25T10:44:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/af/4c/fc2f9ed208a3801a319f59b5fea03cdc20cf3bd8af14be930d3a8de01224/nh3-0.3.5-cp38-abi3-win32.whl", hash = "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f", size = 611445, upload-time = "2026-04-25T10:44:12.317Z" }, - { url = "https://files.pythonhosted.org/packages/db/1a/e4c9b5e2ae13e6092c9ec16d8ca30646cb01fcdea245f36c5b08fd21fbd5/nh3-0.3.5-cp38-abi3-win_amd64.whl", hash = "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30", size = 626502, upload-time = "2026-04-25T10:44:13.682Z" }, - { url = "https://files.pythonhosted.org/packages/80/7c/19cd0671d1ba2762fb388fc149697d20d0568ccfeef833b11280a619e526/nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", size = 611069, upload-time = "2026-04-25T10:44:14.934Z" }, ] [[package]] @@ -2552,9 +2167,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 = [ @@ -2566,7 +2181,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 = [ @@ -2578,27 +2193,19 @@ name = "numba" version = "0.65.1" 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/f6/c5/db2ac3685833d626c0dcae6bd2330cd68433e1fd248d15f70998160d3ad7/numba-0.65.1.tar.gz", hash = "sha256:19357146c32fe9ed25059ab915e8465fb13951cf6b0aace3826b76886373ab23", size = 2765600, upload-time = "2026-04-24T02:02:56.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/bc/76f8f8c5cf9adee47fdb7bbb03be8900f76f902d451d7477cf12b845e1de/numba-0.65.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ac3f1e77c352dd0ea9712732c2d8f9ca507717435eec5b5013bf138ac33c4a08", size = 2681371, upload-time = "2026-04-24T02:02:26.105Z" }, { url = "https://files.pythonhosted.org/packages/69/47/a415af0283e4db0398104c6d1c11c9861a98dc67a7aa442a7769ed5d6196/numba-0.65.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:52bc6f3ceb8fcaff9b2ae26b4c6b1e9fee39db8d355534c0fe4f39a901246b84", size = 3802467, upload-time = "2026-04-24T02:02:27.712Z" }, { url = "https://files.pythonhosted.org/packages/46/36/246f73ec99cfeab2f2cb2ce7d4218766cc36a2da418901223f4f4da9c813/numba-0.65.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90ca10b3463bae0bd70589726fe3c77d01d6b5fc86bee54bcdf9fb6b47c28977", size = 3502628, upload-time = "2026-04-24T02:02:29.763Z" }, - { url = "https://files.pythonhosted.org/packages/db/9e/3c679b2ee078425b9e99a91e44f8d132a6830d8ccce5227bc5e9181aeed8/numba-0.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:5971c632be2a2351500431f46213821dba8d02b18a9f7d02fd36bd2743e41a6a", size = 2750611, upload-time = "2026-04-24T02:02:31.477Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3", size = 2681344, upload-time = "2026-04-24T02:02:33.65Z" }, { url = "https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b", size = 3810619, upload-time = "2026-04-24T02:02:36.041Z" }, { url = "https://files.pythonhosted.org/packages/62/08/e16a8b5d9a018962ebb5c66be662317cde32b9f5dab08441f90bed5522fb/numba-0.65.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:594a8680b3fadac99e97e489b1fd89007177e5336713745c3b769528c635a464", size = 3509783, upload-time = "2026-04-24T02:02:38.245Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl", hash = "sha256:85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62", size = 2750534, upload-time = "2026-04-24T02:02:39.903Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:33f5eb68eb1c843511615d14663ce60258525d6a4c65ab040e2c2b0c4cf17450", size = 2681554, upload-time = "2026-04-24T02:02:41.812Z" }, { url = "https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71e73029bf53a62cc6afcf96be4bd942290d8b4c55f0a454fb536158115790f7", size = 3779602, upload-time = "2026-04-24T02:02:43.726Z" }, { url = "https://files.pythonhosted.org/packages/09/90/b0f09b48752d23640b8284f22aa597737e8adaddc7fbfacc4708b7f73a4c/numba-0.65.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a07635e0be926b9bdbffb09137c230fb13f6ec0e564914ba937cee12ce3eb35", size = 3479532, upload-time = "2026-04-24T02:02:45.427Z" }, - { url = "https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a20fcdabdefbdacf88d85caf70c3b18c4bcb7ebb8f82e6a19486383dd26ab63", size = 2752637, upload-time = "2026-04-24T02:02:47.664Z" }, - { url = "https://files.pythonhosted.org/packages/81/7b/c1a341a9067367778f4152a5f01061cf281fb09582c92c510ec4918cabf6/numba-0.65.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:548dd4b3a4508d5062768d1514b2cd7b015f9a25ec7af651c50dee243965e652", size = 2684600, upload-time = "2026-04-24T02:02:49.653Z" }, { url = "https://files.pythonhosted.org/packages/03/36/98ddbcf3e4f04a6dd07e1c67249955920579ba4af6bb6868e3088f4ed282/numba-0.65.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:78abc28feff2c2ff8307fff3975b6438352759c9acb797ecd6b1fb6e7e39e31d", size = 3817198, upload-time = "2026-04-24T02:02:51.266Z" }, { url = "https://files.pythonhosted.org/packages/a3/83/0dad21057ece5a835599f5d24099b091703995e23dbbf894f259e91c010b/numba-0.65.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7676cb389555805f9b9a1840cbcd1ea6c8bd5376ab6918e3a29c5ea1dbda20", size = 3533862, upload-time = "2026-04-24T02:02:52.987Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/8be7118ffd4c8440881046eac3d0982cc5ab42909508cf5d67024d62a2e4/numba-0.65.1-cp314-cp314t-win_amd64.whl", hash = "sha256:20609346e3bd75204950dcbbfe383a8d7dbf4902f442aedbf00f97fef4aa8f38", size = 2758237, upload-time = "2026-04-24T02:02:54.612Z" }, ] [[package]] @@ -2606,26 +2213,17 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287, upload-time = "2025-11-21T02:49:25.755Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899, upload-time = "2025-11-21T02:49:26.87Z" }, { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814, upload-time = "2025-11-21T02:49:28.547Z" }, { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471, upload-time = "2025-11-21T02:49:30.444Z" }, - { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412, upload-time = "2025-11-21T02:49:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/38/38/071ced5a5fd1c85ba0e14ba721b66b053823e5176298c2f707e50bed11d9/numcodecs-0.16.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25be3a516ab677dad890760d357cfe081a371d9c0a2e9a204562318ac5969de3", size = 1654359, upload-time = "2025-11-21T02:49:33.673Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0107e839ef75b854e969cb577e140b1aadb9847893937636582d23a2a4c6ce50", size = 1144237, upload-time = "2025-11-21T02:49:35.294Z" }, { url = "https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:015a7c859ecc2a06e2a548f64008c0ec3aaecabc26456c2c62f4278d8fc20597", size = 8483064, upload-time = "2025-11-21T02:49:36.454Z" }, { url = "https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84230b4b9dad2392f2a84242bd6e3e659ac137b5a1ce3571d6965fca673e0903", size = 9126063, upload-time = "2025-11-21T02:49:38.018Z" }, - { url = "https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl", hash = "sha256:5088145502ad1ebf677ec47d00eb6f0fd600658217db3e0c070c321c85d6cf3d", size = 799275, upload-time = "2025-11-21T02:49:39.558Z" }, - { url = "https://files.pythonhosted.org/packages/3c/9e/38e7ca8184c958b51f45d56a4aeceb1134ecde2d8bd157efadc98502cc42/numcodecs-0.16.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b05647b8b769e6bc8016e9fd4843c823ce5c9f2337c089fb5c9c4da05e5275de", size = 1654721, upload-time = "2025-11-21T02:49:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/a1/37/260fa42e7b2b08e6e00ad632f8dd620961a60a459426c26cea390f8c68d0/numcodecs-0.16.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3832bd1b5af8bb3e413076b7d93318c8e7d7b68935006b9fa36ca057d1725a8f", size = 1146887, upload-time = "2025-11-21T02:49:41.721Z" }, { url = "https://files.pythonhosted.org/packages/4e/15/e2e1151b5a8b14a15dfd4bb4abccce7fff7580f39bc34092780088835f3a/numcodecs-0.16.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49f7b7d24f103187f53135bed28bb9f0ed6b2e14c604664726487bb6d7c882e1", size = 8476987, upload-time = "2025-11-21T02:49:43.363Z" }, { url = "https://files.pythonhosted.org/packages/6d/30/16a57fc4d9fb0ba06c600408bd6634f2f1753c54a7a351c99c5e09b51ee2/numcodecs-0.16.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aec9736d81b70f337d89c4070ee3ffeff113f386fd789492fa152d26a15043e4", size = 9102377, upload-time = "2025-11-21T02:49:45.508Z" }, - { url = "https://files.pythonhosted.org/packages/31/a5/a0425af36c20d55a3ea884db4b4efca25a43bea9214ba69ca7932dd997b4/numcodecs-0.16.5-cp314-cp314-win_amd64.whl", hash = "sha256:b16a14303800e9fb88abc39463ab4706c037647ac17e49e297faa5f7d7dbbf1d", size = 819022, upload-time = "2025-11-21T02:49:47.39Z" }, ] [[package]] @@ -2634,59 +2232,26 @@ version = "2.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, - { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, - { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, - { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, - { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, - { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, - { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, - { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, - { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, - { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, - { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, - { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, - { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, - { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, - { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, - { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, - { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, ] [[package]] @@ -2694,7 +2259,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 = [ @@ -2715,51 +2280,31 @@ name = "pandas" version = "3.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, + { name = "numpy", marker = "sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/b0/c20bd4d6d3f736e6bd6b55794e9cd0a617b858eaad27c8f410ea05d953b7/pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18", size = 10347921, upload-time = "2026-03-31T06:46:33.36Z" }, - { url = "https://files.pythonhosted.org/packages/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14", size = 9888127, upload-time = "2026-03-31T06:46:36.253Z" }, { url = "https://files.pythonhosted.org/packages/61/a9/16ea9346e1fc4a96e2896242d9bc674764fb9049b0044c0132502f7a771e/pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d", size = 10399577, upload-time = "2026-03-31T06:46:39.224Z" }, { url = "https://files.pythonhosted.org/packages/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f", size = 10880030, upload-time = "2026-03-31T06:46:42.412Z" }, { url = "https://files.pythonhosted.org/packages/da/65/7225c0ea4d6ce9cb2160a7fb7f39804871049f016e74782e5dade4d14109/pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab", size = 11409468, upload-time = "2026-03-31T06:46:45.2Z" }, { url = "https://files.pythonhosted.org/packages/fa/5b/46e7c76032639f2132359b5cf4c785dd8cf9aea5ea64699eac752f02b9db/pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d", size = 11936381, upload-time = "2026-03-31T06:46:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4", size = 9744993, upload-time = "2026-03-31T06:46:51.488Z" }, - { url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118, upload-time = "2026-03-31T06:46:54.548Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105, upload-time = "2026-03-31T06:46:57.327Z" }, - { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088, upload-time = "2026-03-31T06:46:59.935Z" }, { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066, upload-time = "2026-03-31T06:47:02.792Z" }, { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780, upload-time = "2026-03-31T06:47:06.205Z" }, { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181, upload-time = "2026-03-31T06:47:09.383Z" }, { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899, upload-time = "2026-03-31T06:47:12.686Z" }, - { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574, upload-time = "2026-03-31T06:47:15.64Z" }, - { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156, upload-time = "2026-03-31T06:47:18.595Z" }, - { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238, upload-time = "2026-03-31T06:47:22.012Z" }, - { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520, upload-time = "2026-03-31T06:47:25.197Z" }, { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154, upload-time = "2026-03-31T06:47:28.097Z" }, { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449, upload-time = "2026-03-31T06:47:30.85Z" }, { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475, upload-time = "2026-03-31T06:47:34.026Z" }, { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568, upload-time = "2026-03-31T06:47:36.941Z" }, - { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652, upload-time = "2026-03-31T06:47:40.612Z" }, - { url = "https://files.pythonhosted.org/packages/bb/40/c6ea527147c73b24fc15c891c3fcffe9c019793119c5742b8784a062c7db/pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b", size = 10326084, upload-time = "2026-03-31T06:47:43.834Z" }, - { url = "https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288", size = 9914146, upload-time = "2026-03-31T06:47:46.67Z" }, { url = "https://files.pythonhosted.org/packages/8d/77/3a227ff3337aa376c60d288e1d61c5d097131d0ac71f954d90a8f369e422/pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c", size = 10444081, upload-time = "2026-03-31T06:47:49.681Z" }, { url = "https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535", size = 10897535, upload-time = "2026-03-31T06:47:53.033Z" }, { url = "https://files.pythonhosted.org/packages/06/9d/98cc7a7624f7932e40f434299260e2917b090a579d75937cb8a57b9d2de3/pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db", size = 11446992, upload-time = "2026-03-31T06:47:56.193Z" }, { url = "https://files.pythonhosted.org/packages/9a/cd/19ff605cc3760e80602e6826ddef2824d8e7050ed80f2e11c4b079741dc3/pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53", size = 11968257, upload-time = "2026-03-31T06:47:59.137Z" }, - { url = "https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf", size = 9865893, upload-time = "2026-03-31T06:48:02.038Z" }, - { url = "https://files.pythonhosted.org/packages/08/71/e5ec979dd2e8a093dacb8864598c0ff59a0cee0bbcdc0bfec16a51684d4f/pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb", size = 9188644, upload-time = "2026-03-31T06:48:05.045Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6c/7b45d85db19cae1eb524f2418ceaa9d85965dcf7b764ed151386b7c540f0/pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d", size = 10776246, upload-time = "2026-03-31T06:48:07.789Z" }, - { url = "https://files.pythonhosted.org/packages/a8/3e/7b00648b086c106e81766f25322b48aa8dfa95b55e621dbdf2fdd413a117/pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8", size = 10424801, upload-time = "2026-03-31T06:48:10.897Z" }, { url = "https://files.pythonhosted.org/packages/da/6e/558dd09a71b53b4008e7fc8a98ec6d447e9bfb63cdaeea10e5eb9b2dabe8/pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd", size = 10345643, upload-time = "2026-03-31T06:48:13.7Z" }, { url = "https://files.pythonhosted.org/packages/be/e3/921c93b4d9a280409451dc8d07b062b503bbec0531d2627e73a756e99a82/pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d", size = 10743641, upload-time = "2026-03-31T06:48:16.659Z" }, { url = "https://files.pythonhosted.org/packages/56/ca/fd17286f24fa3b4d067965d8d5d7e14fe557dd4f979a0b068ac0deaf8228/pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660", size = 11361993, upload-time = "2026-03-31T06:48:19.475Z" }, { url = "https://files.pythonhosted.org/packages/e4/a5/2f6ed612056819de445a433ca1f2821ac3dab7f150d569a59e9cc105de1d/pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702", size = 11815274, upload-time = "2026-03-31T06:48:22.695Z" }, - { url = "https://files.pythonhosted.org/packages/00/2f/b622683e99ec3ce00b0854bac9e80868592c5b051733f2cf3a868e5fea26/pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276", size = 10888530, upload-time = "2026-03-31T06:48:25.806Z" }, - { url = "https://files.pythonhosted.org/packages/cb/2b/f8434233fab2bd66a02ec014febe4e5adced20e2693e0e90a07d118ed30e/pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482", size = 9455341, upload-time = "2026-03-31T06:48:28.418Z" }, ] [[package]] @@ -2785,8 +2330,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 = [ @@ -2807,7 +2352,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { 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 = [ @@ -2820,67 +2365,42 @@ version = "12.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, ] [[package]] @@ -2888,11 +2408,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" } @@ -2919,7 +2439,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 = [ @@ -2940,7 +2460,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 = [ @@ -2953,26 +2473,14 @@ version = "7.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] @@ -3008,41 +2516,26 @@ version = "24.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, - { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, - { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, - { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, - { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, - { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, - { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, - { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, ] [[package]] @@ -3068,8 +2561,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 = [ @@ -3081,8 +2574,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 = [ @@ -3094,10 +2587,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" } @@ -3124,10 +2617,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 = [ @@ -3139,12 +2632,10 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, @@ -3155,11 +2646,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, - { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, @@ -3170,11 +2656,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, - { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, @@ -3185,11 +2666,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, - { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, - { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, - { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, @@ -3200,11 +2676,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, - { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, - { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, - { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, ] @@ -3214,9 +2685,9 @@ name = "pydantic-settings" version = "2.14.0" 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/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } wheels = [ @@ -3228,13 +2699,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 = [ @@ -3246,7 +2717,7 @@ name = "pydocstyle" version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "snowballstemmer" }, + { name = "snowballstemmer", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796, upload-time = "2023-01-17T20:29:19.838Z" } wheels = [ @@ -3258,17 +2729,13 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/7d/d9/3448a57cb5bd19950de6d6ab08bd8fbb3df60baa71726de91d73d76c481b/pyerfa-2.0.1.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b282d7c60c4c47cf629c484c17ac504fcb04abd7b3f4dfcf53ee042afc3a5944", size = 341818, upload-time = "2024-11-11T15:22:16.467Z" }, - { url = "https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:be1aeb70390dd03a34faf96749d5cabc58437410b4aab7213c512323932427df", size = 329370, upload-time = "2024-11-11T15:22:17.829Z" }, { url = "https://files.pythonhosted.org/packages/cb/96/b6210fc624123c8ae13e1eecb68fb75e3f3adff216d95eee1c7b05843e3e/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0603e8e1b839327d586c8a627cdc634b795e18b007d84f0cda5500a0908254e", size = 692794, upload-time = "2024-11-11T15:22:19.429Z" }, { url = "https://files.pythonhosted.org/packages/e5/e0/050018d855d26d3c0b4a7d1b2ed692be758ce276d8289e2a2b44ba1014a5/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e43c7194e3242083f2350b46c09fd4bf8ba1bcc0ebd1460b98fc47fe2389906", size = 738711, upload-time = "2024-11-11T15:22:20.661Z" }, { url = "https://files.pythonhosted.org/packages/b9/f5/ff91ee77308793ae32fa1e1de95e9edd4551456dd888b4e87c5938657ca5/pyerfa-2.0.1.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:07b80cd70701f5d066b1ac8cce406682cfcd667a1186ec7d7ade597239a6021d", size = 722966, upload-time = "2024-11-11T15:22:21.905Z" }, - { url = "https://files.pythonhosted.org/packages/2c/56/b22b35c8551d2228ff8d445e63787112927ca13f6dc9e2c04f69d742c95b/pyerfa-2.0.1.5-cp39-abi3-win32.whl", hash = "sha256:d30b9b0df588ed5467e529d851ea324a67239096dd44703125072fd11b351ea2", size = 339955, upload-time = "2024-11-11T15:22:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/b4/11/97233cf23ad5411ac6f13b1d6ee3888f90ace4f974d9bf9db887aa428912/pyerfa-2.0.1.5-cp39-abi3-win_amd64.whl", hash = "sha256:66292d437dcf75925b694977aa06eb697126e7b86553e620371ed3e48b5e0ad0", size = 349410, upload-time = "2024-11-11T15:22:24.817Z" }, ] [[package]] @@ -3285,8 +2752,8 @@ name = "pyopenssl" version = "26.1.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/8c/a8/26d36401e3ab8eed9030ad33f381da7856fcfad5691780fccd1b019718fc/pyopenssl-26.1.0.tar.gz", hash = "sha256:737f0a2275c5bc54f3b02137687e1a765931fb3949b9a92a825e4d33b9eec08b", size = 186181, upload-time = "2026-04-24T20:23:48.115Z" } wheels = [ @@ -3316,16 +2783,12 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/11/64/42ec1b0bd72d87f87bde6ceb6869f444d91a2d601f2e67cd05febc0346a1/PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2", size = 6579776, upload-time = "2024-07-19T08:39:19.775Z" }, - { url = "https://files.pythonhosted.org/packages/49/f5/3fb696f4683ea45d68b7e77302eff173493ac81e43d63adb60fa760b9f91/PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6", size = 7016415, upload-time = "2024-07-19T08:39:32.977Z" }, { url = "https://files.pythonhosted.org/packages/b4/8c/4065950f9d013c4b2e588fe33cf04e564c2322842d84dbcbce5ba1dc28b0/PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1", size = 8188103, upload-time = "2024-07-19T08:39:40.561Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/ae5a5b4f9b826b29ea4be841b2f2d951bcf5ae1d802f3732b145b57c5355/PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd", size = 5433308, upload-time = "2024-07-19T08:39:46.932Z" }, - { url = "https://files.pythonhosted.org/packages/56/d5/68eb9f3d19ce65df01b6c7b7a577ad3bbc9ab3a5dd3491a4756e71838ec9/PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517", size = 6865864, upload-time = "2024-07-19T08:39:53.572Z" }, ] [[package]] @@ -3333,8 +2796,6 @@ name = "pyqt5-qt5" version = "5.15.18" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/90/bf01ac2132400997a3474051dd680a583381ebf98b2f5d64d4e54138dc42/pyqt5_qt5-5.15.18-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:8bb997eb903afa9da3221a0c9e6eaa00413bbeb4394d5706118ad05375684767", size = 39715743, upload-time = "2025-11-09T12:56:42.936Z" }, - { url = "https://files.pythonhosted.org/packages/24/8e/76366484d9f9dbe28e3bdfc688183433a7b82e314216e9b14c89e5fab690/pyqt5_qt5-5.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c656af9c1e6aaa7f59bf3d8995f2fa09adbf6762b470ed284c31dca80d686a26", size = 36798484, upload-time = "2025-11-09T12:56:59.998Z" }, { url = "https://files.pythonhosted.org/packages/9a/46/ffe177f99f897a59dc237a20059020427bd2d3853d713992b8081933ddfe/pyqt5_qt5-5.15.18-py3-none-manylinux2014_x86_64.whl", hash = "sha256:bf2457e6371969736b4f660a0c153258fa03dbc6a181348218e6f05421682af7", size = 60864590, upload-time = "2025-11-09T12:57:26.724Z" }, ] @@ -3344,21 +2805,12 @@ version = "12.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f3/31/5ef342de9faee0f3801088946ae103db9b9eaeba3d6a64fefd5ce74df244/pyqt5_sip-12.18.0.tar.gz", hash = "sha256:71c37db75a0664325de149f43e2a712ec5fa1f90429a21dafbca005cb6767f94", size = 104143, upload-time = "2026-01-13T15:53:19.576Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/61/6d78d702016ac23d2b97634a3b6a831c3f7735f0552a1c8b058db96005d1/pyqt5_sip-12.18.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b29e4cda24748e59e5bd1bdad4812091a86b4b5b08c38b7f781eb55a5166f2b7", size = 124614, upload-time = "2026-01-13T15:52:57.59Z" }, { url = "https://files.pythonhosted.org/packages/19/bf/8f3efa10ddd3e76c1253865340ab7c2960ef96681d732b1f666c77430612/pyqt5_sip-12.18.0-cp312-cp312-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:163c2bba5e637c2222ec17d82a2c5aa158184a191923eb7d137cf4cfa0399529", size = 339412, upload-time = "2026-01-13T15:53:00.563Z" }, { url = "https://files.pythonhosted.org/packages/72/48/f1bcf6729d01bae6729cd790b22fd579dbe34014e8be031e6f10c5b9b2aa/pyqt5_sip-12.18.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ead5e0a64ad852ac60797989d8444a6a5bd834768536b04a07b40b2937d922f6", size = 282376, upload-time = "2026-01-13T15:52:59.172Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b7/d84c764ac9f1366be561255ec9bd88ee224fefdbdb349aee250f3003f0ca/pyqt5_sip-12.18.0-cp312-cp312-win32.whl", hash = "sha256:993fe3ed9a62a92e770f32d5344e3df56c2cacf1471f01b7feaf04818a2df1c4", size = 49523, upload-time = "2026-01-13T15:53:03.068Z" }, - { url = "https://files.pythonhosted.org/packages/ab/e7/ef87178d5afa5f63be38556dc0df8af89f9bf74f2555f4dab6824c0fd150/pyqt5_sip-12.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:9b689e02e400abd1ce0a30cd6eae8eceabcf1bbba0395cb5c86e64ba74351d68", size = 58001, upload-time = "2026-01-13T15:53:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/79/67/8d43d0fea10ff48ddecc8534aead8b855dc80df80653b8b1bf9e1f993063/pyqt5_sip-12.18.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9254e5dd7676b76503ba20edcc919e7ac4a97b6c70a6fb2f9dba9e13b4c60509", size = 124605, upload-time = "2026-01-13T15:53:04.991Z" }, { url = "https://files.pythonhosted.org/packages/48/2a/b08bc8efeb49c50c6cdac11417dc2c8eaefcac2f0a6382eae7b26dc0f232/pyqt5_sip-12.18.0-cp313-cp313-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c969631ada7293a81e1012b2264a62d69a91995b517586489dfe24421b87b9af", size = 339918, upload-time = "2026-01-13T15:53:08.502Z" }, { url = "https://files.pythonhosted.org/packages/b6/99/24f82437b2f073cf39296b7c731b6a8bc0f5207911fdd93841a0ea9abe42/pyqt5_sip-12.18.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d84ac384a63285132e67762c87681191c25e28a1df7560287ec3889d9eb223b5", size = 282088, upload-time = "2026-01-13T15:53:06.632Z" }, - { url = "https://files.pythonhosted.org/packages/3e/27/20d3924943df34361fae9c6a0489ae89d0b07571693245c61678d185e4a4/pyqt5_sip-12.18.0-cp313-cp313-win32.whl", hash = "sha256:95bba4670ecf5cba73958b85aa2087c17838a402ed251c38e68060c7665c998b", size = 49501, upload-time = "2026-01-13T15:53:11.159Z" }, - { url = "https://files.pythonhosted.org/packages/3f/36/e251623c12968730730512a9e5150430e36246afbe64894610190b896f61/pyqt5_sip-12.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:aac4adc37df2f2ac1dc259409be1900f07332d140a12c9db7c84112cef64ff59", size = 58076, upload-time = "2026-01-13T15:53:09.928Z" }, - { url = "https://files.pythonhosted.org/packages/37/3a/b46a0116b1aacbb6156b2957eb5cb928c94b49f4626eb2540ca8d16ee757/pyqt5_sip-12.18.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8372ec8704bfd5e09942d0d055a1657eb4f702f4b30847a5e59df0496f99d67f", size = 124594, upload-time = "2026-01-13T15:53:13.159Z" }, { url = "https://files.pythonhosted.org/packages/58/63/df3037f11391c25c5b0ab233d22e58b8f056cb1ce16d7ecadb844421ce75/pyqt5_sip-12.18.0-cp314-cp314-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdb45c7cd2af7eccd7370b994d432bfc7965079f845392760724f26771bb59dc", size = 339056, upload-time = "2026-01-13T15:53:16.558Z" }, { url = "https://files.pythonhosted.org/packages/f5/e7/4f96b84520b8f8b7502682fd43f68f63ca6572b5858f56e5f61c76a54fe2/pyqt5_sip-12.18.0-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:92abe984becbde768954d6d0951f56d80a9868d2fd9e738e61fc944f0ff83dd6", size = 282439, upload-time = "2026-01-13T15:53:14.856Z" }, - { url = "https://files.pythonhosted.org/packages/79/8e/ccdf20d373ceba83e1d1b7f818505c375208ffde4a96376dc7dbe592406c/pyqt5_sip-12.18.0-cp314-cp314-win32.whl", hash = "sha256:bd9e3c6f81346f1b08d6db02305cdee20c009b43aa083d44ee2de47a7da0e123", size = 50713, upload-time = "2026-01-13T15:53:18.634Z" }, - { url = "https://files.pythonhosted.org/packages/7f/21/8486ed45977be615ec5371b24b47298b1cb0e1a455b419eddd0215078dba/pyqt5_sip-12.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:6d948f1be619c645cd3bda54952bfdc1aef7c79242dccea6a6858748e61114b9", size = 59622, upload-time = "2026-01-13T15:53:17.714Z" }, ] [[package]] @@ -3366,32 +2818,22 @@ 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" }, ] -[[package]] -name = "pyreadline3" -version = "3.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, -] - [[package]] name = "pytest" version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { 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/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ @@ -3403,9 +2845,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 = [ @@ -3417,8 +2859,8 @@ name = "pytest-pycodestyle" version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycodestyle" }, - { name = "pytest" }, + { name = "pycodestyle", marker = "sys_platform == 'linux'" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/60/634e069a52137207b988359319c7030b25195f014822724f259325fa3af5/pytest_pycodestyle-2.5.0.tar.gz", hash = "sha256:dd0060039e12a59b521da8e57e17133c965566dd8d17631e589e7545238829ac", size = 5859, upload-time = "2025-07-20T03:18:12.504Z" } @@ -3427,8 +2869,8 @@ name = "pytest-pydocstyle" version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydocstyle" }, - { name = "pytest" }, + { name = "pydocstyle", marker = "sys_platform == 'linux'" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/cd/555a357a1da1c680b6417dec1aff2f5187f5559a06297b6cfab930745fc0/pytest_pydocstyle-2.4.0.tar.gz", hash = "sha256:3770689778ad8d0de8cb51264f3d9b807c11d0ecc31f95e7025426eec126c4d2", size = 5583, upload-time = "2024-10-09T09:35:17.448Z" } @@ -3437,7 +2879,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 = [ @@ -3467,16 +2909,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" } @@ -3485,7 +2927,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 = [ @@ -3498,26 +2940,18 @@ version = "0.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, - { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, - { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, - { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] @@ -3535,8 +2969,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 = [ @@ -3548,77 +2982,30 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/5c/37/3fda13fb2518fdd306528382d6b18c116ceafefff0a7dccd28f1034f4dd2/pywavelets-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30baa0788317d3c938560c83fe4fc43817342d06e6c9662a440f73ba3fb25c9b", size = 4320835, upload-time = "2025-08-04T16:19:04.855Z" }, - { url = "https://files.pythonhosted.org/packages/36/65/a5549325daafc3eae4b52de076798839eaf529a07218f8fb18cccefe76a1/pywavelets-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df7436a728339696a7aa955c020ae65c85b0d9d2b5ff5b4cf4551f5d4c50f2c7", size = 4290469, upload-time = "2025-08-04T16:19:06.178Z" }, { url = "https://files.pythonhosted.org/packages/05/85/901bb756d37dfa56baa26ef4a3577aecfe9c55f50f51366fede322f8c91d/pywavelets-1.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07b26526db2476974581274c43a9c2447c917418c6bd03c8d305ad2a5cd9fac3", size = 4437717, upload-time = "2025-08-04T16:19:07.514Z" }, { url = "https://files.pythonhosted.org/packages/0f/34/0f54dd9c288941294898877008bcb5c07012340cc9c5db9cff1bd185d449/pywavelets-1.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:573b650805d2f3c981a0e5ae95191c781a722022c37a0f6eba3fa7eae8e0ee17", size = 4483843, upload-time = "2025-08-04T16:19:08.857Z" }, { url = "https://files.pythonhosted.org/packages/48/1f/cff6bb4ea64ff508d8cac3fe113c0aa95310a7446d9efa6829027cc2afdf/pywavelets-1.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3747ec804492436de6e99a7b6130480e53406d047e87dc7095ab40078a515a23", size = 4442236, upload-time = "2025-08-04T16:19:11.061Z" }, { url = "https://files.pythonhosted.org/packages/ce/53/a3846eeefe0fb7ca63ae045f038457aa274989a15af793c1b824138caf98/pywavelets-1.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5163665686219c3f43fd5bbfef2391e87146813961dad0f86c62d4aed561f547", size = 4488077, upload-time = "2025-08-04T16:19:12.333Z" }, - { url = "https://files.pythonhosted.org/packages/f7/98/44852d2fe94455b72dece2db23562145179d63186a1c971125279a1c381f/pywavelets-1.9.0-cp312-cp312-win32.whl", hash = "sha256:80b8ab99f5326a3e724f71f23ba8b0a5b03e333fa79f66e965ea7bed21d42a2f", size = 4134094, upload-time = "2025-08-04T16:19:13.564Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a7/0d9ee3fe454d606e0f5c8e3aebf99d2ecddbfb681826a29397729538c8f1/pywavelets-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:92bfb8a117b8c8d3b72f2757a85395346fcbf37f50598880879ae72bd8e1c4b9", size = 4213900, upload-time = "2025-08-04T16:19:14.939Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/dec4e450675d62946ad975f5b4d924437df42d2fae46e91dfddda2de0f5a/pywavelets-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:74f8455c143818e4b026fc67b27fd82f38e522701b94b8a6d1aaf3a45fcc1a25", size = 4316201, upload-time = "2025-08-04T16:19:16.259Z" }, - { url = "https://files.pythonhosted.org/packages/aa/0c/b54b86596c0df68027e48c09210e907e628435003e77048384a2dd6767e3/pywavelets-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c50320fe0a4a23ddd8835b3dc9b53b09ee05c7cc6c56b81d0916f04fc1649070", size = 4286838, upload-time = "2025-08-04T16:19:17.92Z" }, { url = "https://files.pythonhosted.org/packages/5a/9c/333969c3baad8af2e7999e83addcb7bb1d1fd48e2d812fb27e2e89582cb1/pywavelets-1.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6e059265223ed659e5214ab52a84883c88ddf3decbf08d7ec6abb8e4c5ed7be", size = 4430753, upload-time = "2025-08-04T16:19:19.529Z" }, { url = "https://files.pythonhosted.org/packages/e5/1b/a24c6ff03b026b826ad7b9267bd63cd34ce026795a0302f8a5403840b8e7/pywavelets-1.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae10ed46c139c7ddb8b1249cfe0989f8ccb610d93f2899507b1b1573a0e424b5", size = 4491315, upload-time = "2025-08-04T16:19:20.717Z" }, { url = "https://files.pythonhosted.org/packages/d7/c7/e3fbb502fca3469e51ced4f1e1326364c338be91edc5db5a8ddd26b303fa/pywavelets-1.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8f8b1cc2df012401cb837ee6fa2f59607c7b4fe0ff409d9a4f6906daf40dc86", size = 4437654, upload-time = "2025-08-04T16:19:22.359Z" }, { url = "https://files.pythonhosted.org/packages/92/44/c9b25084048d9324881a19b88e0969a4141bcfdc1d218f1b4b680b7af1c1/pywavelets-1.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db43969c7a8fbb17693ecfd14f21616edc3b29f0e47a49b32fa4127c01312a67", size = 4496435, upload-time = "2025-08-04T16:19:23.842Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b6/b27ec18c72b1dee3314e297af39c5f8136d43cc130dd93cb6c178ca820e5/pywavelets-1.9.0-cp313-cp313-win32.whl", hash = "sha256:9e7d60819d87dcd6c68a2d1bc1d37deb1f4d96607799ab6a25633ea484dcda41", size = 4132709, upload-time = "2025-08-04T16:19:25.415Z" }, - { url = "https://files.pythonhosted.org/packages/0a/87/78ef3f9fb36cdb16ee82371d22c3a7c89eeb79ec8c9daef6222060da6c79/pywavelets-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:0d70da9d7858c869e24dc254f16a61dc09d8a224cad85a10c393b2eccddeb126", size = 4213377, upload-time = "2025-08-04T16:19:26.875Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cd/ca0d9db0ff29e3843f6af60c2f5eb588794e05ca8eeb872a595867b1f3f5/pywavelets-1.9.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dc85f44c38d76a184a1aa2cb038f802c3740428c9bb877525f4be83a223b134", size = 4354336, upload-time = "2025-08-04T16:19:28.745Z" }, - { url = "https://files.pythonhosted.org/packages/82/d6/70afefcc1139f37d02018a3b1dba3b8fc87601bb7707d9616b7f7a76e269/pywavelets-1.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7acf6f950c6deaecd210fbff44421f234a8ca81eb6f4da945228e498361afa9d", size = 4335721, upload-time = "2025-08-04T16:19:30.371Z" }, { url = "https://files.pythonhosted.org/packages/cd/3a/713f731b9ed6df0c36269c8fb62be8bb28eb343b9e26b13d6abda37bce38/pywavelets-1.9.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:144d4fc15c98da56654d0dca2d391b812b8d04127b194a37ad4a497f8e887141", size = 4418702, upload-time = "2025-08-04T16:19:31.743Z" }, { url = "https://files.pythonhosted.org/packages/44/e8/f801eb4b5f7a316ba20054948c5d6b27b879c77fab2674942e779974bd86/pywavelets-1.9.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1aa3729585408a979d655736f74b995b511c86b9be1544f95d4a3142f8f4b8b5", size = 4470023, upload-time = "2025-08-04T16:19:32.963Z" }, { url = "https://files.pythonhosted.org/packages/e9/cc/44b002cb16f2a392f2082308dd470b3f033fa4925d3efa7c46f790ce895a/pywavelets-1.9.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e0e24ad6b8eb399c49606dd1fcdcbf9749ad7f6d638be3fe6f59c1f3098821e2", size = 4426498, upload-time = "2025-08-04T16:19:34.151Z" }, { url = "https://files.pythonhosted.org/packages/91/fe/2b70276ede7878c5fe8356ca07574db5da63e222ce39a463e84bfad135e8/pywavelets-1.9.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3830e6657236b53a3aae20c735cccead942bb97c54bbca9e7d07bae01645fe9c", size = 4477528, upload-time = "2025-08-04T16:19:35.932Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ed/d58b540c15e36508cfeded7b0d39493e811b0dce18d9d4e6787fb2e89685/pywavelets-1.9.0-cp313-cp313t-win32.whl", hash = "sha256:81bb65facfbd7b50dec50450516e72cdc51376ecfdd46f2e945bb89d39bfb783", size = 4186493, upload-time = "2025-08-04T16:19:37.198Z" }, - { url = "https://files.pythonhosted.org/packages/84/b2/12a849650d618a86bbe4d8876c7e20a7afe59a8cad6f49c57eca9af26dfa/pywavelets-1.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:47d52cf35e2afded8cfe1133663f6f67106a3220b77645476ae660ad34922cb4", size = 4274821, upload-time = "2025-08-04T16:19:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1f/18c82122547c9eec2232d800b02ada1fbd30ce2136137b5738acca9d653e/pywavelets-1.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:53043d2f3f4e55a576f51ac594fe33181e1d096d958e01524db5070eb3825306", size = 4314440, upload-time = "2025-08-04T16:19:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e1/1c92ac6b538ef5388caf1a74af61cf6af16ea6d14115bb53357469cb38d6/pywavelets-1.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bc36b42b1b125fd9cb56e7956b22f8d0f83c1093f49c77fc042135e588c799", size = 4290162, upload-time = "2025-08-04T16:19:41.322Z" }, { url = "https://files.pythonhosted.org/packages/96/d3/d856a2cac8069c20144598fa30a43ca40b5df2e633230848a9a942faf04a/pywavelets-1.9.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08076eb9a182ddc6054ac86868fb71df6267c341635036dc63d20bdbacd9ad7e", size = 4437162, upload-time = "2025-08-04T16:19:42.556Z" }, { url = "https://files.pythonhosted.org/packages/c9/54/777e0495acd4fb008791e84889be33d6e7fc8af095b441d939390b7d2491/pywavelets-1.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ee1ee7d80f88c64b8ec3b5021dd1e94545cc97f0cd479fb51aa7b10f6def08e", size = 4498169, upload-time = "2025-08-04T16:19:43.791Z" }, { url = "https://files.pythonhosted.org/packages/76/68/81b97f4d18491a18fbe17e06e2eee80a591ce445942f7b6f522de07813c5/pywavelets-1.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3226b6f62838a6ccd7782cb7449ee5d8b9d61999506c1d9b03b2baf41b01b6fd", size = 4443318, upload-time = "2025-08-04T16:19:45.368Z" }, { url = "https://files.pythonhosted.org/packages/92/74/5147f2f0436f7aa131cb1bc13dba32ef5f3862748ae1c7366b4cde380362/pywavelets-1.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb7f4b11d18e2db6dd8deee7b3ce8343d45f195f3f278c2af6e3724b1b93a24", size = 4503294, upload-time = "2025-08-04T16:19:46.632Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d4/af998cc71e869919e0ab45471bd43e91d055ac7bc3ce6f56cc792c9b6bc8/pywavelets-1.9.0-cp314-cp314-win32.whl", hash = "sha256:9902d9fc9812588ab2dce359a1307d8e7f002b53a835640e2c9388fe62a82fd4", size = 4144478, upload-time = "2025-08-04T16:19:47.974Z" }, - { url = "https://files.pythonhosted.org/packages/7d/66/1d071eae5cc3e3ad0e45334462f8ce526a79767ccb759eb851aa5b78a73a/pywavelets-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:7e57792bde40e331d6cc65458e5970fd814dba18cfc4e9add9d051e901a7b7c7", size = 4227186, upload-time = "2025-08-04T16:19:49.57Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1f/da0c03ac99bd9d20409c0acf6417806d4cf333d70621da9f535dd0cf27fa/pywavelets-1.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b47c72fb4b76d665c4c598a5b621b505944e5b761bf03df9d169029aafcb652f", size = 4354391, upload-time = "2025-08-04T16:19:51.221Z" }, - { url = "https://files.pythonhosted.org/packages/95/b6/de9e225d8cc307fbb4fda88aefa79442775d5e27c58ee4d3c8a8580ceba6/pywavelets-1.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:969e369899e7eab546ea5d77074e4125082e6f9dad71966499bf5dee3758be55", size = 4335810, upload-time = "2025-08-04T16:19:52.813Z" }, { url = "https://files.pythonhosted.org/packages/33/3b/336761359d07cd44a4233ca854704ff2a9e78d285879ccc82d254b9daa57/pywavelets-1.9.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8aeffd4f35036c1fade972a61454de5709a7a8fc9a7d177eefe3ac34d76962e5", size = 4422220, upload-time = "2025-08-04T16:19:54.068Z" }, { url = "https://files.pythonhosted.org/packages/98/61/76ccc7ada127f14f65eda40e37407b344fd3713acfca7a94d7f0f67fe57d/pywavelets-1.9.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f63f400fcd4e7007529bd06a5886009760da35cd7e76bb6adb5a5fbee4ffeb8c", size = 4470156, upload-time = "2025-08-04T16:19:55.379Z" }, { url = "https://files.pythonhosted.org/packages/e0/de/142ca27ee729cf64113c2560748fcf2bd45b899ff282d6f6f3c0e7f177bb/pywavelets-1.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a63bcb6b5759a7eb187aeb5e8cd316b7adab7de1f4b5a0446c9a6bcebdfc22fb", size = 4430167, upload-time = "2025-08-04T16:19:56.566Z" }, { url = "https://files.pythonhosted.org/packages/ca/5e/90b39adff710d698c00ba9c3125e2bec99dad7c5f1a3ba37c73a78a6689f/pywavelets-1.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9950eb7c8b942e9bfa53d87c7e45a420dcddbd835c4c5f1aca045a3f775c6113", size = 4477378, upload-time = "2025-08-04T16:19:58.162Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1a/89f5f4ebcb9d34d9b7b2ac0a868c8b6d8c78d699a36f54407a060cea0566/pywavelets-1.9.0-cp314-cp314t-win32.whl", hash = "sha256:097f157e07858a1eb370e0d9c1bd11185acdece5cca10756d6c3c7b35b52771a", size = 4209132, upload-time = "2025-08-04T16:20:00.371Z" }, - { url = "https://files.pythonhosted.org/packages/68/d2/a8065103f5e2e613b916489e6c85af6402a1ec64f346d1429e2d32cb8d03/pywavelets-1.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3b6ff6ba4f625d8c955f68c2c39b0a913776d406ab31ee4057f34ad4019fb33b", size = 4306793, upload-time = "2025-08-04T16:20:02.934Z" }, -] - -[[package]] -name = "pywin32-ctypes" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, -] - -[[package]] -name = "pywinpty" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, - { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, - { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686, upload-time = "2026-02-04T21:52:03.035Z" }, - { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591, upload-time = "2026-02-04T21:52:29.821Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360, upload-time = "2026-02-04T21:55:33.397Z" }, - { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107, upload-time = "2026-02-04T21:51:45.455Z" }, - { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282, upload-time = "2026-02-04T21:52:08.188Z" }, - { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207, upload-time = "2026-02-04T21:51:58.687Z" }, - { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910, upload-time = "2026-02-04T21:52:30.976Z" }, - { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425, upload-time = "2026-02-04T21:51:56.754Z" }, ] [[package]] @@ -3627,44 +3014,26 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] @@ -3672,42 +3041,28 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, - { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, - { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, - { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, ] [[package]] @@ -3715,7 +3070,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 = [ @@ -3727,9 +3082,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 = [ @@ -3741,9 +3096,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 = [ @@ -3755,24 +3110,21 @@ name = "reproject" version = "0.19.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/61/d7/397a281c326114d970ee5c7f1a61024741f71eb0e5078e092ea1366359c6/reproject-0.19.0.tar.gz", hash = "sha256:ba5d6ac9341258a9e270dc87805f3bd7409253ed73b47ae5fa68903b49c172c9", size = 1615254, upload-time = "2025-11-14T15:53:09.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/57/bf1e36697bebf67721d21966a840278d1b43e6a80591ff5292facbf32612/reproject-0.19.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:67cc917218bad54b95b1a871787b347caa42291e052f66c7b7a173c065d9c6ad", size = 998350, upload-time = "2025-11-14T15:53:03.688Z" }, - { url = "https://files.pythonhosted.org/packages/47/b5/3bad351a767b5690461014663570b717373f22dccffbf1a7173c8db0f270/reproject-0.19.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:60f49baabeee8c5dc8ef3a98335bc014b724c38e716a3108337dfd093aa41190", size = 999662, upload-time = "2025-11-14T15:53:05.206Z" }, { url = "https://files.pythonhosted.org/packages/e2/59/a4e5cb06c6c9a64745ee05dda814b8f523a5e11f7af6d027624a5fa140d4/reproject-0.19.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfe3edc8e0f47662e586658e3cbb061e54f6d4ac516916837bad05e2f9be93e5", size = 1583616, upload-time = "2025-11-14T15:53:06.14Z" }, { url = "https://files.pythonhosted.org/packages/bc/c8/e6957a84d7e074730cc59cfc8a68bbf24ac83356db9288764f0ef092eb98/reproject-0.19.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8314eb300e15f55c6dc929646f4a7da19fc4ca30b3e6d7fb057e027566acdb3", size = 1592824, upload-time = "2025-11-14T15:53:07.159Z" }, - { url = "https://files.pythonhosted.org/packages/0c/10/18df4d51fdb124c446515820e58808f4fc38bbcf224d3c460daa9816fb6f/reproject-0.19.0-cp311-abi3-win_amd64.whl", hash = "sha256:cd6cdb407fa7dbf5c555663d3188b462d571bdaffc41bda3ad21763ac39c4546", size = 996067, upload-time = "2025-11-14T15:53:08.18Z" }, ] [[package]] @@ -3780,10 +3132,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 = [ @@ -3795,7 +3147,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 = [ @@ -3807,7 +3159,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 = [ @@ -3837,7 +3189,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 = [ @@ -3849,8 +3201,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 = [ @@ -3872,8 +3224,6 @@ version = "0.30.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, @@ -3884,11 +3234,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, @@ -3899,11 +3244,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, @@ -3914,10 +3254,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, @@ -3928,11 +3264,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, @@ -3943,8 +3274,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, ] [[package]] @@ -3952,57 +3281,37 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, - { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, - { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, - { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, - { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, - { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, - { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, - { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, - { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, - { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, - { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, - { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, - { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, - { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, - { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, ] [[package]] @@ -4010,43 +3319,23 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, - { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, - { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, - { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, - { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, - { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, - { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, - { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, - { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, ] [[package]] @@ -4054,60 +3343,30 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] @@ -4115,8 +3374,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "jeepney", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { 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 = [ @@ -4155,9 +3414,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" } @@ -4166,82 +3425,82 @@ name = "shapepipe" version = "1.1.0" source = { virtual = "." } 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 = "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 = "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 = "black" }, - { name = "build" }, - { name = "fitsio" }, - { name = "ipython" }, - { name = "isort" }, - { name = "jupyterlab" }, - { name = "myst-parser" }, - { name = "numpydoc" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-pycodestyle" }, - { name = "pytest-pydocstyle" }, - { name = "snakemake" }, - { name = "sphinx" }, - { name = "sphinx-book-theme" }, - { name = "sphinxcontrib-bibtex" }, - { name = "twine" }, + { name = "black", marker = "sys_platform == 'linux'" }, + { name = "build", marker = "sys_platform == 'linux'" }, + { name = "fitsio", marker = "sys_platform == 'linux'" }, + { name = "ipython", marker = "sys_platform == 'linux'" }, + { name = "isort", marker = "sys_platform == 'linux'" }, + { name = "jupyterlab", 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 = "pytest-pycodestyle", marker = "sys_platform == 'linux'" }, + { name = "pytest-pydocstyle", 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 = "black" }, - { name = "isort" }, + { name = "black", marker = "sys_platform == 'linux'" }, + { name = "isort", marker = "sys_platform == 'linux'" }, ] release = [ - { name = "build" }, - { name = "twine" }, + { name = "build", marker = "sys_platform == 'linux'" }, + { name = "twine", marker = "sys_platform == 'linux'" }, ] test = [ - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-pycodestyle" }, - { name = "pytest-pydocstyle" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, + { name = "pytest-cov", marker = "sys_platform == 'linux'" }, + { name = "pytest-pycodestyle", marker = "sys_platform == 'linux'" }, + { name = "pytest-pydocstyle", marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -4264,7 +3523,7 @@ requires-dist = [ { name = "modopt", specifier = ">=1.6" }, { name = "mpi4py", specifier = ">=4.0" }, { name = "myst-parser", marker = "extra == 'doc'" }, - { name = "ngmix", git = "https://github.com/aguinot/ngmix?rev=stable_version" }, + { name = "ngmix", git = "https://github.com/esheldon/ngmix?tag=v2.4.0" }, { name = "numba", specifier = ">=0.59" }, { name = "numpy", specifier = ">=2.0" }, { name = "numpydoc", marker = "extra == 'doc'" }, @@ -4316,13 +3575,13 @@ 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 = [ @@ -4343,7 +3602,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 = [ @@ -4364,37 +3623,37 @@ name = "snakemake" version = "9.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appdirs" }, - { 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 = "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 = "appdirs", marker = "sys_platform == 'linux'" }, + { 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 = "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/34/46/feadc4a095b24b09b31e10a283da3ef1cacc2ba82473763ed4f01ac151e1/snakemake-9.19.0.tar.gz", hash = "sha256:c7d3fbbf00b1bdf992bc61b9dcee21b5c3fb95a038cd58d53c6369a7c4f0609e", size = 6779935, upload-time = "2026-03-28T11:42:38.447Z" } wheels = [ @@ -4406,9 +3665,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 = [ @@ -4420,9 +3679,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 = [ @@ -4434,7 +3693,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 = [ @@ -4446,7 +3705,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 = [ @@ -4458,7 +3717,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 = [ @@ -4470,11 +3729,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 = [ @@ -4504,23 +3763,22 @@ name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { 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 = [ @@ -4532,8 +3790,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 = [ @@ -4554,10 +3812,10 @@ name = "sphinxcontrib-bibtex" version = "2.6.5" 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/de/83/1488c9879f2fa3c2cbd6f666c7a3a42a1fa9e08462bec73281fa6c092cba/sphinxcontrib_bibtex-2.6.5.tar.gz", hash = "sha256:9b3224dd6fece9268ebd8c905dc0a83ff2f6c54148a9235fe70e9d1e9ff149c0", size = 118462, upload-time = "2025-06-27T10:40:14.061Z" } wheels = [ @@ -4614,44 +3872,31 @@ 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 = [ - { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, - { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, - { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, - { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, - { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, - { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, - { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, ] @@ -4666,8 +3911,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 = [ @@ -4679,9 +3924,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 = [ @@ -4694,7 +3939,6 @@ version = "4.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9c/99/0556919f96b1c291a7ff83793adb328e2a1a5e6cfb379d5acc637cdec959/swig-4.4.1.tar.gz", hash = "sha256:db9a625653f6454530a6c6553d17cf576d7aa2d253c76dc8cdd5979f0cb15012", size = 25956, upload-time = "2025-12-27T03:25:49.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/92/5c2d8617871eeda00467afe94a7d58a1ad0217d71a39905ed682da9dfef8/swig-4.4.1-py3-none-macosx_10_9_universal2.whl", hash = "sha256:c185b217d2ed96ef475c48956e52dd3572a63d3aa3344bc45ee3fb4dc339ada0", size = 2592417, upload-time = "2025-12-27T03:25:14.681Z" }, { url = "https://files.pythonhosted.org/packages/c0/4d/860e5475fe38b9c7dc36f61d0c370a62b6cc725d2bd11ada1d22a60ff5f7/swig-4.4.1-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:520fde8805b4775ef3814576929b8c44d666220b06d8da625f292192f9e74fe8", size = 1917511, upload-time = "2025-12-27T03:25:18.064Z" }, { url = "https://files.pythonhosted.org/packages/84/e1/5cd4de23ff2b1d225279f7c7826191b1dbdb77fc858fe12ee7be2562ddb7/swig-4.4.1-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d8cca45a0e8d1e045a1490ea2d05bc1c49e2c266dbbe5954048e2a93d125ee07", size = 2066284, upload-time = "2025-12-27T03:25:19.624Z" }, { url = "https://files.pythonhosted.org/packages/96/42/d49992f3c39c90d3d46c431a4afea60fc16bae542cf07f394b298a1b21b5/swig-4.4.1-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:bfe6ce6189fc8b983f65f5ed3db8e1d8be03accb0ec3869f986a8343f014e4d9", size = 1912492, upload-time = "2025-12-27T03:25:21.843Z" }, @@ -4709,8 +3953,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/03/88ca92d3d8e9dd765a66985202db23745bd26a368570a8d8db9a218bfe90/swig-4.4.1-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:6a6a6331a5c7759ba5a910aa83aa5d33022b8e691b6cb466635c7fc2238facd2", size = 2967789, upload-time = "2025-12-27T03:25:40.24Z" }, { url = "https://files.pythonhosted.org/packages/bf/97/5ca3d750f294048c983edfad1f2ad093a83480494fccb2f2e2496304f965/swig-4.4.1-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:2d076358cded172d2f44b3c617923c593dbae664b3fa45051641f028f7f74010", size = 3203253, upload-time = "2025-12-27T03:25:41.953Z" }, { url = "https://files.pythonhosted.org/packages/97/44/96586955c0f08b0b9b6c8b2b68ecb43a815da94d5362cfb6691ede3b9fdf/swig-4.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43a0a876b4660f6addb6ae72d14a72324dc1388e1c168ed38bb00fd123048749", size = 3060721, upload-time = "2025-12-27T03:25:44.322Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2f/7a6b14eea6d0247d3f803649f50df3cf3905714bd82f2c386fcfde9544f3/swig-4.4.1-py3-none-win_amd64.whl", hash = "sha256:4548c06ea2ed60c91f003bbc3892db58e185f572705653ab1470ca770f70495d", size = 2548684, upload-time = "2025-12-27T03:25:46.255Z" }, - { url = "https://files.pythonhosted.org/packages/26/c1/c316899d55a476ee9433bede6b94899b117c5e4b7947a073ef21053d4a21/swig-4.4.1-py3-none-win_arm64.whl", hash = "sha256:063224fe9a2f22fffc7d3b9add250112630f0e00f5f8dd9b9591ac106560177a", size = 2548683, upload-time = "2025-12-27T03:25:48.319Z" }, ] [[package]] @@ -4718,7 +3960,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 = [ @@ -4757,9 +3999,8 @@ name = "terminado" version = "0.18.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "os_name != 'nt'" }, - { name = "pywinpty", 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 = [ @@ -4789,7 +4030,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 = [ @@ -4801,7 +4042,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 = [ @@ -4832,24 +4073,16 @@ version = "6.5.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, ] [[package]] name = "tqdm" version = "4.67.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, @@ -4869,15 +4102,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 = [ @@ -4889,10 +4122,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 = [ @@ -4913,7 +4146,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 = [ @@ -4952,9 +4185,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 = [ @@ -5003,61 +4236,36 @@ version = "2.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, - { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, - { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, - { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, - { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, - { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, - { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, - { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, - { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, - { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, - { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, - { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, - { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, - { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, - { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, ] @@ -5066,9 +4274,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 = [ @@ -5080,12 +4288,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 = [ @@ -5107,25 +4315,13 @@ version = "8.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9f/65/34a6e6e4dfa260c4c55ee02bb2fc53625e126ff0181485286cf0c9d453d6/zope_interface-8.4.tar.gz", hash = "sha256:9dbee7925a23aa6349738892c911019d4095a96cff487b743482073ecbc174a8", size = 257736, upload-time = "2026-04-25T07:22:10.439Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/96/0017b980424125cf98a9851d8fd3e24939818b7a82ecdd19ae672bb2413f/zope_interface-8.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84064876ed96ddd0744e3ad5d37134c758d77885e54113567792671405a02bac", size = 211604, upload-time = "2026-04-25T07:28:08.13Z" }, - { url = "https://files.pythonhosted.org/packages/59/4c/2cf5c45477fdd58a2c786d0c0d1817cbaaff8743d98ae72c643c4fe3be7b/zope_interface-8.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:81ed23698bfb588c48b1756129814b890febac971ff6c8a414f82601773145bb", size = 211783, upload-time = "2026-04-25T07:28:10.028Z" }, { url = "https://files.pythonhosted.org/packages/fa/8c/efabdafc25ed44ef9c1084aad9870bb6c2c9b78e542684efe6865c0f0067/zope_interface-8.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e0b9d7e958657fad414f8272afcdf0b8a873fbbb2bb6a6287232d2f11a232bf8", size = 264752, upload-time = "2026-04-25T07:28:11.773Z" }, { url = "https://files.pythonhosted.org/packages/53/5a/c4d52c58d5fee4ff67cc02f0dec24d0e84428520f67a52f1e4086f0e7779/zope_interface-8.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eef0a49e041f4dc4d2a6ab894b4fd0c5354e0e8037e731fb953531e59b0d3d33", size = 269829, upload-time = "2026-04-25T07:28:13.988Z" }, { url = "https://files.pythonhosted.org/packages/16/d2/df8f339c93bb5adee695546ba90d0daa2917338a4792281f6b8e652a9328/zope_interface-8.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b302f955c36e924e1f4fe70dd9105ff06235857861c6ae72c3b10b016aeee99", size = 269452, upload-time = "2026-04-25T07:28:16.403Z" }, - { url = "https://files.pythonhosted.org/packages/17/4b/bd97b1a21bb2c16d66a42f6c7a43c0a5afcfaf14c68d3b7d2ee6afb28e52/zope_interface-8.4-cp312-cp312-win_amd64.whl", hash = "sha256:4ae6a1e111642dbf724f635424dcaf5a5c8abbde49eac3f452f5323ffaa10232", size = 214420, upload-time = "2026-04-25T07:28:18.405Z" }, - { url = "https://files.pythonhosted.org/packages/7d/85/1477f23cf3b0476608ca987b4338f91439abb5b96564ac26b26d2cde38fd/zope_interface-8.4-cp312-cp312-win_arm64.whl", hash = "sha256:2e9e4aa33b76877af903d5532545e64d24ade0f6f80d9d1a31e6efcea76a60bc", size = 212992, upload-time = "2026-04-25T07:28:20.48Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6a/a08c62bc1fa0e34fe7b8b401646cba4817427c716bfbef6cc88937cd327f/zope_interface-8.4-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:cd55965d715413038774aead54851bc3dbdd74a69f3ce30252182a94407b9905", size = 211924, upload-time = "2026-04-25T07:28:22.219Z" }, - { url = "https://files.pythonhosted.org/packages/50/30/2011f17e00ff078658bc317e1f7eccd7843fc1ce60695b665b0a52c45c1b/zope_interface-8.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0d88c1f106a4f06e074a3ada2d20f4a602e3f2871c4f55726ed5d91e94ec19b1", size = 211995, upload-time = "2026-04-25T07:28:24.107Z" }, { url = "https://files.pythonhosted.org/packages/25/f3/a16fe884571cfa89271412dbb40def6d6865824428d1e14785a82795100c/zope_interface-8.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:36c575356732d59ffd3279ad67e302a6fe517e67db5b061b36b377ee0fa016c4", size = 264443, upload-time = "2026-04-25T07:28:26.401Z" }, { url = "https://files.pythonhosted.org/packages/83/88/e08923fcd8a8c8704af05a90418b07cd897ac90865925b37d7ad8139adfa/zope_interface-8.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:29f09ec8bda65f7b30294328070070a2590b90f252f834ee0817cdb0e2c35f6a", size = 269626, upload-time = "2026-04-25T07:28:28.423Z" }, { url = "https://files.pythonhosted.org/packages/27/67/96c94cd307f9946d0b0f03402a335f7aae7b4f0b129b5734cc56cc78cb65/zope_interface-8.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2bc388cebcb753d21eaf2a0481fd6f0ce6840a47300a40dcec0b56bac27d0f97", size = 269583, upload-time = "2026-04-25T07:28:30.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d4/7e9fcc8bb0dba5d023b9fca92035d68c018457cc550e9d51746670b76a6b/zope_interface-8.4-cp313-cp313-win_amd64.whl", hash = "sha256:3e5866917ccb57d929e515a1136d729bd3fa4f367965fb16e38a4bc72cb05521", size = 214422, upload-time = "2026-04-25T07:28:32.201Z" }, - { url = "https://files.pythonhosted.org/packages/16/26/b0bcde302f6a4c155d047a8ab5cba1003363031919d6e8f3bcdc139c28a6/zope_interface-8.4-cp313-cp313-win_arm64.whl", hash = "sha256:f1f854bef8bc137519e4413bcc1322d55faad28b20b3ca39f7bec49d2f1b26df", size = 213029, upload-time = "2026-04-25T07:28:34.677Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d5/ca60c8b404b303d9490e1417430a5198a77557dbeb17c1cb31616e432318/zope_interface-8.4-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:7cbb887fdbfaacb4c362dbb487033551646e28013ad5ffe72e96eb260003a1a1", size = 212012, upload-time = "2026-04-25T07:28:36.88Z" }, - { url = "https://files.pythonhosted.org/packages/83/64/6bb9f54250c817e24b39e986f173b6cd21ff658bec6c6cc0baad05d761e4/zope_interface-8.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a5638c6be715116d3453e6d099c299c6844d54810de7445ce116424e905ede06", size = 212071, upload-time = "2026-04-25T07:28:38.742Z" }, { url = "https://files.pythonhosted.org/packages/c6/cf/42851262e102723058019dc7d0b48210b85a935f79ae32ce60ddccc2e8fb/zope_interface-8.4-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b8147b40bfcd53803870a9519e0879ff066aeecc2fcff8295663c1b17fc38dc2", size = 266075, upload-time = "2026-04-25T07:28:41.084Z" }, { url = "https://files.pythonhosted.org/packages/d2/a7/e48c79b836f6f0a2c219288e2ec343517f90e95c93de5435a8a23918bf20/zope_interface-8.4-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:049ba3c7b38cc400ae08e011617635706e0f442e1d075db1b015246fcbf6091e", size = 269127, upload-time = "2026-04-25T07:28:42.868Z" }, { url = "https://files.pythonhosted.org/packages/6a/40/0e26f24d3a2f34f0de2cfeaab6458a865284d9d1fa317ab78913aa1f7322/zope_interface-8.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9c4ac009c2c8e43283842f80387c4d4b41bcbc293391c3b9ab71532ae1ccc301", size = 269446, upload-time = "2026-04-25T07:28:44.97Z" }, - { url = "https://files.pythonhosted.org/packages/91/d5/20310601450367fc35fa28b0544c98d0347b8cc25eaf106a2c4cc36841e1/zope_interface-8.4-cp314-cp314-win_amd64.whl", hash = "sha256:4713bf651ec36e7eea49d2ace4f0e89bec2b33a339674874b1121f2537edc62a", size = 215199, upload-time = "2026-04-25T07:28:47.146Z" }, - { url = "https://files.pythonhosted.org/packages/5b/00/0d22ce75126e31f81baa5889e2a40aad37c8e34d1220cf8b18d744f2b5d9/zope_interface-8.4-cp314-cp314-win_arm64.whl", hash = "sha256:d934497c4b72d5f528d2b5ebe9b8b5a7004b5877948ebd4ea00c2432fb27178f", size = 213178, upload-time = "2026-04-25T07:28:48.868Z" }, ] From 60962fff390f1a5b1ee065e6b0e37b5fe992d0c8 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Wed, 20 May 2026 15:41:52 +0000 Subject: [PATCH 40/80] fixing bugs in ngmix --- src/shapepipe/modules/ngmix_package/ngmix.py | 63 +++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 6589478c6..d12f2daf9 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -118,7 +118,6 @@ def __init__( self.weight_vign_cat = SqliteDict(weight_vignet_path) self.flag_vign_cat = SqliteDict(flag_vignet_path) - @classmethod def close(self): self.f_wcs_file.close() self.gal_vign_cat.close() @@ -348,6 +347,8 @@ def compile_results(self, results): 'ntry_fit', 'g1_psfo_ngmix', 'g2_psfo_ngmix', + 'T_psfo_ngmix', + 'T_err_psfo_ngmix', 'r50_psfo_ngmix', 'g1_err_psfo_ngmix', 'g2_err_psfo_ngmix', @@ -356,6 +357,9 @@ def compile_results(self, results): 'g1_err', 'g2', 'g2_err', + 'T', + 'T_err', + 'Tpsf', 'r50', 'r50_err', 'r50psf', @@ -390,7 +394,7 @@ def compile_results(self, results): output_dict[name]["moments_fail"].append( results[idx]["moments_fail"] ) - output_dict[name]["ntry_fit"].append(results[idx][name]["ntry"]) + output_dict[name]["ntry_fit"].append(results[idx][name]["nfev"]) output_dict[name]["g1_psfo_ngmix"].append( results[idx]["g_PSFo"][0] ) @@ -415,18 +419,19 @@ def compile_results(self, results): ) output_dict[name]["T"].append(results[idx][name]["T"]) output_dict[name]["T_err"].append(results[idx][name]["T_err"]) - output_dict[name]["Tpsf"].append(results[idx][name]["Tpsf"]) - output_dict[name]["g1_psf"].append( - results[idx][name]["gpsf"][0] - ) - output_dict[name]["g2_psf"].append( - results[idx][name]["gpsf"][1] - ) + output_dict[name]["Tpsf"].append(results[idx]["T_PSFo"]) + output_dict[name]["g1_psf"].append(results[idx]["g_PSFo"][0]) + output_dict[name]["g2_psf"].append(results[idx]["g_PSFo"][1]) output_dict[name]['r50'].append(results[idx][name]['pars'][4]) output_dict[name]['r50_err'].append(results[idx][name]['pars_err'][4]) - output_dict[name]['r50psf'].append(results[idx][name]['r50psf']) - output_dict[name]['g1_psf'].append( - results[idx][name]['gpsf'][0] + output_dict[name]['r50psf'].append(results[idx]["r50_PSFo"]) + output_dict[name]["g1"].append(results[idx][name]["g"][0]) + output_dict[name]["g2"].append(results[idx][name]["g"][1]) + output_dict[name]["g1_err"].append( + np.sqrt(results[idx][name]["g_cov"][0, 0]) + ) + output_dict[name]["g2_err"].append( + np.sqrt(results[idx][name]["g_cov"][1, 1]) ) output_dict[name]["mag"].append(mag) output_dict[name]["mag_err"].append(mag_err) @@ -440,7 +445,7 @@ def compile_results(self, results): output_dict[name]["flags"].append(results[idx][name]["flags"]) output_dict[name]["mcal_flags"].append( - results[idx]["mcal_flags"] + results[idx].get("mcal_flags", 0) ) return output_dict @@ -503,8 +508,8 @@ def get_last_id(self, cat_path): for hdu_no in range(2, 6): if id_last != cat._cat_data[hdu_no].data["id"][-1]: raise ValueError( - "Last ID {cat._cat_data[hdu_no].data['id'][-1]} in HDU" - + f" #{hdu_no} inconsistent with {id_las}" + f"Last ID {cat._cat_data[hdu_no].data['id'][-1]} in HDU" + + f" #{hdu_no} inconsistent with {id_last}" ) return id_last @@ -673,6 +678,20 @@ def process(self): res['obj_id'] = obj_id res['n_epoch_model'] = len(stamp.gals) + res['moments_fail'] = sum( + 1 for k in ['noshear', '1p', '1m', '2p', '2m'] + if res.get(k, {}).get('flags', 0) != 0 + ) + r50_psfo = np.sqrt(max(psf_res['T_psf'], 0) / 2) + res['g_PSFo'] = psf_res['g_psf'] + res['g_err_PSFo'] = psf_res['g_psf_err'] + res['T_PSFo'] = psf_res['T_psf'] + res['T_err_PSFo'] = psf_res['T_psf_err'] + res['r50_PSFo'] = r50_psfo + res['r50_err_PSFo'] = ( + psf_res['T_psf_err'] / (2 * r50_psfo) if r50_psfo > 0 else np.nan + ) + res['mcal_flags'] = 0 final_res.append(res) n_fitted += 1 count_batch += 1 @@ -910,7 +929,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): return sig_noise -def prepare_ngmix_weights(gal,weight,flag,tile_cat): +def prepare_ngmix_weights(gal, weight, flag): """bookkeeping for ngmix weights. runs on a single galaxy and epoch pixel scale and galaxy guess TO DO: decide if we want galaxy guess stuff @@ -1017,7 +1036,7 @@ def make_ngmix_observation(gal,weight,flag,psf,wcs): return gal_obs -def average_multiepoch_psf(obsdict,nepoch): +def average_multiepoch_psf(obsdict): """ averages psf information over multiple epochs we may need to do this for original psf as well Parameters @@ -1034,7 +1053,7 @@ def average_multiepoch_psf(obsdict,nepoch): # create dictionary names = ['T_psf', 'T_psf_err', 'g_psf', 'g_psf_err'] psf_dict = {k: [] for k in names} - # include relevant psf quantities- check how they are presented for multi-epoch observations + nepoch = len(obsdict['noshear']) wsum = 0 g_psf_sum = np.array([0., 0.]) g_psf_err_sum = np.array([0., 0.]) @@ -1045,7 +1064,7 @@ def average_multiepoch_psf(obsdict,nepoch): T_psf_err=obsdict['noshear'][n_e].psf.meta['result']['T_err'] g_psf=obsdict['noshear'][n_e].psf.meta['result']['g'] g_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] - ne_wsum = obsdict['noshear'][0].weight.sum() + ne_wsum = obsdict['noshear'][n_e].weight.sum() # we probably want to handle cases when there is no psf # how are we dealing with the error, what is npsf @@ -1158,11 +1177,11 @@ def do_ngmix_metacal( # this "bootstrapper" runs the metacal image shearing as well as both psf # and object measurements boot = ngmix.metacal.MetacalBootstrapper( - metacal_pars, - runner=runner, + runner=runner, psf_runner=psf_runner, ignore_failed_psf=True, - rng=rng + rng=rng, + **metacal_pars, ) # this is the actual fit resdict, obsdict = boot.go(gal_obs_list) From d146cae9fb0312a1f97694a465e70219a6ff4dfa Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Thu, 21 May 2026 08:49:31 +0000 Subject: [PATCH 41/80] fixed merge errors --- .../cfis/config_tile_Ng_batch_psfex_uc.ini | 2 + src/shapepipe/modules/ngmix_package/ngmix.py | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/example/cfis/config_tile_Ng_batch_psfex_uc.ini b/example/cfis/config_tile_Ng_batch_psfex_uc.ini index 3d8e37140..9b57396c5 100644 --- a/example/cfis/config_tile_Ng_batch_psfex_uc.ini +++ b/example/cfis/config_tile_Ng_batch_psfex_uc.ini @@ -70,5 +70,7 @@ MAG_ZP = 30.0 # Pixel scale in arcsec PIXEL_SCALE = 0.186 +SAVE_BATCH = 1000 + ID_OBJ_MIN = -1 ID_OBJ_MAX = -1 diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 073fd2b02..b2516e605 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -1037,6 +1037,83 @@ def average_multiepoch_psf(obsdict): g_psf=obsdict['noshear'][n_e].psf.meta['result']['g'] g_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] ne_wsum = obsdict['noshear'][n_e].weight.sum() + + wsum += ne_wsum + g_psf_sum += g_psf * ne_wsum + g_psf_err_sum += g_psf_err * ne_wsum + T_psf_sum += T_psf * ne_wsum + T_psf_err_sum += T_psf_err * ne_wsum + + if wsum == 0: + raise ZeroDivisionError('Sum of weights = 0, division by zero') + + psf_dict['g_psf'] = g_psf_sum / wsum + psf_dict['g_psf_err'] = g_psf_err_sum / wsum + psf_dict['T_psf'] = T_psf_sum / wsum + psf_dict['T_psf_err'] = T_psf_err_sum / wsum + + return psf_dict + + +def do_ngmix_metacal(stamp, prior, flux_guess, rng): + """Do Ngmix Metacal. + + Performs metacalibration on a single multi-epoch object and returns the + joint shape measurement with NGMIX. + + Parameters + ---------- + stamp : Postage_stamp + Postage stamps for all epochs of one galaxy. + prior : ngmix.joint_prior.PriorSimpleSep + Priors for the fitting parameters. + flux_guess : float + Initial flux guess. + rng : numpy.random.RandomState + Random state for guesses and priors. + + Returns + ------- + tuple + (resdict, psf_res) where resdict is the MetacalBootstrapper result + dict and psf_res is the averaged PSF dict from average_multiepoch_psf. + """ + n_epoch = len(stamp.gals) + if n_epoch == 0: + raise ValueError("0 epoch to process") + + psf_model = 'gauss' + gal_model = 'gauss' + + gal_obs_list = ObsList() + for n_e in range(n_epoch): + gal_obs = make_ngmix_observation( + stamp.gals[n_e], + stamp.weights[n_e], + stamp.flags[n_e], + stamp.psfs[n_e], + stamp.jacobs[n_e], + ) + gal_obs_list.append(gal_obs) + + fitter = ngmix.fitting.Fitter(model=gal_model, prior=prior) + guesser = ngmix.guessers.TPSFFluxAndPriorGuesser(rng=rng, T=0.25, prior=prior) + + psf_fitter = ngmix.fitting.Fitter(model=psf_model, prior=prior) + psf_guesser = ngmix.guessers.TFluxGuesser(rng=rng, T=0.25, prior=prior, flux=flux_guess) + + psf_runner = ngmix.runners.PSFRunner(fitter=psf_fitter, guesser=psf_guesser, ntry=2) + runner = ngmix.runners.Runner(fitter=fitter, guesser=guesser, ntry=5) + + metacal_pars = { + 'types': ['noshear', '1p', '1m', '2p', '2m'], + 'step': 0.01, + 'psf': 'fitgauss', + 'fixnoise': True, + 'use_noise_image': True, + } + + boot = ngmix.metacal.MetacalBootstrapper( runner=runner, psf_runner=psf_runner, ignore_failed_psf=True, @@ -1047,6 +1124,7 @@ def average_multiepoch_psf(obsdict): psf_res = average_multiepoch_psf(obsdict) return resdict, psf_res + # Define the SExtractor parameters for a galaxy def sextractor_e1e2(e,theta): """sextractor_e1e2 From 67079de901642d4ff0ce84e7a3d03ab06a636d62 Mon Sep 17 00:00:00 2001 From: martinkilbinger Date: Fri, 22 May 2026 13:34:39 +0000 Subject: [PATCH 42/80] debugged ngmix --- src/shapepipe/modules/ngmix_package/ngmix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index b2516e605..e62c83707 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -423,6 +423,8 @@ def compile_results(self, results): output_dict[name]["g2_err"].append( np.sqrt(results[idx][name]["g_cov"][1, 1]) ) + output_dict[name]["flux"].append(results[idx][name]["flux"]) + output_dict[name]["flux_err"].append(results[idx][name]["flux_err"]) output_dict[name]["mag"].append(mag) output_dict[name]["mag_err"].append(mag_err) From 22f1f0a13b69875677ef1cf5f12b194edf48b37c Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Tue, 2 Jun 2026 02:25:27 +0200 Subject: [PATCH 43/80] fix(ngmix): thread seeded rng through noise-image generation prepare_ngmix_weights drew the noise image and masked-pixel fill from the unseeded global numpy.random, defeating the per-tile self._rng = RandomState(seed) the module sets for reproducibility. Same tile + same seed gave different shears (observed max|dg1| ~ 3e-5). Thread rng through make_ngmix_observation -> prepare_ngmix_weights and draw via rng.standard_normal. Adds test_metacal_is_reproducible_with_fixed_seed (same seed -> identical noshear g); fails before this fix, passes after. Co-Authored-By: Claude Opus 4.8 --- src/shapepipe/modules/ngmix_package/ngmix.py | 19 +++++--- src/shapepipe/tests/test_ngmix.py | 47 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index e62c83707..08e0729b8 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -920,7 +920,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): return sig_noise -def prepare_ngmix_weights(gal, weight, flag): +def prepare_ngmix_weights(gal, weight, flag, rng): """bookkeeping for ngmix weights. runs on a single galaxy and epoch pixel scale and galaxy guess TO DO: decide if we want galaxy guess stuff @@ -930,6 +930,9 @@ def prepare_ngmix_weights(gal, weight, flag): gal : numpy.ndarray weight : numpy.ndarray flag : numpy.ndarray + rng : numpy.random.RandomState + Random state for the noise realisations (seeded per tile for + reproducibility). Returns ------- @@ -945,8 +948,8 @@ def prepare_ngmix_weights(gal, weight, flag): sig_noise = sigma_mad(gal) - noise_img = np.random.randn(*gal.shape) * sig_noise - noise_img_gal = np.random.randn(*gal.shape) * sig_noise + noise_img = rng.standard_normal(gal.shape) * sig_noise + noise_img_gal = rng.standard_normal(gal.shape) * sig_noise gal_masked = np.copy(gal) if (weight_map == 0).any(): @@ -956,7 +959,7 @@ def prepare_ngmix_weights(gal, weight, flag): return gal_masked, weight_map, noise_img -def make_ngmix_observation(gal, weight, flag, psf, wcs): +def make_ngmix_observation(gal, weight, flag, psf, wcs, rng): """Build an ngmix Observation for a single galaxy epoch. The galaxy Jacobian is re-centered on the HSM centroid so that the @@ -970,6 +973,9 @@ def make_ngmix_observation(gal, weight, flag, psf, wcs): psf : numpy.ndarray wcs : galsim.BaseWCS Local WCS Jacobian at the object position. + rng : numpy.random.RandomState + Random state for the noise realisations (seeded per tile for + reproducibility). Returns ------- @@ -982,7 +988,9 @@ def make_ngmix_observation(gal, weight, flag, psf, wcs): ) psf_obs = Observation(psf, jacobian=psf_jacob) - gal_masked, weight_map, noise_img = prepare_ngmix_weights(gal, weight, flag) + gal_masked, weight_map, noise_img = prepare_ngmix_weights( + gal, weight, flag, rng + ) # Re-center Jacobian on HSM centroid (pixel offset from stamp center). # Fixes: centroid prior biases fit when galaxy is offset from stamp center. @@ -1095,6 +1103,7 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): stamp.flags[n_e], stamp.psfs[n_e], stamp.jacobs[n_e], + rng, ) gal_obs_list.append(gal_obs) diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index bbfae11ec..aeb2274c7 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -47,3 +47,50 @@ def test_megacam_flip_leaves_unrotated_ccds_unchanged(rows, ccd_nb): vign = np.array(rows, dtype=float) npt.assert_allclose(Ngmix.MegaCamFlip(vign, ccd_nb), vign) + + +def _metacal_noshear_g(seed): + """Run do_ngmix_metacal on a fixed simulated stamp with RandomState(seed). + + The simulated data is drawn from a separate, fixed seed so the only + seed-dependent randomness in play is the noise/metacal RNG inside the + fitter — exactly the channel that must be reproducible. + """ + from shapepipe.modules.ngmix_package.ngmix import ( + Postage_stamp, + do_ngmix_metacal, + get_prior, + ) + from shapepipe.testing.simulate import make_data + + rng = np.random.RandomState(seed) + prior = get_prior(0.1857, rng) + gals, psfs, _, weights, flags, jacobs = make_data( + rng=np.random.RandomState(123), + shear=(0.02, 0.0), + noise=1e-4, + n_epochs=2, + img_size=51, + ) + stamp = Postage_stamp(bkg_sub=False, megacam_flip=False) + stamp.gals, stamp.psfs, stamp.weights, stamp.flags, stamp.jacobs = ( + gals, + psfs, + weights, + flags, + jacobs, + ) + res, _ = do_ngmix_metacal(stamp, prior, 1.0, rng) + return np.asarray(res["noshear"]["g"]) + + +def test_metacal_is_reproducible_with_fixed_seed(): + """Same seed -> identical metacal shear. + + The module seeds ``self._rng = RandomState(seed)`` per tile precisely so a + rerun reproduces. This guards the noise-image and masked-pixel draws in + ``prepare_ngmix_weights`` against silently falling back to the unseeded + global ``numpy.random`` state, which would make shear estimates + irreproducible from one run to the next. + """ + npt.assert_array_equal(_metacal_noshear_g(42), _metacal_noshear_g(42)) From 5cdb1bbf562f9f2168e2a9a7e7202f4561f393ea Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Tue, 2 Jun 2026 02:25:27 +0200 Subject: [PATCH 44/80] test: smoke-import the shipped ngmix-v2.0 validation scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_imports.py walks the shapepipe package, but standalone scripts/ files are outside it and never get imported. This extends import-smoke coverage to the validation scripts added with ngmix v2.0. Currently fails on centroid_bias.py, which imports get_guess — a helper removed in the module rewrite; that script's status (restore get_guess / fix import / relocate as legacy) is a developer decision. Co-Authored-By: Claude Opus 4.8 --- tests/unit/test_scripts_import.py | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/unit/test_scripts_import.py diff --git a/tests/unit/test_scripts_import.py b/tests/unit/test_scripts_import.py new file mode 100644 index 000000000..e670ee5be --- /dev/null +++ b/tests/unit/test_scripts_import.py @@ -0,0 +1,43 @@ +"""Smoke-import the standalone validation scripts shipped under ``scripts/``. + +``test_imports.py`` walks the installed ``shapepipe`` package, but the +standalone scripts under ``scripts/`` are not part of the package, so the +package walk never reaches them. This test extends import-smoke coverage to the +validation scripts added alongside the ngmix v2.0 work: each is expected to at +least import cleanly (its ``if __name__ == "__main__"`` guard keeps ``main`` +from running). A broken top-level import — e.g. importing a helper that no +longer exists in the module — fails here instead of only at run time. +""" + +import importlib.util +from pathlib import Path + +import pytest +import shapepipe + + +REPO_ROOT = Path(shapepipe.__file__).resolve().parents[2] + +# Scripts added by the ngmix v2.0 work that are meant to run against the current +# shapepipe module and carry a __main__ guard (so importing them is side-effect +# free). The jupyter export scripts/jupyter/test_centroid_shift.py is excluded: +# it has no __main__ guard and executes on import. +VALIDATION_SCRIPTS = [ + "scripts/validation/centroid/centroid_bias.py", + "scripts/validation/centroid/centroid_bias_v2.py", + "scripts/python/fitting.py", +] + + +@pytest.mark.parametrize("relpath", VALIDATION_SCRIPTS) +def test_validation_script_imports_cleanly(relpath): + + path = REPO_ROOT / relpath + if not path.exists(): + pytest.skip(f"{relpath} not found at {path}") + + spec = importlib.util.spec_from_file_location( + f"_smoke_{path.stem}", path + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) From dd4f656aa0aadc821e1ffd42164265499a0e777a Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Tue, 2 Jun 2026 13:02:34 +0200 Subject: [PATCH 45/80] remove stranded v1 centroid_bias.py from ngmix_v2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit centroid_bias.py is the old-API (ngmix 1.x) bias-validation script: it imports get_guess, a helper the v2.0 rewrite removed (v2.0 seeds fits via ngmix's own guessers). It is only ever run against the test_centroid_bug worktree — the centroid_bug/centroid_fix cases invoke it as SHAPEPIPE_PATH/TEST_SCRIPT with SHAPEPIPE_PATH pointing at that worktree (see run_bias_test.sh), which has its own copy. The duplicate on this branch was never executed and only broke import-smoke. centroid_bias_v2.py supersedes it for ngmix_v2.0. Drops it from the scripts import-smoke list accordingly. Co-Authored-By: Claude Opus 4.8 --- scripts/validation/centroid/centroid_bias.py | 402 ------------------- tests/unit/test_scripts_import.py | 5 +- 2 files changed, 3 insertions(+), 404 deletions(-) delete mode 100644 scripts/validation/centroid/centroid_bias.py diff --git a/scripts/validation/centroid/centroid_bias.py b/scripts/validation/centroid/centroid_bias.py deleted file mode 100644 index d9c0e0fbd..000000000 --- a/scripts/validation/centroid/centroid_bias.py +++ /dev/null @@ -1,402 +0,0 @@ -#!/usr/bin/env python -"""centroid_bias.py - -Metacal multiplicative-bias validation for ShapePipe/ngmix. - -Measures the multiplicative shear bias m by running metacalibration on -simulated exponential galaxies with a Moffat PSF and comparing recovered -shear to the true input shear. - -Simulation data (make_data) is imported from shapepipe.testing.simulate so -it stays stable across branches. All processing code here is -version-specific and changes with each shapepipe/ngmix branch. - -Usage ------ - python centroid_bias.py [--ntrial N] [--seed S] [--noise SIGMA] - -Expected output (centroid_bug branch, ngmix stable_version): - m ~ -28e-3 (large negative bias, the bug) - -Expected output (centroid_fix branch, ngmix fix_jac_centroid): - m ~ 0 (bias consistent with zero) - -Autors ------- - Axel Guinot - Martin Kilbinger -""" - -import argparse - -import numpy as np -import ngmix -from ngmix import Observation, ObsList -from numpy.random import uniform as urand - -from shapepipe.modules.ngmix_package.ngmix import get_guess, get_noise -from shapepipe.testing.simulate import make_data -from modopt.math.stats import sigma_mad - - -# --------------------------------------------------------------------------- -# Priors (ngmix-version-specific) -# --------------------------------------------------------------------------- - -def get_prior(pixel_scale): - """Build ngmix joint prior for 6-parameter galaxy model.""" - g_prior = ngmix.priors.GPriorBA(0.4) - cen_prior = ngmix.priors.CenPrior(0.0, 0.0, pixel_scale, pixel_scale) - T_prior = ngmix.priors.FlatPrior(-10.0, 1.0e6) - F_prior = ngmix.priors.FlatPrior(-1.0e4, 1.0e9) - return ngmix.joint_prior.PriorSimpleSep(cen_prior, g_prior, T_prior, F_prior) - - -# --------------------------------------------------------------------------- -# Fitting (ngmix-version-specific) -# --------------------------------------------------------------------------- - -def make_galsimfit(obs, model, guess0, prior=None, ntry=5): - """Fit an observation with GalsimSimple, retrying with perturbed guesses. - - Parameters - ---------- - obs : ngmix.Observation or ObsList - model : str - guess0 : numpy.ndarray shape (6,) - prior : ngmix prior, optional - ntry : int, optional - - Returns - ------- - dict - Fit result with flags, g, T, T_err, pars, pars_err, etc. - - Raises - ------ - ngmix.gexceptions.BootGalFailure - """ - limit = 0.1 - guess = np.copy(guess0) - fres = {"flags": 1} - - for it in range(ntry): - guess[0:5] += urand(low=-limit, high=limit) - guess[5:] *= 1 + urand(low=-limit, high=limit) - try: - fitter = ngmix.galsimfit.GalsimSimple(obs, model, prior=prior) - fitter.go(guess) - fres = fitter.get_result() - except Exception: - continue - if fres["flags"] == 0: - break - - if fres["flags"] != 0: - raise ngmix.gexceptions.BootGalFailure( - "Failed to fit galaxy image with galsimfit" - ) - fres["ntry"] = it + 1 - return fres - - -# --------------------------------------------------------------------------- -# Metacal pipeline (ngmix-version-specific) -# --------------------------------------------------------------------------- - -def do_ngmix_metacal( - gals, psfs, psfs_sigma, weights, flags, jacob_list, - prior, pixel_scale, sig_noise=None, -): - """Run metacalibration on a multi-epoch object. - - Parameters - ---------- - gals, psfs, psfs_sigma, weights, flags : lists - Per-epoch image arrays and metadata. - jacob_list : list of galsim Jacobians - prior : ngmix joint prior - pixel_scale : float [arcsec] - sig_noise : float or None - If None, estimated from the first epoch. - - Returns - ------- - dict - Metacal result dict keyed by shear type ('noshear', '1p', '1m'). - """ - n_epoch = len(gals) - if n_epoch == 0: - raise ValueError("0 epochs to process") - - gal_obs_list = ObsList() - T_guess_psf = [] - psf_res_gT = { - "g_PSFo": np.array([0.0, 0.0]), - "g_err_PSFo": np.array([0.0, 0.0]), - "T_PSFo": 0.0, - "T_err_PSFo": 0.0, - } - gal_guess = [] - gal_guess_flag = True - wsum = 0 - - for n_e in range(n_epoch): - psf_jacob = ngmix.Jacobian( - row=(psfs[0].shape[0] - 1) / 2, - col=(psfs[0].shape[1] - 1) / 2, - wcs=jacob_list[n_e], - ) - psf_obs = Observation(psfs[n_e], jacobian=psf_jacob) - psf_T = psfs_sigma[n_e] * 1.17741 * pixel_scale - - weight_map = np.copy(weights[n_e]) - weight_map[flags[n_e] != 0] = 0.0 - weight_map[weight_map != 0] = 1 - - psf_guess = np.array([0.0, 0.0, 0.0, 0.0, psf_T, 1.0]) - try: - psf_res = make_galsimfit(psf_obs, "gauss", psf_guess) - except Exception: - continue - - try: - gal_guess_tmp = get_guess( - gals[n_e], pixel_scale, - guess_size_type="T", guess_centroid_unit="img", - ) - except Exception: - gal_guess_flag = False - gal_guess_tmp = np.array([0.0, 0.0, 0.0, 0.0, 1, 100]) - - gal_jacob = ngmix.Jacobian( - row=(gals[n_e].shape[0] - 1) / 2 + gal_guess_tmp[1], - col=(gals[n_e].shape[1] - 1) / 2 + gal_guess_tmp[0], - wcs=jacob_list[n_e], - ) - - if sig_noise is None: - sig_noise = ( - get_noise(gals[n_e], weight_map, gal_guess_tmp, pixel_scale) - if gal_guess_flag - else sigma_mad(gals[n_e]) - ) - - noise_img = np.random.randn(*gals[n_e].shape) * sig_noise - noise_img_gal = np.random.randn(*gals[n_e].shape) * sig_noise - - gal_masked = np.copy(gals[n_e]) - if (weight_map == 0).any(): - gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] - weight_map *= 1 / sig_noise ** 2 - - w_tmp = np.sum(weight_map) - psf_res_gT["g_PSFo"] += psf_res["g"] * w_tmp - psf_res_gT["g_err_PSFo"] += ( - np.array([psf_res["pars_err"][2], psf_res["pars_err"][3]]) * w_tmp - ) - psf_res_gT["T_PSFo"] += psf_res["T"] * w_tmp - psf_res_gT["T_err_PSFo"] += psf_res["T_err"] * w_tmp - wsum += w_tmp - - gal_obs = Observation( - gal_masked, weight=weight_map, - jacobian=gal_jacob, psf=psf_obs, noise=noise_img, - ) - if gal_guess_flag: - final_gal_guess = np.copy(gal_guess_tmp) - final_gal_guess[:2] = 0 - gal_guess.append(final_gal_guess) - - gal_obs_list.append(gal_obs) - T_guess_psf.append(psf_T) - gal_guess_flag = True - - if wsum == 0: - raise ZeroDivisionError("Sum of weights = 0") - - for key in psf_res_gT: - psf_res_gT[key] /= wsum - - fail_get_guess = len(gal_guess) == 0 - gal_pars = [0.0, 0.0, 0.0, 0.0, 1, 100] if fail_get_guess else np.mean(gal_guess, 0) - - metacal_pars = { - "types": ["noshear", "1p", "1m"], - "step": 0.01, - "psf": "gauss", - "fixnoise": True, - "cheatnoise": False, - "symmetrize_psf": False, - "use_noise_image": True, - } - Tguess = np.mean(T_guess_psf) - - obs_dict_mcal = ngmix.metacal.get_all_metacal(gal_obs_list, **metacal_pars) - res = {"mcal_flags": 0} - - for key in sorted(obs_dict_mcal): - fres = make_galsimfit(obs_dict_mcal[key], "gauss", gal_pars, prior=prior) - res["mcal_flags"] |= fres["flags"] - tres = dict(fres) - - wsum_psf = 0 - Tpsf_sum = 0 - gpsf_sum = np.zeros(2) - for obs in obs_dict_mcal[key]: - psf_obs_fit = getattr(obs, "psf_nopix", obs.psf) - try: - psf_res = make_galsimfit( - psf_obs_fit, "gauss", - np.array([0.0, 0.0, 0.0, 0.0, Tguess, 1.0]), - ) - except Exception: - continue - tw = obs.weight.sum() - wsum_psf += tw - gpsf_sum += np.array(psf_res["g"]) * tw - Tpsf_sum += psf_res["T"] * tw - - tres["gpsf"] = gpsf_sum / wsum_psf if wsum_psf > 0 else np.zeros(2) - tres["Tpsf"] = Tpsf_sum / wsum_psf if wsum_psf > 0 else 0.0 - res[key] = tres - - res.update(psf_res_gT) - res["moments_fail"] = fail_get_guess - return res - - -# --------------------------------------------------------------------------- -# Analysis utilities (stable) -# --------------------------------------------------------------------------- - -def progress(total, miniters=1): - """Minimal progress printer.""" - sl = str(len(str(total))) - fmt = "%" + sl + "d/%" + sl + "d %3d%%" - last_print_n = 0 - last_len = 0 - for i in range(total): - yield i - num = i + 1 - if i == 0 or num == total or num - last_print_n >= miniters: - meter = fmt % (num, total, 100 * num // total) - print("\r" + meter + " " * max(last_len - len(meter), 0), - end="", flush=True) - last_len = len(meter) - last_print_n = num - print(flush=True) - - -def make_struct(res, shear_type): - """Pack a metacal result into a structured array row.""" - dt = [ - ("flags", "i4"), ("shear_type", "U7"), - ("s2n", "f8"), ("g", "f8", 2), ("T", "f8"), ("Tpsf", "f8"), - ] - data = np.zeros(1, dtype=dt) - data["shear_type"] = shear_type - data["flags"] = res["flags"] - if res["flags"] == 0: - data["s2n"] = res["flux"] / res["flux_err"] - data["g"] = res["g"] - data["T"] = res["T"] - data["Tpsf"] = res["Tpsf"] - else: - data["s2n"] = data["g"] = data["T"] = data["Tpsf"] = np.nan - return data - - -def select(data, shear_type): - """Return indices where flags==0 and shear_type matches.""" - return np.where( - (data["flags"] == 0) & (data["shear_type"] == shear_type) - )[0] - - -# --------------------------------------------------------------------------- -# Main -# --------------------------------------------------------------------------- - -def main(ntrial=50, seed=42, sig_noise=1e-10, n_epochs=3, pixel_scale=0.1857): - rng_prior = np.random.RandomState(seed) - prior = get_prior(pixel_scale) - shear_types = ["noshear", "1p", "1m"] - - rng = np.random.RandomState(seed) - seeds = rng.randint(0, 2 ** 30, size=ntrial) - - dlist_p, dlist_m = [], [] - - for i in progress(ntrial, miniters=10): - d_ = [] - for shear_true in [(0.02, 0.00), (-0.02, 0.00)]: - gals, psfs, psfs_sigmas, weights, flags, jacob_lists = make_data( - rng=np.random.RandomState(seeds[i]), - noise=sig_noise, - shear=shear_true, - n_epochs=n_epochs, - share_shift=False, - ) - try: - resdict = do_ngmix_metacal( - gals, psfs, psfs_sigmas, weights, flags, jacob_lists, - prior=prior, pixel_scale=pixel_scale, sig_noise=sig_noise, - ) - stt = [make_struct(resdict[st], st) for st in shear_types] - except Exception: - continue - d_.append(np.hstack(stt)) - - if len(d_) != 2: - continue - dlist_p.extend(d_[0]) - dlist_m.extend(d_[1]) - - print() - - data_p = np.hstack(dlist_p) - data_m = np.hstack(dlist_m) - - w = select(data_p, "noshear") - w_1p = select(data_p, "1p") - w_1m = select(data_p, "1m") - R11_p = np.atleast_2d((data_p["g"][w_1p, 0] - data_p["g"][w_1m, 0]) / 0.02).T - - w = select(data_m, "noshear") - w_1p = select(data_m, "1p") - w_1m = select(data_m, "1m") - R11_m = np.atleast_2d((data_m["g"][w_1p, 0] - data_m["g"][w_1m, 0]) / 0.02).T - - g_p = data_p["g"][select(data_p, "noshear")] - g_m = data_m["g"][select(data_m, "noshear")] - shear_ = (g_p - g_m) / (R11_p + R11_m) - shear = np.mean(shear_, axis=0) - shear_err = np.std(shear_, axis=0) / np.sqrt(len(shear_)) - - m = shear[0] / 0.02 - 1 - merr = shear_err[0] / 0.02 - s2n = data_p["s2n"][select(data_p, "noshear")].mean() - - print("S/N: %g" % s2n) - print("R11: %g %g" % (np.mean(R11_p), np.mean(R11_m))) - print("m[1e-3, 3sigmas]: %g +/- %g (99.7%% conf)" % (m / 1e-3, merr * 3 / 1e-3)) - print("c[1e-5, 3sigmas]: %g +/- %g (99.7%% conf)" % (shear[1] / 1e-5, shear_err[1] * 3 / 1e-5)) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Metacal centroid bias validation") - parser.add_argument("--ntrial", type=int, default=50, help="number of trials") - parser.add_argument("--seed", type=int, default=42, help="random seed") - parser.add_argument("--noise", type=float, default=1e-10, help="per-pixel noise sigma") - parser.add_argument("--n-epochs", type=int, default=3, help="epochs per galaxy") - parser.add_argument("--pixel-scale", type=float, default=0.1857, help="pixel scale [arcsec]") - args = parser.parse_args() - - main( - ntrial=args.ntrial, - seed=args.seed, - sig_noise=args.noise, - n_epochs=args.n_epochs, - pixel_scale=args.pixel_scale, - ) diff --git a/tests/unit/test_scripts_import.py b/tests/unit/test_scripts_import.py index e670ee5be..440c856d0 100644 --- a/tests/unit/test_scripts_import.py +++ b/tests/unit/test_scripts_import.py @@ -21,9 +21,10 @@ # Scripts added by the ngmix v2.0 work that are meant to run against the current # shapepipe module and carry a __main__ guard (so importing them is side-effect # free). The jupyter export scripts/jupyter/test_centroid_shift.py is excluded: -# it has no __main__ guard and executes on import. +# it has no __main__ guard and executes on import. The v1 centroid_bias.py is +# excluded by deletion: it targets the old test_centroid_bug branch/env (run via +# run_bias_test.sh's SHAPEPIPE_PATH), not this module. VALIDATION_SCRIPTS = [ - "scripts/validation/centroid/centroid_bias.py", "scripts/validation/centroid/centroid_bias_v2.py", "scripts/python/fitting.py", ] From bd60dc8ebbcc9fd36765fd20a6c62b45aea6e49c Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Fri, 5 Jun 2026 21:21:56 +0200 Subject: [PATCH 46/80] cleanup(ngmix): drop dead code flagged in v2.0 review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical cleanups from the #741 review that Martin greenlit / are unambiguous — no behaviour change on current configs: - ngmix.py: remove unreachable print() after return in MegaCamFlip (293) - ngmix.py: remove unused sextractor_e1e2() (copy-pasted docstring, no callers anywhere in src/) (1140) - ngmix.py + ngmix_runner.py: remove the dead CHECK_EXISTING_DIR resume path (_check_existing_dir set but never read; get_last_id defined but never called) and the stale '# MKDEBUG check whether still used' marker on _f_wcs_path (which IS used). Martin: 'a hack to resume interrupted runs ... can be removed now.' (254/257/464) - ngmix.py: masked-fraction cut divides by v_flag_tmp.size instead of the literal 51*51 — identical for 51x51 stamps, auto-tracks any future stamp size (766) - scripts/python/fitting.py: remove — verbatim copy of esheldon's ngmix example, no callers, duplicates testing/simulate.py Co-Authored-By: Claude Opus 4.8 --- scripts/python/fitting.py | 265 ------------------- src/shapepipe/modules/ngmix_package/ngmix.py | 85 +----- src/shapepipe/modules/ngmix_runner.py | 10 - 3 files changed, 1 insertion(+), 359 deletions(-) delete mode 100644 scripts/python/fitting.py diff --git a/scripts/python/fitting.py b/scripts/python/fitting.py deleted file mode 100644 index 84c48a8d5..000000000 --- a/scripts/python/fitting.py +++ /dev/null @@ -1,265 +0,0 @@ -""" -example fitting an exponential model. The psf is fit -using a set of coelliptical gaussians - -Despite this being a fit to single image we use the full fitting framework. We -use a generic bootstrapper to "bootstrap" the process, first fitting the psf -and then a psf flux for the object. Then the object is fit including the PSF, -so the inferred parameters are "pre-psf". The guess for the fit is made -based on the psf flux fit and a generic rough guess for size. - -To faciliate this bootstrapping process we define the fitters for psf and -object as well as objects to provide guesses. - -Bootstrappers are especially useful when you will perform the same fit on many -objects. - -A run of the code should produce output something like thid - - > python fitting_bd_empsf.py - - S/N: 920.5078121454815 - true flux: 100 meas flux: 95.3763 +/- 0.653535 (99.7% conf) - true g1: 0.05 meas g1: 0.0508 +/- 0.00960346 (99.7% conf) - true g2: -0.02 meas g2: -0.0261123 +/- 0.0095837 (99.7% conf) - true fracdev: 0.5 meas fracdev: 0.514028 +/- 0.011873 (99.7% conf) -""" -import numpy as np -import galsim -import ngmix - - -def main(): - - args = get_args() - rng = np.random.RandomState(args.seed) - - nepoch=3 - obs, obj_pars = make_data(rng=rng, noise=args.noise) - gal_obs_list = ngmix.observation.ObsList() - for i in np.arange(nepoch): - gal_obs_list.append(obs) - print(len(gal_obs_list)) - # fit the object to an exponential disk - prior = get_prior(rng=rng, scale=obs.jacobian.scale) - # fit using the levenberg marquards algorithm - fitter = ngmix.fitting.Fitter(model='gauss', prior=prior) - # make parameter guesses based on a psf flux and a rough T - guesser = ngmix.guessers.TPSFFluxAndPriorGuesser( - rng=rng, - T=0.25, - prior=prior, - ) - - # psf fitting with coelliptical gaussians - psf_ngauss = 1 - psf_fitter = fitter - # special guesser for coelliptical gaussians - psf_guesser = ngmix.guessers.TFluxGuesser(rng=rng, - T=0.25, - prior=prior, - flux=100, - - ) - # this runs the fitter. We set ntry=2 to retry the fit if it fails - psf_runner = ngmix.runners.PSFRunner( - fitter=psf_fitter, guesser=psf_guesser, - ntry=2, - ) - runner = ngmix.runners.Runner( - fitter=fitter, guesser=guesser, - ntry=2, - ) - - metacal_pars = { - 'types': ['noshear', '1p', '1m', '2p', '2m'], - 'step': 0.01, - 'psf': 'fitgauss', - 'fixnoise': True, - 'use_noise_image': False - } - # this bootstraps the process, first fitting psfs then the object - boot = ngmix.metacal.MetacalBootstrapper( - runner=runner, - psf_runner=psf_runner, - rng=rng - ) - - res, obsdict = boot.go(gal_obs_list) - print(res.keys()) - print(res['noshear'].keys()) - print('psf_options',obsdict['noshear'][0].weight.sum()) - #print('psf_options',obsdict['noshear'].psf.meta['result'].keys()) - print('S/N:', res['noshear']['s2n']) - print('true flux: %g meas flux: %g +/- %g (99.7%% conf)' % ( - obj_pars['flux'], res['noshear']['flux'], res['noshear']['flux_err']*3, - )) - print('true g1: %g meas g1: %g +/- %g (99.7%% conf)' % ( - obj_pars['g1'], res['noshear']['g'][0], res['noshear']['g_err'][0]*3, - )) - print('true g2: %g meas g2: %g +/- %g (99.7%% conf)' % ( - obj_pars['g2'], res['noshear']['g'][1], res['noshear']['g_err'][1]*3, - )) - print( - "Delta g / sig g = (" - + f"{(obj_pars['g1'] - res['noshear']['g'][0]) / res['noshear']['g_err'][0]:.2f}," - + f" {(obj_pars['g2'] - res['noshear']['g'][1]) / res['noshear']['g_err'][1]:.2f})" - ) - - if args.show: - try: - import images - except ImportError: - from espy import images - - imfit = res.make_image() - - images.compare_images(obs.image, imfit) - - -def get_prior(*, rng, scale, T_range=None, F_range=None, nband=None): - """ - get a prior for use with the maximum likelihood fitter - - Parameters - ---------- - rng: np.random.RandomState - The random number generator - scale: float - Pixel scale - T_range: (float, float), optional - The range for the prior on T - F_range: (float, float), optional - Fhe range for the prior on flux - nband: int, optional - number of bands - """ - if T_range is None: - T_range = [-1.0, 1.e3] - if F_range is None: - F_range = [-100.0, 1.e9] - - g_prior = ngmix.priors.GPriorBA(sigma=0.1, rng=rng) - cen_prior = ngmix.priors.CenPrior( - cen1=0, cen2=0, sigma1=scale, sigma2=scale, rng=rng, - ) - T_prior = ngmix.priors.FlatPrior(minval=T_range[0], maxval=T_range[1], rng=rng) - F_prior = ngmix.priors.FlatPrior(minval=F_range[0], maxval=F_range[1], rng=rng) - - if nband is not None: - F_prior = [F_prior]*nband - - prior = ngmix.joint_prior.PriorSimpleSep( - cen_prior=cen_prior, - g_prior=g_prior, - T_prior=T_prior, - F_prior=F_prior, - ) - - return prior - - -def make_data(rng, noise, g1=0.05, g2=-0.02, flux=100.0): - """ - simulate an exponential object with moffat psf - - Parameters - ---------- - rng: np.random.RandomState - The random number generator - noise: float - Noise for the image - g1: float - object g1, default 0.05 - g2: float - object g2, default -0.02 - flux: float, optional - default 100 - - Returns - ------- - ngmix.Observation, pars dict - """ - - psf_noise = 1.0e-6 - - scale = 0.263 - - psf_fwhm = 0.9 - gal_hlr = 0.5 - dy, dx = rng.uniform(low=-scale/2, high=scale/2, size=2) - - psf = galsim.Moffat( - beta=2.5, fwhm=psf_fwhm, - ).shear( - g1=-0.01, - g2=-0.01, - ) - - obj0 = galsim.Exponential( - half_light_radius=gal_hlr, - flux=flux, - ).shear( - g1=g1, - g2=g2, - ).shift( - dx=dx, - dy=dy, - ) - - obj = galsim.Convolve(psf, obj0) - - psf_im = psf.drawImage(scale=scale).array - im = obj.drawImage(scale=scale).array - - psf_im += rng.normal(scale=psf_noise, size=psf_im.shape) - im += rng.normal(scale=noise, size=im.shape) - - cen = (np.array(im.shape)-1.0)/2.0 - psf_cen = (np.array(psf_im.shape)-1.0)/2.0 - - jacobian = ngmix.DiagonalJacobian( - row=cen[0] + dy/scale, col=cen[1] + dx/scale, scale=scale, - ) - psf_jacobian = ngmix.DiagonalJacobian( - row=psf_cen[0], col=psf_cen[1], scale=scale, - ) - - wt = im*0 + 1.0/noise**2 - psf_wt = psf_im*0 + 1.0/psf_noise**2 - - psf_obs = ngmix.Observation( - psf_im, - weight=psf_wt, - jacobian=psf_jacobian, - ) - - obs = ngmix.Observation( - im, - weight=wt, - jacobian=jacobian, - psf=psf_obs, - ) - - obj_pars = { - 'g1': g1, - 'g2': g2, - 'flux': flux, - } - return obs, obj_pars - - -def get_args(): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--seed', type=int, default=42, - help='seed for rng') - parser.add_argument('--show', action='store_true', - help='show plot comparing model and data') - parser.add_argument('--noise', type=float, default=0.01, - help='noise for images') - return parser.parse_args() - - -if __name__ == '__main__': - main() diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 08e0729b8..630e1ade8 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -188,8 +188,6 @@ class Ngmix(object): Path to merged single-exposure single-HDU headers w_log : logging.Logger Logging instance - check_existing_dir : str, optional - Directory or previous run, default is ``None`` save_batch : int, optional Save output catalogue in batches of this size; detaul is ``-1`` (no batch save) @@ -216,7 +214,6 @@ def __init__( pixel_scale, f_wcs_path, w_log, - check_existing_dir=None, save_batch=-1, id_obj_min=-1, id_obj_max=-1, @@ -251,10 +248,8 @@ def __init__( self._zero_point = zero_point self._pixel_scale = pixel_scale - # MKDEBUG check whether still used self._f_wcs_path = f_wcs_path - self._check_existing_dir = check_existing_dir self._save_batch = save_batch self._id_obj_min = id_obj_min self._id_obj_max = id_obj_max @@ -290,7 +285,6 @@ def MegaCamFlip(self, vign, ccd_nb): if ccd_nb < 18 or ccd_nb in [36, 37]: # swap x axis so origin is on top-right return np.rot90(vign, k=2) - print('rotating megapipe image') else: # swap y axis so origin is on bottom-left return vign @@ -461,52 +455,6 @@ def get_output_path(self, directory): return f"{directory}/ngmix{self._file_number_string}.fits" - def get_last_id(self, cat_path): - """Get Last ID. - - Return ID of last projessed objects found in input catalogue file. - - Parameters - ---------- - cat_path: str - input catalogue file path - - Returns - -------- - int - object ID - - """ - cat = file_io.FITSCatalogue( - cat_path, - SEx_catalogue=True, - open_mode=file_io.BaseCatalogue.OpenMode.ReadOnly, - ) - cat.open() - - if len(cat._cat_data) != 6: - raise IndexError( - f"Found {len(cat._cat_data)} HDUs instead of 6 in catalogue" - + f" {cat_path}" - ) - - ids = cat._cat_data[1].data["id"] - - # No object foun - if len(ids) == 0: - return -1 - - id_last = ids[-1] - for hdu_no in range(2, 6): - if id_last != cat._cat_data[hdu_no].data["id"][-1]: - raise ValueError( - f"Last ID {cat._cat_data[hdu_no].data['id'][-1]} in HDU" - + f" #{hdu_no} inconsistent with {id_last}" - ) - - return id_last - - def save_results(self, output_dict): """Save Results. @@ -763,7 +711,7 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): flag_vign[np.where(tile_vign == -1e30)] = 2**10 v_flag_tmp = flag_vign.ravel() # remove objects that are more than 1/3 masked - if len(np.where(v_flag_tmp != 0)[0]) / (51 * 51) > 1 / 3.0: + if len(np.where(v_flag_tmp != 0)[0]) / v_flag_tmp.size > 1 / 3.0: continue weight_vign = ( @@ -1134,34 +1082,3 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): resdict, obsdict = boot.go(gal_obs_list) psf_res = average_multiepoch_psf(obsdict) return resdict, psf_res - - -# Define the SExtractor parameters for a galaxy -def sextractor_e1e2(e,theta): - """sextractor_e1e2 - - computes ellipticity from sextrator quantities - Parameters - ---------- - stamp : Postage_stamp - List of the galaxy vignets. List indices run over epochs - prior : ngmix.priors - Priors for the fitting parameters - flux_guess : np.ndarray - guess for flux - pixel_scale : float - pixel scale in arcsec - rng : numpy.random.RandomState - Random state for guesses and priors - - Returns - ------- - np.ndarray - ellipticity - - """ - # Convert the position angle from degrees to radians - phi = np.radians(theta) - np.pi/2 - # Calculate the ellipticity vector - e_vec = e * np.array([np.cos(2*phi), np.sin(2*phi)]) - return e_vec diff --git a/src/shapepipe/modules/ngmix_runner.py b/src/shapepipe/modules/ngmix_runner.py index b0e91a236..1b5150ef0 100644 --- a/src/shapepipe/modules/ngmix_runner.py +++ b/src/shapepipe/modules/ngmix_runner.py @@ -50,15 +50,6 @@ def ngmix_runner( # Path to merged single-exposure single-HDU headers f_wcs_path = input_file_list[6] - # Input directory to check for already retrieved files - if config.has_option(module_config_sec, "CHECK_EXISTING_DIR"): - check_existing_dir = config.getexpanded( - module_config_sec, - "CHECK_EXISTING_DIR", - ) - else: - check_existing_dir = None - # Batch save option if config.has_option(module_config_sec, "SAVE_BATCH"): save_batch = config.getint( @@ -83,7 +74,6 @@ def ngmix_runner( pixel_scale, f_wcs_path, w_log, - check_existing_dir=check_existing_dir, save_batch=save_batch, id_obj_min=id_obj_min, id_obj_max=id_obj_max, From d6531b1bb70b6a3bc612f883f5f5ac019f21f838 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Wed, 10 Jun 2026 03:56:14 +0200 Subject: [PATCH 47/80] fix(ngmix): emit true half-light radii in r50 columns; dedupe PSF size columns The v2.0 size columns were mislabeled: galaxy r50/r50_err were bit-for-bit copies of the area T = 2 sigma^2 (pars[4]), and the PSF r50 columns held bare sigma, missing the sqrt(2 ln 2) = 1.17741 factor. No column in the file was a true half-light radius, and the same name root meant an area on the galaxy side and a length on the PSF side. Now every r50 column is a genuine half-light radius (r50 = 1.17741 sigma = sqrt(ln 2 * T)) with propagated errors, matching the UNIONS-3500 WL I convention (r_h primary, T = 2 sigma^2 the derived DES area): - galaxy: r50 = sqrt(ln 2 * T), r50_err = r50 * T_err / (2 T); NaN when the fitted T is non-positive - PSF: r50psf = sqrt(2 ln 2) * sigma_psf, with the corrected error d sigma / d T = 1 / (4 sigma) (the previous T_err / (2 sigma) was a factor-2 over-estimate) - retire the duplicate columns T_psfo_ngmix (== Tpsf), T_err_psfo_ngmix, r50_psfo_ngmix, r50_err_psfo_ngmix (== r50psf and its error); add the missing Tpsf_err so areas carry their error on both sides - make_cat: read Tpsf instead of the retired T_psfo_ngmix (output column NGMIX_T_PSFo_ unchanged, so downstream consumers are unaffected) - fix the get_noise docstring: guess[4] is T, not r50 Tests pin the new semantics: both r50 columns on the same scale (their ratio is sqrt(T/Tpsf)), retired names absent, NaN for non-positive T. Co-Authored-By: Claude Fable 5 --- .../modules/make_cat_package/make_cat.py | 2 +- src/shapepipe/modules/ngmix_package/ngmix.py | 50 +++++---- src/shapepipe/tests/test_ngmix.py | 104 ++++++++++++++++++ 3 files changed, 135 insertions(+), 21 deletions(-) diff --git a/src/shapepipe/modules/make_cat_package/make_cat.py b/src/shapepipe/modules/make_cat_package/make_cat.py index 72024b3a4..890e24713 100644 --- a/src/shapepipe/modules/make_cat_package/make_cat.py +++ b/src/shapepipe/modules/make_cat_package/make_cat.py @@ -449,7 +449,7 @@ def _save_ngmix_data(self, ngmix_cat_path, moments=False): ) self._add2dict(f"NGMIX{m}_ELL_PSFo_{key}", g_psf, idx) - t_psfo = ncf_data["T_psfo_ngmix"][ind[0]] + t_psfo = ncf_data["Tpsf"][ind[0]] self._add2dict(f"NGMIX{m}_T_PSFo_{key}", t_psfo, idx) self._add2dict( diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 630e1ade8..e6f183875 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -19,6 +19,11 @@ from shapepipe.pipeline import file_io +# Gaussian half-light radius per unit sigma: r50 = sqrt(2 ln 2) * sigma +# = 1.17741 * sigma. With the ngmix area parameter T = 2 sigma^2 this +# gives r50 = SIGMA_TO_R50 * sqrt(T / 2) = sqrt(ln 2 * T). +SIGMA_TO_R50 = np.sqrt(2.0 * np.log(2.0)) + def get_prior(pixel_scale, rng, T_range=None, F_range=None): """Build ngmix joint prior for a 6-parameter galaxy model. @@ -331,12 +336,8 @@ def compile_results(self, results): 'ntry_fit', 'g1_psfo_ngmix', 'g2_psfo_ngmix', - 'T_psfo_ngmix', - 'T_err_psfo_ngmix', - 'r50_psfo_ngmix', 'g1_err_psfo_ngmix', 'g2_err_psfo_ngmix', - 'r50_err_psfo_ngmix', 'g1', 'g1_err', 'g2', @@ -344,9 +345,11 @@ def compile_results(self, results): 'T', 'T_err', 'Tpsf', + 'Tpsf_err', 'r50', 'r50_err', 'r50psf', + 'r50psf_err', 'g1_psf', 'g2_psf', 'flux', @@ -391,24 +394,28 @@ def compile_results(self, results): output_dict[name]["g2_err_psfo_ngmix"].append( results[idx]["g_err_PSFo"][1] ) - output_dict[name]["T_psfo_ngmix"].append(results[idx]["T_PSFo"]) - output_dict[name]["T_err_psfo_ngmix"].append( - results[idx]["T_err_PSFo"] - ) - output_dict[name]['r50_psfo_ngmix'].append( - results[idx]['r50_PSFo'] - ) - output_dict[name]['r50_err_psfo_ngmix'].append( - results[idx]['r50_err_PSFo'] - ) output_dict[name]["T"].append(results[idx][name]["T"]) output_dict[name]["T_err"].append(results[idx][name]["T_err"]) output_dict[name]["Tpsf"].append(results[idx]["T_PSFo"]) + output_dict[name]["Tpsf_err"].append(results[idx]["T_err_PSFo"]) output_dict[name]["g1_psf"].append(results[idx]["g_PSFo"][0]) output_dict[name]["g2_psf"].append(results[idx]["g_PSFo"][1]) - output_dict[name]['r50'].append(results[idx][name]['pars'][4]) - output_dict[name]['r50_err'].append(results[idx][name]['pars_err'][4]) + + # Galaxy half-light radius from the fitted area T = 2 sigma^2: + # r50 = sqrt(ln 2 * T), with d r50 / d T = r50 / (2 T) + T_gal = results[idx][name]["T"] + T_gal_err = results[idx][name]["T_err"] + if T_gal > 0: + r50_gal = SIGMA_TO_R50 * np.sqrt(T_gal / 2) + r50_gal_err = r50_gal * T_gal_err / (2 * T_gal) + else: + r50_gal = r50_gal_err = np.nan + output_dict[name]['r50'].append(r50_gal) + output_dict[name]['r50_err'].append(r50_gal_err) output_dict[name]['r50psf'].append(results[idx]["r50_PSFo"]) + output_dict[name]['r50psf_err'].append( + results[idx]["r50_err_PSFo"] + ) output_dict[name]["g1"].append(results[idx][name]["g"][0]) output_dict[name]["g2"].append(results[idx][name]["g"][1]) output_dict[name]["g1_err"].append( @@ -621,14 +628,17 @@ def process(self): 1 for k in ['noshear', '1p', '1m', '2p', '2m'] if res.get(k, {}).get('flags', 0) != 0 ) - r50_psfo = np.sqrt(max(psf_res['T_psf'], 0) / 2) + # PSF half-light radius r50 = sqrt(2 ln 2) * sigma with + # sigma = sqrt(T / 2); error from d sigma / d T = 1 / (4 sigma) + sigma_psfo = np.sqrt(max(psf_res['T_psf'], 0) / 2) res['g_PSFo'] = psf_res['g_psf'] res['g_err_PSFo'] = psf_res['g_psf_err'] res['T_PSFo'] = psf_res['T_psf'] res['T_err_PSFo'] = psf_res['T_psf_err'] - res['r50_PSFo'] = r50_psfo + res['r50_PSFo'] = SIGMA_TO_R50 * sigma_psfo res['r50_err_PSFo'] = ( - psf_res['T_psf_err'] / (2 * r50_psfo) if r50_psfo > 0 else np.nan + SIGMA_TO_R50 * psf_res['T_psf_err'] / (4 * sigma_psfo) + if sigma_psfo > 0 else np.nan ) res['mcal_flags'] = 0 final_res.append(res) @@ -838,7 +848,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): Weight image guess : list Gaussian parameters fot the window function - ``[x0, y0, g1, g2, r50, flux]`` + ``[x0, y0, g1, g2, T, flux]`` pixel_scale : float Pixel scale of the galaxy image thresh : float, optional diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index aeb2274c7..c324dfa6e 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -94,3 +94,107 @@ def test_metacal_is_reproducible_with_fixed_seed(): irreproducible from one run to the next. """ npt.assert_array_equal(_metacal_noshear_g(42), _metacal_noshear_g(42)) + + +def _fake_metacal_result(T, T_err, T_psf, T_psf_err): + """Build one minimal metacal result as produced by the process loop. + + The PSF size entries mirror the conversion done at the source: + ``r50_PSFo = sqrt(2 ln 2) * sigma`` with ``sigma = sqrt(T_psf / 2)``. + """ + from shapepipe.modules.ngmix_package.ngmix import SIGMA_TO_R50 + + sigma_psf = np.sqrt(max(T_psf, 0) / 2) + per_type = { + "nfev": 1, + "g": [0.01, -0.02], + "g_cov": np.diag([1e-4, 1e-4]), + "T": T, + "T_err": T_err, + "flux": 100.0, + "flux_err": 1.0, + "s2n": 50.0, + "flags": 0, + } + res = { + "obj_id": 1, + "n_epoch_model": 1, + "moments_fail": 0, + "g_PSFo": [0.001, 0.002], + "g_err_PSFo": [1e-5, 1e-5], + "T_PSFo": T_psf, + "T_err_PSFo": T_psf_err, + "r50_PSFo": SIGMA_TO_R50 * sigma_psf, + "r50_err_PSFo": ( + SIGMA_TO_R50 * T_psf_err / (4 * sigma_psf) + if sigma_psf > 0 + else np.nan + ), + "mcal_flags": 0, + } + res.update( + {name: dict(per_type) for name in ("1m", "1p", "2m", "2p", "noshear")} + ) + return res + + +def test_compile_results_size_columns_are_half_light_radii(): + """Every r50 column is a true half-light radius, on both sides. + + Galaxy ``r50 = sqrt(ln 2 * T)`` (not the raw area ``pars[4]``), PSF + ``r50psf = sqrt(2 ln 2) * sigma_psf`` (not bare sigma), and the + redundant ``*_psfo_ngmix`` size duplicates are gone. + """ + from shapepipe.modules.ngmix_package.ngmix import Ngmix, SIGMA_TO_R50 + + T, T_err = 0.18, 0.02 + T_psf, T_psf_err = 0.09, 0.001 + + inst = object.__new__(Ngmix) + inst._zero_point = 30.0 + out = inst.compile_results([_fake_metacal_result(T, T_err, T_psf, T_psf_err)]) + + noshear = out["noshear"] + + # galaxy: r50 = sqrt(ln2 * T) = 1.17741 * sqrt(T / 2), error dT * r50/(2T) + r50_expected = np.sqrt(np.log(2) * T) + npt.assert_allclose(noshear["r50"], [r50_expected]) + npt.assert_allclose(noshear["r50_err"], [r50_expected * T_err / (2 * T)]) + + # PSF: r50psf = sqrt(2 ln2) * sigma, sigma = sqrt(T_psf / 2) + sigma_psf = np.sqrt(T_psf / 2) + npt.assert_allclose(noshear["r50psf"], [SIGMA_TO_R50 * sigma_psf]) + npt.assert_allclose( + noshear["r50psf_err"], [SIGMA_TO_R50 * T_psf_err / (4 * sigma_psf)] + ) + + # galaxy/PSF r50 are now commensurable: same convention on both sides + npt.assert_allclose( + np.array(noshear["r50"]) / np.array(noshear["r50psf"]), + [np.sqrt(T / T_psf)], + ) + + # areas pass through untouched, with the err asymmetry fixed + npt.assert_allclose(noshear["Tpsf"], [T_psf]) + npt.assert_allclose(noshear["Tpsf_err"], [T_psf_err]) + + # the *_psfo_ngmix size duplicates are retired + for retired in ( + "T_psfo_ngmix", + "T_err_psfo_ngmix", + "r50_psfo_ngmix", + "r50_err_psfo_ngmix", + ): + assert retired not in noshear + + +def test_compile_results_nonpositive_T_gives_nan_r50(): + """A non-positive fitted area cannot yield a real half-light radius.""" + from shapepipe.modules.ngmix_package.ngmix import Ngmix + + inst = object.__new__(Ngmix) + inst._zero_point = 30.0 + out = inst.compile_results([_fake_metacal_result(-0.05, 0.02, 0.09, 0.001)]) + + assert np.isnan(out["noshear"]["r50"]).all() + assert np.isnan(out["noshear"]["r50_err"]).all() From 6ddea5b2912fc761a2c6840d5cedc8a19591c84b Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 6 Jun 2026 02:01:17 +0200 Subject: [PATCH 48/80] fix(ngmix): restore weight mask binarization --- src/shapepipe/modules/ngmix_package/ngmix.py | 1 + src/shapepipe/tests/test_ngmix.py | 21 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 630e1ade8..b4043567b 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -893,6 +893,7 @@ def prepare_ngmix_weights(gal, weight, flag, rng): """ weight_map = np.copy(weight) weight_map[flag != 0] = 0.0 + weight_map[weight_map != 0] = 1 sig_noise = sigma_mad(gal) diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index aeb2274c7..9424d7496 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -94,3 +94,24 @@ def test_metacal_is_reproducible_with_fixed_seed(): irreproducible from one run to the next. """ npt.assert_array_equal(_metacal_noshear_g(42), _metacal_noshear_g(42)) + + +def test_weight_map_recovers_injected_inverse_variance(): + """A supplied inverse-variance map must not be renormalized twice.""" + from shapepipe.modules.ngmix_package.ngmix import prepare_ngmix_weights + from shapepipe.testing.simulate import make_data + + noise = 1e-3 + gals, _, _, weights, flags, _ = make_data( + rng=np.random.RandomState(123), + shear=(0.0, 0.0), + noise=noise, + n_epochs=1, + img_size=201, + ) + _, weight_map, _ = prepare_ngmix_weights( + gals[0], weights[0], flags[0], np.random.RandomState(0) + ) + + recovered = np.median(weight_map[weight_map > 0]) + npt.assert_allclose(recovered, 1.0 / noise**2, rtol=0.15) From f466c987f3445df77b19d9fb821fd229eb95b765 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 6 Jun 2026 02:07:25 +0200 Subject: [PATCH 49/80] feat(ngmix): support background-rms weights --- .../modules/ngmix_package/__init__.py | 4 + src/shapepipe/modules/ngmix_package/ngmix.py | 87 ++++++++++++++----- src/shapepipe/modules/ngmix_runner.py | 18 +++- src/shapepipe/tests/test_ngmix.py | 51 +++++++++++ 4 files changed, 135 insertions(+), 25 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/__init__.py b/src/shapepipe/modules/ngmix_package/__init__.py index db546523e..db9ced5a1 100644 --- a/src/shapepipe/modules/ngmix_package/__init__.py +++ b/src/shapepipe/modules/ngmix_package/__init__.py @@ -38,6 +38,10 @@ ID_OBJ_MAX : int ID of last galaxy object to be processed; not used if set to ``-1`` (default) +BKG_RMS_VIGNET_PATH : str, optional + Path to a ``background_rms_vignet*.sqlite`` file produced by + ``vignetmaker_runner``. The string may contain + ``{file_number_string}``, which is replaced by the current tile ID. """ diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index b4043567b..8c5459261 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -124,6 +124,7 @@ def __init__( self.psfs = [] self.weights = [] self.flags = [] + self.bkg_rms = [] self.jacobs = [] self.bkg_sub = bkg_sub self.megacam_flip = megacam_flip @@ -141,6 +142,7 @@ class Vignet(): weight_vignet_path flag_vignet_path f_wcs_path + bkg_rms_vignet_path """ def __init__( self, @@ -149,7 +151,8 @@ def __init__( psf_vignet_path, weight_vignet_path, flag_vignet_path, - f_wcs_path + f_wcs_path, + bkg_rms_vignet_path=None, ): self.f_wcs_file = SqliteDict(f_wcs_path) @@ -158,6 +161,11 @@ def __init__( self.psf_vign_cat = SqliteDict(psf_vignet_path) self.weight_vign_cat = SqliteDict(weight_vignet_path) self.flag_vign_cat = SqliteDict(flag_vignet_path) + self.bkg_rms_vign_cat = ( + SqliteDict(bkg_rms_vignet_path) + if bkg_rms_vignet_path is not None + else None + ) def close(self): self.f_wcs_file.close() @@ -166,6 +174,8 @@ def close(self): self.flag_vign_cat.close() self.weight_vign_cat.close() self.psf_vign_cat.close() + if self.bkg_rms_vign_cat is not None: + self.bkg_rms_vign_cat.close() class Ngmix(object): """Ngmix. @@ -219,10 +229,10 @@ def __init__( id_obj_max=-1, ): - if len(input_file_list) != 6: + if len(input_file_list) not in {6, 7}: raise IndexError( f"Input file list has length {len(input_file_list)}," - + " required is 6" + + " required is 6 or 7" ) self._tile_cat_path = input_file_list[0] @@ -232,7 +242,8 @@ def __init__( input_file_list[3], input_file_list[4], input_file_list[5], - f_wcs_path + f_wcs_path, + input_file_list[6] if len(input_file_list) == 7 else None, ) #self._gal_vignet_path = input_file_list[1] #self._bkg_vignet_path = input_file_list[2] @@ -714,8 +725,11 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): if len(np.where(v_flag_tmp != 0)[0]) / v_flag_tmp.size > 1 / 3.0: continue - weight_vign = ( - vignet.weight_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + weight_vign = vignet.weight_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + bkg_rms_vign = ( + vignet.bkg_rms_vign_cat[str(obj_id)][expccd_name]['VIGNET'] + if vignet.bkg_rms_vign_cat is not None + else None ) jacob = get_galsim_jacobian( @@ -729,11 +743,16 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): ) # rescale by relative zero-points - gal_vign_scaled, weight_vign_scaled = rescale_epoch_fluxes( + ( + gal_vign_scaled, + weight_vign_scaled, + bkg_rms_vign_scaled, + ) = rescale_epoch_fluxes( gal_vign_sub_bkg, weight_vign, - header - ) + header, + bkg_rms_vign, + ) # gather postage stamps in all of the epochs stamp.gals.append(gal_vign_scaled) @@ -742,6 +761,7 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): ) stamp.weights.append(weight_vign_scaled) stamp.flags.append(flag_vign) + stamp.bkg_rms.append(bkg_rms_vign_scaled) stamp.jacobs.append(jacob) return stamp @@ -767,7 +787,7 @@ def background_subtract(gal,bkg): return gal_vign_sub_bkg -def rescale_epoch_fluxes(gal,weight,header): +def rescale_epoch_fluxes(gal, weight, header, bkg_rms=None): """rescale epochs by relative zeropoints to be on the same flux scale Parameters @@ -778,6 +798,8 @@ def rescale_epoch_fluxes(gal,weight,header): weight image header : image header + bkg_rms : numpy.ndarray, optional + Background RMS image Returns ------- @@ -785,13 +807,16 @@ def rescale_epoch_fluxes(gal,weight,header): rescaled galaxy image numpy.ndarray rescaled weight image + numpy.ndarray or None + rescaled background RMS image """ Fscale = header['FSCALE'] gal_scaled = gal * Fscale weight_scaled = weight * 1 / Fscale ** 2 + bkg_rms_scaled = bkg_rms * Fscale if bkg_rms is not None else None - return gal_scaled, weight_scaled + return gal_scaled, weight_scaled, bkg_rms_scaled def get_galsim_jacobian(wcs, ra, dec): """Get local wcs. @@ -868,7 +893,7 @@ def get_noise(gal, weight, guess, pixel_scale, thresh=1.2): return sig_noise -def prepare_ngmix_weights(gal, weight, flag, rng): +def prepare_ngmix_weights(gal, weight, flag, rng, bkg_rms=None): """bookkeeping for ngmix weights. runs on a single galaxy and epoch pixel scale and galaxy guess TO DO: decide if we want galaxy guess stuff @@ -881,6 +906,9 @@ def prepare_ngmix_weights(gal, weight, flag, rng): rng : numpy.random.RandomState Random state for the noise realisations (seeded per tile for reproducibility). + bkg_rms : numpy.ndarray, optional + Per-pixel background RMS map. If supplied, unmasked pixels use + ``1 / bkg_rms**2`` as the ngmix inverse variance. Returns ------- @@ -891,24 +919,33 @@ def prepare_ngmix_weights(gal, weight, flag, rng): numpy.ndarray Noise image. """ - weight_map = np.copy(weight) - weight_map[flag != 0] = 0.0 - weight_map[weight_map != 0] = 1 - - sig_noise = sigma_mad(gal) + mask = np.copy(weight) != 0 + mask[flag != 0] = False + + if bkg_rms is None: + sig_noise = sigma_mad(gal) + weight_map = mask.astype(float) * 1 / sig_noise ** 2 + else: + valid_rms = np.isfinite(bkg_rms) & (bkg_rms > 0) + mask &= valid_rms + weight_map = np.zeros_like(gal, dtype=float) + weight_map[mask] = 1.0 / bkg_rms[mask] ** 2 + sig_noise = ( + np.median(bkg_rms[mask]) + if mask.any() + else sigma_mad(gal) + ) noise_img = rng.standard_normal(gal.shape) * sig_noise noise_img_gal = rng.standard_normal(gal.shape) * sig_noise gal_masked = np.copy(gal) - if (weight_map == 0).any(): - gal_masked[weight_map == 0] = noise_img_gal[weight_map == 0] - - weight_map *= 1 / sig_noise ** 2 + if (~mask).any(): + gal_masked[~mask] = noise_img_gal[~mask] return gal_masked, weight_map, noise_img -def make_ngmix_observation(gal, weight, flag, psf, wcs, rng): +def make_ngmix_observation(gal, weight, flag, psf, wcs, rng, bkg_rms=None): """Build an ngmix Observation for a single galaxy epoch. The galaxy Jacobian is re-centered on the HSM centroid so that the @@ -925,6 +962,8 @@ def make_ngmix_observation(gal, weight, flag, psf, wcs, rng): rng : numpy.random.RandomState Random state for the noise realisations (seeded per tile for reproducibility). + bkg_rms : numpy.ndarray, optional + Per-pixel background RMS map. Returns ------- @@ -938,7 +977,7 @@ def make_ngmix_observation(gal, weight, flag, psf, wcs, rng): psf_obs = Observation(psf, jacobian=psf_jacob) gal_masked, weight_map, noise_img = prepare_ngmix_weights( - gal, weight, flag, rng + gal, weight, flag, rng, bkg_rms=bkg_rms ) # Re-center Jacobian on HSM centroid (pixel offset from stamp center). @@ -1046,6 +1085,7 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): gal_obs_list = ObsList() for n_e in range(n_epoch): + bkg_rms = stamp.bkg_rms[n_e] if len(stamp.bkg_rms) > n_e else None gal_obs = make_ngmix_observation( stamp.gals[n_e], stamp.weights[n_e], @@ -1053,6 +1093,7 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): stamp.psfs[n_e], stamp.jacobs[n_e], rng, + bkg_rms=bkg_rms, ) gal_obs_list.append(gal_obs) diff --git a/src/shapepipe/modules/ngmix_runner.py b/src/shapepipe/modules/ngmix_runner.py index 1b5150ef0..bc0e6290f 100644 --- a/src/shapepipe/modules/ngmix_runner.py +++ b/src/shapepipe/modules/ngmix_runner.py @@ -6,6 +6,8 @@ """ +import os + from shapepipe.modules.module_decorator import module_runner from shapepipe.modules.ngmix_package.ngmix import Ngmix @@ -50,6 +52,19 @@ def ngmix_runner( # Path to merged single-exposure single-HDU headers f_wcs_path = input_file_list[6] + if config.has_option(module_config_sec, "BKG_RMS_VIGNET_PATH"): + bkg_rms_vignet_path = config.getexpanded( + module_config_sec, + "BKG_RMS_VIGNET_PATH", + ).format(file_number_string=file_number_string) + if not os.path.exists(bkg_rms_vignet_path): + raise FileNotFoundError( + f"Background RMS vignet file not found: {bkg_rms_vignet_path}" + ) + input_file_list = input_file_list[:6] + [bkg_rms_vignet_path] + else: + input_file_list = input_file_list[:6] + # Batch save option if config.has_option(module_config_sec, "SAVE_BATCH"): save_batch = config.getint( @@ -65,9 +80,8 @@ def ngmix_runner( id_obj_max = config.getint(module_config_sec, "ID_OBJ_MAX") # Initialise class instance - # input_file_list[6] is log_exp_headers, already extracted as f_wcs_path ngmix_inst = Ngmix( - input_file_list[:6], + input_file_list, run_dirs["output"], file_number_string, zero_point, diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index 9424d7496..680333fef 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -115,3 +115,54 @@ def test_weight_map_recovers_injected_inverse_variance(): recovered = np.median(weight_map[weight_map > 0]) npt.assert_allclose(recovered, 1.0 / noise**2, rtol=0.15) + + +def test_background_rms_builds_per_pixel_inverse_variance(): + """BACKGROUND_RMS supplies the variance, while weight and flag supply masks.""" + from shapepipe.modules.ngmix_package.ngmix import prepare_ngmix_weights + + gal = np.ones((3, 3)) + weight = np.ones((3, 3)) + flag = np.zeros((3, 3)) + bkg_rms = np.array( + [ + [1.0, 2.0, 4.0], + [0.5, 0.0, np.nan], + [3.0, 2.0, 1.0], + ] + ) + weight[2, 0] = 0 + flag[2, 1] = 1 + + _, weight_map, noise_img = prepare_ngmix_weights( + gal, weight, flag, np.random.RandomState(0), bkg_rms=bkg_rms + ) + + expected = np.array( + [ + [1.0, 0.25, 0.0625], + [4.0, 0.0, 0.0], + [0.0, 0.0, 1.0], + ] + ) + npt.assert_allclose(weight_map, expected) + assert noise_img.shape == gal.shape + + +def test_rescale_epoch_fluxes_scales_background_rms_like_image_counts(): + from astropy.io import fits + from shapepipe.modules.ngmix_package.ngmix import rescale_epoch_fluxes + + header = fits.Header() + header["FSCALE"] = 2.0 + + gal_scaled, weight_scaled, bkg_rms_scaled = rescale_epoch_fluxes( + np.ones((2, 2)), + np.ones((2, 2)) * 8.0, + header, + np.ones((2, 2)) * 3.0, + ) + + npt.assert_allclose(gal_scaled, 2.0) + npt.assert_allclose(weight_scaled, 2.0) + npt.assert_allclose(bkg_rms_scaled, 6.0) From 7a44abda75ab644d04c1b2a99c05c655393f8a0d Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 6 Jun 2026 02:12:56 +0200 Subject: [PATCH 50/80] config(ngmix): wire background-rms vignets --- example/cfis/config_exp_mccd.ini | 3 +-- example/cfis/config_exp_psfex.ini | 2 +- example/cfis/config_tile_MiViSmVi.ini | 4 ++-- example/cfis/config_tile_Ng_batch_psfex_uc.ini | 2 ++ example/cfis/config_tile_Ng_template.ini | 2 ++ example/cfis/config_tile_Ng_template_batch.ini | 2 ++ example/cfis/config_tile_PiViVi_canfar_sx.ini | 4 ++-- example/cfis/config_tile_PiViVi_canfar_uc.ini | 4 ++-- example/cfis_simu/config_tile_MiViSmVi.ini | 4 ++-- example/cfis_simu/config_tile_Ng_template.ini | 2 ++ example/cfis_simu/config_tile_Sx_exp_mccd.ini | 3 +-- example/cfis_simu/config_tile_Sx_exp_psfex.ini | 4 ++-- 12 files changed, 21 insertions(+), 15 deletions(-) diff --git a/example/cfis/config_exp_mccd.ini b/example/cfis/config_exp_mccd.ini index 8307a6aeb..092df0417 100644 --- a/example/cfis/config_exp_mccd.ini +++ b/example/cfis/config_exp_mccd.ini @@ -116,7 +116,7 @@ BKG_FROM_HEADER = False # Type of image check (optional), default not used, can be a list of # BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, MINIBACK_RMS, -BACKGROUND, # FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND +CHECKIMAGE = BACKGROUND, BACKGROUND_RMS # File name suffix for the output sextractor files (optional) SUFFIX = tile SUFFIX = sexcat @@ -235,4 +235,3 @@ PSF_MODEL_PATTERN = fitted_model # PSF model separator PSF_MODEL_SEPARATOR = - - diff --git a/example/cfis/config_exp_psfex.ini b/example/cfis/config_exp_psfex.ini index 218b498f0..940c56642 100644 --- a/example/cfis/config_exp_psfex.ini +++ b/example/cfis/config_exp_psfex.ini @@ -112,7 +112,7 @@ BKG_FROM_HEADER = False # Type of image check (optional), default not used, can be a list of # BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, MINIBACK_RMS, -BACKGROUND, # FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND +CHECKIMAGE = BACKGROUND, BACKGROUND_RMS # File name suffix for the output sextractor files (optional) SUFFIX = tile SUFFIX = sexcat diff --git a/example/cfis/config_tile_MiViSmVi.ini b/example/cfis/config_tile_MiViSmVi.ini index 4a6eb79cc..02bd9a005 100644 --- a/example/cfis/config_tile_MiViSmVi.ini +++ b/example/cfis/config_tile_MiViSmVi.ini @@ -173,5 +173,5 @@ PREFIX = # Additional parameters for path and file pattern corresponding to single-exposure # run outputs -ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2 -ME_IMAGE_PATTERN = flag, image, weight, background +ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2, sextractor_runner_run_2 +ME_IMAGE_PATTERN = flag, image, weight, background, background_rms diff --git a/example/cfis/config_tile_Ng_batch_psfex_uc.ini b/example/cfis/config_tile_Ng_batch_psfex_uc.ini index 9b57396c5..1eef9a318 100644 --- a/example/cfis/config_tile_Ng_batch_psfex_uc.ini +++ b/example/cfis/config_tile_Ng_batch_psfex_uc.ini @@ -64,6 +64,8 @@ FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 +BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_tile_PiViVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite + # Magnitude zero-point MAG_ZP = 30.0 diff --git a/example/cfis/config_tile_Ng_template.ini b/example/cfis/config_tile_Ng_template.ini index 70cf91de7..74bde8342 100644 --- a/example/cfis/config_tile_Ng_template.ini +++ b/example/cfis/config_tile_Ng_template.ini @@ -64,6 +64,8 @@ FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 +BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite + # Magnitude zero-point MAG_ZP = 30.0 diff --git a/example/cfis/config_tile_Ng_template_batch.ini b/example/cfis/config_tile_Ng_template_batch.ini index 1ddb00d44..1b0c15cc4 100644 --- a/example/cfis/config_tile_Ng_template_batch.ini +++ b/example/cfis/config_tile_Ng_template_batch.ini @@ -64,6 +64,8 @@ FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 +BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite + # Directory of previous run, optional. CHECK_EXISTING_DIR = $SP_RUN/output/run_sp_tile_ngmix_Ng1u_prev/ngmix_runner/output diff --git a/example/cfis/config_tile_PiViVi_canfar_sx.ini b/example/cfis/config_tile_PiViVi_canfar_sx.ini index dcd8a48cc..ca7efd9ec 100644 --- a/example/cfis/config_tile_PiViVi_canfar_sx.ini +++ b/example/cfis/config_tile_PiViVi_canfar_sx.ini @@ -164,5 +164,5 @@ PREFIX = # run outputs. ME_IMAGE_EXP_DIR/ME_IMAGE_EXP_RUNNERS replace ME_IMAGE_DIR for # the v2.0 per-exposure pipeline; output dirs are discovered by scanning $SP_EXP. ME_IMAGE_EXP_DIR = $SP_EXP -ME_IMAGE_EXP_RUNNERS = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner -ME_IMAGE_PATTERN = flag, image, weight, background +ME_IMAGE_EXP_RUNNERS = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner, sextractor_runner +ME_IMAGE_PATTERN = flag, image, weight, background, background_rms diff --git a/example/cfis/config_tile_PiViVi_canfar_uc.ini b/example/cfis/config_tile_PiViVi_canfar_uc.ini index 32c530788..d59af1c4a 100644 --- a/example/cfis/config_tile_PiViVi_canfar_uc.ini +++ b/example/cfis/config_tile_PiViVi_canfar_uc.ini @@ -162,5 +162,5 @@ PREFIX = # run outputs. ME_IMAGE_EXP_DIR/ME_IMAGE_EXP_RUNNERS replace ME_IMAGE_DIR for # the v2.0 per-exposure pipeline; output dirs are discovered by scanning $SP_EXP. ME_IMAGE_EXP_DIR = $SP_EXP -ME_IMAGE_EXP_RUNNERS = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner -ME_IMAGE_PATTERN = flag, image, weight, background +ME_IMAGE_EXP_RUNNERS = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner, sextractor_runner +ME_IMAGE_PATTERN = flag, image, weight, background, background_rms diff --git a/example/cfis_simu/config_tile_MiViSmVi.ini b/example/cfis_simu/config_tile_MiViSmVi.ini index dfb584bda..62edd4252 100644 --- a/example/cfis_simu/config_tile_MiViSmVi.ini +++ b/example/cfis_simu/config_tile_MiViSmVi.ini @@ -176,6 +176,6 @@ PREFIX = # Additional parameters for path and file pattern corresponding to single-exposure # run outputs -ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2 -ME_IMAGE_PATTERN = flag, image, weight, background +ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2, sextractor_runner_run_2 +ME_IMAGE_PATTERN = flag, image, weight, background, background_rms ME_LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite diff --git a/example/cfis_simu/config_tile_Ng_template.ini b/example/cfis_simu/config_tile_Ng_template.ini index f97e8bc9d..9779f46e9 100644 --- a/example/cfis_simu/config_tile_Ng_template.ini +++ b/example/cfis_simu/config_tile_Ng_template.ini @@ -67,6 +67,8 @@ NUMBERING_SCHEME = -000-000 # Multi-epoch mode: Path to file with single-exposure WCS header information LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite +BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite + # Magnitude zero-point MAG_ZP = 30.0 diff --git a/example/cfis_simu/config_tile_Sx_exp_mccd.ini b/example/cfis_simu/config_tile_Sx_exp_mccd.ini index 6adfef76d..8080d77df 100644 --- a/example/cfis_simu/config_tile_Sx_exp_mccd.ini +++ b/example/cfis_simu/config_tile_Sx_exp_mccd.ini @@ -179,7 +179,7 @@ BKG_FROM_HEADER = False # Type of image check (optional), default not used, can be a list of # BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, MINIBACK_RMS, -BACKGROUND, # FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND +CHECKIMAGE = BACKGROUND, BACKGROUND_RMS # File name suffix for the output sextractor files (optional) SUFFIX = tile SUFFIX = sexcat @@ -278,4 +278,3 @@ PLOT_HISTOGRAMS = True # REMOVE_OUTLIERS: Remove validated stars that are outliers in terms of shape # before drawing the plots. REMOVE_OUTLIERS = False - diff --git a/example/cfis_simu/config_tile_Sx_exp_psfex.ini b/example/cfis_simu/config_tile_Sx_exp_psfex.ini index 1271eb012..cd353198b 100644 --- a/example/cfis_simu/config_tile_Sx_exp_psfex.ini +++ b/example/cfis_simu/config_tile_Sx_exp_psfex.ini @@ -100,7 +100,7 @@ BKG_FROM_HEADER = False # BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, # MINIBACK_RMS, -BACKGROUND, #FILTERED, # OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND +CHECKIMAGE = BACKGROUND, BACKGROUND_RMS # File name suffix for the output sextractor files (optional) SUFFIX = sexcat @@ -181,7 +181,7 @@ BKG_FROM_HEADER = False # Type of image check (optional), default not used, can be a list of # BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, MINIBACK_RMS, -BACKGROUND, # FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND +CHECKIMAGE = BACKGROUND, BACKGROUND_RMS # File name suffix for the output sextractor files (optional) SUFFIX = tile SUFFIX = sexcat From f79de46dc3bf11a56f9f06ab530ba065668f2976 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 6 Jun 2026 02:17:29 +0200 Subject: [PATCH 51/80] test(ngmix): guard optional rms vignet input --- src/shapepipe/tests/test_ngmix.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index 680333fef..452d22e5e 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -4,6 +4,7 @@ from hypothesis import strategies as st import numpy as np import numpy.testing as npt +from sqlitedict import SqliteDict from shapepipe.modules.ngmix_package.ngmix import Ngmix @@ -13,6 +14,44 @@ ) +class _NullLogger: + def info(self, *_args, **_kwargs): + pass + + +def _empty_sqlite(path): + db = SqliteDict(path) + db.close() + + +def test_ngmix_accepts_optional_background_rms_vignet(tmp_path): + """The optional seventh ngmix input is the BACKGROUND_RMS vignet sqlite.""" + names = ("gal", "bkg", "psf", "weight", "flag", "headers", "bkg_rms") + sqlite_paths = [ + tmp_path / f"{name}.sqlite" for name in names + ] + for sqlite_path in sqlite_paths: + _empty_sqlite(str(sqlite_path)) + input_paths = ( + ["tile_cat.fits"] + + [str(path) for path in sqlite_paths[:5]] + + [str(sqlite_paths[6])] + ) + + ngmix = Ngmix( + input_paths, + str(tmp_path), + "-001-001", + 30.0, + 0.186, + str(sqlite_paths[5]), + _NullLogger(), + ) + + assert ngmix._vignet_cat.bkg_rms_vign_cat is not None + ngmix._vignet_cat.close() + + @given( st.lists( st.lists( From 4aa3b2a1ed720b4217c0f1652f210731a3571657 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 01:59:24 +0200 Subject: [PATCH 52/80] fix(ngmix): zero fallback weights for degenerate constant stamps sigma_mad(gal) == 0 on a constant stamp made the scalar fallback compute mask * inf, which is NaN wherever the mask is 0 (a fully-masked constant stamp emitted an all-NaN weight map). Guard on sig_noise > 0 and return all-zero weights instead; the downstream wsum == 0 epoch cut already handles the zero-weight case. Pre-existing v1/v2 edge, not introduced by the #604 work. --- src/shapepipe/modules/ngmix_package/ngmix.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 8c5459261..6ab987005 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -924,7 +924,13 @@ def prepare_ngmix_weights(gal, weight, flag, rng, bkg_rms=None): if bkg_rms is None: sig_noise = sigma_mad(gal) - weight_map = mask.astype(float) * 1 / sig_noise ** 2 + # Guard the degenerate constant stamp (sigma_mad == 0): 0 * inf + # would otherwise put NaN in a fully-masked weight map. + weight_map = ( + mask.astype(float) / sig_noise ** 2 + if sig_noise > 0 + else np.zeros_like(gal, dtype=float) + ) else: valid_rms = np.isfinite(bkg_rms) & (bkg_rms > 0) mask &= valid_rms From 158986a4a6a5d888e9bf6b1c1c06658c9ccd8cec Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 01:59:24 +0200 Subject: [PATCH 53/80] test(ngmix): pin spatially-varying RMS through the observation chain Close the #604 coverage gap flagged in review: the per-pixel RMS branch was pinned only by a 3x3 hand-computed matrix and the rescale unit test. make_data already accepts a per-pixel noise map (document it), so inject heteroscedastic truth and assert the Observation weight equals 1/(Fscale*rms)^2 exactly through rescale_epoch_fluxes -> prepare_ngmix_weights -> make_ngmix_observation, with Megapipe-masked and flagged pixels zeroed. Also add the degenerate constant-stamp guard test (np.errstate raise: no divide/invalid warnings, all-zero weights). --- src/shapepipe/testing/simulate.py | 5 ++- src/shapepipe/tests/test_ngmix.py | 68 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/shapepipe/testing/simulate.py b/src/shapepipe/testing/simulate.py index 1b3d65ca5..81b9b6ab0 100644 --- a/src/shapepipe/testing/simulate.py +++ b/src/shapepipe/testing/simulate.py @@ -30,8 +30,9 @@ def make_data( Random number generator. shear : tuple of float True shear (g1, g2). - noise : float, optional - Per-pixel noise sigma. Default 1e-5. + noise : float or numpy.ndarray, optional + Noise sigma — a scalar, or a per-pixel map of shape + ``(img_size, img_size)`` for spatially-varying noise. Default 1e-5. n_epochs : int, optional Number of epochs. Default 1. share_shift : bool, optional diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index 452d22e5e..819489b9d 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -205,3 +205,71 @@ def test_rescale_epoch_fluxes_scales_background_rms_like_image_counts(): npt.assert_allclose(gal_scaled, 2.0) npt.assert_allclose(weight_scaled, 2.0) npt.assert_allclose(bkg_rms_scaled, 6.0) + + +def test_spatially_varying_rms_survives_rescale_to_observation(): + """A spatially-varying RMS map reaches the Observation as exact per-pixel + inverse variance through the full rescale -> prepare -> Observation chain. + + This is the #604 integration guard: the injected heteroscedastic truth + ``1 / (Fscale * rms)**2`` must come back exactly (the RMS branch is + deterministic and never looks at galaxy flux), with Megapipe-masked and + flagged pixels zeroed. + """ + from astropy.io import fits + from shapepipe.modules.ngmix_package.ngmix import ( + make_ngmix_observation, + rescale_epoch_fluxes, + ) + from shapepipe.testing.simulate import make_data + + img_size = 51 + yy, xx = np.mgrid[:img_size, :img_size] + rms = 1e-3 * (1.0 + 1.5 * xx / (img_size - 1) + 0.5 * yy / (img_size - 1)) + + gals, psfs, _, weights, flags, jacobs = make_data( + rng=np.random.RandomState(123), + shear=(0.0, 0.0), + noise=rms, + n_epochs=1, + img_size=img_size, + ) + weights[0][:3, :3] = 0.0 # Megapipe-masked corner + flags[0][-3:, -3:] = 8 # flagged corner + + header = fits.Header() + header["FSCALE"] = 2.0 + gal_scaled, weight_scaled, rms_scaled = rescale_epoch_fluxes( + gals[0], weights[0], header, bkg_rms=rms + ) + + obs = make_ngmix_observation( + gal_scaled, + weight_scaled, + flags[0], + psfs[0], + jacobs[0], + np.random.RandomState(0), + bkg_rms=rms_scaled, + ) + + good = (weights[0] != 0) & (flags[0] == 0) + expected = np.zeros_like(rms) + expected[good] = 1.0 / (header["FSCALE"] * rms[good]) ** 2 + npt.assert_allclose(obs.weight, expected) + + +def test_constant_stamp_fallback_yields_finite_zero_weights(): + """A degenerate constant stamp (sigma_mad == 0) must not emit NaN weights.""" + from shapepipe.modules.ngmix_package.ngmix import prepare_ngmix_weights + + gal = np.ones((4, 4)) + weight = np.zeros((4, 4)) # fully masked + flag = np.zeros((4, 4)) + + with np.errstate(divide="raise", invalid="raise"): + _, weight_map, _ = prepare_ngmix_weights( + gal, weight, flag, np.random.RandomState(0) + ) + + npt.assert_array_equal(weight_map, 0.0) From 0df6bf69f6894a82ec4ecc39aa02b3b63fd21004 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 01:59:24 +0200 Subject: [PATCH 54/80] test: prune deleted fitting.py from validation-script smoke list scripts/python/fitting.py was removed by the v2.0 dead-code cleanup (bd60dc8e), leaving a silently-skipping stale entry. Drop it, and turn the missing-file skip into a hard failure so the list keeps reflecting reality. --- tests/unit/test_scripts_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_scripts_import.py b/tests/unit/test_scripts_import.py index 440c856d0..d0b7c306e 100644 --- a/tests/unit/test_scripts_import.py +++ b/tests/unit/test_scripts_import.py @@ -26,7 +26,6 @@ # run_bias_test.sh's SHAPEPIPE_PATH), not this module. VALIDATION_SCRIPTS = [ "scripts/validation/centroid/centroid_bias_v2.py", - "scripts/python/fitting.py", ] @@ -34,8 +33,9 @@ def test_validation_script_imports_cleanly(relpath): path = REPO_ROOT / relpath - if not path.exists(): - pytest.skip(f"{relpath} not found at {path}") + # A listed script that no longer exists is a stale entry: fail loudly so + # the list keeps reflecting reality instead of silently skipping. + assert path.exists(), f"{relpath} not found at {path}; prune the list" spec = importlib.util.spec_from_file_location( f"_smoke_{path.stem}", path From 0bc6016e603e3e152d81352b3533c02dd0781257 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 01:59:24 +0200 Subject: [PATCH 55/80] docs(example): state BKG_RMS_VIGNET_PATH all-or-nothing semantics When the option is set, the RMS sqlite must exist for every tile (fail-fast FileNotFoundError, no per-tile fallback); the scalar sigma_mad fallback engages only when the option is absent from the config entirely. --- example/cfis/config_tile_Ng_batch_psfex_uc.ini | 4 ++++ example/cfis/config_tile_Ng_template.ini | 4 ++++ example/cfis/config_tile_Ng_template_batch.ini | 4 ++++ example/cfis_simu/config_tile_Ng_template.ini | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/example/cfis/config_tile_Ng_batch_psfex_uc.ini b/example/cfis/config_tile_Ng_batch_psfex_uc.ini index 1eef9a318..b223e05a1 100644 --- a/example/cfis/config_tile_Ng_batch_psfex_uc.ini +++ b/example/cfis/config_tile_Ng_batch_psfex_uc.ini @@ -64,6 +64,10 @@ FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 +# BKG_RMS_VIGNET_PATH (optional): per-pixel BACKGROUND_RMS vignets, used as +# 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for +# every tile (missing file -> error, no per-tile fallback); omit the option +# entirely to fall back to the scalar sigma_mad noise estimate. BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_tile_PiViVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite # Magnitude zero-point diff --git a/example/cfis/config_tile_Ng_template.ini b/example/cfis/config_tile_Ng_template.ini index 74bde8342..5c4e0f401 100644 --- a/example/cfis/config_tile_Ng_template.ini +++ b/example/cfis/config_tile_Ng_template.ini @@ -64,6 +64,10 @@ FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 +# BKG_RMS_VIGNET_PATH (optional): per-pixel BACKGROUND_RMS vignets, used as +# 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for +# every tile (missing file -> error, no per-tile fallback); omit the option +# entirely to fall back to the scalar sigma_mad noise estimate. BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite # Magnitude zero-point diff --git a/example/cfis/config_tile_Ng_template_batch.ini b/example/cfis/config_tile_Ng_template_batch.ini index 1b0c15cc4..c717b4d0a 100644 --- a/example/cfis/config_tile_Ng_template_batch.ini +++ b/example/cfis/config_tile_Ng_template_batch.ini @@ -64,6 +64,10 @@ FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 +# BKG_RMS_VIGNET_PATH (optional): per-pixel BACKGROUND_RMS vignets, used as +# 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for +# every tile (missing file -> error, no per-tile fallback); omit the option +# entirely to fall back to the scalar sigma_mad noise estimate. BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite # Directory of previous run, optional. diff --git a/example/cfis_simu/config_tile_Ng_template.ini b/example/cfis_simu/config_tile_Ng_template.ini index 9779f46e9..1dced6bc1 100644 --- a/example/cfis_simu/config_tile_Ng_template.ini +++ b/example/cfis_simu/config_tile_Ng_template.ini @@ -67,6 +67,10 @@ NUMBERING_SCHEME = -000-000 # Multi-epoch mode: Path to file with single-exposure WCS header information LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite +# BKG_RMS_VIGNET_PATH (optional): per-pixel BACKGROUND_RMS vignets, used as +# 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for +# every tile (missing file -> error, no per-tile fallback); omit the option +# entirely to fall back to the scalar sigma_mad noise estimate. BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite # Magnitude zero-point From 16789eb38dd4b32c7c8307a2e38eaab8a59a5adc Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 02:09:29 +0200 Subject: [PATCH 56/80] config(example): drop removed CHECK_EXISTING_DIR from ngmix template The ngmix resume path was deleted in bd60dc8e (Martin: 'a hack to resume interrupted runs ... can be removed now'); this template entry was the last reference wired to ngmix_runner. The mask/get_images CHECK_EXISTING_DIR entries elsewhere are live features and stay. Co-Authored-By: Claude Fable 5 --- example/cfis/config_tile_Ng_template_batch.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/example/cfis/config_tile_Ng_template_batch.ini b/example/cfis/config_tile_Ng_template_batch.ini index c717b4d0a..ed6276bdb 100644 --- a/example/cfis/config_tile_Ng_template_batch.ini +++ b/example/cfis/config_tile_Ng_template_batch.ini @@ -70,9 +70,6 @@ NUMBERING_SCHEME = -000-000 # entirely to fall back to the scalar sigma_mad noise estimate. BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite -# Directory of previous run, optional. -CHECK_EXISTING_DIR = $SP_RUN/output/run_sp_tile_ngmix_Ng1u_prev/ngmix_runner/output - # Number of objects to batch save during processing, optional. Omit or set # to -1 for no batch saving SAVE_BATCH = 1000 From d4ada3d2ff51277d7ea725e89609d584d708ed06 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 02:10:35 +0200 Subject: [PATCH 57/80] fix(sextractor): declare positional WCS input in runner decorator The WCS moved from the named LOG_WCS config option into the positional input list (input_file_list[-1] when MAKE_POST_PROCESS is True), but the @module_runner metadata and the package docs still described the old contract. Declare log_exp_headers/.sqlite from merge_headers_runner in the decorator (matching the ngmix_runner convention) and replace the stale LOG_WCS docs with the positional contract. Co-Authored-By: Claude Fable 5 --- src/shapepipe/modules/sextractor_package/__init__.py | 9 +++++---- src/shapepipe/modules/sextractor_runner.py | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/shapepipe/modules/sextractor_package/__init__.py b/src/shapepipe/modules/sextractor_package/__init__.py index e3cd01188..abb0b4572 100644 --- a/src/shapepipe/modules/sextractor_package/__init__.py +++ b/src/shapepipe/modules/sextractor_package/__init__.py @@ -4,7 +4,8 @@ :Author: Axel Guinot -:Parent module: ``mask_runner`` +:Parent modules: ``mask_runner``, ``merge_headers_runner`` (the latter only + when ``MAKE_POST_PROCESS`` is ``True``) :Input: Single-exposure single-CCD image, weight and flag files @@ -57,9 +58,9 @@ PREFIX : str, optional Output file name prefix MAKE_POST_PROCESS : bool - Option to run post-processing steps -LOG_WCS : str, optional - Path to world coordinate system log file (``*sqlite``) + Option to run post-processing steps; if ``True`` the merged WCS header + log file (``log_exp_headers.sqlite`` from ``merge_headers_runner``) must + be the *last* entry of ``FILE_PATTERN``/``FILE_EXT`` WORLD_POSITION : list, optional List of world coordinates to use to match objects CCD_SIZE : list, optional diff --git a/src/shapepipe/modules/sextractor_runner.py b/src/shapepipe/modules/sextractor_runner.py index dfc28c8f9..c90322b18 100644 --- a/src/shapepipe/modules/sextractor_runner.py +++ b/src/shapepipe/modules/sextractor_runner.py @@ -13,11 +13,15 @@ from shapepipe.pipeline.execute import execute +# The trailing log_exp_headers input (merged WCS headers from +# merge_headers_runner) is only consumed when MAKE_POST_PROCESS is True; +# configs without post-processing override FILE_PATTERN/FILE_EXT with the +# first three entries only. @module_runner( version="1.0.1", - input_module="mask_runner", - file_pattern=["image", "weight", "flag"], - file_ext=[".fits", ".fits", ".fits"], + input_module=["mask_runner", "merge_headers_runner"], + file_pattern=["image", "weight", "flag", "log_exp_headers"], + file_ext=[".fits", ".fits", ".fits", ".sqlite"], executes=["source-extractor"], depends=["numpy"], ) From b2dcd7937f7e70b2984c9713aecb4e70e3d63d59 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 02:11:52 +0200 Subject: [PATCH 58/80] docs(ngmix): drop stale LOG_WCS option; headers input is positional ngmix_runner takes the merged WCS header log positionally (input_file_list[6]) and never reads LOG_WCS. Document the positional contract, add merge_headers_runner to the parent modules, and document the real SAVE_BATCH option in its place. Co-Authored-By: Claude Fable 5 --- src/shapepipe/modules/ngmix_package/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/__init__.py b/src/shapepipe/modules/ngmix_package/__init__.py index db9ced5a1..230606bba 100644 --- a/src/shapepipe/modules/ngmix_package/__init__.py +++ b/src/shapepipe/modules/ngmix_package/__init__.py @@ -9,6 +9,7 @@ - ``sextractor_runner`` - ``psfex_interp_runner`` or ``mccd_interp_runner`` - ``vignetmaker_runner`` +- ``merge_headers_runner`` :Input: Galaxy image vignets @@ -23,6 +24,10 @@ The metacalibration routines of NGMIX are also called to provide all of the measurements required to calibrate the shear values. +The merged single-exposure WCS header log (``log_exp_headers.sqlite`` from +``merge_headers_runner``) is passed as the last entry of +``FILE_PATTERN``/``FILE_EXT``, not via a config option. + Module-specific config file entries =================================== @@ -30,8 +35,9 @@ Photometric zero point PIXEL_SCALE : float Pixel scale in arcseconds -LOG_WCS : str - Path to world coordinate system log file (``*sqlite``) +SAVE_BATCH : int, optional + Save the output catalogue in batches of this size; default is ``-1`` + (no batch saving) ID_OBJ_MIN : int ID of first galaxy object to be processed; not used if set to ``-1`` (default) From fcd117fc9d711ea5d806df71d357e374584fa64f Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:06:39 +0200 Subject: [PATCH 59/80] fix(sextractor): skip TILE_ID metadata key when sizing the CCD scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit merge_headers writes TILE_ID as the first key of the tile-level log_exp_headers.sqlite, and make_post_process derived n_hdu from the first key's value — len(tile_id_string) instead of the CCD count — so every epoch on the unscanned CCDs was silently dropped from N_EPOCH/EPOCH_* (and hence from ME vignets and shape measurement). Regression test builds the tile-mode sqlite via merge_headers and asserts an object on the last CCD keeps all its epochs. Co-Authored-By: Claude Fable 5 --- .../sextractor_package/sextractor_script.py | 9 +- .../tests/test_sextractor_post_process.py | 138 ++++++++++++++++++ 2 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/shapepipe/tests/test_sextractor_post_process.py diff --git a/src/shapepipe/modules/sextractor_package/sextractor_script.py b/src/shapepipe/modules/sextractor_package/sextractor_script.py index 3238edf46..f124612b7 100644 --- a/src/shapepipe/modules/sextractor_package/sextractor_script.py +++ b/src/shapepipe/modules/sextractor_package/sextractor_script.py @@ -84,10 +84,13 @@ def make_post_process(cat_path, f_wcs_path, pos_params, ccd_size): cat.open() f_wcs = SqliteDict(f_wcs_path) - key_list = list(f_wcs.keys()) - if len(key_list) == 0: + # Tile-level logs from merge_headers carry a "TILE_ID" metadata entry + # (inserted first); n_hdu must be derived from a real exposure entry, + # otherwise it measures the tile ID string and truncates the CCD scan. + exp_keys = [key for key in f_wcs.keys() if key != "TILE_ID"] + if len(exp_keys) == 0: raise IOError(f"Could not read sql file '{f_wcs_path}'") - n_hdu = len(f_wcs[key_list[0]]) + n_hdu = len(f_wcs[exp_keys[0]]) history = [] for idx in cat.get_data(1)[0][0]: diff --git a/src/shapepipe/tests/test_sextractor_post_process.py b/src/shapepipe/tests/test_sextractor_post_process.py new file mode 100644 index 000000000..26be99895 --- /dev/null +++ b/src/shapepipe/tests/test_sextractor_post_process.py @@ -0,0 +1,138 @@ +"""UNIT TESTS FOR SEXTRACTOR POST-PROCESS. + +Regression test for the tile-mode header log: ``merge_headers`` writes a +``TILE_ID`` metadata entry as the *first* key of +``log_exp_headers.sqlite``, and ``make_post_process`` derives the +number of CCDs from the first key's value. With the metadata key first, +``n_hdu`` becomes ``len(tile_id_string)`` instead of the number of CCDs, +silently dropping every epoch on the unscanned CCDs. + +""" + +import numpy as np +from astropy.io import fits +from astropy.wcs import WCS + +from shapepipe.modules.merge_headers_package.merge_headers import merge_headers +from shapepipe.modules.sextractor_package import sextractor_script + +N_CCD = 3 +CCD_NPIX = 100 + + +def _make_ccd_wcs(ccd): + """TAN WCS per CCD, each covering a disjoint sky patch.""" + header = fits.Header() + header["NAXIS"] = 2 + header["NAXIS1"] = CCD_NPIX + header["NAXIS2"] = CCD_NPIX + header["CTYPE1"] = "RA---TAN" + header["CTYPE2"] = "DEC--TAN" + header["CRPIX1"] = 50.0 + header["CRPIX2"] = 50.0 + header["CRVAL1"] = 180.0 + 0.5 * ccd + header["CRVAL2"] = 30.0 + header["CD1_1"] = -1.0e-3 + header["CD1_2"] = 0.0 + header["CD2_1"] = 0.0 + header["CD2_2"] = 1.0e-3 + return WCS(header), header + + +def _write_exposure_headers(path): + """Mimic split_exp: object array of {WCS, header} dicts, one per CCD.""" + entries = np.zeros(N_CCD, dtype="O") + for ccd in range(N_CCD): + wcs, header = _make_ccd_wcs(ccd) + entries[ccd] = {"WCS": wcs, "header": header.tostring()} + np.save(path, entries) + + +def _write_sex_ldac(path, exp_names, world_positions): + """Minimal SExtractor LDAC catalogue with HISTORY exposure cards.""" + cards = np.array( + [[f"HISTORY input image {name}p.fits" for name in exp_names]], + dtype="U80", + ) + imhead = fits.BinTableHDU.from_columns( + [ + fits.Column( + name="Field Header Card", + format=f"{80 * len(exp_names)}A", + dim=f"(80,{len(exp_names)})", + array=cards, + ) + ], + name="LDAC_IMHEAD", + ) + objects = fits.BinTableHDU.from_columns( + [ + fits.Column( + name="NUMBER", + format="J", + array=np.arange(1, len(world_positions) + 1), + ), + fits.Column( + name="XWIN_WORLD", + format="D", + array=world_positions[:, 0], + ), + fits.Column( + name="YWIN_WORLD", + format="D", + array=world_positions[:, 1], + ), + ], + name="LDAC_OBJECTS", + ) + fits.HDUList([fits.PrimaryHDU(), imhead, objects]).writeto(path) + + +def test_post_process_scans_all_ccds_despite_tile_id_key(tmp_path): + """All CCDs are scanned even though TILE_ID is the sqlite's first key. + + The tile number ("51", length 2) is shorter than the CCD count (3): if + n_hdu is derived from the TILE_ID entry, CCD 2 is never scanned and the + object there loses both its epochs. + + """ + exp_names = ["123456", "654321"] + header_files = [] + for name in exp_names: + npy_path = tmp_path / f"headers-{name}.npy" + _write_exposure_headers(npy_path) + header_files.append([str(npy_path)]) + + tile_number = "51" + merge_headers(header_files, str(tmp_path), tile_number=tile_number) + sqlite_path = tmp_path / f"log_exp_headers{tile_number}.sqlite" + assert sqlite_path.is_file() + + # One object at the centre of CCD 0, one at the centre of CCD 2. + positions = np.array( + [ + _make_ccd_wcs(ccd)[0].all_pix2world([[50.0, 50.0]], 0)[0] + for ccd in (0, 2) + ] + ) + cat_path = tmp_path / "sexcat.fits" + _write_sex_ldac(cat_path, exp_names, positions) + + sextractor_script.make_post_process( + str(cat_path), + str(sqlite_path), + ["XWIN_WORLD", "YWIN_WORLD"], + ["0", str(CCD_NPIX), "0", str(CCD_NPIX)], + ) + + with fits.open(cat_path) as hdu_list: + n_epoch = hdu_list["LDAC_OBJECTS"].data["N_EPOCH"] + ccd_n = { + idx: hdu_list[f"EPOCH_{idx}"].data["CCD_N"] + for idx in range(len(exp_names)) + } + + # Both objects appear in both exposures, on their true CCDs. + np.testing.assert_array_equal(n_epoch, [2, 2]) + for idx in range(len(exp_names)): + np.testing.assert_array_equal(ccd_n[idx], [0, 2]) From 450f3c194ff31ac0522b5fb549e4cc528ac423b2 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:11:50 +0200 Subject: [PATCH 60/80] fix(ngmix): NaN-fill failed fit types in compile_results ngmix 2.x run_fitter returns flags != 0 on failure instead of raising, and the failed result carries none of the measurement keys (g, g_cov, T, T_err, flux, flux_err, s2n); compile_results indexed them directly, so a single failed object crashed the whole tile with a KeyError at save time. Failed types are now recorded as NaN with their flags preserved. --- src/shapepipe/modules/ngmix_package/ngmix.py | 64 +++++++++++--------- src/shapepipe/tests/test_ngmix.py | 32 ++++++++++ 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 16c24d876..ae92f52d1 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -374,16 +374,24 @@ def compile_results(self, results): output_dict = {k: {kk: [] for kk in names2} for k in names} for idx in range(len(results)): for name in names: - - mag = ( - -2.5 * np.log10(results[idx][name]["flux"]) - + self._zero_point - ) - mag_err = np.abs( - -2.5 - * results[idx][name]["flux_err"] - / (results[idx][name]["flux"] * np.log(10)) + fit = results[idx][name] + + # ngmix 2.x does not raise on fit failure: after ntry the + # result keeps flags != 0 and carries none of the + # measurement keys (g, g_cov, T, T_err, flux, flux_err, + # s2n). NaN-fill those so failed types are recorded with + # their flags instead of crashing the tile on a KeyError. + flux = fit.get("flux", np.nan) + flux_err = fit.get("flux_err", np.nan) + g = np.asarray(fit.get("g", (np.nan, np.nan))) + g_cov = np.asarray( + fit.get("g_cov", np.full((2, 2), np.nan)) ) + T_gal = fit.get("T", np.nan) + T_gal_err = fit.get("T_err", np.nan) + + mag = -2.5 * np.log10(flux) + self._zero_point + mag_err = np.abs(-2.5 * flux_err / (flux * np.log(10))) output_dict[name]["id"].append(results[idx]["obj_id"]) output_dict[name]["n_epoch_model"].append( @@ -392,7 +400,9 @@ def compile_results(self, results): output_dict[name]["moments_fail"].append( results[idx]["moments_fail"] ) - output_dict[name]["ntry_fit"].append(results[idx][name]["nfev"]) + output_dict[name]["ntry_fit"].append( + fit.get("nfev", np.nan) + ) output_dict[name]["g1_psfo_ngmix"].append( results[idx]["g_PSFo"][0] ) @@ -405,8 +415,8 @@ def compile_results(self, results): output_dict[name]["g2_err_psfo_ngmix"].append( results[idx]["g_err_PSFo"][1] ) - output_dict[name]["T"].append(results[idx][name]["T"]) - output_dict[name]["T_err"].append(results[idx][name]["T_err"]) + output_dict[name]["T"].append(T_gal) + output_dict[name]["T_err"].append(T_gal_err) output_dict[name]["Tpsf"].append(results[idx]["T_PSFo"]) output_dict[name]["Tpsf_err"].append(results[idx]["T_err_PSFo"]) output_dict[name]["g1_psf"].append(results[idx]["g_PSFo"][0]) @@ -414,8 +424,6 @@ def compile_results(self, results): # Galaxy half-light radius from the fitted area T = 2 sigma^2: # r50 = sqrt(ln 2 * T), with d r50 / d T = r50 / (2 T) - T_gal = results[idx][name]["T"] - T_gal_err = results[idx][name]["T_err"] if T_gal > 0: r50_gal = SIGMA_TO_R50 * np.sqrt(T_gal / 2) r50_gal_err = r50_gal * T_gal_err / (2 * T_gal) @@ -427,27 +435,25 @@ def compile_results(self, results): output_dict[name]['r50psf_err'].append( results[idx]["r50_err_PSFo"] ) - output_dict[name]["g1"].append(results[idx][name]["g"][0]) - output_dict[name]["g2"].append(results[idx][name]["g"][1]) - output_dict[name]["g1_err"].append( - np.sqrt(results[idx][name]["g_cov"][0, 0]) - ) - output_dict[name]["g2_err"].append( - np.sqrt(results[idx][name]["g_cov"][1, 1]) - ) - output_dict[name]["flux"].append(results[idx][name]["flux"]) - output_dict[name]["flux_err"].append(results[idx][name]["flux_err"]) + output_dict[name]["g1"].append(g[0]) + output_dict[name]["g2"].append(g[1]) + output_dict[name]["g1_err"].append(np.sqrt(g_cov[0, 0])) + output_dict[name]["g2_err"].append(np.sqrt(g_cov[1, 1])) + output_dict[name]["flux"].append(flux) + output_dict[name]["flux_err"].append(flux_err) output_dict[name]["mag"].append(mag) output_dict[name]["mag_err"].append(mag_err) - if "s2n" in results[idx][name]: - output_dict[name]["s2n"].append(results[idx][name]["s2n"]) - elif "s2n_r" in results[idx][name]: - output_dict[name]["s2n"].append(results[idx][name]["s2n_r"]) + if "s2n" in fit: + output_dict[name]["s2n"].append(fit["s2n"]) + elif "s2n_r" in fit: + output_dict[name]["s2n"].append(fit["s2n_r"]) + elif fit["flags"] != 0: + output_dict[name]["s2n"].append(np.nan) else: raise KeyError("No SNR key (s2n, s2n_r) found in results") - output_dict[name]["flags"].append(results[idx][name]["flags"]) + output_dict[name]["flags"].append(fit["flags"]) output_dict[name]["mcal_flags"].append( results[idx].get("mcal_flags", 0) ) diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index a5ce0277e..80aafc903 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -227,6 +227,38 @@ def test_compile_results_size_columns_are_half_light_radii(): assert retired not in noshear +def test_compile_results_nan_fills_failed_fit_types(): + """A failed fit type must be recorded with NaNs, not crash the tile. + + ngmix 2.x ``run_fitter`` does not raise on failure: after ``ntry`` it + returns a result with ``flags != 0`` that carries none of the + measurement keys (g, g_cov, T, T_err, flux, flux_err, s2n). + ``compile_results`` previously indexed those keys directly, so a single + failed object KeyError-crashed the whole tile at save time, hours in. + """ + from shapepipe.modules.ngmix_package.ngmix import Ngmix + + res = _fake_metacal_result(0.18, 0.02, 0.09, 0.001) + res["1p"] = {"flags": 0x8, "nfev": 5} # failed fit: only flags/nfev + res["moments_fail"] = 1 + + inst = object.__new__(Ngmix) + inst._zero_point = 30.0 + out = inst.compile_results([res]) + + failed = out["1p"] + for col in ( + "g1", "g2", "g1_err", "g2_err", "T", "T_err", "flux", "flux_err", + "s2n", "mag", "mag_err", "r50", "r50_err", + ): + assert np.isnan(failed[col]).all(), col + assert failed["flags"] == [0x8] + + # successful types are untouched + npt.assert_allclose(out["noshear"]["flux"], [100.0]) + assert out["noshear"]["flags"] == [0] + + def test_compile_results_nonpositive_T_gives_nan_r50(): """A non-positive fitted area cannot yield a real half-light radius.""" from shapepipe.modules.ngmix_package.ngmix import Ngmix From 00e9f898b577a3de02cbebe76d9bad39c0832bd7 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:13:39 +0200 Subject: [PATCH 61/80] fix(ngmix): skip failed-PSF epochs in multiepoch PSF average With ignore_failed_psf=True a failed PSF epoch stays in obsdict carrying only flags/pars, so reading result['T'] KeyError-dropped the whole object even when the shear fit succeeded on the surviving epochs. The average now skips flags != 0 epochs (all-failed still hits the wsum == 0 guard), and n_epoch_model counts surviving epochs instead of submitted ones. --- src/shapepipe/modules/ngmix_package/ngmix.py | 33 ++++++++++------- src/shapepipe/tests/test_ngmix.py | 38 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index ae92f52d1..5825ab6f5 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -640,7 +640,9 @@ def process(self): continue res['obj_id'] = obj_id - res['n_epoch_model'] = len(stamp.gals) + # epochs that survived the PSF fit and entered the model, + # not the number of epochs submitted (v1 contract) + res['n_epoch_model'] = psf_res['n_epoch'] res['moments_fail'] = sum( 1 for k in ['noshear', '1p', '1m', '2p', '2m'] if res.get(k, {}).get('flags', 0) != 0 @@ -1040,29 +1042,33 @@ def average_multiepoch_psf(obsdict): Returns ------- dict - Keys: 'g_psf', 'g_psf_err', 'T_psf', 'T_psf_err' (weighted averages). + Keys: 'g_psf', 'g_psf_err', 'T_psf', 'T_psf_err' (weighted + averages over the epochs whose PSF fit succeeded) and 'n_epoch' + (the number of those surviving epochs). """ - # create dictionary - names = ['T_psf', 'T_psf_err', 'g_psf', 'g_psf_err'] - psf_dict = {k: [] for k in names} + psf_dict = {} nepoch = len(obsdict['noshear']) + n_epoch_used = 0 wsum = 0 g_psf_sum = np.array([0., 0.]) g_psf_err_sum = np.array([0., 0.]) T_psf_sum = 0 T_psf_err_sum = 0 for n_e in np.arange(nepoch): - T_psf=obsdict['noshear'][n_e].psf.meta['result']['T'] - T_psf_err=obsdict['noshear'][n_e].psf.meta['result']['T_err'] - g_psf=obsdict['noshear'][n_e].psf.meta['result']['g'] - g_psf_err=obsdict['noshear'][n_e].psf.meta['result']['g_err'] + result = obsdict['noshear'][n_e].psf.meta['result'] + # ignore_failed_psf=True drops failed-PSF epochs from the galaxy + # fit but keeps them in obsdict; their result carries only + # flags/pars (no T/g), so skip them here too. + if result['flags'] != 0: + continue ne_wsum = obsdict['noshear'][n_e].weight.sum() + n_epoch_used += 1 wsum += ne_wsum - g_psf_sum += g_psf * ne_wsum - g_psf_err_sum += g_psf_err * ne_wsum - T_psf_sum += T_psf * ne_wsum - T_psf_err_sum += T_psf_err * ne_wsum + g_psf_sum += result['g'] * ne_wsum + g_psf_err_sum += result['g_err'] * ne_wsum + T_psf_sum += result['T'] * ne_wsum + T_psf_err_sum += result['T_err'] * ne_wsum if wsum == 0: raise ZeroDivisionError('Sum of weights = 0, division by zero') @@ -1071,6 +1077,7 @@ def average_multiepoch_psf(obsdict): psf_dict['g_psf_err'] = g_psf_err_sum / wsum psf_dict['T_psf'] = T_psf_sum / wsum psf_dict['T_psf_err'] = T_psf_err_sum / wsum + psf_dict['n_epoch'] = n_epoch_used return psf_dict diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index 80aafc903..c842b4bba 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -271,6 +271,44 @@ def test_compile_results_nonpositive_T_gives_nan_r50(): assert np.isnan(out["noshear"]["r50_err"]).all() +def test_average_multiepoch_psf_skips_failed_psf_epochs(): + """A failed-PSF epoch must be skipped, not KeyError the whole object. + + With ``ignore_failed_psf=True`` the bootstrapper drops failed-PSF + epochs from the galaxy fit but leaves them in the returned obsdict, + where their ``psf.meta['result']`` carries only flags/pars (no T/g). + Averages must come from the surviving epochs only, and ``n_epoch`` + must count those survivors (v1 counted PSF-fit-successful epochs). + """ + from types import SimpleNamespace + from shapepipe.modules.ngmix_package.ngmix import average_multiepoch_psf + + def epoch(result, weight_value): + return SimpleNamespace( + psf=SimpleNamespace(meta={"result": result}), + weight=np.full((2, 2), weight_value), + ) + + good = { + "flags": 0, + "T": 0.2, + "T_err": 0.01, + "g": np.array([0.01, 0.02]), + "g_err": np.array([1e-3, 2e-3]), + } + failed = {"flags": 3, "nfev": 5, "pars": np.zeros(6)} # no T/g keys + + psf_res = average_multiepoch_psf( + {"noshear": [epoch(good, 1.0), epoch(failed, 4.0)]} + ) + + npt.assert_allclose(psf_res["T_psf"], 0.2) + npt.assert_allclose(psf_res["T_psf_err"], 0.01) + npt.assert_allclose(psf_res["g_psf"], [0.01, 0.02]) + npt.assert_allclose(psf_res["g_psf_err"], [1e-3, 2e-3]) + assert psf_res["n_epoch"] == 1 + + def test_weight_map_recovers_injected_inverse_variance(): """A supplied inverse-variance map must not be renormalized twice.""" from shapepipe.modules.ngmix_package.ngmix import prepare_ngmix_weights From 7a1b3b1610457c38fea098ee35da046e639e79e2 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:15:17 +0200 Subject: [PATCH 62/80] fix(ngmix): restore mcal_flags as OR of per-type fit flags The rewrite hard-coded res['mcal_flags'] = 0, so the NGMIX_MCAL_FLAGS column written by make_cat was constant-zero and any mcal_flags == 0 quality cut passed every object, failed fits included. Restores the v1 contract: mcal_flags = bitwise OR of all per-type fit flags, so failed objects (now NaN-recorded rather than crashing) carry nonzero flags. --- src/shapepipe/modules/ngmix_package/ngmix.py | 28 ++++++++++++++++++-- src/shapepipe/tests/test_ngmix.py | 28 ++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 5825ab6f5..af3864de0 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -24,6 +24,30 @@ # gives r50 = SIGMA_TO_R50 * sqrt(T / 2) = sqrt(ln 2 * T). SIGMA_TO_R50 = np.sqrt(2.0 * np.log(2.0)) +METACAL_TYPES = ('noshear', '1p', '1m', '2p', '2m') + + +def get_mcal_flags(res): + """Get Metacal Flags. + + Bitwise OR of the per-type metacal fit flags, the v1 contract for the + downstream NGMIX_MCAL_FLAGS column: nonzero whenever any metacal + type's galaxy fit failed. + + Parameters + ---------- + res : dict + MetacalBootstrapper result dict with one entry per metacal type. + + Returns + ------- + int + OR of all per-type ``flags``. + """ + return int(np.bitwise_or.reduce( + [res.get(name, {}).get('flags', 0) for name in METACAL_TYPES] + )) + def get_prior(pixel_scale, rng, T_range=None, F_range=None): """Build ngmix joint prior for a 6-parameter galaxy model. @@ -644,9 +668,10 @@ def process(self): # not the number of epochs submitted (v1 contract) res['n_epoch_model'] = psf_res['n_epoch'] res['moments_fail'] = sum( - 1 for k in ['noshear', '1p', '1m', '2p', '2m'] + 1 for k in METACAL_TYPES if res.get(k, {}).get('flags', 0) != 0 ) + res['mcal_flags'] = get_mcal_flags(res) # PSF half-light radius r50 = sqrt(2 ln 2) * sigma with # sigma = sqrt(T / 2); error from d sigma / d T = 1 / (4 sigma) sigma_psfo = np.sqrt(max(psf_res['T_psf'], 0) / 2) @@ -659,7 +684,6 @@ def process(self): SIGMA_TO_R50 * psf_res['T_psf_err'] / (4 * sigma_psfo) if sigma_psfo > 0 else np.nan ) - res['mcal_flags'] = 0 final_res.append(res) n_fitted += 1 count_batch += 1 diff --git a/src/shapepipe/tests/test_ngmix.py b/src/shapepipe/tests/test_ngmix.py index c842b4bba..61a826af7 100644 --- a/src/shapepipe/tests/test_ngmix.py +++ b/src/shapepipe/tests/test_ngmix.py @@ -235,12 +235,17 @@ def test_compile_results_nan_fills_failed_fit_types(): measurement keys (g, g_cov, T, T_err, flux, flux_err, s2n). ``compile_results`` previously indexed those keys directly, so a single failed object KeyError-crashed the whole tile at save time, hours in. + + The failed object must also carry nonzero ``mcal_flags`` (the OR of + the per-type fit flags, computed as the process loop does) so the + downstream NGMIX_MCAL_FLAGS quality cut can see it. """ - from shapepipe.modules.ngmix_package.ngmix import Ngmix + from shapepipe.modules.ngmix_package.ngmix import Ngmix, get_mcal_flags res = _fake_metacal_result(0.18, 0.02, 0.09, 0.001) res["1p"] = {"flags": 0x8, "nfev": 5} # failed fit: only flags/nfev res["moments_fail"] = 1 + res["mcal_flags"] = get_mcal_flags(res) inst = object.__new__(Ngmix) inst._zero_point = 30.0 @@ -253,10 +258,29 @@ def test_compile_results_nan_fills_failed_fit_types(): ): assert np.isnan(failed[col]).all(), col assert failed["flags"] == [0x8] + assert failed["mcal_flags"] == [0x8] and failed["mcal_flags"][0] != 0 - # successful types are untouched + # successful types are untouched, but share the object's mcal_flags npt.assert_allclose(out["noshear"]["flux"], [100.0]) assert out["noshear"]["flags"] == [0] + assert out["noshear"]["mcal_flags"] == [0x8] + + +def test_get_mcal_flags_ors_per_type_fit_flags(): + """mcal_flags is the bitwise OR of all per-type fit flags (v1 contract). + + The rewrite hard-coded ``res['mcal_flags'] = 0``, making the + NGMIX_MCAL_FLAGS column constant-zero so any mcal_flags == 0 quality + cut passed everything. + """ + from shapepipe.modules.ngmix_package.ngmix import get_mcal_flags + + res = {name: {"flags": 0} for name in ("noshear", "1p", "1m", "2p", "2m")} + assert get_mcal_flags(res) == 0 + + res["1p"]["flags"] = 0x8 + res["2m"]["flags"] = 0x2 + assert get_mcal_flags(res) == 0xA def test_compile_results_nonpositive_T_gives_nan_r50(): From 3eb6a66f4cbf49666853f074a6bc3464e82acccc Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:30:12 +0200 Subject: [PATCH 63/80] fix(example): migrate configs to positional WCS-headers input The cfis_simu configs still used the removed LOG_WCS/ME_LOG_WCS options, so the runners' positional reads (ngmix input_file_list[6], mccd_interp/ vignetmaker [1], sextractor [-1] with MAKE_POST_PROCESS) would IndexError or grab the wrong file. Migrated them to the merge_headers_runner input pattern used by example/cfis; sextractor exposure runs gain an explicit FILE_EXT so the 3-entry pattern override no longer mismatches the runner's 4-entry default. Also renamed stale run_sp_exp_Mh references in the cfis templates to run_sp_tile_Mh_exp, the name config_tile_Mh_exp.ini actually produces. --- example/cfis/config_tile_MiViSmVi.ini | 4 ++-- example/cfis/config_tile_Ng_template.ini | 2 +- example/cfis/config_tile_Ng_template_batch.ini | 2 +- example/cfis/config_tile_Sx_nomask.ini | 2 +- example/cfis_simu/config_tile_MiViSmVi.ini | 16 ++++++---------- example/cfis_simu/config_tile_Ng_template.ini | 9 +++------ example/cfis_simu/config_tile_Sx_exp_mccd.ini | 11 +++++------ example/cfis_simu/config_tile_Sx_exp_psfex.ini | 13 ++++++------- 8 files changed, 25 insertions(+), 34 deletions(-) diff --git a/example/cfis/config_tile_MiViSmVi.ini b/example/cfis/config_tile_MiViSmVi.ini index 02bd9a005..f87d80daa 100644 --- a/example/cfis/config_tile_MiViSmVi.ini +++ b/example/cfis/config_tile_MiViSmVi.ini @@ -57,7 +57,7 @@ TIMEOUT = 96:00:00 [MCCD_INTERP_RUNNER] -INPUT_DIR = last:sextractor_runner_run_1, run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = last:sextractor_runner_run_1, run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = sexcat, log_exp_headers @@ -143,7 +143,7 @@ OUTPUT_MODE = new # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = last:sextractor_runner_run_1, run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = last:sextractor_runner_run_1, run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = sexcat, log_exp_headers diff --git a/example/cfis/config_tile_Ng_template.ini b/example/cfis/config_tile_Ng_template.ini index 5c4e0f401..18f3177a8 100644 --- a/example/cfis/config_tile_Ng_template.ini +++ b/example/cfis/config_tile_Ng_template.ini @@ -55,7 +55,7 @@ TIMEOUT = 96:00:00 # Model-fitting shapes with ngmix [NGMIX_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2,run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2,run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers diff --git a/example/cfis/config_tile_Ng_template_batch.ini b/example/cfis/config_tile_Ng_template_batch.ini index ed6276bdb..cd4e28ba9 100644 --- a/example/cfis/config_tile_Ng_template_batch.ini +++ b/example/cfis/config_tile_Ng_template_batch.ini @@ -55,7 +55,7 @@ TIMEOUT = 96:00:00 # Model-fitting shapes with ngmix [NGMIX_RUNNER] -INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2,run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = run_sp_tile_Sx:sextractor_runner,last:X_interp_runner,last:vignetmaker_runner_run_2,run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers diff --git a/example/cfis/config_tile_Sx_nomask.ini b/example/cfis/config_tile_Sx_nomask.ini index 81bb89c61..1b445e06b 100644 --- a/example/cfis/config_tile_Sx_nomask.ini +++ b/example/cfis/config_tile_Sx_nomask.ini @@ -55,7 +55,7 @@ TIMEOUT = 96:00:00 [SEXTRACTOR_RUNNER] -INPUT_DIR = run_sp_Git:get_images_runner, last:uncompress_fits_runner, run_sp_exp_Mh:merge_headers_runner +INPUT_DIR = run_sp_Git:get_images_runner, last:uncompress_fits_runner, run_sp_tile_Mh_exp:merge_headers_runner FILE_PATTERN = CFIS_image, CFIS_weight, log_exp_headers diff --git a/example/cfis_simu/config_tile_MiViSmVi.ini b/example/cfis_simu/config_tile_MiViSmVi.ini index 62edd4252..abf075930 100644 --- a/example/cfis_simu/config_tile_MiViSmVi.ini +++ b/example/cfis_simu/config_tile_MiViSmVi.ini @@ -57,11 +57,11 @@ TIMEOUT = 96:00:00 [MCCD_INTERP_RUNNER] -INPUT_DIR = last:sextractor_runner_run_1 +INPUT_DIR = last:sextractor_runner_run_1, last:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -84,9 +84,6 @@ PSF_MODEL_PATTERN = fitted_model PSF_MODEL_SEPARATOR = - -# Multi-epoch mode: Path to file with single-exposure WCS header information -ME_LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite - [VIGNETMAKER_RUNNER_RUN_1] @@ -146,11 +143,11 @@ OUTPUT_MODE = new # Create multi-epoch vignets for tiles corresponding to # positions on single-exposures -INPUT_DIR = last:sextractor_runner_run_1 +INPUT_DIR = last:sextractor_runner_run_1, last:merge_headers_runner -FILE_PATTERN = sexcat +FILE_PATTERN = sexcat, log_exp_headers -FILE_EXT = .fits +FILE_EXT = .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -178,4 +175,3 @@ PREFIX = # run outputs ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2, sextractor_runner_run_2 ME_IMAGE_PATTERN = flag, image, weight, background, background_rms -ME_LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite diff --git a/example/cfis_simu/config_tile_Ng_template.ini b/example/cfis_simu/config_tile_Ng_template.ini index 1dced6bc1..5b20e5951 100644 --- a/example/cfis_simu/config_tile_Ng_template.ini +++ b/example/cfis_simu/config_tile_Ng_template.ini @@ -55,18 +55,15 @@ TIMEOUT = 96:00:00 # Model-fitting shapes with ngmix [NGMIX_RUNNER] -INPUT_DIR = last:sextractor_runner_run_1,last:MCCD_interp_runner,last:vignetmaker_runner_run_2 +INPUT_DIR = last:sextractor_runner_run_1,last:MCCD_interp_runner,last:vignetmaker_runner_run_2,last:merge_headers_runner -FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet +FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers -FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite +FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite - # BKG_RMS_VIGNET_PATH (optional): per-pixel BACKGROUND_RMS vignets, used as # 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for # every tile (missing file -> error, no per-tile fallback); omit the option diff --git a/example/cfis_simu/config_tile_Sx_exp_mccd.ini b/example/cfis_simu/config_tile_Sx_exp_mccd.ini index 8080d77df..524d3c97d 100644 --- a/example/cfis_simu/config_tile_Sx_exp_mccd.ini +++ b/example/cfis_simu/config_tile_Sx_exp_mccd.ini @@ -58,11 +58,11 @@ TIMEOUT = 96:00:00 ## Detection on tile [SEXTRACTOR_RUNNER_RUN_1] -INPUT_DIR = last:get_images_runner_run_1, last:get_images_runner_run_1, last:mask_runner_run_1 +INPUT_DIR = last:get_images_runner_run_1, last:get_images_runner_run_1, last:mask_runner_run_1, last:merge_headers_runner -FILE_PATTERN = CFIS_simu_image, CFIS_simu_weight, pipeline_flag +FILE_PATTERN = CFIS_simu_image, CFIS_simu_weight, pipeline_flag, log_exp_headers -FILE_EXT = .fits, .fits, .fits +FILE_EXT = .fits, .fits, .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -110,9 +110,6 @@ SUFFIX = sexcat # Necessary for tiles, to enable multi-exposure processing MAKE_POST_PROCESS = True -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite - # World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y WORLD_POSITION = XWIN_WORLD,YWIN_WORLD @@ -131,6 +128,8 @@ INPUT_MODULE = split_exp_runner, mask_runner_run_2 # Read pipeline flag files created by mask module FILE_PATTERN = image, weight, flag +FILE_EXT = .fits + NUMBERING_SCHEME = -0000000-0 # SExtractor executable path diff --git a/example/cfis_simu/config_tile_Sx_exp_psfex.ini b/example/cfis_simu/config_tile_Sx_exp_psfex.ini index cd353198b..a7e221673 100644 --- a/example/cfis_simu/config_tile_Sx_exp_psfex.ini +++ b/example/cfis_simu/config_tile_Sx_exp_psfex.ini @@ -56,13 +56,13 @@ TIMEOUT = 96:00:00 [SEXTRACTOR_RUNNER_RUN_1] -INPUT_MODULE = get_images_runner_run_1, uncompress_fits_runner, mask_runner_run_1 +INPUT_MODULE = get_images_runner_run_1, uncompress_fits_runner, mask_runner_run_1, merge_headers_runner -INPUT_DIR = last:get_images_runner_run_1, last:uncompress_fits_runner, last:mask_runner_run_1 +INPUT_DIR = last:get_images_runner_run_1, last:uncompress_fits_runner, last:mask_runner_run_1, last:merge_headers_runner -FILE_PATTERN = CFIS_image, CFIS_weight, pipeline_flag +FILE_PATTERN = CFIS_image, CFIS_weight, pipeline_flag, log_exp_headers -FILE_EXT = .fits, .fits, .fits +FILE_EXT = .fits, .fits, .fits, .sqlite # NUMBERING_SCHEME (optional) string with numbering pattern for input files NUMBERING_SCHEME = -000-000 @@ -110,9 +110,6 @@ SUFFIX = sexcat # Necessary for tiles, to enable multi-exposure processing MAKE_POST_PROCESS = True -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite - # World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y WORLD_POSITION = XWIN_WORLD,YWIN_WORLD @@ -133,6 +130,8 @@ INPUT_MODULE = split_exp_runner, mask_runner # Read pipeline flag files created by mask module FILE_PATTERN = image, weight, pipeline_flag +FILE_EXT = .fits + NUMBERING_SCHEME = -0000000-0 # SExtractor executable path From 436bcc8a615dec5d541895de2425ce7fcda66bf3 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:30:56 +0200 Subject: [PATCH 64/80] fix(ngmix): rename ntry_fit column to nfev_fit The column now stores ngmix 2.x's nfev (solver function-evaluation count, ~tens-hundreds, -1 on some failures), not the v1 1-5 retry count; the old name misrepresented the value. No downstream consumer reads it: make_cat's _save_ngmix_data never touches it, and the only ntry matches in sp_validation are base64 image blobs in notebook outputs. --- src/shapepipe/modules/ngmix_package/ngmix.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index af3864de0..6f13d1365 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -368,7 +368,7 @@ def compile_results(self, results): 'id', 'n_epoch_model', 'moments_fail', - 'ntry_fit', + 'nfev_fit', 'g1_psfo_ngmix', 'g2_psfo_ngmix', 'g1_err_psfo_ngmix', @@ -424,7 +424,10 @@ def compile_results(self, results): output_dict[name]["moments_fail"].append( results[idx]["moments_fail"] ) - output_dict[name]["ntry_fit"].append( + # ngmix 2.x reports the solver's function-evaluation count + # (nfev, ~tens-hundreds; -1 on some failures), not the v1 + # 1-5 retry count, so the column is named accordingly. + output_dict[name]["nfev_fit"].append( fit.get("nfev", np.nan) ) output_dict[name]["g1_psfo_ngmix"].append( From 05f1584e2c32604fc76414b609aeef3bde3dea18 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 03:31:22 +0200 Subject: [PATCH 65/80] chore(ngmix): drop dead copyfile import and unused Tile_cat columns copyfile was orphaned by the resume-path removal; Tile_cat's size/e/ theta attributes were read from the catalog but never consumed anywhere in src/ or scripts/. get_noise stays: scripts/jupyter/ test_centroid_shift.py imports and calls it. --- src/shapepipe/modules/ngmix_package/ngmix.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 6f13d1365..428808aa5 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -11,7 +11,6 @@ import ngmix import galsim import numpy as np -from shutil import copyfile from astropy.io import fits from modopt.math.stats import sigma_mad from ngmix.observation import Observation, ObsList @@ -124,9 +123,6 @@ def get_data(self, cat_path): # Optional columns — may be absent in external (non-SExtractor) catalogs self.flux = np.copy(data['FLUX_AUTO']) if 'FLUX_AUTO' in cols else None self.vign = np.copy(data['VIGNET']) if 'VIGNET' in cols else None - self.size = np.copy(data['FWHM_WORLD']) if 'FWHM_WORLD' in cols else None - self.e = np.copy(data['ELLIPTICITY']) if 'ELLIPTICITY' in cols else None - self.theta = np.copy(data['THETA_WIN_WORLD']) if 'THETA_WIN_WORLD' in cols else None tile_cat.close() From ceeb62d05b178e47c22e469c906d20420d674993 Mon Sep 17 00:00:00 2001 From: martin kilbinger Date: Thu, 11 Jun 2026 14:17:31 +0200 Subject: [PATCH 66/80] removed obsolete cfis_simu directory; superceded by cfis_image_sims (soon to be merged --- example/cfis_simu/config.mask_simu | 86 ---- example/cfis_simu/config_GitFeGie_symlink.ini | 146 ------ example/cfis_simu/config_MCCD.ini | 120 ----- example/cfis_simu/config_MaMa.ini | 105 ---- example/cfis_simu/config_exp_SpMh.ini | 84 ---- example/cfis_simu/config_make_cat_mccd.ini | 77 --- .../config_merge_sep_cats_template.ini | 75 --- example/cfis_simu/config_tile.mask_simu | 90 ---- example/cfis_simu/config_tile_MiViSmVi.ini | 177 ------- example/cfis_simu/config_tile_Ng_template.ini | 102 ---- example/cfis_simu/config_tile_Sx_exp_mccd.ini | 279 ----------- .../cfis_simu/config_tile_Sx_exp_psfex.ini | 247 ---------- example/cfis_simu/default.conv | 5 - example/cfis_simu/default.param | 69 --- example/cfis_simu/default.psfex | 85 ---- example/cfis_simu/default_exp.sex | 133 ----- example/cfis_simu/default_tile.sex | 133 ----- example/cfis_simu/job_sp_simu.bash | 462 ------------------ .../mask_default/MEGAPRIME_star_i_13.8.reg | 24 - .../mask_default/Messier_catalog.npy | Bin 4209 -> 0 bytes .../mask_default/Messier_catalog_updated.fits | Bin 8640 -> 0 bytes example/cfis_simu/mask_default/default.ww | 40 -- example/cfis_simu/mask_default/halo_mask.reg | 50 -- example/cfis_simu/mask_default/ngc_cat.fits | Bin 201600 -> 0 bytes example/cfis_simu/readme.txt | 7 - example/cfis_simu/star_selection.setools | 104 ---- example/cfis_simu/tile_numbers.txt | 1 - 27 files changed, 2701 deletions(-) delete mode 100644 example/cfis_simu/config.mask_simu delete mode 100644 example/cfis_simu/config_GitFeGie_symlink.ini delete mode 100644 example/cfis_simu/config_MCCD.ini delete mode 100644 example/cfis_simu/config_MaMa.ini delete mode 100644 example/cfis_simu/config_exp_SpMh.ini delete mode 100644 example/cfis_simu/config_make_cat_mccd.ini delete mode 100644 example/cfis_simu/config_merge_sep_cats_template.ini delete mode 100644 example/cfis_simu/config_tile.mask_simu delete mode 100644 example/cfis_simu/config_tile_MiViSmVi.ini delete mode 100644 example/cfis_simu/config_tile_Ng_template.ini delete mode 100644 example/cfis_simu/config_tile_Sx_exp_mccd.ini delete mode 100644 example/cfis_simu/config_tile_Sx_exp_psfex.ini delete mode 100644 example/cfis_simu/default.conv delete mode 100644 example/cfis_simu/default.param delete mode 100644 example/cfis_simu/default.psfex delete mode 100644 example/cfis_simu/default_exp.sex delete mode 100644 example/cfis_simu/default_tile.sex delete mode 100755 example/cfis_simu/job_sp_simu.bash delete mode 100644 example/cfis_simu/mask_default/MEGAPRIME_star_i_13.8.reg delete mode 100644 example/cfis_simu/mask_default/Messier_catalog.npy delete mode 100644 example/cfis_simu/mask_default/Messier_catalog_updated.fits delete mode 100644 example/cfis_simu/mask_default/default.ww delete mode 100644 example/cfis_simu/mask_default/halo_mask.reg delete mode 100644 example/cfis_simu/mask_default/ngc_cat.fits delete mode 100644 example/cfis_simu/readme.txt delete mode 100644 example/cfis_simu/star_selection.setools delete mode 100644 example/cfis_simu/tile_numbers.txt diff --git a/example/cfis_simu/config.mask_simu b/example/cfis_simu/config.mask_simu deleted file mode 100644 index c06a14a02..000000000 --- a/example/cfis_simu/config.mask_simu +++ /dev/null @@ -1,86 +0,0 @@ -# Mask module configuration file for single-exposure images - -## Paths to executables -[PROGRAM_PATH] - -WW_PATH = ww -WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww - -# Indicate cds client executable if no external star catalogue is available -# (e.g. no internet access on run nodes) -CDSCLIENT_PATH = findgsc2.2 - - -## Border mask -[BORDER_PARAMETERS] - -BORDER_MAKE = True - -BORDER_WIDTH = 50 -BORDER_FLAG_VALUE = 4 - - -## Halo mask -[HALO_PARAMETERS] - -HALO_MAKE = False - -HALO_MASKMODEL_PATH = $SP_CONFIG/mask_default/halo_mask.reg -HALO_MAG_LIM = 13. -HALO_SCALE_FACTOR = 0.05 -HALO_MAG_PIVOT = 13.8 -HALO_FLAG_VALUE = 2 -HALO_REG_FILE = halo.reg - - -## Diffraction spike mask -[SPIKE_PARAMETERS] - -SPIKE_MAKE = False - -SPIKE_MASKMODEL_PATH = $SP_CONFIG/mask_default/MEGAPRIME_star_i_13.8.reg -SPIKE_MAG_LIM = 18. -SPIKE_SCALE_FACTOR = 0.3 -SPIKE_MAG_PIVOT = 13.8 -SPIKE_FLAG_VALUE = 128 -SPIKE_REG_FILE = spike.reg - - -## Messier mask -[MESSIER_PARAMETERS] - -MESSIER_MAKE = False - -MESSIER_CAT_PATH = $SP_CONFIG/mask_default/Messier_catalog_updated.fits -MESSIER_SIZE_PLUS = 0. -MESSIER_FLAG_VALUE = 16 - - -## NGC mask -[NGC_PARAMETERS] - -NGC_MAKE = False - -NGC_CAT_PATH = $SP_CONFIG/mask_default/ngc_cat.fits -NGC_SIZE_PLUS = 0. -NGC_FLAG_VALUE = 32 - - - -## Missing data parameters -[MD_PARAMETERS] - -MD_MAKE = False - -MD_THRESH_FLAG = 0.3 -MD_THRESH_REMOVE = 0.75 -MD_REMOVE = False - - -## Other parameters -[OTHER] - -TEMP_DIRECTORY = .temp - -KEEP_REG_FILE = False -KEEP_INDIVIDUAL_MASK = False diff --git a/example/cfis_simu/config_GitFeGie_symlink.ini b/example/cfis_simu/config_GitFeGie_symlink.ini deleted file mode 100644 index 533e6a959..000000000 --- a/example/cfis_simu/config_GitFeGie_symlink.ini +++ /dev/null @@ -1,146 +0,0 @@ -# ShapePipe configuration file for: get images - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = False - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_GitFeGie - -# Add date and time to RUN_NAME, optional, default: False -RUN_DATETIME = True - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = get_images_runner, find_exposures_runner, get_images_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names -INPUT_DIR = $SP_RUN - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 1 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -# Get tiles -[GET_IMAGES_RUNNER_RUN_1] - -FILE_PATTERN = tile_numbers - -FILE_EXT = .txt - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = - -# Paths - -# Input path where original images are stored. Can be local path or vos url. -# Single string or list of strings -INPUT_PATH = $SP_RUN/input_tiles, $SP_RUN/input_tiles - -# Input file pattern including tile number as dummy template -INPUT_FILE_PATTERN = CFIS_simu_image-000-000, CFIS_simu_weight-000-000 - -# Input file extensions -INPUT_FILE_EXT = .fits, .fits - -# Input numbering scheme, python regexp -INPUT_NUMBERING = \d{3}-\d{3} - -# Output file pattern without number -OUTPUT_FILE_PATTERN = CFIS_simu_image-, CFIS_simu_weight- - -# Copy/download method, one in 'vos', 'symlink' -RETRIEVE = symlink - -# Copy command options, optional -RETRIEVE_OPTIONS = -L - - -[FIND_EXPOSURES_RUNNER] - -INPUT_MODULE = get_images_runner_run_1 - -FILE_PATTERN = CFIS_simu_image - -FILE_EXT = .fits - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -COLNUM = 2 - -EXP_PREFIX = simu_image- -# Get exposures -[GET_IMAGES_RUNNER_RUN_2] - -INPUT_MODULE = find_exposures_runner - -FILE_PATTERN = exp_numbers - -FILE_EXT = .txt - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - - -# Paths - -# Output path (optional, default is [FILE]:OUTPUT_DIR -# OUTPUT_PATH = input_images - -# Input path where original images are stored. Can be local path or vos url. -# Single string or list of strings -INPUT_PATH = $SP_RUN/input_exp/images,$SP_RUN/input_exp/images,$SP_RUN/input_exp/images - -# Input file pattern including tile number as dummy template -INPUT_FILE_PATTERN = simu_image-0000000,simu_weight-0000000,simu_flag-0000000 - -# Input file extensions -INPUT_FILE_EXT = .fits, .fits,.fits - -# Input numbering scheme, python regexp -INPUT_NUMBERING = \d{7} - -# Output file pattern without number -OUTPUT_FILE_PATTERN = image-,weight-,flag- - -# Method to retrieve images, one in 'vos', 'symlink' -RETRIEVE = symlink - -# If RETRIEVE=vos, number of attempts to download -# Optional, default=3 -N_TRY = 3 - -# Retrieve command options, optional -RETRIEVE_OPTIONS = -L diff --git a/example/cfis_simu/config_MCCD.ini b/example/cfis_simu/config_MCCD.ini deleted file mode 100644 index bf5e6852e..000000000 --- a/example/cfis_simu/config_MCCD.ini +++ /dev/null @@ -1,120 +0,0 @@ -# Configuration file for the MCCD method - -[INPUTS] -INPUT_DIR = . -PREPROCESSED_OUTPUT_DIR = ./output -OUTPUT_DIR = ./output -INPUT_REGEX_FILE_PATTERN = star_split_ratio_80-*-*.fits -INPUT_SEPARATOR = - -MIN_N_STARS = 20 -OUTLIER_STD_MAX = 100. -USE_SNR_WEIGHTS = False - -[INSTANCE] -N_COMP_LOC = 8 -D_COMP_GLOB = 8 -KSIG_LOC = 0.00 -KSIG_GLOB = 0.00 -FILTER_PATH = None -D_HYB_LOC = 2 -MIN_D_COMP_GLOB = None -RMSE_THRESH = 1.25 -CCD_STAR_THRESH = 0.15 -FP_GEOMETRY = CFIS - -[FIT] -LOC_MODEL = hybrid -PSF_SIZE = 6.2 -PSF_SIZE_TYPE = R2 -N_EIGENVECTS = 5 -N_ITER_RCA = 1 -N_ITER_GLOB = 2 -N_ITER_LOC = 2 -NB_SUBITER_S_LOC = 300 -NB_SUBITER_A_LOC = 400 -NB_SUBITER_S_GLOB = 100 -NB_SUBITER_A_GLOB = 200 - -[VALIDATION] -VAL_DATA_INPUT_DIR = . -VAL_PREPROCESSED_OUTPUT_DIR = ./output -VAL_MODEL_INPUT_DIR = ./output -VAL_OUTPUT_DIR = ./output -VAL_REGEX_FILE_PATTERN = star_split_ratio_20-*-*.fits -VAL_SEPARATOR = - -APPLY_DEGRADATION = True -MCCD_DEBUG = False -GLOBAL_POL_INTERP = False - - -# Parameter description: -# -# -# [INPUTS] -# INPUT_DIR : (Required) Must be a valid directory containing the input -# MCCD files. -# INPUT_REGEX_FILE_PATTERN : File pattern of the input files to use. It should -# follow regex (regular expression) standards. -# INPUT_SEPARATOR : Separator of the different fields in the filename, -# ie sexcat[SEP]catalog_id[SEP]CCD_id.fits -# MIN_N_STARS : Minimum number of stars to keep a CCD for the training. -# OUTLIER_STD_MAX : Maximum standard deviation used for the outlier rejection. -# Should not be too low as a hihg quantity of low quality -# stars will be rejected. ie 9 is a conservative rejection. -# USE_SNR_WEIGHTS : Boolean to determine if the SNR weighting strategy will -# be used. -# For now, it needs the SNR estimations from SExtractor. -# PREPROCESSED_OUTPUT_DIR : (Required) Must be a valid directory to write the -# preprocessed input files. -# OUTPUT_DIR : (Required) Must be a valid directory to write the output files. -# The constructed models will be saved. -# -# -# [INSTANCE] -# N_COMP_LOC : Number of components of the Local model. If LOC_MODEL is poly, -# will be the max degree D of the polynomial. -# D_COMP_GLOB : Max degree of the global polynomial model. -# KSIG_LOC : Denoising parameter of the local model. -# ie 1 is a normal denoising, 3 is a hard denoising. -# KSIG_GLOB : Denoising parameter of the global model. -# ie 1 is a normal denoising, 3 is a hard denoising. -# FILTER_PATH : Path for predefined filters. -# -# -# [FIT] -# LOC_MODEL : Defines the type of local model to use, it can be: 'rca', -# 'poly' or 'hybrid'. -# When the poly model is used, N_COMP_LOC should be used -# as the D_LOC (max degree of the poly model) -# PSF_SIZE : First guess of the PSF size. A size estimation is done anyways. -# PSF_SIZE_TYPE : Type of the size information. It can be: fwhm, R2, sigma -# N_EIGENVECTS : Number of eigenvectors to keep for the graph constraint -# construction. -# N_ITER_RCA : Number of global epochs in the algorithm. Alternation between -# global and local estimations. -# N_ITER_GLOB : Number of epochs for each global optimization. Alternations -# between A_GLOB and S_GLOB. -# N_ITER_LOC : Number of epochs for each local optimization. Alternations -# between the different A_LOC and S_LOC. -# NB_SUBITER_S_LOC : Iterations for the optimization algorithm over S_LOC. -# NB_SUBITER_A_LOC : Iterations for the optimization algorithm over A_LOC. -# NB_SUBITER_S_GLOB : Iterations for the optimization algorithm over S_GLOB. -# NB_SUBITER_A_GLOB : Iterations for the optimization algorithm over A_GLOB. -# -# -# [VALIDATION] -# MODEL_INPUT_DIR : (Required) Must be a valid directory which contains the -# saved trained models. -# VAL_DATA_INPUT_DIR : (Required) Must be a valid directory which contains the -# validation input data (test dataset). -# VAL_REGEX_FILE_PATTERN : Same as INPUT_REGEX_FILE_PATTERN but for validation. -# VAL_SEPARATOR : Same as INPUT_SEPARATOR but for validation. -# VAL_OUTPUT_DIR : (Required) Must be a valid directory where to save the -# validation outputs, test PSFs and interpolated PSFs. -# APPLY_DEGRADATION : Whether the PSF models should be degraded -# (sampling/shifts/flux) to match stars; use True if you -# plan on making pixel-based comparisons (residuals etc.). -# MCCD_DEBUG : Debug mode. Returns the local and global contributions. -# GLOBAL_POL_INTERP : Uses polynomial interpolation for the global model -# instead of RBF kernel interpolation. -# diff --git a/example/cfis_simu/config_MaMa.ini b/example/cfis_simu/config_MaMa.ini deleted file mode 100644 index 4a13e7946..000000000 --- a/example/cfis_simu/config_MaMa.ini +++ /dev/null @@ -1,105 +0,0 @@ -# ShapePipe configuration file for masking of tiles and exposures - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_MaMa - -# Add date and time to RUN_NAME, optional, default: False -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = mask_runner, mask_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 8 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -### Mask tiles -[MASK_RUNNER_RUN_1] - -# Input directory, containing input files, single string or list of names -INPUT_DIR = last:get_images_runner_run_1, last:get_images_runner_run_2 - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# Input file pattern(s), list of strings with length matching number of expected input file types -# Cannot contain wild cards -FILE_PATTERN = CFIS_simu_image, CFIS_simu_weight - -# FILE_EXT (optional) list of string extensions to identify input files -FILE_EXT = .fits, .fits - -# Path of mask config file -MASK_CONFIG_PATH = $SP_CONFIG/config_tile.mask_simu - -# External mask file flag, use if True, otherwise ignore -USE_EXT_FLAG = False - -# External star catalogue flag, use external cat if True, -# obtain from online catalogue if False -USE_EXT_STAR = False - -# File name suffix for the output flag files (optional) -PREFIX = pipeline - -### Mask exposures -[MASK_RUNNER_RUN_2] - -# Parent module -INPUT_DIR = last:split_exp_runner - -# Update numbering convention, accounting for HDU number of -# single-exposure single-HDU files -NUMBERING_SCHEME = -0000000-0 - -# Path of mask config file -MASK_CONFIG_PATH = $SP_CONFIG/config.mask_simu - -# External mask file flag, use if True, otherwise ignore -USE_EXT_FLAG = True - -# External star catalogue flag, use external cat if True, -# obtain from online catalogue if False -USE_EXT_STAR = False - -# File name suffix for the output flag files (optional) -PREFIX = pipeline diff --git a/example/cfis_simu/config_exp_SpMh.ini b/example/cfis_simu/config_exp_SpMh.ini deleted file mode 100644 index 58dc56e6e..000000000 --- a/example/cfis_simu/config_exp_SpMh.ini +++ /dev/null @@ -1,84 +0,0 @@ -# ShapePipe configuration file for single-exposures, -# split images, merge headers - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_exp_SpMh - -# Add date and time to RUN_NAME, optional, default: True -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = split_exp_runner, merge_headers_runner - -# Run mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names with length matching FILE_PATTERN -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 16 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -[SPLIT_EXP_RUNNER] - -INPUT_DIR = last:get_images_runner_run_2 - -# Matches compressed single-exposure files -FILE_EXT = .fits, .fits, .fits - -NUMBERING_SCHEME = -0000000 - -# OUTPUT_SUFFIX, actually file name prefixes. -# Expected keyword "flag" will lead to a behavior where the data are save as int. -# The code also expects the image data to use the "image" suffix -# (default value in the pipeline). -OUTPUT_SUFFIX = image, weight, flag - -# Number of HDUs/CCDs of mosaic -N_HDU = 40 - - -[MERGE_HEADERS_RUNNER] - -FILE_PATTERN = headers - -FILE_EXT = .npy - -# Single-exposure numbering scheme -NUMBERING_SCHEME = -0000000 - -OUTPUT_PATH = $SP_RUN/output diff --git a/example/cfis_simu/config_make_cat_mccd.ini b/example/cfis_simu/config_make_cat_mccd.ini deleted file mode 100644 index 5aa33d272..000000000 --- a/example/cfis_simu/config_make_cat_mccd.ini +++ /dev/null @@ -1,77 +0,0 @@ -# ShapePipe post-run configuration file: create final catalogs - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_Mc - -# Add date and time to RUN_NAME, optional, default: True -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = make_cat_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names with length matching FILE_PATTERN -INPUT_DIR = last:sextractor_runner_run_1, last:spread_model_runner, last:mccd_interp_runner, last:merge_sep_cats_runner - -# Output directory -OUTPUT_DIR = ./output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 8 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -[MAKE_CAT_RUNNER] - -# Input file pattern(s), list of strings with length matching number of expected input file types -# Cannot contain wild cards -FILE_PATTERN = sexcat, sexcat_sm, galaxy_psf, ngmix -#, galsim - -# FILE_EXT (optional) list of string extensions to identify input files -FILE_EXT = .fits, .fits, .sqlite, .fits -#, .fits - -# Numbering convention, string that exemplifies a numbering pattern. -# Matches input single exposures (with 'p' removed) -# Needs to be given in this section, will be updated in module -# sections below -NUMBERING_SCHEME = -000-000 - -SM_DO_CLASSIFICATION = True -SM_STAR_THRESH = 0.003 -SM_GAL_THRESH = 0.01 - -SHAPE_MEASUREMENT_TYPE = ngmix -#, galsim diff --git a/example/cfis_simu/config_merge_sep_cats_template.ini b/example/cfis_simu/config_merge_sep_cats_template.ini deleted file mode 100644 index fb848475f..000000000 --- a/example/cfis_simu/config_merge_sep_cats_template.ini +++ /dev/null @@ -1,75 +0,0 @@ -# ShapePipe post-run configuration file: merge separated catalogues - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_Ms - -# Add date and time to RUN_NAME, optional, default: True -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = merge_sep_cats_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names with length matching FILE_PATTERN -INPUT_DIR = ./output/run_sp_tile_ngmix_Ng1u/ngmix_runner/output -#, ./output/run_sp_tile_ngmix_Ng1u/galsim_shapes_v2_runner/output - -# Output directory -OUTPUT_DIR = ./output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 8 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -[MERGE_SEP_CATS_RUNNER] - -# Input file pattern(s), list of strings with length matching number of expected input file types -# Cannot contain wild cards -FILE_PATTERN = ngmix -#, galsim - -# FILE_EXT (optional) list of string extensions to identify input files -FILE_EXT = .fits -#, .fits - -# Numbering convention, string that exemplifies a numbering pattern. -NUMBERING_SCHEME = -000-000 - -# WARNING (optional, default is 'error'). Use 'always'/'ignore' to -# display/ignore warnings, and not raise error -WARNING = always - -# Maximum number of separated catalogues per input -N_SPLIT_MAX = 0 diff --git a/example/cfis_simu/config_tile.mask_simu b/example/cfis_simu/config_tile.mask_simu deleted file mode 100644 index 2c0a53e04..000000000 --- a/example/cfis_simu/config_tile.mask_simu +++ /dev/null @@ -1,90 +0,0 @@ -# Mask module config file for tiles - -## Paths to executables -[PROGRAM_PATH] - -WW_PATH = ww -WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww - -# Indicate cds client executable if no external star catalogue is available -# (e.g. no internet access on run nodes) -CDSCLIENT_PATH = findgsc2.2 - -## Border parameters -[BORDER_PARAMETERS] - -BORDER_MAKE = True - -BORDER_WIDTH = 1 -BORDER_FLAG_VALUE = 4 - - -## Halo parameters -[HALO_PARAMETERS] - -HALO_MAKE = False - -HALO_MASKMODEL_PATH = $SP_CONFIG/mask_default/halo_mask.reg -HALO_MAG_LIM = 13. -HALO_SCALE_FACTOR = 0.05 -HALO_MAG_PIVOT = 13.8 -HALO_FLAG_VALUE = 2 -HALO_REG_FILE = halo.reg - - -## Diffraction pike parameters -[SPIKE_PARAMETERS] - -SPIKE_MAKE = False - -SPIKE_MASKMODEL_PATH = $SP_CONFIG/mask_default/MEGAPRIME_star_i_13.8.reg -SPIKE_MAG_LIM = 18. -SPIKE_SCALE_FACTOR = 0.3 -SPIKE_MAG_PIVOT = 13.8 -SPIKE_FLAG_VALUE = 128 -SPIKE_REG_FILE = spike.reg - - -## Messier parameters -[MESSIER_PARAMETERS] - -MESSIER_MAKE = False - -MESSIER_CAT_PATH = $SP_CONFIG/mask_default/Messier_catalog_updated.fits -MESSIER_PIXEL_SCALE = 0.187 -MESSIER_SIZE_PLUS = 0. -MESSIER_FLAG_VALUE = 16 - -## NGC mask -[NGC_PARAMETERS] - -NGC_MAKE = False - -NGC_CAT_PATH = $SP_CONFIG/mask_default/ngc_cat.fits -NGC_SIZE_PLUS = 0. -NGC_FLAG_VALUE = 32 - - -## External flag -[EXTERNAL_FLAG] - -EF_MAKE = False - - -## Missing data parameters -[MD_PARAMETERS] - -MD_MAKE = False - -MD_THRESH_FLAG = 0.3 -MD_THRESH_REMOVE = 0.75 -MD_REMOVE = False - - -## Other parameters -[OTHER] - -KEEP_REG_FILE = False -KEEP_INDIVIDUAL_MASK = False - -TEMP_DIRECTORY = .temp_tiles diff --git a/example/cfis_simu/config_tile_MiViSmVi.ini b/example/cfis_simu/config_tile_MiViSmVi.ini deleted file mode 100644 index abf075930..000000000 --- a/example/cfis_simu/config_tile_MiViSmVi.ini +++ /dev/null @@ -1,177 +0,0 @@ -# ShapePipe configuration file for tiles, from detection up to shape measurement. -# MCCD PSF model. - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_MiViSmVi - -# Add date and time to RUN_NAME, optional, default: False -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = mccd_interp_runner, - vignetmaker_runner, spread_model_runner, - vignetmaker_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 12 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -[MCCD_INTERP_RUNNER] - -INPUT_DIR = last:sextractor_runner_run_1, last:merge_headers_runner - -FILE_PATTERN = sexcat, log_exp_headers - -FILE_EXT = .fits, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = MULTI-EPOCH - -# Column names of position parameters -POSITION_PARAMS = XWIN_WORLD,YWIN_WORLD - -# If True, measure and store ellipticity of the PSF -GET_SHAPES = True - -PSF_MODEL_DIR = mccd_fit_val_runner - -PSF_MODEL_PATTERN = fitted_model - -PSF_MODEL_SEPARATOR = - - - -[VIGNETMAKER_RUNNER_RUN_1] - -# Create vignets for tile weights - -INPUT_DIR = last:sextractor_runner_run_1, last:get_images_runner_run_1 - -FILE_PATTERN = sexcat, CFIS_simu_weight - -FILE_EXT = .fits, .fits - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -MASKING = False -MASK_VALUE = 0 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = CLASSIC - -# Coordinate frame type, one in PIX (pixel frame), SPHE (spherical coordinates) -COORD = PIX -POSITION_PARAMS = XWIN_IMAGE,YWIN_IMAGE - -# Vignet size in pixels -STAMP_SIZE = 51 - -# Output file name prefix, file name is _vignet.fits -PREFIX = weight - - -[SPREAD_MODEL_RUNNER] - -INPUT_DIR = last:sextractor_runner_run_1, last:mccd_interp_runner, last:vignetmaker_runner_run_1 - -FILE_PATTERN = sexcat, galaxy_psf, weight_vignet - -FILE_EXT = .fits, .sqlite, .fits - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# Pixel scale in arcsec -PIXEL_SCALE = 0.186 - -# Output mode: -# new: create a new catalog with: [number, mag, sm, sm_err] -# add: create a copy of the input SExtractor with the column sm and sm_err -OUTPUT_MODE = new - - -[VIGNETMAKER_RUNNER_RUN_2] - -# Create multi-epoch vignets for tiles corresponding to -# positions on single-exposures - -INPUT_DIR = last:sextractor_runner_run_1, last:merge_headers_runner - -FILE_PATTERN = sexcat, log_exp_headers - -FILE_EXT = .fits, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -MASKING = False -MASK_VALUE = 0 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = MULTI-EPOCH - -# Coordinate frame type, one in PIX (pixel frame), SPHE (spherical coordinates) -COORD = SPHE -POSITION_PARAMS = XWIN_WORLD,YWIN_WORLD - -# Vignet size in pixels -STAMP_SIZE = 51 - -# Output file name prefix, file name is vignet.fits -PREFIX = - -# Additional parameters for path and file pattern corresponding to single-exposure -# run outputs -ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2, sextractor_runner_run_2 -ME_IMAGE_PATTERN = flag, image, weight, background, background_rms diff --git a/example/cfis_simu/config_tile_Ng_template.ini b/example/cfis_simu/config_tile_Ng_template.ini deleted file mode 100644 index 5b20e5951..000000000 --- a/example/cfis_simu/config_tile_Ng_template.ini +++ /dev/null @@ -1,102 +0,0 @@ -# ShapePipe configuration file for tiles: ngmix + KSB - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_ngmix_NgXu - -# Add date and time to RUN_NAME, optional, default: False -RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = ngmix_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 16 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -# Model-fitting shapes with ngmix -[NGMIX_RUNNER] - -INPUT_DIR = last:sextractor_runner_run_1,last:MCCD_interp_runner,last:vignetmaker_runner_run_2,last:merge_headers_runner - -FILE_PATTERN = sexcat, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet, log_exp_headers - -FILE_EXT = .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# BKG_RMS_VIGNET_PATH (optional): per-pixel BACKGROUND_RMS vignets, used as -# 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for -# every tile (missing file -> error, no per-tile fallback); omit the option -# entirely to fall back to the scalar sigma_mad noise estimate. -BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite - -# Magnitude zero-point -MAG_ZP = 30.0 - -# Pixel scale in arcsec -PIXEL_SCALE = 0.186 - -ID_OBJ_MIN = -1 -ID_OBJ_MAX = -1 - - -# Moment-based (KSB) shapes with galsim -[GALSIM_SHAPES_V2_RUNNER] - -INPUT_DIR = last:sextractor_runner_run_2, last:vignetmaker_runner_run_1, last:X_interp_runner,last:vignetmaker_runner_run_2 - -FILE_PATTERN = sexcat, weight_vignet, image_vignet, background_vignet, galaxy_psf, weight_vignet, flag_vignet - -FILE_EXT = .fits, .fits, .sqlite, .sqlite, .sqlite, .sqlite, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# Multi-epoch mode: Path to file with single-exposure WCS header information -LOG_WCS = $SP_RUN/output/log_exp_headers.sqlite - -# Magnitude zero-point -MAG_ZP = 30.0 - -ID_OBJ_MIN = -1 -ID_OBJ_MAX = -1 diff --git a/example/cfis_simu/config_tile_Sx_exp_mccd.ini b/example/cfis_simu/config_tile_Sx_exp_mccd.ini deleted file mode 100644 index 524d3c97d..000000000 --- a/example/cfis_simu/config_tile_Sx_exp_mccd.ini +++ /dev/null @@ -1,279 +0,0 @@ -# ShapePipe configuration file for single-exposures, MCCD PSF model. -# Process exposures after masking, from star detection to PSF model. - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_Sx_exp_SxSePsf - -# Add date and time to RUN_NAME, optional, default: True -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = sextractor_runner, sextractor_runner, setools_runner, - mccd_preprocessing_runner, mccd_fit_val_runner, - merge_starcat_runner, mccd_plots_runner - -# Run mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names with length matching FILE_PATTERN -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 4 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -## Detection on tile -[SEXTRACTOR_RUNNER_RUN_1] - -INPUT_DIR = last:get_images_runner_run_1, last:get_images_runner_run_1, last:mask_runner_run_1, last:merge_headers_runner - -FILE_PATTERN = CFIS_simu_image, CFIS_simu_weight, pipeline_flag, log_exp_headers - -FILE_EXT = .fits, .fits, .fits, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# SExtractor executable path -EXEC_PATH = sex - -# SExtractor configuration files -DOT_SEX_FILE = $SP_CONFIG/default_tile.sex -DOT_PARAM_FILE = $SP_CONFIG/default.param -DOT_CONV_FILE = $SP_CONFIG/default.conv - -# Use input weight image if True -WEIGHT_IMAGE = True - -# Use input flag image if True -FLAG_IMAGE = True - -# Use input PSF file if True -PSF_FILE = False - -# Use distinct image for detection (SExtractor in -# dual-image mode) if True -DETECTION_IMAGE = False - -# Distinct weight image for detection (SExtractor -# in dual-image mode) -DETECTION_WEIGHT = False - -ZP_FROM_HEADER = False - -BKG_FROM_HEADER = False - -# Type of image check (optional), default not used, can be a list of -# BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, -# MINIBACK_RMS, -BACKGROUND, #FILTERED, -# OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -#CHECKIMAGE = BACKGROUND - -# File name suffix for the output sextractor files (optional) -SUFFIX = sexcat - -## Post-processing - -# Necessary for tiles, to enable multi-exposure processing -MAKE_POST_PROCESS = True - -# World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y -WORLD_POSITION = XWIN_WORLD,YWIN_WORLD - -# Number of pixels in x,y of a CCD. Format: Nx,Ny -CCD_SIZE = 33,2080,1,4612 - - -## Detection on single exposures -[SEXTRACTOR_RUNNER_RUN_2] - -INPUT_DIR = last:split_exp_runner, last:mask_runner_run_2 - -# Input from two modules -INPUT_MODULE = split_exp_runner, mask_runner_run_2 - -# Read pipeline flag files created by mask module -FILE_PATTERN = image, weight, flag - -FILE_EXT = .fits - -NUMBERING_SCHEME = -0000000-0 - -# SExtractor executable path -EXEC_PATH = sex - -# SExtractor configuration files -DOT_SEX_FILE = $SP_CONFIG/default_exp.sex -DOT_PARAM_FILE = $SP_CONFIG//default.param -DOT_CONV_FILE = $SP_CONFIG/default.conv - -# Use input weight image if True -WEIGHT_IMAGE = True - -# Use input flag image if True -FLAG_IMAGE = True - -# Use input PSF file if True -PSF_FILE = False - -# Use distinct image for detection (SExtractor in -# dual-image mode) if True. -DETECTION_IMAGE = False - -# Distinct weight image for detection (SExtractor -# in dual-image mode) if True -DETECTION_WEIGHT = False - -# Se to True if photometry zero-point is to be read from exposure image header -ZP_FROM_HEADER = True - -# If ZP_FROM_HEADER is True, zero-point key name -ZP_KEY = PHOTZP - -# Background information from image header. -# If BKG_FROM_HEADER is True, background value will be read from header. -# In that case, the value of BACK_TYPE will be set atomatically to MANUAL. -# This is used e.g. for the LSB images. -BKG_FROM_HEADER = False -# LSB images: -# BKG_FROM_HEADER = True - -# If BKG_FROM_HEADER is True, background value key name -# LSB images: -#BKG_KEY = IMMODE - -# Type of image check (optional), default not used, can be a list of -# BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, MINIBACK_RMS, -BACKGROUND, -# FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND, BACKGROUND_RMS - -# File name suffix for the output sextractor files (optional) SUFFIX = tile -SUFFIX = sexcat - -## Post-processing - -# Not required for single exposures -MAKE_POST_PROCESS = FALSE - - -[SETOOLS_RUNNER] - -INPUT_MODULE = sextractor_runner_run_2 - -# Note: Make sure this doe not match the SExtractor background images -# (sexcat_background*) -FILE_PATTERN = sexcat - -NUMBERING_SCHEME = -0000000-0 - -# SETools config file -SETOOLS_CONFIG_PATH = $SP_CONFIG/star_selection.setools - - -[MCCD_PREPROCESSING_RUNNER] - -# Path to MCCD config file -CONFIG_PATH = $SP_CONFIG/config_MCCD.ini - -MODE = FIT_VALIDATION - -VERBOSE = False - -INPUT_DIR = last:setools_runner - -# Input are individual CCDs, thus single-exposure single-HDU images -NUMBERING_SCHEME = -0000000-0 - -FILE_PATTERN = star_split_ratio_80, star_split_ratio_20 - -FILE_EXT = .fits, .fits - - -[MCCD_FIT_VAL_RUNNER] - -# Path to MCCD config file -CONFIG_PATH = $SP_CONFIG/config_MCCD.ini - -MODE = FIT_VALIDATION - -VERBOSE = False - -NUMBERING_SCHEME = -0000000 - - -[MERGE_STARCAT_RUNNER] - -INPUT_DIR = last:mccd_fit_val_runner - -# Path to MCCD config file -CONFIG_PATH = $SP_CONFIG/config_MCCD.ini - -MODE = FIT_VALIDATION - -VERBOSE = False - -PSF_MODEL = mccd - -NUMBERING_SCHEME = -0000000 - - -[MCCD_PLOTS_RUNNER] - -# Path to MCCD config file -CONFIG_PATH = $SP_CONFIG/config_MCCD.ini - -MODE = FIT_VALIDATION - -VERBOSE = False - -# Now MCCD has created a focal-plane PSF model, including all CCDS per images, -# thus single-exposure files -NUMBERING_SCHEME = -0000000 - -PSF = mccd - -PLOT_MEANSHAPES = True - -# X_GRID, Y_GRID: correspond to the number of bins in each direction of each -# CCD from the focal plane. Ex: each CCD will be binned in 5x10 regular grids. -X_GRID = 5 -Y_GRID = 10 - -PLOT_HISTOGRAMS = True - -# REMOVE_OUTLIERS: Remove validated stars that are outliers in terms of shape -# before drawing the plots. -REMOVE_OUTLIERS = False diff --git a/example/cfis_simu/config_tile_Sx_exp_psfex.ini b/example/cfis_simu/config_tile_Sx_exp_psfex.ini deleted file mode 100644 index a7e221673..000000000 --- a/example/cfis_simu/config_tile_Sx_exp_psfex.ini +++ /dev/null @@ -1,247 +0,0 @@ -# ShapePipe configuration file for single-exposures. PSFex PSF model. -# Process exposures after masking, from star detection to PSF model. - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_tile_Sx_exp_SxSePsf - -# Add date and time to RUN_NAME, optional, default: True -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = sextractor_runner, sextractor_runner, setools_runner, psfex_runner, psfex_interp_runner - - -# Run mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names with length matching FILE_PATTERN -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 40 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -[SEXTRACTOR_RUNNER_RUN_1] - -INPUT_MODULE = get_images_runner_run_1, uncompress_fits_runner, mask_runner_run_1, merge_headers_runner - -INPUT_DIR = last:get_images_runner_run_1, last:uncompress_fits_runner, last:mask_runner_run_1, last:merge_headers_runner - -FILE_PATTERN = CFIS_image, CFIS_weight, pipeline_flag, log_exp_headers - -FILE_EXT = .fits, .fits, .fits, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# SExtractor executable path -EXEC_PATH = sex - -# SExtractor configuration files -DOT_SEX_FILE = $SP_CONFIG/default_tile.sex -DOT_PARAM_FILE = $SP_CONFIG/default.param -DOT_CONV_FILE = $SP_CONFIG/default.conv - -# Use input weight image if True -WEIGHT_IMAGE = True - -# Use input flag image if True -FLAG_IMAGE = True - -# Use input PSF file if True -PSF_FILE = False - -# Use distinct image for detection (SExtractor in -# dual-image mode) if True -DETECTION_IMAGE = False - -# Distinct weight image for detection (SExtractor -# in dual-image mode) -DETECTION_WEIGHT = False - -ZP_FROM_HEADER = False - -BKG_FROM_HEADER = False - -# Type of image check (optional), default not used, can be a list of -# BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, -# MINIBACK_RMS, -BACKGROUND, #FILTERED, -# OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND, BACKGROUND_RMS - -# File name suffix for the output sextractor files (optional) -SUFFIX = sexcat - -## Post-processing - -# Necessary for tiles, to enable multi-exposure processing -MAKE_POST_PROCESS = True - -# World coordinate keywords, SExtractor output. Format: KEY_X,KEY_Y -WORLD_POSITION = XWIN_WORLD,YWIN_WORLD - -# Number of pixels in x,y of a CCD. Format: Nx,Ny -CCD_SIZE = 33,2080,1,4612 - - -[SEXTRACTOR_RUNNER_RUN_2] - -# Somehow this works but not -# - omitting -# - $SP_RUN/output -#INPUT_DIR = . - -# Input from two modules -INPUT_MODULE = split_exp_runner, mask_runner - -# Read pipeline flag files created by mask module -FILE_PATTERN = image, weight, pipeline_flag - -FILE_EXT = .fits - -NUMBERING_SCHEME = -0000000-0 - -# SExtractor executable path -EXEC_PATH = sex - -# SExtractor configuration files -DOT_SEX_FILE = $SP_CONFIG/default_exp.sex -DOT_PARAM_FILE = $SP_CONFIG//default.param -DOT_CONV_FILE = $SP_CONFIG/default.conv - -# Use input weight image if True -WEIGHT_IMAGE = True - -# Use input flag image if True -FLAG_IMAGE = True - -# Use input PSF file if True -PSF_FILE = False - -# Use distinct image for detection (SExtractor in -# dual-image mode) if True. -DETECTION_IMAGE = False - -# Distinct weight image for detection (SExtractor -# in dual-image mode) -DETECTION_WEIGHT = False - -# True if photometry zero-point is to be read from exposure image header -ZP_FROM_HEADER = True - -# If ZP_FROM_HEADER is True, zero-point key name -ZP_KEY = PHOTZP - -# Background information from image header. -# If BKG_FROM_HEADER is True, background value will be read from header. -# In that case, the value of BACK_TYPE will be set atomatically to MANUAL. -# This is used e.g. for the LSB images. -BKG_FROM_HEADER = False -# LSB images: -# BKG_FROM_HEADER = True - -# If BKG_FROM_HEADER is True, background value key name -# LSB images: -#BKG_KEY = IMMODE - -# Type of image check (optional), default not used, can be a list of -# BACKGROUND, BACKGROUND_RMS, INIBACKGROUND, MINIBACK_RMS, -BACKGROUND, -# FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, APERTURES -CHECKIMAGE = BACKGROUND, BACKGROUND_RMS - -# File name suffix for the output sextractor files (optional) SUFFIX = tile -SUFFIX = sexcat - -## Post-processing - -# Not required for single exposures -MAKE_POST_PROCESS = FALSE - - -[SETOOLS_RUNNER] - -INPUT_MODULE = sextractor_runner_run_2 - -# Note: Make sure this doe not match the SExtractor background images -# (sexcat_background*) -FILE_PATTERN = sexcat - -NUMBERING_SCHEME = -0000000-0 - -# SETools config file -SETOOLS_CONFIG_PATH = $SP_CONFIG/star_selection.setools - - -[PSFEX_RUNNER] - -# Use 80% sample for PSF model -FILE_PATTERN = star_split_ratio_80 - -NUMBERING_SCHEME = -0000000-0 - -# Path to executable for the PSF model (optional) -EXEC_PATH = psfex - -# Default psfex configuration file -DOT_PSFEX_FILE = $SP_CONFIG/default.psfex - - -[PSFEX_INTERP_RUNNER] - -# Use 20% sample for PSF validation -FILE_PATTERN = star_split_ratio_80, star_split_ratio_20, psfex_cat - -FILE_EXT = .psf, .fits, .cat - -NUMBERING_SCHEME = -0000000-0 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = VALIDATION - -# Column names of position parameters -POSITION_PARAMS = XWIN_IMAGE,YWIN_IMAGE - -# If True, measure and store ellipticity of the PSF (using moments) -GET_SHAPES = True - -# Minimum number of stars per CCD for PSF model to be computed -STAR_THRESH = 22 - -# Maximum chi^2 for PSF model to be computed on CCD -CHI2_THRESH = 2 diff --git a/example/cfis_simu/default.conv b/example/cfis_simu/default.conv deleted file mode 100644 index 2590b9cba..000000000 --- a/example/cfis_simu/default.conv +++ /dev/null @@ -1,5 +0,0 @@ -CONV NORM -# 3x3 ``all-ground'' convolution mask with FWHM = 2 pixels. -1 2 1 -2 4 2 -1 2 1 diff --git a/example/cfis_simu/default.param b/example/cfis_simu/default.param deleted file mode 100644 index 6521a94d5..000000000 --- a/example/cfis_simu/default.param +++ /dev/null @@ -1,69 +0,0 @@ -NUMBER #Running object number -EXT_NUMBER #FITS extension number - -FLUX_AUTO #Flux within a Kron-like elliptical aperture [count] -FLUXERR_AUTO #RMS error for AUTO flux [count] -MAG_AUTO #Kron-like elliptical aperture magnitude [mag] -MAGERR_AUTO #RMS error for AUTO magnitude [mag] -FLUX_WIN #Gaussian-weighted flux [count] -FLUXERR_WIN #RMS error for WIN flux [count] -MAG_WIN #Gaussian-weighted magnitude [mag] -MAGERR_WIN #RMS error for MAG_WIN [mag] -FLUX_APER(1) -FLUXERR_APER(1) - -FLUX_RADIUS #Fraction-of-light radii [pixel] - -SNR_WIN #Gaussian-weighted SNR - -BACKGROUND #Background at centroid position [count] -THRESHOLD #Detection threshold above background [count] - -X_IMAGE #Object position along x [pixel] -Y_IMAGE #Object position along y [pixel] - -X_WORLD #Barycenter position along world x axis [deg] -Y_WORLD #Barycenter position along world y axis [deg] - -X2_IMAGE #Variance along x [pixel**2] -Y2_IMAGE #Variance along y [pixel**2] -XY_IMAGE #Covariance between x and y [pixel**2] -ERRX2_IMAGE #Variance of position along x [pixel**2] -ERRY2_IMAGE #Variance of position along y [pixel**2] -ERRXY_IMAGE #Covariance of position between x and y [pixel**2] - -XWIN_IMAGE #Windowed position estimate along x [pixel] -YWIN_IMAGE #Windowed position estimate along y [pixel] - -XWIN_WORLD #Windowed position along world x axis [deg] -YWIN_WORLD #Windowed position along world y axis [deg] - -X2WIN_IMAGE #Windowed variance along x [pixel**2] -Y2WIN_IMAGE #Windowed variance along y [pixel**2] -XYWIN_IMAGE #Windowed covariance between x and y [pixel**2] -ERRX2WIN_IMAGE #Variance of windowed pos along x [pixel**2] -ERRY2WIN_IMAGE #Variance of windowed pos along y [pixel**2] -ERRXYWIN_IMAGE #Covariance of windowed pos between x and y [pixel**2] - -MU_THRESHOLD #Analysis threshold above background [mag * arcsec**(-2)] -MU_MAX #Peak surface brightness above background [mag * arcsec**(-2)] - -FLAGS #Extraction flags -FLAGS_WIN #Flags for WINdowed parameters - -#!!! REQUIRE FLAG_IMAGE !!! -IMAFLAGS_ISO #FLAG-image flags OR'ed over the iso. profile !!! REQUIRE FLAG_IMAGE !!! - -FWHM_IMAGE #FWHM assuming a gaussian core [pixel] -FWHM_WORLD #FWHM assuming a gaussian core [deg] -ELONGATION #A_IMAGE/B_IMAGE -ELLIPTICITY #1 - B_IMAGE/A_IMAGE - -VIGNET(51,51) #Pixel data around detection [count] - -#VECTOR_ASSOC #ASSOCiated parameter vector -#NUMBER_ASSOC #Number of ASSOCiated IDs - -#SPREAD_MODEL -#SPREADERR_MODEL -#FWHMPSF_IMAGE diff --git a/example/cfis_simu/default.psfex b/example/cfis_simu/default.psfex deleted file mode 100644 index 15af2dc29..000000000 --- a/example/cfis_simu/default.psfex +++ /dev/null @@ -1,85 +0,0 @@ -# Default configuration file for PSFEx 3.17.1 -# EB 2017-11-30 -# - -#-------------------------------- PSF model ---------------------------------- - -BASIS_TYPE PIXEL # NONE, PIXEL, GAUSS-LAGUERRE or FILE -BASIS_NUMBER 20 # Basis number or parameter -BASIS_NAME basis.fits # Basis filename (FITS data-cube) -BASIS_SCALE 1.0 # Gauss-Laguerre beta parameter -NEWBASIS_TYPE NONE # Create new basis: NONE, PCA_INDEPENDENT - # or PCA_COMMON -NEWBASIS_NUMBER 8 # Number of new basis vectors -PSF_SAMPLING 1. # Sampling step in pixel units (0.0 = auto) -PSF_PIXELSIZE 1.0 # Effective pixel size in pixel step units -PSF_ACCURACY 0.01 # Accuracy to expect from PSF "pixel" values -PSF_SIZE 51,51 # Image size of the PSF model -PSF_RECENTER N # Allow recentering of PSF-candidates Y/N ? -MEF_TYPE INDEPENDENT # INDEPENDENT or COMMON - -#------------------------- Point source measurements ------------------------- - -CENTER_KEYS XWIN_IMAGE,YWIN_IMAGE # Catalogue parameters for source pre-centering -PHOTFLUX_KEY FLUX_AUTO # Catalogue parameter for photometric norm. -PHOTFLUXERR_KEY FLUXERR_AUTO # Catalogue parameter for photometric error - -#----------------------------- PSF variability ------------------------------- - -PSFVAR_KEYS XWIN_IMAGE,YWIN_IMAGE # Catalogue or FITS (preceded by :) params -PSFVAR_GROUPS 1,1 # Group tag for each context key -PSFVAR_DEGREES 2 # Polynom degree for each group -PSFVAR_NSNAP 9 # Number of PSF snapshots per axis -HIDDENMEF_TYPE COMMON # INDEPENDENT or COMMON -STABILITY_TYPE EXPOSURE # EXPOSURE or SEQUENCE - -#----------------------------- Sample selection ------------------------------ - -SAMPLE_AUTOSELECT N # Automatically select the FWHM (Y/N) ? - -BADPIXEL_FILTER N # Filter bad-pixels in samples (Y/N) ? -BADPIXEL_NMAX 0 # Maximum number of bad pixels allowed - -#----------------------- PSF homogeneisation kernel -------------------------- - -HOMOBASIS_TYPE NONE # NONE or GAUSS-LAGUERRE -HOMOBASIS_NUMBER 10 # Kernel basis number or parameter -HOMOBASIS_SCALE 1.0 # GAUSS-LAGUERRE beta parameter -HOMOPSF_PARAMS 2.0, 3.0 # Moffat parameters of the idealised PSF -HOMOKERNEL_DIR # Where to write kernels (empty=same as input) -HOMOKERNEL_SUFFIX .homo.fits # Filename extension for homogenisation kernels - -#----------------------------- Output catalogs ------------------------------- - -OUTCAT_TYPE FITS_LDAC # NONE, ASCII_HEAD, ASCII, FITS_LDAC - -#------------------------------- Check-plots ---------------------------------- - -CHECKPLOT_DEV PNG # NULL, XWIN, TK, PS, PSC, XFIG, PNG, - # JPEG, AQT, PDF or SVG -CHECKPLOT_RES 0 # Check-plot resolution (0 = default) -CHECKPLOT_ANTIALIAS Y # Anti-aliasing using convert (Y/N) ? -CHECKPLOT_TYPE NONE # FWHM,ELLIPTICITY,COUNTS, COUNT_FRACTION, CHI2, RESIDUALS -CHECKPLOT_TYPE FWHM,ELLIPTICITY,COUNTS, COUNT_FRACTION, CHI2, RESIDUALS - # or NONE -CHECKPLOT_NAME fwhm, ellipticity, counts, countfrac, chi2, resi - -#------------------------------ Check-Images --------------------------------- - -# Note: Check-image types can be set the ShapePipe config file, psfex_runner section -####### -#CHECKIMAGE_TYPE NONE # CHI,PROTOTYPES,SAMPLES,RESIDUALS,SNAPSHOTS - # or MOFFAT,-MOFFAT,-SYMMETRICAL -#CHECKIMAGE_NAME chi.fits,proto.fits,samp.fits,resi.fits,snap.fits - # Check-image filenames -#CHECKIMAGE_CUBE N # Save check-images as datacubes (Y/N) ? - -#----------------------------- Miscellaneous --------------------------------- - -PSF_SUFFIX .psf # Filename extension for output PSF filename -VERBOSE_TYPE NORMAL # can be QUIET,NORMAL,LOG or FULL -WRITE_XML N # Write XML file (Y/N)? - -NTHREADS 1 # Number of simultaneous threads for - # the SMP version of PSFEx - # 0 = automatic diff --git a/example/cfis_simu/default_exp.sex b/example/cfis_simu/default_exp.sex deleted file mode 100644 index b87275ecb..000000000 --- a/example/cfis_simu/default_exp.sex +++ /dev/null @@ -1,133 +0,0 @@ -# Default configuration file for SExtractor 2.19.5 -# EB 2017-11-30 -# - -#-------------------------------- Catalog ------------------------------------ - -CATALOG_TYPE FITS_LDAC - -PARAMETERS_NAME default.param - -#------------------------------- Extraction ---------------------------------- - -DETECT_TYPE CCD # CCD (linear) or PHOTO (with gamma correction) -DETECT_MINAREA 5 # min. # of pixels above threshold -DETECT_MAXAREA 0 # max. # of pixels above threshold (0=unlimited) -THRESH_TYPE RELATIVE # threshold type: RELATIVE (in sigmas) - # or ABSOLUTE (in ADUs) -DETECT_THRESH 1.5 # or , in mag.arcsec-2 -ANALYSIS_THRESH 1.5 # or , in mag.arcsec-2 - -FILTER Y # apply filter for detection (Y or N)? -FILTER_NAME default.conv -FILTER_THRESH # Threshold[s] for retina filtering - -DEBLEND_NTHRESH 32 # Number of deblending sub-thresholds -DEBLEND_MINCONT 0.001 # Minimum contrast parameter for deblending - -CLEAN Y # Clean spurious detections? (Y or N)? -CLEAN_PARAM 1.0 # Cleaning efficiency - -MASK_TYPE CORRECT # type of detection MASKing: can be one of - # NONE, BLANK or CORRECT - -#-------------------------------- WEIGHTing ---------------------------------- - -WEIGHT_TYPE MAP_WEIGHT # type of WEIGHTing: NONE, BACKGROUND, - # MAP_RMS, MAP_VAR or MAP_WEIGHT -RESCALE_WEIGHTS Y # Rescale input weights/variances (Y/N)? -WEIGHT_IMAGE weight.fits # weight-map filename -WEIGHT_GAIN Y # modulate gain (E/ADU) with weights? (Y/N) -WEIGHT_THRESH # weight threshold[s] for bad pixels - -#-------------------------------- FLAGging ----------------------------------- - -FLAG_IMAGE flag.fits # filename for an input FLAG-image -FLAG_TYPE OR # flag pixel combination: OR, AND, MIN, MAX - # or MOST - -#------------------------------ Photometry ----------------------------------- - -PHOT_APERTURES 5 # MAG_APER aperture diameter(s) in pixels -PHOT_AUTOPARAMS 2.5, 3.5 # MAG_AUTO parameters: , -PHOT_PETROPARAMS 2.0, 3.5 # MAG_PETRO parameters: , - # -PHOT_AUTOAPERS 0.0,0.0 # , minimum apertures - # for MAG_AUTO and MAG_PETRO -PHOT_FLUXFRAC 0.5 # flux fraction[s] used for FLUX_RADIUS - -SATUR_KEY SATURATE # keyword for saturation level (in ADUs) - -MAG_ZEROPOINT 30.0 # magnitude zero-point -MAG_GAMMA 4.0 # gamma of emulsion (for photographic scans) - -GAIN_KEY GAIN # keyword for detector gain in e-/ADU -PIXEL_SCALE 0. # size of pixel in arcsec (0=use FITS WCS info) - -#------------------------- Star/Galaxy Separation ---------------------------- - -SEEING_FWHM 0.6 # stellar FWHM in arcsec -STARNNW_NAME default.nnw - -#------------------------------ Background ----------------------------------- - -BACK_TYPE AUTO # AUTO or MANUAL -BACK_VALUE 0.0 # Default background value in MANUAL mode -BACK_SIZE 64 # Background mesh: or , -BACK_FILTERSIZE 3 # Background filter: or , - -BACKPHOTO_TYPE GLOBAL # can be GLOBAL or LOCAL -BACKPHOTO_THICK 24 # thickness of the background LOCAL annulus -BACK_FILTTHRESH 0.0 # Threshold above which the background- - # map filter operates - -#------------------------------ Check Image ---------------------------------- - -####### -## AG : This parameter is set in pipeline config file. -####### -# CHECKIMAGE_TYPE NONE #BACKGROUND_RMS,BACKGROUND -# can be NONE, BACKGROUND, BACKGROUND_RMS, - # MINIBACKGROUND, MINIBACK_RMS, -BACKGROUND, - # FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, - # or APERTURES -# CHECKIMAGE_NAME check.fits,back.fits -# Filename for the check-image - -#--------------------- Memory (change with caution!) ------------------------- - -MEMORY_OBJSTACK 3000 # number of objects in stack -MEMORY_PIXSTACK 300000 # number of pixels in stack -MEMORY_BUFSIZE 1024 # number of lines in buffer - -#------------------------------- ASSOCiation --------------------------------- - -ASSOC_NAME sky.list # name of the ASCII file to ASSOCiate -ASSOC_DATA 2,3,4 # columns of the data to replicate (0=all) -ASSOC_PARAMS 2,3,4 # columns of xpos,ypos[,mag] -ASSOCCOORD_TYPE PIXEL # ASSOC coordinates: PIXEL or WORLD -ASSOC_RADIUS 2.0 # cross-matching radius (pixels) -ASSOC_TYPE NEAREST # ASSOCiation method: FIRST, NEAREST, MEAN, - # MAG_MEAN, SUM, MAG_SUM, MIN or MAX -ASSOCSELEC_TYPE MATCHED # ASSOC selection type: ALL, MATCHED or -MATCHED - -#----------------------------- Miscellaneous --------------------------------- - -VERBOSE_TYPE NORMAL # can be QUIET, NORMAL or FULL -HEADER_SUFFIX .head # Filename extension for additional headers -WRITE_XML N # Write XML file (Y/N)? - -NTHREADS 1 # 1 single thread - -FITS_UNSIGNED N # Treat FITS integer values as unsigned (Y/N)? -INTERP_MAXXLAG 16 # Max. lag along X for 0-weight interpolation -INTERP_MAXYLAG 16 # Max. lag along Y for 0-weight interpolation -INTERP_TYPE ALL # Interpolation type: NONE, VAR_ONLY or ALL - -#--------------------------- Experimental Stuff ----------------------------- - -#PSF_NAME default.psf # File containing the PSF model -#PSF_NMAX 1 # Max.number of PSFs fitted simultaneously -#PATTERN_TYPE RINGS-HARMONIC # can RINGS-QUADPOLE, RINGS-OCTOPOLE, - # RINGS-HARMONICS or GAUSS-LAGUERRE -#SOM_NAME default.som # File containing Self-Organizing Map weights diff --git a/example/cfis_simu/default_tile.sex b/example/cfis_simu/default_tile.sex deleted file mode 100644 index ff3b25213..000000000 --- a/example/cfis_simu/default_tile.sex +++ /dev/null @@ -1,133 +0,0 @@ -# Default configuration file for SExtractor 2.19.5 -# EB 2017-11-30 -# - -#-------------------------------- Catalog ------------------------------------ - -CATALOG_TYPE FITS_LDAC - -PARAMETERS_NAME default.param - -#------------------------------- Extraction ---------------------------------- - -DETECT_TYPE CCD # CCD (linear) or PHOTO (with gamma correction) -DETECT_MINAREA 5 # min. # of pixels above threshold -DETECT_MAXAREA 0 # max. # of pixels above threshold (0=unlimited) -THRESH_TYPE RELATIVE # threshold type: RELATIVE (in sigmas) - # or ABSOLUTE (in ADUs) -DETECT_THRESH 1.5 # or , in mag.arcsec-2 -ANALYSIS_THRESH 1.5 # or , in mag.arcsec-2 - -FILTER Y # apply filter for detection (Y or N)? -FILTER_NAME default.conv -FILTER_THRESH # Threshold[s] for retina filtering - -DEBLEND_NTHRESH 32 # Number of deblending sub-thresholds -DEBLEND_MINCONT 0.0005 # Minimum contrast parameter for deblending - -CLEAN Y # Clean spurious detections? (Y or N)? -CLEAN_PARAM 1.0 # Cleaning efficiency - -MASK_TYPE CORRECT # type of detection MASKing: can be one of - # NONE, BLANK or CORRECT - -#-------------------------------- WEIGHTing ---------------------------------- - -WEIGHT_TYPE MAP_WEIGHT # type of WEIGHTing: NONE, BACKGROUND, - # MAP_RMS, MAP_VAR or MAP_WEIGHT -RESCALE_WEIGHTS Y # Rescale input weights/variances (Y/N)? -WEIGHT_IMAGE weight.fits # weight-map filename -WEIGHT_GAIN Y # modulate gain (E/ADU) with weights? (Y/N) -WEIGHT_THRESH # weight threshold[s] for bad pixels - -#-------------------------------- FLAGging ----------------------------------- - -FLAG_IMAGE flag.fits # filename for an input FLAG-image -FLAG_TYPE OR # flag pixel combination: OR, AND, MIN, MAX - # or MOST - -#------------------------------ Photometry ----------------------------------- - -PHOT_APERTURES 5 # MAG_APER aperture diameter(s) in pixels -PHOT_AUTOPARAMS 2.5, 3.5 # MAG_AUTO parameters: , -PHOT_PETROPARAMS 2.0, 3.5 # MAG_PETRO parameters: , - # -PHOT_AUTOAPERS 0.0,0.0 # , minimum apertures - # for MAG_AUTO and MAG_PETRO -PHOT_FLUXFRAC 0.5 # flux fraction[s] used for FLUX_RADIUS - -SATUR_KEY SATURATE # keyword for saturation level (in ADUs) - -MAG_ZEROPOINT 30.0 # magnitude zero-point -MAG_GAMMA 4.0 # gamma of emulsion (for photographic scans) - -GAIN_KEY GAIN # keyword for detector gain in e-/ADU -PIXEL_SCALE 0. # size of pixel in arcsec (0=use FITS WCS info) - -#------------------------- Star/Galaxy Separation ---------------------------- - -SEEING_FWHM 0.6 # stellar FWHM in arcsec -STARNNW_NAME default.nnw - -#------------------------------ Background ----------------------------------- - -BACK_TYPE MANUAL # AUTO or MANUAL -BACK_VALUE 0.0 # Default background value in MANUAL mode -BACK_SIZE 64 # Background mesh: or , -BACK_FILTERSIZE 3 # Background filter: or , - -BACKPHOTO_TYPE GLOBAL # can be GLOBAL or LOCAL -BACKPHOTO_THICK 24 # thickness of the background LOCAL annulus -BACK_FILTTHRESH 0.0 # Threshold above which the background- - # map filter operates - -#------------------------------ Check Image ---------------------------------- - -####### -## AG : This parameter is set in pipeline config file. -####### -# CHECKIMAGE_TYPE NONE #BACKGROUND_RMS,BACKGROUND -# can be NONE, BACKGROUND, BACKGROUND_RMS, - # MINIBACKGROUND, MINIBACK_RMS, -BACKGROUND, - # FILTERED, OBJECTS, -OBJECTS, SEGMENTATION, - # or APERTURES -# CHECKIMAGE_NAME check.fits,back.fits -# Filename for the check-image - -#--------------------- Memory (change with caution!) ------------------------- - -MEMORY_OBJSTACK 3000 # number of objects in stack -MEMORY_PIXSTACK 300000 # number of pixels in stack -MEMORY_BUFSIZE 1024 # number of lines in buffer - -#------------------------------- ASSOCiation --------------------------------- - -ASSOC_NAME sky.list # name of the ASCII file to ASSOCiate -ASSOC_DATA 2,3,4 # columns of the data to replicate (0=all) -ASSOC_PARAMS 2,3,4 # columns of xpos,ypos[,mag] -ASSOCCOORD_TYPE PIXEL # ASSOC coordinates: PIXEL or WORLD -ASSOC_RADIUS 2.0 # cross-matching radius (pixels) -ASSOC_TYPE NEAREST # ASSOCiation method: FIRST, NEAREST, MEAN, - # MAG_MEAN, SUM, MAG_SUM, MIN or MAX -ASSOCSELEC_TYPE MATCHED # ASSOC selection type: ALL, MATCHED or -MATCHED - -#----------------------------- Miscellaneous --------------------------------- - -VERBOSE_TYPE NORMAL # can be QUIET, NORMAL or FULL -HEADER_SUFFIX .head # Filename extension for additional headers -WRITE_XML N # Write XML file (Y/N)? - -NTHREADS 1 # 1 single thread - -FITS_UNSIGNED N # Treat FITS integer values as unsigned (Y/N)? -INTERP_MAXXLAG 16 # Max. lag along X for 0-weight interpolation -INTERP_MAXYLAG 16 # Max. lag along Y for 0-weight interpolation -INTERP_TYPE ALL # Interpolation type: NONE, VAR_ONLY or ALL - -#--------------------------- Experimental Stuff ----------------------------- - -#PSF_NAME default.psf # File containing the PSF model -#PSF_NMAX 1 # Max.number of PSFs fitted simultaneously -#PATTERN_TYPE RINGS-HARMONIC # can RINGS-QUADPOLE, RINGS-OCTOPOLE, - # RINGS-HARMONICS or GAUSS-LAGUERRE -#SOM_NAME default.som # File containing Self-Organizing Map weights diff --git a/example/cfis_simu/job_sp_simu.bash b/example/cfis_simu/job_sp_simu.bash deleted file mode 100755 index 09915cae3..000000000 --- a/example/cfis_simu/job_sp_simu.bash +++ /dev/null @@ -1,462 +0,0 @@ -#!/usr/bin/env bash - -# Name: job_sp_simu.bash -# Description: General script to process one or more tiles -# with all contributing exposures. -# This works as job submission script. -# called in interactive mode on a virtual -# machine. -# Author: Martin Kilbinger -# Date: v1.0 11/2020 -# v1.1 01/2021 - - -# VM home, required for canfar run. -## On other machines set to $HOME -export VM_HOME=/home/ubuntu -if [ ! -d "$VM_HOME" ]; then - export VM_HOME=$HOME -fi - -# Command line arguments -## Default values -job=255 -#config_dir='vos:cfis/cosmostat/kilbinger/cfis' -config_dir="$VM_HOME/SP_simu" -psf='mccd' -#retrieve='vos' -retrieve=symlink -results="$VM_HOME/SP_simu" -nsh_step=3200 -nsh_max=-1 -nsh_jobs=8 - -## Help string -usage="Usage: $(basename "$0") [OPTIONS] TILE_ID_1 [TILE_ID_2 [...]] -\n\nOptions:\n - -h\tthis message\n - -j, --job JOB\tRunning JOB, bit-coded\n - \t 1: retrieve images (online if method=vos)\n - \t 2: prepare images (offline)\n - \t 4: mask (online)\n - \t 8: detection of galaxies on tiles; processing of stars on exposures (offline)\n - \t 16: galaxy selection on tiles (offline)\n - \t 32: shapes and morphology (offline)\n - \t 64: paste catalogues (offline)\n - \t 128: upload results (online)\n - -c, --config_dir DIR\n - \t config file directory, default='$config_dir'\n - -p, --psf MODEL\n - \tPSF model, one in ['psfex'|'mccd'], default='$psf'\n - -r, --retrieve METHOD\n - \tmethod to retrieve images, one in ['vos'|'symlink]', default='$retrieve'\n - -o, --output_dir\n - \toutput (upload) directory on vos:cfis, default='$results'\n - --nsh_jobs NJOB\n - \tnumber of shape measurement parallel jobs, default=$nsh_jobs\n - --nsh_step NSTEP\n - \tnumber of objects per parallel shape module call, \n - \t default: $nsh_step\n - --nsh_max NMAX\n - \tmax number of objects per parallel shape module call, \n - \t default: unlimited; has precedent over --nsh_step\n - TILE_ID_i\n - \ttile ID(s), e.g. 283.247 214.242\n -" - -## Help if no arguments -if [ -z $1 ]; then - echo -ne $usage - exit 1 -fi - -## Parse command line -TILE_ARR=() -while [ $# -gt 0 ]; do - case "$1" in - -h) - echo -ne $usage - exit 0 - ;; - -j|--job) - job="$2" - shift - ;; - -c|--config_dir) - config_dir="$2" - shift - ;; - -p|--psf) - psf="$2" - shift - ;; - -r|--retrieve) - retrieve="$2" - shift - ;; - -o|--output_dir) - results="$2" - shift - ;; - --nsh_max) - nsh_max="$2" - shift - ;; - --nsh_step) - nsh_step="$2" - shift - ;; - --nsh_jobs) - nsh_jobs="$2" - shift - ;; - *) - TILE_ARR+=("$1") - ;; - esac - shift -done - -## Check options -if [ "$psf" != "psfex" ] && [ "$psf" != "mccd" ]; then - echo "PSF (option -p) needs to be 'psfex' or 'mccd'" - exit 2 -fi -n_tile=${#TILE_ARR[@]} -if [ "$n_tile" == "0" ]; then - echo "No tile ID given" - exit 3 -fi -if [ $nsh_max != -1 ]; then - nsh_step=$nsh_max -fi - -# For tar archives. Should be unique to each job -export ID=`echo ${TILE_ARR[@]} | tr ' ' '_'` - -## Paths - -# SExtractor library bug work-around -export PATH="$PATH:$VM_HOME/bin" - -## Path variables used in shapepipe config files - -# Run path and location of input image directories -export SP_RUN=`pwd` - -# Config file path -export SP_CONFIG=$SP_RUN -export SP_CONFIG_MOD=$SP_RUN - - -## Other variables - -# Input tile numbers ASCII file -export TILE_NUMBERS_PATH=tile_numbers.txt - -# Output -OUTPUT=$SP_RUN/output - -# For tar archives -output_rel=`realpath --relative-to=. $OUTPUT` - -# Stop on error, default=1 -STOP=1 - -# Verbose mode (1: verbose, 0: quiet) -VERBOSE=1 - -# VCP options -export CERTFILE=$VM_HOME/.ssl/cadcproxy.pem -export VCP="vcp --certfile=$CERTFILE" - - -## Functions - -# Print string, executes command, and prints return value. -function command () { - cmd=$1 - str=$2 - - #RED='\033[0;31m' - #GREEN='\033[0;32m' - #NC='\033[0m' # No Color - # Color escape characters show up in log files - RED='' - GREEN='' - NC='' - - - if [ $# == 2 ]; then - if [ $VERBOSE == 1 ]; then - echo "$str: running '$cmd'" - fi - $cmd - else - if [ $VERBOSE == 1 ]; then - echo "$str: running '$cmd $4 \"$5 $6\"'" - fi - $cmd $4 "$5 $6" - fi - res=$? - - if [ $VERBOSE == 1 ]; then - if [ $res == 0 ]; then - echo -e "${GREEN}success, return value = $res${NC}" - else - echo -e "${RED}error, return value = $res${NC}" - if [ $STOP == 1 ]; then - echo "${RED}exiting 'canfar_sp.bash', error in command '$cmd'${NC}" - exit $res - else - echo "${RED}continuing 'canfar_sp.bash', error in command '$cmd'${NC}" - fi - fi - fi - - #return $res -} - -# Run shapepipe command. If error occurs, upload sp log files before stopping script. -command_sp() { - cmd=$1 - str=$2 - - command "$1" "$2" -} - -# Tar and upload files to vos -function upload() { - base=$1 - shift - ID=$1 - shift - verbose=$1 - shift - upl=("$@") - - echo "Counting upload files" - n_upl=(`ls -l ${upl[@]} | wc`) - if [ $n_upl == 0 ]; then - if [ $STOP == 1 ]; then - echo "Exiting script, no file found for '$base' tar ball" - exit 3 - fi - fi - tar czf ${base}_${ID}.tgz ${upl[@]} - command "$VCP ${base}_${ID}.tgz vos:cfis/$results" "Upload tar ball" -} - -# Upload log files -function upload_logs() { - id=$1 - verbose=$2 - - upl="$output_rel/*/*/logs $output_rel/*/logs" - upload "logs" "$id" "$verbose" "${upl[@]}" -} - -# Print script variables -function print_env() { - echo "*** Environment ***" - echo "Data:" - echo " TILE_ARR=${TILE_ARR[@]}" - echo "Paths:" - echo " VM_HOME=$VM_HOME" - echo " SP_RUN=$SP_RUN" - echo " TILE_NUMBERS_PATH=$TILE_NUMBERS_PATH" - echo " OUTPUT=$OUTPUT" - echo " SP_CONFIG=$SP_CONFIG" - echo "Other variables:" - echo " VCP=$VCP" - echo " CERTFILE=$CERTFILE" - echo "***" -} - - -### Start ### - -echo "Start" - -echo "Processing $n_tile tile(s)" - -# Create input and output directories -mkdir -p $SP_RUN -cd $SP_RUN -mkdir -p $OUTPUT - -# Processing - -## Retrieve config files and images (online if retrieve=vos) -(( do_job= $job & 1 )) -if [[ $do_job != 0 ]]; then - - # Write tile numbers to ASCII input file - rm -rf $TILE_NUMBERS_PATH - for TILE in ${TILE_ARR[@]}; do - echo $TILE >> $TILE_NUMBERS_PATH - done - - ### Retrieve config files - if [[ $config_dir == *"vos:"* ]]; then - command_sp "$VCP $config_dir ." "Retrieve shapepipe config files" - else - if [[ ! -L cfis ]]; then - command_sp "ln -s $config_dir cfis" "Retrieve shapepipe config files" - fi - fi - - ### Retrieve files - command_sp "shapepipe_run -c $SP_CONFIG/config_GitFeGie_$retrieve.ini" "Retrieve images" - -fi - -## Prepare images (offline) -(( do_job= $job & 2 )) -if [[ $do_job != 0 ]]; then - - ### Uncompress tile weights - #command_sp "shapepipe_run -c $SP_CONFIG/config_tile_Uz.ini" "Run shapepipe (uncompress tile weights)" - - ### Split images into single-HDU files, merge headers for WCS info - command_sp "shapepipe_run -c $SP_CONFIG/config_exp_SpMh.ini" "Run shapepipe (split images, merge headers)" - -fi - -## Mask tiles and exposures: add star, halo, and Messier object masks (online) -(( do_job= $job & 4 )) -if [[ $do_job != 0 ]]; then - - ### Mask tiles and exposures - command_sp "shapepipe_run -c $SP_CONFIG/config_MaMa.ini" "Run shapepipe (mask)" - -fi - - -## Remaining exposure processing (offline) -(( do_job= $job & 8 )) -if [[ $do_job != 0 ]]; then - - ### Star detection, selection, PSF model. setools can exit with an error for CCD with insufficient stars, - ### the script should continue - STOP=0 - command_sp "shapepipe_run -c $SP_CONFIG/config_tile_Sx_exp_${psf}.ini" "Run shapepipe (tile detection, exp $psf)" - STOP=1 - -fi - -## Process tiles up to shape measurement -(( do_job= $job & 16 )) -if [[ $do_job != 0 ]]; then - - ### PSF model letter: 'P' (psfex) or 'M' (mccd) - letter=${psf:0:1} - Letter=${letter^} - command_sp "shapepipe_run -c $SP_CONFIG/config_tile_${Letter}iViSmVi.ini" "Run shapepipe (tile PsfInterp=$Letter}: up to ngmix+galsim)" - -fi - -## Shape measurement (offline) -(( do_job= $job & 32 )) -if [[ $do_job != 0 ]]; then - - ### Prepare config files - mkdir -p $SP_CONFIG_MOD - n_min=0 - n_max=$((nsh_step - 1)) - for k in $(seq 1 $nsh_jobs); do - cat $SP_CONFIG/config_tile_Ng_template.ini | \ - perl -ane \ - 's/(ID_OBJ_MIN =) X/$1 '$n_min'/; s/(ID_OBJ_MAX =) X/$1 '$n_max'/; s/NgXu/Ng'$k'u/; s/X_interp/'$psf'_interp/g; print' \ - > $SP_CONFIG_MOD/config_tile_Ng${k}u.ini - n_min=$((n_min + nsh_step)) - if [ "$k" == $((nsh_jobs - 1)) ] && [ $nsh_max == -1 ]; then - n_max=-1 - else - n_max=$((n_min + nsh_step - 1)) - fi - done - - ### Shapes, run $nsh_jobs parallel processes - VERBOSE=0 - for k in $(seq 1 $nsh_jobs); do - command_sp "shapepipe_run -c $SP_CONFIG_MOD/config_tile_Ng${k}u.ini" "Run shapepipe (tile: ngmix+galsim $k)" & - done - wait - VERBOSE=1 - -fi - -## Create final catalogues (offline) -(( do_job= $job & 64 )) -if [[ $do_job != 0 ]]; then - - cat $SP_CONFIG/config_merge_sep_cats_template.ini | \ - perl -ane \ - 's/(N_SPLIT_MAX =) X/$1 '$nsh_jobs'/; print' \ - > $SP_CONFIG_MOD/config_merge_sep_cats.ini - - ### Merge separated shapes catalogues - command_sp "shapepipe_run -c $SP_CONFIG_MOD/config_merge_sep_cats.ini" "Run shapepipe (tile: merge sep cats)" "$VERBOSE" "$ID" - - ### Merge all relevant information into final catalogue - command_sp "shapepipe_run -c $SP_CONFIG/config_make_cat_$psf.ini" "Run shapepipe (tile: create final cat $psf)" "$VERBOSE" "$ID" - -fi - -## Upload results (online) -(( do_job= $job & 128 )) -if [[ $do_job != 0 ]]; then - - ### module and pipeline log files - upload_logs "$ID" "$VERBOSE" - - ### Final shape catalog - ### pipeline_flags are the tile masks, for random cats - ### SETools masks (selection), stats and plots - ### ${psf}_interp_exp for diagnostics, validation with leakage, - ### validation with residuals, rho stats - - NAMES=( - "final_cat" - "pipeline_flag" - "setools_mask" - "setools_stat" - "setools_plot" - ) - DIRS=( - "*/make_cat_runner/output" - "*/mask_runner_run_1/output" - "*/setools_runner/output/mask" - "*/setools_runner/output/stat" - "*/setools_runner/output/plot" - ) - PATTERNS=( - "final_cat-*" - "pipeline_flag-???-???*" - "*" - "*" - "*" - ) - - # PSF validation - pattern="validation_psf-*" - if [ "$psf" == "psfex" ]; then - name="psfex_interp_exp" - dir="*/psfex_interp_runner/output" - else - name="mccd_fit_val_runner" - dir="*/mccd_fit_val_runner/output" - fi - upl=$output_rel/$dir/$pattern - upload "$name" "$ID" "$VERBOSE" "${upl[@]}" - - for n in "${!NAMES[@]}"; do - name=${NAMES[$n]} - dir=${DIRS[$n]} - pattern=${PATTERNS[$n]} - upl=$output_rel/$dir/$pattern - upload "$name" "$ID" "$VERBOSE" "${upl[@]}" - done - -fi diff --git a/example/cfis_simu/mask_default/MEGAPRIME_star_i_13.8.reg b/example/cfis_simu/mask_default/MEGAPRIME_star_i_13.8.reg deleted file mode 100644 index 4e4164aaf..000000000 --- a/example/cfis_simu/mask_default/MEGAPRIME_star_i_13.8.reg +++ /dev/null @@ -1,24 +0,0 @@ --11.5 68 --6 186.5 -7 188 -10 64.5 -31 55 -50 38.5 -56.5 11.5 -188 8 -192 -4 -59.5 -11.5 -45 -33 -13.5 -64 -5 -154 --6 -155 --11 -64.5 --40 -44.5 --51.5 -30.5 --62.5 -22.5 --68 -9.5 --177 -2 --176 3 --78 12.5 --67.5 14.5 --38.5 50 diff --git a/example/cfis_simu/mask_default/Messier_catalog.npy b/example/cfis_simu/mask_default/Messier_catalog.npy deleted file mode 100644 index ef07eb032b08de4fcf508418524692e27f92ad75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4209 zcmb7{dvKIj6~H$S0z_V+Av_zF=Wf!F0D%A@VJ{fCEf+Q}Ge}K(fi%99nrA zDunWq77DdgDz+6YRTR<6s3U8&j-3Igj&%@bimd}oA2U?zlbKdM_uP9w(m(ve`$y*c zzCFM5y62pGb0i#Dx@=XB?^WN{Ks?dak_puMR|f+dThsACg+H)u`P4vJg+CbRYTc6H z4~^zMo@mjpb;;T0Jrn)RYq*17Tj|%eB$n*RU?!Sg*O7@Qw1q{{wyp$y?@C2G6Z9UO zJ*|SwZ1?}atFSw_CB3P=b8}@&M!PQkRQogQ9WJxR1Qh>397tE&bDwclCSdu1(AH6 z4(J=nqclLjNNy$y{Uf>I4k$$U+;$icOckc`Be|`!t%iZDp)MQ_Fa8fL{-b;tQzLp( z1zK|XR4X~i+6ssD2bS^mr21)@q|-L|e`)L2lj@%utcLWY`f8xj0Yf~cz08Nmysu}I z@{^g-7~8uRih{w2Z!HWBN-@I6ONt@pAt6OgeuG|8G~c*H>$1v0xQBsaDf~%ZQ5pK+ zj9#xu(RJZAt&dbL!Y~#qk)qmYi%L-!8?ToXsYiGI;Jwj}Di2{ei;a+?!V|0AWsRl> zym+Os9#lTUNERCeCHYt zJUw6aL%5fLQZM+Z%ud_-6Iwr&qT_?AH)5Y0Q~eRfvY5YD%-O+3yrf8%k}`2kQ5lS5 zF$!?}vw9tm%8M`a(@7E=S+VZ$>vz-ugb6G*QHqIP%nRl)Kw>5JofNeih%kx8CQH%J ziDe7S6Z!TxZ4=cXgaC_C$oH}-GAe=*UV1+mP=gW5Sgf4nY({85j5bP5FwOf$skxcg z`ew}uH3Z>41}X@U&gz10ej=di1-q4`b^hq(-Jdoa+e?F#{-Xl{Y#R$_Fm@dV%C0vB0w`Q0Pu=9N?r10aa z1fiP6W{_UL)7z_%e`NK-A8(x7p+->k!%PNhq!_BzUb;+rXK_@tft|MwtqK197itW` zEEbzBMTsX?^IN<3T|d4!Y5&KoR4Kw77ON%ms|DMaqS5$4ebivn6}t8M9V&xgsj&!i z8K@&*oT+IkM%@MMhIoJxJ1e4yYTsCB3-YHfhMU}t3w%~Us)i{I@1A>5M z-PR@B7L@6soDj-%^Uc>N)_z{GGL!si)iAfUj; z5chgC? z3g;oLX0bJ-x6I~7j4@k7dKc!~37<-tWQGvdGVllib}C9S%nNnqSzCL_=S?yN!a4@l z6EN0g|6g7*%bn5qnc^)}C2BrGlz|ul+q+-jSP-A>&68b=j-t$X$xCiGfE6@H@}#1%&`}s!}C4|3xRHS%ncEW8jNY^fzTg z8JXpd^5&~XtPx=|i*1pj&>|L+d5Nq!9g?sS0L0-4w@4|Wsp3qZx=0U6~aLVzCyqRm{F~3&hHc>Jfya3>+iC-sHUVhk$^KK6XW4(DKwegl8D|DgmX=&0@1}8gR!+><@H9 z+WAAZ9^qLAjuT*e#Csq>?XndVKW$FmD8h3Le2oCVEeJOk2$!g6{6`KDwc_*QyqTuQPCx0DJ5Z>D8!r%S;}ikXzqWafEL$@B#sK&c5wX z9YclwMt!znzPT4B5ME^9B?1bpOTA8Mbw^hpRnUe7=8}*^_$C9V2=Fdu-YK1(=oR%* zt|gU1IL*MfJ`;oX=kB5NT zfba?f-uzXxoJl@$H4aq@J{Kw+tpw#gl=-AJ5(Csd;(*Y{{>v*ks<&9 diff --git a/example/cfis_simu/mask_default/Messier_catalog_updated.fits b/example/cfis_simu/mask_default/Messier_catalog_updated.fits deleted file mode 100644 index 6a9f00096170565aeb7fab3429c2256ce56f3533..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8640 zcmeHL4NO&K82bxk?Xcg61^5lKgn(d%puWZoviN(qOUV;yoAdgbQ9S23R5C zGJc%mUqCidTB!L;nwUE8!IV-Emzk_l)0xbfKNvc7R`!19<}PlRM&quncFy*U!+YNM z`JV52-?M$^Ch6l6Vxt8?D|on!!gOJNj?I!|&(0GZIf71aOcL@OCY#x0H+#s(5YrnI z^eKJtg(9EHZZ{PQW|PAtI0|zm&n)h!0aEly_^(HOFCm{TKRZLR3ptjD%+_qlmS@ee z<+(|E-9@JuqYY7g@ELn0_VZRD>u0W-raGWHpgN#BpgN#BpgN#B@ZWcUA19Oa@dm9B z$n*vyu-NFpUb~tuWLRw`d!gVkWn@W$RN!zOPJ5+1di$@QpJ)8@^Rsdu5B7Y14xWX4 zS&}W&v4Ee)%@0rF0_OL1DSR_L^MwZY!H4;MT?t}*vH{QW{rFzUwCCjK3b`h`DO++# zcF*4nulyY!*XREJQ20EvxYZhU`smoGB+vRn9^rG1jKwNtnfqonipd7O(vLv1l&P%K zd;Z)D_w!ESQ~KdWJ`L|jpvgWz+iDYpf$N*;MLuI{LbS_2yUFFz!1U?j6XRTb_*Jc3 z1LNcR9`<9t7x=h77oYd^x%!)DEt2Npr~knA%k{bVyr<7KFN(fYZ|HOJc~4)6GS3UG zHswc^f&1s;18>Zq`uoOTE~2Q~q|Mla|%g za-h=JE%N?j_uw*?bnuLd%4718+1H`mV&Nd-x*iGk9UB7p&}C4$PlbH^_CmVeV&Nd- z=89JNK<*p#3k)C#Ou1FXUhvhhA8^?Mtb-}voMsd9Z@}Xk4L!{R2OH5KblZ)dP$=zM!+^*|xtz<#td_j+GQ1-&6ZGat%$AEx3wsX?S2kT*O z(P(-PbvTh(obuFT5Z{>yK3MWT-;4Ig4*6`)85)KG1QE-xCN>W;=W>HMh*)Vk)ja6M z@syJyu$7@FVSU62dK5DdMBK3RxExb`isC~K0*QVOCn9^$ zbp(OdhJ(xyr{#o-Fcz?5&!N=z7J3m$omj$lnD%Km2LvbsqXagOsg}PA-bQ25LIe>V zY_+_lgL<|@I z07LL-iY?$qV<2@4R4q?dbnI3#Dr+G=U=KWr-9NxdBlb3dj*Wz&SaOD!L_8P^mIHNs zdT16GHv7tAi0Nvf^(agkZEeVSNc<=n#-hoUN=9c0WOjG+Cb?dqlKPu$YDEYqwBwk(z^jfXBY2yG-{#>NlWyeBp( z@5@r2p?zSM!|o1OUcz)9@V|8#^oBHrL)mTU;l)flYcOm;Vdr^9f&XuELZ^<7!rdZ> z7d$Tc4;qc|FXe?ZLlMI-okBI`F@A;7=F| BE!zM9 diff --git a/example/cfis_simu/mask_default/default.ww b/example/cfis_simu/mask_default/default.ww deleted file mode 100644 index c2797f904..000000000 --- a/example/cfis_simu/mask_default/default.ww +++ /dev/null @@ -1,40 +0,0 @@ -#--------------------------------- Weights ------------------------------------ - -WEIGHT_NAMES weightin.fits # Filename(s) of the input WEIGHT map(s) - -WEIGHT_MIN 0. # Pixel below those thresholds will be flagged -WEIGHT_MAX 1000. # Pixels above those thresholds will be flagged -WEIGHT_OUTFLAGS 1 # FLAG values for thresholded pixels - -#---------------------------------- Flags ------------------------------------- - -FLAG_NAMES flagin.fits # Filename(s) of the input FLAG map(s) - -FLAG_WMASKS 0xff # Bits which will nullify the WEIGHT-map pixels -FLAG_MASKS 0x01 # Bits which will be converted as output FLAGs -FLAG_OUTFLAGS 2 # Translation of the FLAG_MASKS bits - -#---------------------------------- Polygons ---------------------------------- - -POLY_NAMES "" # Filename(s) of input DS9 regions -POLY_OUTFLAGS # FLAG values for polygon masks -POLY_OUTWEIGHTS 0.0 # Weight values for polygon masks -POLY_INTERSECT Y # Use inclusive OR for polygon intersects (Y/N)? - -#---------------------------------- Output ------------------------------------ - -OUTWEIGHT_NAME "w.fits" # Output WEIGHT-map filename -OUTFLAG_NAME flag.fits # Output FLAG-map filename - -#----------------------------- Miscellaneous --------------------------------- - -GETAREA N # Compute area for flags and weights (Y/N)? -GETAREA_WEIGHT 0.0 # Weight threshold for area computation -GETAREA_FLAGS 1 # Bit mask for flag pixels not counted in area -MEMORY_BUFSIZE 256 # Buffer size in lines -VERBOSE_TYPE NORMAL # can be QUIET, NORMAL or FULL -WRITE_XML N # Write XML file (Y/N)? -XML_NAME ww.xml # Filename for XML output -XSL_URL file:///usr/local/share/weightwatcher/ww.xsl - # Filename for XSL style-sheet -NTHREADS 1 # 1 single thread \ No newline at end of file diff --git a/example/cfis_simu/mask_default/halo_mask.reg b/example/cfis_simu/mask_default/halo_mask.reg deleted file mode 100644 index c44f25167..000000000 --- a/example/cfis_simu/mask_default/halo_mask.reg +++ /dev/null @@ -1,50 +0,0 @@ - 274.66813 -1.25966 - 272.54579 32.47406 - 266.21222 65.67579 - 255.76731 97.82190 - 241.37579 128.40544 - 223.26462 156.94408 - 201.71942 182.98775 - 177.07997 206.12573 - 149.73486 225.99312 - 120.11532 242.27660 - 88.68848 254.71937 - 55.94996 263.12519 - 22.41606 267.36151 - -11.38436 267.36151 - -44.91826 263.12519 - -77.65678 254.71937 --109.08362 242.27660 --138.70315 225.99312 --166.04827 206.12573 --190.68772 182.98775 --212.23292 156.94408 --230.34409 128.40544 --244.73561 97.82190 --255.18052 65.67579 --261.51409 32.47406 --263.63643 -1.25966 --261.51409 -34.99339 --255.18052 -68.19511 --244.73561 -100.34123 --230.34409 -130.92476 --212.23292 -159.46341 --190.68772 -185.50708 --166.04827 -208.64506 --138.70315 -228.51245 --109.08362 -244.79593 - -77.65678 -257.23870 - -44.91826 -265.64452 - -11.38436 -269.88084 - 22.41606 -269.88084 - 55.94996 -265.64452 - 88.68848 -257.23870 - 120.11532 -244.79593 - 149.73486 -228.51245 - 177.07997 -208.64506 - 201.71942 -185.50708 - 223.26462 -159.46341 - 241.37579 -130.92476 - 255.76731 -100.34123 - 266.21222 -68.19511 - 272.54579 -34.99339 diff --git a/example/cfis_simu/mask_default/ngc_cat.fits b/example/cfis_simu/mask_default/ngc_cat.fits deleted file mode 100644 index f51546da7fc88ae1ca919a3c13882fe1a7df6768..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201600 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

4h9YH;BjCK1aktyi9r? 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 0 -MAG_AUTO < 21 -FWHM_IMAGE > 0.3 / 0.187 -FWHM_IMAGE < 1.5 / 0.187 -FLAGS == 0 -IMAFLAGS_ISO == 0 -NO_SAVE - -[MASK:flag] -FLAGS == 0 -IMAFLAGS_ISO == 0 -NO_SAVE - - -[MASK:star_selection] -# Star selection using the FWHM mode -MAG_AUTO > 18. -MAG_AUTO < 22. -FWHM_IMAGE <= mode(FWHM_IMAGE{preselect}) + 0.2 -FWHM_IMAGE >= mode(FWHM_IMAGE{preselect}) - 0.2 -FLAGS == 0 -IMAFLAGS_ISO == 0 -#CLASS_STAR != 0 - -[MASK:fwhm_mag_cut] -FWHM_IMAGE > 0 -FWHM_IMAGE < 40 -MAG_AUTO < 35 -FLAGS == 0 -IMAFLAGS_ISO == 0 -NO_SAVE - -# Split the 'star_selection' sample into -# two random sub-samples with ratio 80/20 -[RAND_SPLIT:star_split] -RATIO = 20 -MASK = star_selection - -# The following selection is only used for plotting - -[PLOT:size_mag] -TYPE = plot -FORMAT = png -X_1 = FWHM_IMAGE{fwhm_mag_cut} -Y_1 = MAG_AUTO{fwhm_mag_cut} -X_2 = FWHM_IMAGE{star_selection} -Y_2 = MAG_AUTO{star_selection} -MARKER_1 = + -MARKER_2 = . -MARKERSIZE_1 = 3 -MARKERSIZE_2 = 3 -LABEL_1 = All -LABEL_2 = "Stars, mean FWHM: @mean(FWHM_IMAGE{star_selection})*0.187@ arcsec" -TITLE = "Stellar locus" -XLABEL = "FWHM (pix)" -YLABEL = Mag - -[PLOT:hist_mag_stars] -TYPE = hist -FORMAT = png -Y = MAG_AUTO{star_selection} -BIN = 20 -LABEL = "stars" -XLABEL = "Magnitude" -YLABEL = "Number" -TITLE = "Magnitude of stars" - -[PLOT:fwhm_field] -TYPE = scatter -FORMAT = png -X = X_IMAGE{star_selection} -Y = Y_IMAGE{star_selection} -SCATTER = FWHM_IMAGE{star_selection}*0.186 -MARKER = . -LABEL = "FWHM (arcsec)" -TITLE = "FWHM of stars" -XLABEL = "X (pix)" -YLABEL = "Y (pix)" - -[PLOT:mag_star_field] -TYPE = scatter -FORMAT = png -X = X_IMAGE{star_selection} -Y = Y_IMAGE{star_selection} -SCATTER = MAG_AUTO{star_selection} -MARKER = . -LABEL = "Magnitude" -TITLE = "Magnitude of stars" -XLABEL = "X (pix)" -YLABEL = "Y (pix)" - -[STAT:star_stat] -"Nb objects full cat" = len(FWHM_IMAGE) -"Nb objects not masked" = len(FWHM_IMAGE{flag}) -"Nb stars" = len(FWHM_IMAGE{star_selection}) -"stars/deg^2" = len(FWHM_IMAGE{star_selection})/4612./0.187*3600.*1./2048./0.187*3600. -"Mean star fwhm selected (arcsec)" = mean(FWHM_IMAGE{star_selection})*0.187 -"Standard deviation fwhm star selected (arcsec)" = std(FWHM_IMAGE{star_selection})*0.187 -"Mode fwhm used (arcsec)" = mode(FWHM_IMAGE{preselect})*0.187 -"Min fwhm cut (arcesec)" = mode(FWHM_IMAGE{preselect})*0.187-0.1*0.187 -"Max fwhm cut (arcsec)" = mode(FWHM_IMAGE{preselect})*0.187+0.1*0.187 diff --git a/example/cfis_simu/tile_numbers.txt b/example/cfis_simu/tile_numbers.txt deleted file mode 100644 index 3c0ce178a..000000000 --- a/example/cfis_simu/tile_numbers.txt +++ /dev/null @@ -1 +0,0 @@ -224-295 From ff612b7eff1ec50721f83e405af418d514be49ad Mon Sep 17 00:00:00 2001 From: martin kilbinger Date: Thu, 11 Jun 2026 14:21:19 +0200 Subject: [PATCH 67/80] removed obsolete config file --- example/cfis/config_tile_MiViSmVi.ini | 177 -------------------------- 1 file changed, 177 deletions(-) delete mode 100644 example/cfis/config_tile_MiViSmVi.ini diff --git a/example/cfis/config_tile_MiViSmVi.ini b/example/cfis/config_tile_MiViSmVi.ini deleted file mode 100644 index f87d80daa..000000000 --- a/example/cfis/config_tile_MiViSmVi.ini +++ /dev/null @@ -1,177 +0,0 @@ -# ShapePipe configuration file for tiles, from detection up to shape measurement. -# MCCD PSF model. - - -## Default ShapePipe options -[DEFAULT] - -# verbose mode (optional), default: True, print messages on terminal -VERBOSE = True - -# Name of run (optional) default: shapepipe_run -RUN_NAME = run_sp_MiViSmVi - -# Add date and time to RUN_NAME, optional, default: False -; RUN_DATETIME = False - - -## ShapePipe execution options -[EXECUTION] - -# Module name, single string or comma-separated list of valid module runner names -MODULE = mccd_interp_runner, - vignetmaker_runner, spread_model_runner, - vignetmaker_runner - -# Parallel processing mode, SMP or MPI -MODE = SMP - - -## ShapePipe file handling options -[FILE] - -# Log file master name, optional, default: shapepipe -LOG_NAME = log_sp - -# Runner log file name, optional, default: shapepipe_runs -RUN_LOG_NAME = log_run_sp - -# Input directory, containing input files, single string or list of names -INPUT_DIR = . - -# Output directory -OUTPUT_DIR = $SP_RUN/output - - -## ShapePipe job handling options -[JOB] - -# Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 12 - -# Timeout value (optional), default is None, i.e. no timeout limit applied -TIMEOUT = 96:00:00 - - -## Module options - -[MCCD_INTERP_RUNNER] - -INPUT_DIR = last:sextractor_runner_run_1, run_sp_tile_Mh_exp:merge_headers_runner - -FILE_PATTERN = sexcat, log_exp_headers - -FILE_EXT = .fits, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = MULTI-EPOCH - -# Column names of position parameters -POSITION_PARAMS = XWIN_WORLD,YWIN_WORLD - -# If True, measure and store ellipticity of the PSF -GET_SHAPES = True - -PSF_MODEL_DIR = mccd_fit_val_runner - -PSF_MODEL_PATTERN = fitted_model - -PSF_MODEL_SEPARATOR = - - - -[VIGNETMAKER_RUNNER_RUN_1] - -# Create vignets for tile weights - -INPUT_DIR = last:sextractor_runner_run_1, last:uncompress_fits_runner - -FILE_PATTERN = sexcat, CFIS_weight - -FILE_EXT = .fits, .fits - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -MASKING = False -MASK_VALUE = 0 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = CLASSIC - -# Coordinate frame type, one in PIX (pixel frame), SPHE (spherical coordinates) -COORD = PIX -POSITION_PARAMS = XWIN_IMAGE,YWIN_IMAGE - -# Vignet size in pixels -STAMP_SIZE = 51 - -# Output file name prefix, file name is _vignet.fits -PREFIX = weight - - -[SPREAD_MODEL_RUNNER] - -INPUT_DIR = last:sextractor_runner_run_1, last:mccd_interp_runner, last:vignetmaker_runner_run_1 - -FILE_PATTERN = sexcat, galaxy_psf, weight_vignet - -FILE_EXT = .fits, .sqlite, .fits - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -# Pixel scale in arcsec -PIXEL_SCALE = 0.186 - -# Output mode: -# new: create a new catalog with: [number, mag, sm, sm_err] -# add: create a copy of the input SExtractor with the column sm and sm_err -OUTPUT_MODE = new - - -[VIGNETMAKER_RUNNER_RUN_2] - -# Create multi-epoch vignets for tiles corresponding to -# positions on single-exposures - -INPUT_DIR = last:sextractor_runner_run_1, run_sp_tile_Mh_exp:merge_headers_runner - -FILE_PATTERN = sexcat, log_exp_headers - -FILE_EXT = .fits, .sqlite - -# NUMBERING_SCHEME (optional) string with numbering pattern for input files -NUMBERING_SCHEME = -000-000 - -MASKING = False -MASK_VALUE = 0 - -# Run mode for psfex interpolation: -# CLASSIC: 'classical' run, interpolate to object positions -# MULTI-EPOCH: interpolate for multi-epoch images -# VALIDATION: validation for single-epoch images -MODE = MULTI-EPOCH - -# Coordinate frame type, one in PIX (pixel frame), SPHE (spherical coordinates) -COORD = SPHE -POSITION_PARAMS = XWIN_WORLD,YWIN_WORLD - -# Vignet size in pixels -STAMP_SIZE = 51 - -# Output file name prefix, file name is vignet.fits -PREFIX = - -# Additional parameters for path and file pattern corresponding to single-exposure -# run outputs -ME_IMAGE_DIR = split_exp_runner, split_exp_runner, split_exp_runner, sextractor_runner_run_2, sextractor_runner_run_2 -ME_IMAGE_PATTERN = flag, image, weight, background, background_rms From 6f4901b84db51f526c80804e847b91eacfb16a77 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 15:03:07 +0200 Subject: [PATCH 68/80] fix(example): explicit FILE_EXT in config_exp_psfex.ini Same latent FILE_PATTERN/FILE_EXT length mismatch 3eb6a66f fixed elsewhere: the 3-entry FILE_PATTERN override fell back on the decorator's new 4-entry FILE_EXT default, tripping the length check at startup. Co-Authored-By: Claude Fable 5 --- example/cfis/config_exp_psfex.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/cfis/config_exp_psfex.ini b/example/cfis/config_exp_psfex.ini index 940c56642..4050a398e 100644 --- a/example/cfis/config_exp_psfex.ini +++ b/example/cfis/config_exp_psfex.ini @@ -64,6 +64,10 @@ INPUT_DIR = last:split_exp_runner, last:mask_runner # Read pipeline flag files created by mask module FILE_PATTERN = image, weight, pipeline_flag +# Explicit extensions: a 3-entry FILE_PATTERN override must not fall back on +# the decorator's 4-entry FILE_EXT default (length check fails at startup) +FILE_EXT = .fits, .fits, .fits + NUMBERING_SCHEME = -0000000-0 # SExtractor executable path From 0433a36ab39e0a717cfbd92d290a0eaded7c84ed Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 15:08:21 +0200 Subject: [PATCH 69/80] fix(example): Ng templates point BKG_RMS_VIGNET_PATH at surviving producer run_sp_MiViSmVi was deleted with its config (ff612b7e); the surviving background_rms vignet producers run as run_sp_tile_PiViVi (matching config_tile_Ng_batch_psfex_uc.ini). Co-Authored-By: Claude Fable 5 --- example/cfis/config_tile_Ng_template.ini | 2 +- example/cfis/config_tile_Ng_template_batch.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/cfis/config_tile_Ng_template.ini b/example/cfis/config_tile_Ng_template.ini index 18f3177a8..482abb305 100644 --- a/example/cfis/config_tile_Ng_template.ini +++ b/example/cfis/config_tile_Ng_template.ini @@ -68,7 +68,7 @@ NUMBERING_SCHEME = -000-000 # 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for # every tile (missing file -> error, no per-tile fallback); omit the option # entirely to fall back to the scalar sigma_mad noise estimate. -BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite +BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_tile_PiViVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite # Magnitude zero-point MAG_ZP = 30.0 diff --git a/example/cfis/config_tile_Ng_template_batch.ini b/example/cfis/config_tile_Ng_template_batch.ini index cd4e28ba9..f15849674 100644 --- a/example/cfis/config_tile_Ng_template_batch.ini +++ b/example/cfis/config_tile_Ng_template_batch.ini @@ -68,7 +68,7 @@ NUMBERING_SCHEME = -000-000 # 1/RMS^2 inverse-variance ngmix weights. When set, the file must exist for # every tile (missing file -> error, no per-tile fallback); omit the option # entirely to fall back to the scalar sigma_mad noise estimate. -BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_MiViSmVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite +BKG_RMS_VIGNET_PATH = $SP_RUN/output/run_sp_tile_PiViVi/vignetmaker_runner_run_2/output/background_rms_vignet{file_number_string}.sqlite # Number of objects to batch save during processing, optional. Omit or set # to -1 for no batch saving From bb93c2889b2bd7cda8e41f0947f3d019ada1221d Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 14:43:18 +0200 Subject: [PATCH 70/80] fix(ngmix): per-pixel fixnoise noise image in the background-RMS branch The rms branch drew the metacal noise image at a scalar median(rms) while the weights claim per-pixel variance; under a strong RMS gradient (factor 8 across a 48px stamp) fixnoise then floods the low-noise half and the ivar weights measure WORSE than binary weights through metacal (noshear shape scatter 0.116 vs 0.078 over 50 paired realisations). Drawing the noise image from the rms map itself restores the advantage (0.053 vs 0.078); constant maps are bit-identical to the old behaviour. Co-Authored-By: Claude Fable 5 --- src/shapepipe/modules/ngmix_package/ngmix.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 428808aa5..97da6d549 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -977,8 +977,13 @@ def prepare_ngmix_weights(gal, weight, flag, rng, bkg_rms=None): mask &= valid_rms weight_map = np.zeros_like(gal, dtype=float) weight_map[mask] = 1.0 / bkg_rms[mask] ** 2 + # Per-pixel noise sigma for the realisations below: metacal's + # fixnoise bookkeeping (1/w + 1/w_noise) assumes the noise image + # is a faithful realisation of the per-pixel variance the weights + # claim; a scalar sigma there mis-reports errors and erodes the + # inverse-variance advantage whenever the RMS map actually varies. sig_noise = ( - np.median(bkg_rms[mask]) + np.where(valid_rms, bkg_rms, np.median(bkg_rms[mask])) if mask.any() else sigma_mad(gal) ) From c5a9cc4ddd094b15e78e53257f5a5bb977d0bf7a Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 14:43:36 +0200 Subject: [PATCH 71/80] test(ngmix): galsim noise-injection validation of background-RMS weights Known Gaussian galaxies with noise of known per-pixel variance run through the module's own observation/weight building and fitter stack (do_ngmix_metacal's runner construction is factored into make_runners so the test fits with literally the module's configuration). Teeth: mean reduced chi2 = 1.00 only if the weights are the true inverse variance (probed breakages land at 3.0-1.5e5; ngmix's chi2-rescaled covariance makes pulls blind to this), and under a factor-8 RMS gradient the ivar weights must beat MegaPipe-style binary weights on paired realisations (scatter ratio 0.59 direct, 0.68 through metacal; inverted or transposed maps push it past 1.6). Accuracy and pull calibration asserted per case. Co-Authored-By: Claude Fable 5 --- src/shapepipe/modules/ngmix_package/ngmix.py | 46 ++- .../tests/test_ngmix_weight_validation.py | 305 ++++++++++++++++++ 2 files changed, 340 insertions(+), 11 deletions(-) create mode 100644 src/shapepipe/tests/test_ngmix_weight_validation.py diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 97da6d549..8f2a2d179 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -1110,6 +1110,40 @@ def average_multiepoch_psf(obsdict): return psf_dict +def make_runners(prior, flux_guess, rng): + """Build the module's galaxy and PSF runners. + + Single source of truth for the fitter configuration (Gaussian galaxy + and PSF models, guessers, retry counts), shared by the metacal + bootstrap below and by validation tests that fit module-built + observations directly. + + Parameters + ---------- + prior : ngmix.joint_prior.PriorSimpleSep + Priors for the fitting parameters. + flux_guess : float + Initial flux guess. + rng : numpy.random.RandomState + Random state for the guessers. + + Returns + ------- + tuple + (runner, psf_runner) : ngmix.runners.Runner, ngmix.runners.PSFRunner + """ + fitter = ngmix.fitting.Fitter(model='gauss', prior=prior) + guesser = ngmix.guessers.TPSFFluxAndPriorGuesser(rng=rng, T=0.25, prior=prior) + + psf_fitter = ngmix.fitting.Fitter(model='gauss', prior=prior) + psf_guesser = ngmix.guessers.TFluxGuesser(rng=rng, T=0.25, prior=prior, flux=flux_guess) + + return ( + ngmix.runners.Runner(fitter=fitter, guesser=guesser, ntry=5), + ngmix.runners.PSFRunner(fitter=psf_fitter, guesser=psf_guesser, ntry=2), + ) + + def do_ngmix_metacal(stamp, prior, flux_guess, rng): """Do Ngmix Metacal. @@ -1137,9 +1171,6 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): if n_epoch == 0: raise ValueError("0 epoch to process") - psf_model = 'gauss' - gal_model = 'gauss' - gal_obs_list = ObsList() for n_e in range(n_epoch): bkg_rms = stamp.bkg_rms[n_e] if len(stamp.bkg_rms) > n_e else None @@ -1154,14 +1185,7 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): ) gal_obs_list.append(gal_obs) - fitter = ngmix.fitting.Fitter(model=gal_model, prior=prior) - guesser = ngmix.guessers.TPSFFluxAndPriorGuesser(rng=rng, T=0.25, prior=prior) - - psf_fitter = ngmix.fitting.Fitter(model=psf_model, prior=prior) - psf_guesser = ngmix.guessers.TFluxGuesser(rng=rng, T=0.25, prior=prior, flux=flux_guess) - - psf_runner = ngmix.runners.PSFRunner(fitter=psf_fitter, guesser=psf_guesser, ntry=2) - runner = ngmix.runners.Runner(fitter=fitter, guesser=guesser, ntry=5) + runner, psf_runner = make_runners(prior, flux_guess, rng) metacal_pars = { 'types': ['noshear', '1p', '1m', '2p', '2m'], diff --git a/src/shapepipe/tests/test_ngmix_weight_validation.py b/src/shapepipe/tests/test_ngmix_weight_validation.py new file mode 100644 index 000000000..ac21af759 --- /dev/null +++ b/src/shapepipe/tests/test_ngmix_weight_validation.py @@ -0,0 +1,305 @@ +"""GalSim noise-injection validation of the background-RMS ngmix weights. + +Empirical back pressure for the per-pixel inverse-variance weights built +from the SExtractor BACKGROUND_RMS map (``prepare_ngmix_weights`` / +``make_ngmix_observation``): galaxies with known shape are drawn with +galsim, noise of *known* per-pixel variance is injected, and the stamps +are pushed through the module's own observation/weight building and +fitter configuration. Three statistics give the test teeth: + +* **chi2per**: with weights equal to the true inverse variance, the mean + reduced chi^2 of the fit residuals is 1 to ~0.5%. This is the absolute + check — ngmix scales its parameter covariance by reduced chi^2 + (``pcov = pcov0 * s_sq`` in ``run_leastsq``), so *pulls cannot see* a + mis-normalised weight map, but chi2per sees every wiring break + measured here: normalisation off x4 -> 3.99, rms/variance confusion + -> 13.2, inverted weights -> 1.5e5, transposed map -> 13.9, scalar + sigma_mad fallback under a gradient -> 4.8 (all vs 1.00). +* **paired shape scatter**: under a strong RMS gradient (factor 8 across + the galaxy), inverse-variance weights must beat the old + MegaPipe-style binary weights (``bkg_rms=None``: binarised mask / + sigma_mad^2) on the *same* noise realisations. Measured ratio ~0.59 + direct, ~0.68 through metacal; inverted or transposed weights push it + above 1.6. +* **pulls and mean accuracy**: reported errors are usable (pull std ~1) + and the recovered shape is unbiased within its standard error. + +The error-calibration (pull/chi2per) assertions run on direct fits of +module-built observations because metacal's fixnoise step adds a second +noise field and deconvolution-correlated noise: even with perfect +weights and flat noise the metacal noshear pull std is ~1.5 (an ngmix +fixnoise property, independent of the weight wiring), so through-metacal +pulls test ngmix, not the weights. Accuracy and the ivar-vs-binary +scatter comparison are additionally asserted end-to-end through +``do_ngmix_metacal``. +""" + +import galsim +import ngmix +import numpy as np +import numpy.testing as npt +import pytest + +from shapepipe.modules.ngmix_package.ngmix import ( + Postage_stamp, + do_ngmix_metacal, + get_prior, + make_ngmix_observation, + make_runners, +) + +PIX_SCALE = 0.1857 # arcsec, CFIS convention +NPIX = 48 +PSF_FWHM = 0.55 # arcsec +GAL_HLR = 0.5 # arcsec +GAL_FLUX = 2800.0 # S/N ~ 100 on flat noise +G_TRUE = np.array([0.03, -0.02]) +SIGMA0 = 3.0 # base noise rms; deliberately != 1 so that any +# normalisation confusion (1/rms vs 1/rms^2, missing square, ...) +# shifts chi2per away from 1 instead of hiding at sigma = 1 +N_REAL = 50 +ACC_NSIGMA = 4.0 # accuracy tolerance in units of the standard error +PULL_STD_TOL = 4.0 / np.sqrt(2 * N_REAL) # ~3 sigma on std of 2N pulls +CHI2_TOL = 0.05 # mean chi2per; smallest broken-wiring signature is 3.0 + +WCS = galsim.PixelScale(PIX_SCALE) + + +def rms_flat(): + """Constant noise map at SIGMA0.""" + return np.full((NPIX, NPIX), SIGMA0) + + +def rms_ramp(): + """Strong noise gradient: rms ramps x8 from SIGMA0 across the galaxy. + + The ramp runs through the stamp core (columns 20-28), the regime that + motivated the rms weights: scalar noise estimates are wrong on both + sides and flat weights average factor-64 variance differences. + """ + x = np.broadcast_to(np.arange(NPIX, dtype=float), (NPIX, NPIX)) + return SIGMA0 * (1.0 + 7.0 * np.clip((x - 20.0) / 8.0, 0.0, 1.0)) + + +def draw_stamp(rng, rms_map): + """Known Gaussian galaxy x Gaussian PSF stamp with injected noise. + + Noise is drawn per pixel with the exact sigma of ``rms_map``, so the + map fed to the module as BACKGROUND_RMS is the truth by construction. + """ + dy, dx = rng.uniform(low=-PIX_SCALE / 2, high=PIX_SCALE / 2, size=2) + psf = galsim.Gaussian(fwhm=PSF_FWHM) + obj = galsim.Convolve( + psf, + galsim.Gaussian(half_light_radius=GAL_HLR, flux=GAL_FLUX).shear( + g1=G_TRUE[0], g2=G_TRUE[1] + ), + ).shift(dx, dy) + gal_im = obj.drawImage(nx=NPIX, ny=NPIX, wcs=WCS).array.astype(np.float64) + psf_im = psf.drawImage(nx=NPIX, ny=NPIX, wcs=WCS).array.astype(np.float64) + gal_im += rng.normal(size=gal_im.shape) * rms_map + psf_im += rng.normal(scale=1e-6, size=psf_im.shape) + return gal_im, psf_im + + +def fit_direct(gal, psf_im, bkg_rms, seed): + """Fit one module-built Observation with the module's runner stack. + + Identical to ``do_ngmix_metacal`` minus the metacal image + manipulation: same observation builder, same ``make_runners`` + configuration, plain Bootstrapper instead of MetacalBootstrapper. + """ + rng = np.random.RandomState(seed) + obs = make_ngmix_observation( + gal, + np.ones_like(gal), + np.zeros_like(gal), + psf_im, + WCS.jacobian(), + rng, + bkg_rms=bkg_rms, + ) + prior = get_prior(PIX_SCALE, rng) + runner, psf_runner = make_runners(prior, GAL_FLUX, rng) + boot = ngmix.bootstrap.Bootstrapper( + runner=runner, psf_runner=psf_runner, ignore_failed_psf=True + ) + obs_list = ngmix.observation.ObsList() + obs_list.append(obs) + return boot.go(obs_list) + + +def fit_metacal(gal, psf_im, bkg_rms, seed): + """End-to-end module path: Postage_stamp -> do_ngmix_metacal noshear.""" + rng = np.random.RandomState(seed) + stamp = Postage_stamp(bkg_sub=False, megacam_flip=False) + stamp.gals = [gal] + stamp.psfs = [psf_im] + stamp.weights = [np.ones_like(gal)] + stamp.flags = [np.zeros_like(gal)] + stamp.jacobs = [WCS.jacobian()] + if bkg_rms is not None: + stamp.bkg_rms = [bkg_rms] + res, _ = do_ngmix_metacal(stamp, get_prior(PIX_SCALE, rng), GAL_FLUX, rng) + return res["noshear"] + + +def run_ensemble(fit_func, rms_map, use_ivar): + """N_REAL seeded realisations; NaN rows mark failed fits (pairing-safe). + + Image seeds depend only on the realisation index, so ivar and binary + runs over the same ``rms_map`` see identical noise: the scatter + comparison is paired. + """ + g = np.full((N_REAL, 2), np.nan) + g_err = np.full((N_REAL, 2), np.nan) + chi2per = np.full(N_REAL, np.nan) + for i in range(N_REAL): + gal, psf_im = draw_stamp(np.random.RandomState(1000 + i), rms_map) + res = fit_func(gal, psf_im, rms_map if use_ivar else None, 5000 + i) + if res["flags"] != 0: + continue + g[i] = res["g"] + g_err[i] = np.sqrt(np.diag(res["g_cov"])) + chi2per[i] = res.get("chi2per", np.nan) + ok = np.isfinite(g[:, 0]) + assert ok.mean() >= 0.9, f"fit failure rate {1 - ok.mean():.0%} > 10%" + return { + "ok": ok, + "resid": g - G_TRUE, + "pulls": ((g - G_TRUE) / g_err)[ok].ravel(), + "chi2per": chi2per[ok], + } + + +@pytest.fixture(scope="module") +def direct_flat_ivar(): + return run_ensemble(fit_direct, rms_flat(), use_ivar=True) + + +@pytest.fixture(scope="module") +def direct_ramp_ivar(): + return run_ensemble(fit_direct, rms_ramp(), use_ivar=True) + + +@pytest.fixture(scope="module") +def direct_ramp_binary(): + return run_ensemble(fit_direct, rms_ramp(), use_ivar=False) + + +@pytest.fixture(scope="module") +def metacal_flat_ivar(): + return run_ensemble(fit_metacal, rms_flat(), use_ivar=True) + + +@pytest.fixture(scope="module") +def metacal_ramp_ivar(): + return run_ensemble(fit_metacal, rms_ramp(), use_ivar=True) + + +@pytest.fixture(scope="module") +def metacal_ramp_binary(): + return run_ensemble(fit_metacal, rms_ramp(), use_ivar=False) + + +def assert_unbiased(ens): + """Mean recovered shape consistent with truth, per component. + + Tolerance is ACC_NSIGMA standard errors of the measured sample — + nothing hand-tuned, it tightens automatically if the scatter drops. + """ + resid = ens["resid"][ens["ok"]] + bias = resid.mean(axis=0) + std_err = resid.std(axis=0, ddof=1) / np.sqrt(len(resid)) + npt.assert_array_less( + np.abs(bias), + ACC_NSIGMA * std_err, + err_msg=f"shape bias {bias} exceeds {ACC_NSIGMA} x SE {std_err}", + ) + + +def paired_scatter_ratio(ens_a, ens_b): + """Std of shape residuals of a relative to b, on common successes.""" + both = ens_a["ok"] & ens_b["ok"] + return ( + ens_a["resid"][both].std(ddof=1) / ens_b["resid"][both].std(ddof=1) + ) + + +def test_direct_flat_ivar_unbiased(direct_flat_ivar): + assert_unbiased(direct_flat_ivar) + + +def test_direct_ramp_ivar_unbiased(direct_ramp_ivar): + assert_unbiased(direct_ramp_ivar) + + +def test_direct_ramp_binary_unbiased(direct_ramp_binary): + """Binary weights are inefficient under a gradient, not biased.""" + assert_unbiased(direct_ramp_binary) + + +@pytest.mark.parametrize("case", ["direct_flat_ivar", "direct_ramp_ivar"]) +def test_direct_ivar_pulls_calibrated(case, request): + """Pull std == 1 within ~3 sigma: reported errors are real errors.""" + pull_std = request.getfixturevalue(case)["pulls"].std(ddof=1) + assert abs(pull_std - 1.0) < PULL_STD_TOL, ( + f"{case}: pull std {pull_std:.3f} outside 1 +/- {PULL_STD_TOL:.2f}" + ) + + +@pytest.mark.parametrize("case", ["direct_flat_ivar", "direct_ramp_ivar"]) +def test_direct_ivar_chi2_proves_absolute_normalisation(case, request): + """Mean reduced chi^2 == 1: the weights are the true inverse variance. + + The one statistic ngmix's chi^2-rescaled covariance cannot launder. + Every probed wiring break (normalisation, rms vs variance, inversion, + transposition) lands at >= 3.0; correct wiring sits at 1.00 +/- 0.005. + """ + mean_chi2 = request.getfixturevalue(case)["chi2per"].mean() + assert abs(mean_chi2 - 1.0) < CHI2_TOL, ( + f"{case}: mean chi2per {mean_chi2:.3f} != 1 -> weight map is not" + " the inverse of the injected per-pixel variance" + ) + + +def test_direct_ivar_beats_binary_under_noise_gradient( + direct_ramp_ivar, direct_ramp_binary +): + """The discriminating case: paired scatter ratio well below 1. + + Same noise realisations, only the weights differ. Measured ~0.59; + binary parity gives 1.0, inverted/transposed weights give > 1.6, so + a threshold of 0.8 fails for any wiring that does not actually + downweight the high-variance pixels. + """ + ratio = paired_scatter_ratio(direct_ramp_ivar, direct_ramp_binary) + assert ratio < 0.8, ( + f"ivar/binary shape-scatter ratio {ratio:.3f} >= 0.8: rms weights" + " give no precision gain under a factor-8 noise gradient" + ) + + +def test_metacal_flat_ivar_unbiased(metacal_flat_ivar): + assert_unbiased(metacal_flat_ivar) + + +def test_metacal_ramp_ivar_unbiased(metacal_ramp_ivar): + assert_unbiased(metacal_ramp_ivar) + + +def test_metacal_ivar_beats_binary_under_noise_gradient( + metacal_ramp_ivar, metacal_ramp_binary +): + """End-to-end through do_ngmix_metacal the ivar gain must survive. + + This pins the per-pixel fixnoise noise image: with the noise image + drawn at a scalar median(rms) instead, fixnoise floods the low-noise + half of the stamp and this ratio inverts to ~1.5 (ivar *worse* than + binary). Measured ~0.68 with the per-pixel noise image. + """ + ratio = paired_scatter_ratio(metacal_ramp_ivar, metacal_ramp_binary) + assert ratio < 0.9, ( + f"metacal ivar/binary shape-scatter ratio {ratio:.3f} >= 0.9: the" + " rms-weight advantage does not survive the metacal/fixnoise path" + ) From 6bae2a41ec669cc61f5744b974305960301678d1 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 15:11:39 +0200 Subject: [PATCH 72/80] test(ngmix): tighten validation docstrings per review (ratio-test scope, pull-tol derivation) Co-Authored-By: Claude Fable 5 --- .../tests/test_ngmix_weight_validation.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/shapepipe/tests/test_ngmix_weight_validation.py b/src/shapepipe/tests/test_ngmix_weight_validation.py index ac21af759..243cbe837 100644 --- a/src/shapepipe/tests/test_ngmix_weight_validation.py +++ b/src/shapepipe/tests/test_ngmix_weight_validation.py @@ -59,7 +59,12 @@ # shifts chi2per away from 1 instead of hiding at sigma = 1 N_REAL = 50 ACC_NSIGMA = 4.0 # accuracy tolerance in units of the standard error -PULL_STD_TOL = 4.0 / np.sqrt(2 * N_REAL) # ~3 sigma on std of 2N pulls +# Deliberately loose: with 2N = 100 pulls the naive SE of the sample std +# is ~1/sqrt(200) ~ 0.071, but g1/g2 pulls within a realisation are +# correlated (empirical jackknife SE ~ 0.097), so 0.4 is ~4 sigma. +# Absolute error calibration is carried by the chi2per assertion, which a +# uniform 2x weight error fails (chi2per 0.50) while pulls pass (1.07). +PULL_STD_TOL = 4.0 / np.sqrt(2 * N_REAL) CHI2_TOL = 0.05 # mean chi2per; smallest broken-wiring signature is 3.0 WCS = galsim.PixelScale(PIX_SCALE) @@ -219,7 +224,13 @@ def assert_unbiased(ens): def paired_scatter_ratio(ens_a, ens_b): - """Std of shape residuals of a relative to b, on common successes.""" + """Std of shape residuals of a relative to b, on common successes. + + The std pools g1/g2 residuals about a pooled mean, so a small + differential per-component bias inflates "scatter"; negligible at + current magnitudes (~0.01 offsets vs ~0.07 scatter) and symmetric + between numerator and denominator. + """ both = ens_a["ok"] & ens_b["ok"] return ( ens_a["resid"][both].std(ddof=1) / ens_b["resid"][both].std(ddof=1) @@ -241,7 +252,7 @@ def test_direct_ramp_binary_unbiased(direct_ramp_binary): @pytest.mark.parametrize("case", ["direct_flat_ivar", "direct_ramp_ivar"]) def test_direct_ivar_pulls_calibrated(case, request): - """Pull std == 1 within ~3 sigma: reported errors are real errors.""" + """Pull std == 1 within ~4 sigma: reported errors are real errors.""" pull_std = request.getfixturevalue(case)["pulls"].std(ddof=1) assert abs(pull_std - 1.0) < PULL_STD_TOL, ( f"{case}: pull std {pull_std:.3f} outside 1 +/- {PULL_STD_TOL:.2f}" @@ -270,8 +281,10 @@ def test_direct_ivar_beats_binary_under_noise_gradient( Same noise realisations, only the weights differ. Measured ~0.59; binary parity gives 1.0, inverted/transposed weights give > 1.6, so - a threshold of 0.8 fails for any wiring that does not actually - downweight the high-variance pixels. + the 0.8 threshold catches gross wiring failures (inversion, no + downweighting). Functional-form errors that still downweight in the + right direction — e.g. 1/rms instead of 1/rms^2 — pass the ratio + (0.727 < 0.8) and are caught by the direct-layer chi2per assertion. """ ratio = paired_scatter_ratio(direct_ramp_ivar, direct_ramp_binary) assert ratio < 0.8, ( From 519a786c8978772770337003c9208c97a328c99a Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 17:14:10 +0200 Subject: [PATCH 73/80] fix(run): only import mpi4py when an MPI launcher is present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Importing mpi4py initializes MPI at import time, which aborts the whole process when Open MPI detects a SLURM step environment but no PMI server — i.e. inside any srun-launched shell on a cluster whose container OMPI lacks SLURM PMI support. Even shapepipe_run -h dies before printing (#744; empirically bisected to SLURM_STEP_ID being set). Gate the import on launcher-set env vars (OMPI_COMM_WORLD_SIZE for mpirun, PMI_RANK for srun --mpi=pmi2, PMIX_RANK for srun --mpi=pmix). A bare shapepipe_run never touches MPI and runs SMP as before; mpirun launches are unchanged. Verified on candide: bare run under srun now exits 0 (was OPAL abort), mpirun -n 1 and -n 2 paths intact. Behavior note: a config with MODE = MPI launched without any MPI launcher now falls back to SMP instead of running single-rank MPI. Closes #744 Co-Authored-By: Claude Fable 5 --- src/shapepipe/run.py | 26 +++++++++++++---- src/shapepipe/tests/test_run.py | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 src/shapepipe/tests/test_run.py diff --git a/src/shapepipe/run.py b/src/shapepipe/run.py index 8212fdfb5..f377c24ba 100644 --- a/src/shapepipe/run.py +++ b/src/shapepipe/run.py @@ -6,6 +6,7 @@ """ +import os import sys from datetime import datetime from importlib.metadata import requires @@ -22,12 +23,27 @@ from shapepipe.pipeline.job_handler import JobHandler from shapepipe.pipeline.mpi_run import split_mpi_jobs, submit_mpi_jobs -try: - from mpi4py import MPI -except ImportError: # pragma: no cover - import_mpi = False +# Importing mpi4py initializes MPI immediately, which aborts the whole +# process when no MPI launcher is available — e.g. inside an +# ``srun``-launched shell on a SLURM cluster, where Open MPI detects the +# SLURM step environment, expects a PMI server that srun never started, +# and calls MPI_Abort before even ``shapepipe_run -h`` can print (#744). +# Only import (and hence initialize) MPI when a launcher environment is +# actually present: ``mpirun``/``orterun`` set OMPI_COMM_WORLD_SIZE, +# ``srun --mpi=pmi2`` sets PMI_RANK and ``srun --mpi=pmix`` sets +# PMIX_RANK. A bare ``shapepipe_run`` (login node, compute-node shell, +# container) runs in SMP mode without ever touching MPI. +_MPI_LAUNCHER_VARS = ("OMPI_COMM_WORLD_SIZE", "PMI_RANK", "PMIX_RANK") + +if any(var in os.environ for var in _MPI_LAUNCHER_VARS): + try: + from mpi4py import MPI + except ImportError: # pragma: no cover + import_mpi = False + else: + import_mpi = True else: - import_mpi = True + import_mpi = False class ShapePipe: diff --git a/src/shapepipe/tests/test_run.py b/src/shapepipe/tests/test_run.py new file mode 100644 index 000000000..bb60370ca --- /dev/null +++ b/src/shapepipe/tests/test_run.py @@ -0,0 +1,52 @@ +"""UNIT TESTS FOR RUN. + +This module contains unit tests for the shapepipe.run module, in +particular the MPI-launcher gating of the mpi4py import (#744): a bare +``shapepipe_run`` must never initialize MPI, otherwise the whole process +aborts inside an ``srun``-launched shell whose Open MPI lacks SLURM PMI +support. + +:Author: Claude (on behalf of Cail Daley) + +""" + +import os +import subprocess +import sys + +import pytest + +SNIPPET = "import shapepipe.run as r; print(r.import_mpi)" + +# Env vars that either mark an MPI launcher (the gate) or make Open MPI +# believe it was direct-launched by srun (the failure mode under test). +_SCRUBBED_PREFIXES = ("OMPI_", "PMI_", "PMIX_", "SLURM_") + + +def _import_mpi_flag(extra_env): + """Report shapepipe.run.import_mpi in a subprocess with a clean env.""" + env = { + key: value + for key, value in os.environ.items() + if not key.startswith(_SCRUBBED_PREFIXES) + } + env.update(extra_env) + result = subprocess.run( + [sys.executable, "-c", SNIPPET], + env=env, + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + + +def test_bare_launch_skips_mpi(): + """A bare launch (no MPI launcher env) must not import/init MPI.""" + assert _import_mpi_flag({}) == "False" + + +def test_mpirun_launch_imports_mpi(): + """An mpirun-style env (OMPI_COMM_WORLD_SIZE) must import MPI.""" + pytest.importorskip("mpi4py") + assert _import_mpi_flag({"OMPI_COMM_WORLD_SIZE": "1"}) == "True" From dab2d5c1cf1cac701c2985bb4cd5a6efb8df9803 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 17:25:00 +0200 Subject: [PATCH 74/80] =?UTF-8?q?fix(run):=20review=20nits=20=E2=80=94=20m?= =?UTF-8?q?odule=5Fdep=20self-append,=20surface=20test=20stderr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The else-branch of the mpi4py dependency line appended the whole list to itself (harmless only because DependencyHandler dedups); with the launcher gate this became the common path. And the regression test now surfaces the subprocess stderr on failure instead of swallowing it in a bare CalledProcessError. Co-Authored-By: Claude Fable 5 --- src/shapepipe/run.py | 2 +- src/shapepipe/tests/test_run.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shapepipe/run.py b/src/shapepipe/run.py index f377c24ba..7a1faf869 100644 --- a/src/shapepipe/run.py +++ b/src/shapepipe/run.py @@ -194,7 +194,7 @@ def _check_dependencies(self): module_dep = self._get_module_depends("depends") + __installs__ module_exe = self._get_module_depends("executes") - module_dep += ["mpi4py"] if import_mpi else module_dep + module_dep += ["mpi4py"] if import_mpi else [] exe_to_module = { exe: module diff --git a/src/shapepipe/tests/test_run.py b/src/shapepipe/tests/test_run.py index bb60370ca..03e6da22f 100644 --- a/src/shapepipe/tests/test_run.py +++ b/src/shapepipe/tests/test_run.py @@ -36,7 +36,9 @@ def _import_mpi_flag(extra_env): env=env, capture_output=True, text=True, - check=True, + ) + assert result.returncode == 0, ( + f"subprocess failed (exit {result.returncode}): {result.stderr}" ) return result.stdout.strip() From c23ea8451af9af2f2c5cfc0d73d283365d3c2766 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 22:52:11 +0200 Subject: [PATCH 75/80] refactor(run): replace -e/--exclusive flag with NUMBER_LIST (#746) The exclusive input-ID flag and the NUMBER_LIST config option converged in FileHandler._format_process_list and did the same thing for a single ID; NUMBER_LIST is now the one mechanism. Pipeline: - remove -e/--exclusive from args.py and its plumbing through run.py, FileHandler, and JobHandler (where it was stored but never used) - NUMBER_LIST entries are now validated against the input file numbers found on disk, preserving -e's early failure on a wrong ID: the run aborts at start-up instead of when a module first opens files - unit tests for the validation (subset passes, typo raises, no-list scan path unchanged) Canfar chain (script-level -e options are unchanged; one ID per headless job remains the interface): - job_sp_canfar.bash, job_sp_canfar_v2.0.bash, and init_run_exclusive_canfar.sh write NUMBER_LIST into a per-job config copy (set_config_number_list: insert-or-replace under [FILE], ID in numbering-scheme form: leading dash, dots->dashes) instead of passing -e to shapepipe_run. Side benefit: the processed ID is recorded in the config copied to the run's log directory. Co-Authored-By: Claude Fable 5 --- docs/source/configuration.md | 5 +- scripts/sh/init_run_exclusive_canfar.sh | 25 ++++++++- scripts/sh/job_sp_canfar.bash | 36 +++++++++--- scripts/sh/job_sp_canfar_v2.0.bash | 39 ++++++++++--- src/shapepipe/pipeline/args.py | 6 -- src/shapepipe/pipeline/file_handler.py | 38 +++++-------- src/shapepipe/pipeline/job_handler.py | 4 -- src/shapepipe/run.py | 4 -- src/shapepipe/tests/test_file_handler.py | 71 ++++++++++++++++++++++++ 9 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 src/shapepipe/tests/test_file_handler.py diff --git a/docs/source/configuration.md b/docs/source/configuration.md index 3336123c1..42c3aeaa0 100644 --- a/docs/source/configuration.md +++ b/docs/source/configuration.md @@ -84,7 +84,10 @@ The following options can be added to the `[FILE]` section of the config file (*e.g.* `.`, `-`, `:`, *etc.*). *optional*ly a regular expression can also be passed if it is preceded by `RE:` (*e.g.* `RE:-\d{9}`). - `NUMBER_LIST` : (`str` or `list`, *optional*) A list of number strings - matching the numbering scheme or a file name. + matching the numbering scheme or a file name. Restricts the run to these + numbers; every entry must match an input file found on disk, otherwise the + run fails at start-up. This is also how a single image is processed per + job (formerly the `-e`/`--exclusive` command-line flag). - `CORRECT_FILE_PATTERN` : (`bool`, *optional*) Option to allow substring file patterns. Default value is `True`. diff --git a/scripts/sh/init_run_exclusive_canfar.sh b/scripts/sh/init_run_exclusive_canfar.sh index cb7c9af69..c1de00847 100755 --- a/scripts/sh/init_run_exclusive_canfar.sh +++ b/scripts/sh/init_run_exclusive_canfar.sh @@ -144,6 +144,25 @@ function message() { fi } +# Write an updated copy of a shapepipe config with NUMBER_LIST set to the +# given image ID, expressed in the numbering scheme (leading dash, dots -> +# dashes). Replaces the retired shapepipe_run -e/--exclusive flag (#746). +function set_config_number_list() { + local config_orig=$1 + local config_upd=$2 + local _id=$3 + + local number="-$(echo $_id | tr '.' '-')" + local config_tmp="${config_upd}.tmp" + + if grep -q "^NUMBER_LIST" "$config_orig"; then + perl -pe 's/^NUMBER_LIST\s*=.*/NUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" + else + perl -pe 's/^\[FILE\][ \t]*$/[FILE]\nNUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" + fi + mv "$config_tmp" "$config_upd" +} + # Init message message "test=$test_only" $debug_out -1 @@ -267,7 +286,8 @@ if [ "$fix" == "1" ]; then message "Unzip weight ($dry_run)" $debug_out -1 command "cd tile_runs/$ID" $dry_run export SP_RUN=`pwd` - command "shapepipe_run -c cfis/config_tile_Uz.ini -e $ID" $dry_run + command "set_config_number_list cfis/config_tile_Uz.ini config_tile_Uz_upd.ini $ID" $dry_run + command "shapepipe_run -c config_tile_Uz_upd.ini" $dry_run cd $dir else @@ -384,7 +404,8 @@ if [ $do_job != 0 ] && [ "$sp_local" == "1" ]; then fi command "update_runs_log_file.py" $dry_run export SP_RUN=`pwd` - command "shapepipe_run -c cfis/config_exp_Sp.ini -e $exp_ID" $dry_run + command "set_config_number_list cfis/config_exp_Sp.ini config_exp_Sp_upd.ini $exp_ID" $dry_run + command "shapepipe_run -c config_exp_Sp_upd.ini" $dry_run # Only keep CCD of this ID command "mkdir -p output/run_sp_exp_Sp_shdu/split_exp_runner/output" $dry_run diff --git a/scripts/sh/job_sp_canfar.bash b/scripts/sh/job_sp_canfar.bash index 54036c932..3fdaba0ad 100755 --- a/scripts/sh/job_sp_canfar.bash +++ b/scripts/sh/job_sp_canfar.bash @@ -261,18 +261,19 @@ function command_sp() { function command_cfg_shapepipe() { local config_name=$1 local str=$2 - local _n_smp=$3 + local _n_smp=$3 local _exclusive=$4 - if [ "$exclusive" != "" ]; then - exclusive_flag="-e $_exclusive" - else - exclusive_flag="" + config_upd=$(set_config_n_smp $config_name $_n_smp) + + # Run a single image ID via NUMBER_LIST in an updated config copy; + # replaces the retired shapepipe_run -e/--exclusive flag (#746) + if [ "$_exclusive" != "" ]; then + set_config_number_list "$config_upd" "$SP_CONFIG_MOD/$config_name" "$_exclusive" + config_upd="$SP_CONFIG_MOD/$config_name" fi - config_upd=$(set_config_n_smp $config_name $_n_smp) - #local cmd="/arc/home/kilbinger/.conda/envs/shapepipe/bin/shapepipe_run -c $config_upd $exclusive_flag" - local cmd="shapepipe_run -c $config_upd $exclusive_flag" + local cmd="shapepipe_run -c $config_upd" command_sp "$cmd" "$str" } @@ -337,6 +338,25 @@ function update_config() { | perl -ane 's/'$key'\s+=.+/'$key' = '$val_upd'/; print' > $config_upd } +# Write an updated copy of a shapepipe config with NUMBER_LIST set to the +# given image ID, expressed in the numbering scheme (leading dash, dots -> +# dashes). Replaces the retired shapepipe_run -e/--exclusive flag (#746). +function set_config_number_list() { + local config_orig=$1 + local config_upd=$2 + local _id=$3 + + local number="-$(echo $_id | tr '.' '-')" + local config_tmp="${config_upd}.tmp" + + if grep -q "^NUMBER_LIST" "$config_orig"; then + perl -pe 's/^NUMBER_LIST\s*=.*/NUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" + else + perl -pe 's/^\[FILE\][ \t]*$/[FILE]\nNUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" + fi + mv "$config_tmp" "$config_upd" +} + ### Start ### echo "Start processing" diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index c1fa6cd29..fe2b8f9a5 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -165,6 +165,9 @@ export SP_RUN=`pwd` # Config file path export SP_CONFIG=$SP_RUN/cfis +# Path for updated (per-job) config file copies +export SP_CONFIG_MOD=$SP_RUN/cfis_mod + # Root directory for per-exposure work directories. # Set SP_EXP in the environment to override; otherwise falls back to the # conventional layout (SP_RUN = .../v2.0/tiles/IDra/ID, three levels up + exp). @@ -243,18 +246,40 @@ function command () { fi } +# Write an updated copy of a shapepipe config with NUMBER_LIST set to the +# given image ID, expressed in the numbering scheme (leading dash, dots -> +# dashes). Replaces the retired shapepipe_run -e/--exclusive flag (#746). +function set_config_number_list() { + local config_orig=$1 + local config_upd=$2 + local _id=$3 + + local number="-$(echo $_id | tr '.' '-')" + local config_tmp="${config_upd}.tmp" + + if grep -q "^NUMBER_LIST" "$config_orig"; then + perl -pe 's/^NUMBER_LIST\s*=.*/NUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" + else + perl -pe 's/^\[FILE\][ \t]*$/[FILE]\nNUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" + fi + mv "$config_tmp" "$config_upd" +} + # Set up config file and call shapepipe_run. -# Batch size is passed via --batch_size flag; no config editing needed. +# Batch size is passed via --batch_size flag. function command_cfg_shapepipe() { local config_name=$1 local str=$2 local _n_smp=$3 local _exclusive=$4 - if [ "$exclusive" != "" ]; then - exclusive_flag="-e $_exclusive" - else - exclusive_flag="" + local config="$SP_CONFIG/$config_name" + + # Run a single image ID via NUMBER_LIST in an updated config copy; + # replaces the retired shapepipe_run -e/--exclusive flag (#746) + if [ "$_exclusive" != "" ]; then + set_config_number_list "$config" "$SP_CONFIG_MOD/$config_name" "$_exclusive" + config="$SP_CONFIG_MOD/$config_name" fi local batch_flag="" @@ -262,8 +287,7 @@ function command_cfg_shapepipe() { batch_flag="--batch_size $_n_smp" fi - local config="$SP_CONFIG/$config_name" - local cmd="shapepipe_run.py -c $config $exclusive_flag $batch_flag" + local cmd="shapepipe_run.py -c $config $batch_flag" command "$cmd" "$str" } @@ -275,6 +299,7 @@ echo "Start processing" mkdir -p $SP_RUN cd $SP_RUN mkdir -p $OUTPUT +mkdir -p $SP_CONFIG_MOD # Processing diff --git a/src/shapepipe/pipeline/args.py b/src/shapepipe/pipeline/args.py index d402132f2..3770f11c3 100644 --- a/src/shapepipe/pipeline/args.py +++ b/src/shapepipe/pipeline/args.py @@ -135,12 +135,6 @@ def create_arg_parser(): help="configuration file name", ) - optional.add_argument( - "-e", - "--exclusive", - help="exclusive input file number string", - ) - optional.add_argument( "-b", "--batch_size", diff --git a/src/shapepipe/pipeline/file_handler.py b/src/shapepipe/pipeline/file_handler.py index 91f200d1d..b471449b5 100644 --- a/src/shapepipe/pipeline/file_handler.py +++ b/src/shapepipe/pipeline/file_handler.py @@ -32,14 +32,12 @@ class FileHandler(object): List of modules to be run config : CustomParser Configuaration parser instance - exclusive : str, optional - Run this file number string exclusively if given, the default is None verbose : bool, optional Verbose setting, default is True """ - def __init__(self, run_name, modules, config, exclusive=None, verbose=True): + def __init__(self, run_name, modules, config, verbose=True): self._run_name = run_name @@ -48,7 +46,6 @@ def __init__(self, run_name, modules, config, exclusive=None, verbose=True): raise ValueError("Invalid module list, check for a trailing comma") self._config = config - self._exclusive = exclusive self._verbose = verbose self.module_runners = get_module_runners(self._module_list) @@ -1089,7 +1086,20 @@ def _format_process_list( if isinstance(self._number_list, type(None)): number_list = np.load(memory_map, mmap_mode="r") else: + # NUMBER_LIST comes from the config on faith; intersect it + # with the numbers actually found on disk so that a wrong ID + # fails here, at start-up, rather than when a module first + # tries to open the (non-existent) files (#746). number_list = self._number_list + scanned = set(np.load(memory_map, mmap_mode="r")) + missing = [num for num in number_list if num not in scanned] + if missing: + raise ValueError( + f"No input file found matching NUMBER_LIST " + f"entr{'ies' if len(missing) > 1 else 'y'} " + f"{missing}; {len(scanned)} input file number(s) " + f"found on disk." + ) if len(number_list) == 0: msg = "Empty number list" @@ -1107,20 +1117,6 @@ def _format_process_list( + f'numbering scheme "{num_scheme}".' ) - # If "exclusive" options is set: discard all non-matching IDs - if self._exclusive is not None: - id_to_test = f"-{self._exclusive.replace('.', '-')}" - if number == id_to_test: - if self._verbose: - print( - f"-- Using exclusive number {self._exclusive} ({id_to_test})" - ) - else: - if self._verbose: - # print(f"Skipping {number}, not equal to {self._exclusive} ({id_to_test})") - pass - continue - if run_method == "serial": process_items = [] else: @@ -1134,11 +1130,7 @@ def _format_process_list( process_list.append(process_items) if len(process_list) == 0: - msg = "Empty process list" - if self._exclusive is not None: - if len(number_list) > 0: - msg = f"{msg}. No input file found matching exclusive ID" - raise ValueError(msg) + raise ValueError("Empty process list") return process_list diff --git a/src/shapepipe/pipeline/job_handler.py b/src/shapepipe/pipeline/job_handler.py index 9a6e01f1f..15807fb3b 100644 --- a/src/shapepipe/pipeline/job_handler.py +++ b/src/shapepipe/pipeline/job_handler.py @@ -42,8 +42,6 @@ class JobHandler(object): Joblib backend, the default is None (which corresponds to 'loky') timeout : int, optional Timeout limit for a given job in seconds, the default is None - exclusive : str, optional - Run this file number string exclusively if given, the default is None verbose : bool, optional Verbose setting, default is True @@ -60,7 +58,6 @@ def __init__( batch_size=None, backend=None, timeout=None, - exclusive=None, verbose=True, ): @@ -75,7 +72,6 @@ def __init__( self._module = module self._module_runner = self.filehd.module_runners[self._module] self.error_count = 0 - self.exclusive = exclusive self._verbose = verbose # Add the job parameters to the log diff --git a/src/shapepipe/run.py b/src/shapepipe/run.py index 4138d2617..962953ede 100644 --- a/src/shapepipe/run.py +++ b/src/shapepipe/run.py @@ -68,13 +68,11 @@ def set_up(self): self._set_run_name() self.modules = self.config.getlist("EXECUTION", "MODULE") self.mode = self.config.get("EXECUTION", "MODE").lower() - self.exclusive = self._args.exclusive self.verbose = self.config.getboolean("DEFAULT", "VERBOSE") self.filehd = FileHandler( self._run_name, self.modules, self.config, - exclusive=self._args.exclusive, verbose=self.verbose, ) self.error_count = 0 @@ -355,7 +353,6 @@ def run_smp(pipe): config=pipe.config, log=pipe.log, job_type=pipe.run_method[module], - exclusive=pipe.exclusive, verbose=pipe.verbose, batch_size=pipe._args.batch_size, ) @@ -415,7 +412,6 @@ def run_mpi(pipe, comm): log=pipe.log, job_type=pipe.run_method[module], parallel_mode="mpi", - exclusive=pipe.exclusive, verbose=verbose, batch_size=pipe._args.batch_size, ) diff --git a/src/shapepipe/tests/test_file_handler.py b/src/shapepipe/tests/test_file_handler.py new file mode 100644 index 000000000..12dcd32d7 --- /dev/null +++ b/src/shapepipe/tests/test_file_handler.py @@ -0,0 +1,71 @@ +"""UNIT TESTS FOR FILE_HANDLER. + +This module contains unit tests for the shapepipe.pipeline.file_handler +module, in particular the early validation of NUMBER_LIST entries +against the input file numbers actually found on disk (#746): a typo in +NUMBER_LIST must fail at start-up, not when a module first tries to +open the non-existent files. + +:Author: Claude (on behalf of Cail Daley) + +""" + +import numpy as np +import pytest + +from shapepipe.pipeline.file_handler import FileHandler + +RE_PATTERN = r"-\d{3}-\d{3}" +NUM_SCHEME = f"RE:{RE_PATTERN}" + + +def _format_process_list(tmp_path, number_list, scanned): + """Run FileHandler._format_process_list on a bare instance.""" + handler = FileHandler.__new__(FileHandler) + handler._number_list = number_list + handler._verbose = False + + memory_map = str(tmp_path / "match.npy") + np.save(memory_map, np.array(scanned)) + + return handler._format_process_list( + [("image", ".fits", str(tmp_path))], + memory_map, + RE_PATTERN, + NUM_SCHEME, + "parallel", + ) + + +def test_number_list_subset_of_scanned_passes(tmp_path): + """NUMBER_LIST entries found on disk yield exactly those processes.""" + process_list = _format_process_list( + tmp_path, + number_list=["-277-282"], + scanned=["-277-282", "-300-301"], + ) + assert process_list == [ + ["-277-282", f"{tmp_path}/image-277-282.fits"], + ] + + +def test_number_list_typo_fails_early(tmp_path): + """A NUMBER_LIST entry absent from disk must raise at start-up.""" + with pytest.raises(ValueError, match="NUMBER_LIST"): + _format_process_list( + tmp_path, + number_list=["-999-999"], + scanned=["-277-282"], + ) + + +def test_no_number_list_uses_scanned(tmp_path): + """Without NUMBER_LIST the scanned numbers drive the process list.""" + process_list = _format_process_list( + tmp_path, + number_list=None, + scanned=["-277-282"], + ) + assert process_list == [ + ["-277-282", f"{tmp_path}/image-277-282.fits"], + ] From e9473a0b06e89896a1c718537e352c4bcd9971b5 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Thu, 11 Jun 2026 23:09:35 +0200 Subject: [PATCH 76/80] fix(scripts): fail fast if NUMBER_LIST cannot be set; dead -e guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review hardening: set_config_number_list now verifies the key landed in the config copy and aborts otherwise — a config with no NUMBER_LIST line and no bare [FILE] header would previously install an unmodified copy and silently process every image. Also fix init_run_exclusive_canfar.sh's missing-ID guard, which tested an unset variable and never fired. Co-Authored-By: Claude Fable 5 --- scripts/sh/init_run_exclusive_canfar.sh | 6 +++++- scripts/sh/job_sp_canfar.bash | 4 ++++ scripts/sh/job_sp_canfar_v2.0.bash | 4 ++++ src/shapepipe/pipeline/file_handler.py | 8 ++++---- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/sh/init_run_exclusive_canfar.sh b/scripts/sh/init_run_exclusive_canfar.sh index c1de00847..27efadc11 100755 --- a/scripts/sh/init_run_exclusive_canfar.sh +++ b/scripts/sh/init_run_exclusive_canfar.sh @@ -160,6 +160,10 @@ function set_config_number_list() { else perl -pe 's/^\[FILE\][ \t]*$/[FILE]\nNUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" fi + if ! grep -q "^NUMBER_LIST = $number$" "$config_tmp"; then + echo "set_config_number_list: failed to set NUMBER_LIST in $config_orig" >&2 + exit 1 + fi mv "$config_tmp" "$config_upd" } @@ -184,7 +188,7 @@ if [ "$job" == "-1" ]; then message "No job indicated, use option -j" $debug_out 2 fi -if [ "$exclusive" == "-1" ]; then +if [ "$ID" == "-1" ]; then message "No image ID indicated, use option -e" $debug_out 3 fi diff --git a/scripts/sh/job_sp_canfar.bash b/scripts/sh/job_sp_canfar.bash index 3fdaba0ad..c99435823 100755 --- a/scripts/sh/job_sp_canfar.bash +++ b/scripts/sh/job_sp_canfar.bash @@ -354,6 +354,10 @@ function set_config_number_list() { else perl -pe 's/^\[FILE\][ \t]*$/[FILE]\nNUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" fi + if ! grep -q "^NUMBER_LIST = $number$" "$config_tmp"; then + echo "set_config_number_list: failed to set NUMBER_LIST in $config_orig" >&2 + exit 1 + fi mv "$config_tmp" "$config_upd" } diff --git a/scripts/sh/job_sp_canfar_v2.0.bash b/scripts/sh/job_sp_canfar_v2.0.bash index fe2b8f9a5..1c1ce89ba 100755 --- a/scripts/sh/job_sp_canfar_v2.0.bash +++ b/scripts/sh/job_sp_canfar_v2.0.bash @@ -262,6 +262,10 @@ function set_config_number_list() { else perl -pe 's/^\[FILE\][ \t]*$/[FILE]\nNUMBER_LIST = '$number'/' "$config_orig" > "$config_tmp" fi + if ! grep -q "^NUMBER_LIST = $number$" "$config_tmp"; then + echo "set_config_number_list: failed to set NUMBER_LIST in $config_orig" >&2 + exit 1 + fi mv "$config_tmp" "$config_upd" } diff --git a/src/shapepipe/pipeline/file_handler.py b/src/shapepipe/pipeline/file_handler.py index b471449b5..9352248de 100644 --- a/src/shapepipe/pipeline/file_handler.py +++ b/src/shapepipe/pipeline/file_handler.py @@ -1086,10 +1086,10 @@ def _format_process_list( if isinstance(self._number_list, type(None)): number_list = np.load(memory_map, mmap_mode="r") else: - # NUMBER_LIST comes from the config on faith; intersect it - # with the numbers actually found on disk so that a wrong ID - # fails here, at start-up, rather than when a module first - # tries to open the (non-existent) files (#746). + # NUMBER_LIST comes from the config on faith; check every + # entry against the numbers actually found on disk so that a + # wrong ID fails here, at start-up, rather than when a module + # first tries to open the (non-existent) files (#746). number_list = self._number_list scanned = set(np.load(memory_map, mmap_mode="r")) missing = [num for num in number_list if num not in scanned] From 6eb99aab0e654c0609253b2ac02e43b2be85171e Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Fri, 12 Jun 2026 20:39:45 +0200 Subject: [PATCH 77/80] test: candide-aware pytest architecture + two scientific guardrails Consolidate the test suite around one discovery root with three suite-level tiers under tests/, keeping module-unit tests next to the code in src/shapepipe/tests/. The root conftest.py is the single source of markers, candide detection, and the off-cluster skip policy, so the same suite is green on a laptop, in CI, and on the cluster. Markers (declared in pyproject.toml, --strict-markers on): slow heavy compute; excluded from the fast inner loop candide needs the cluster and/or its real data; auto-skipped elsewhere Guardrails: - tests/science/test_mbias.py (fast, local): inject g1=0.02 through make_ngmix_observation / do_ngmix_metacal, build the per-object metacal response R11, assert |m| < 5e-3 in the ideal limit. Recovers g1=0.019964 (m=-1.8e-3). - tests/cluster/test_star_shear_response.py (slow + candide): faithful reproduction of Fabian's R-function. Reads ngmix metacal catalogs (HDU [2]=1p [1]=1m [4]=2p [3]=2m), mask 20, within 0 +/- 0.03. Parametrized outputs base_dir; the SLURM regeneration chain is wired (tests/helpers/cluster.py) but opt-in only, never launched by the suite. Helpers: tests/helpers/{star_response,cluster,artifacts}.py. Artifacts seam: the star-response test emits a plot + status JSON + markdown to tests/_artifacts/ (on pass and fail) for a later GitHub Pages step. Emit only; the deploy is intentionally not built here. Co-Authored-By: Claude Opus 4.8 --- conftest.py | 98 +++++++++++++++- pyproject.toml | 10 +- tests/README.md | 90 +++++++++++++++ tests/_artifacts/.gitignore | 7 ++ tests/_artifacts/README.md | 17 +++ tests/cluster/__init__.py | 1 + tests/cluster/test_star_shear_response.py | 101 +++++++++++++++++ tests/helpers/__init__.py | 1 + tests/helpers/artifacts.py | 129 ++++++++++++++++++++++ tests/helpers/cluster.py | 125 +++++++++++++++++++++ tests/helpers/star_response.py | 127 +++++++++++++++++++++ tests/science/__init__.py | 1 + tests/science/test_mbias.py | 85 ++++++++++++++ 13 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/_artifacts/.gitignore create mode 100644 tests/_artifacts/README.md create mode 100644 tests/cluster/__init__.py create mode 100644 tests/cluster/test_star_shear_response.py create mode 100644 tests/helpers/__init__.py create mode 100644 tests/helpers/artifacts.py create mode 100644 tests/helpers/cluster.py create mode 100644 tests/helpers/star_response.py create mode 100644 tests/science/__init__.py create mode 100644 tests/science/test_mbias.py diff --git a/conftest.py b/conftest.py index 617aa3658..5889c46f9 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,24 @@ -"""Shared pytest configuration for the ShapePipe test suite.""" +"""Shared pytest configuration for the ShapePipe test suite. + +Markers, environment detection, and the candide skip policy live here so +every test module — wherever it sits in the tree — sees the same rules. + +Markers (also declared in ``pyproject.toml`` so ``--strict-markers`` is on): + +* ``slow`` — heavy compute (minutes), not part of the fast inner loop. +* ``candide`` — needs the candide cluster and/or its real on-disk data; + meaningless (and auto-skipped) anywhere else. + +A ``candide``-marked test only runs on a candide node. Everywhere else it +is skipped with a clear reason, so the same suite is green on a laptop, in +CI, and on the cluster — the cluster-only tests simply do not fire off it. +""" import os +import re +import socket + +import pytest from hypothesis import settings @@ -8,3 +26,81 @@ settings.register_profile("ci", derandomize=True, max_examples=50) settings.register_profile("dev", max_examples=200) settings.load_profile(os.environ.get("HYPOTHESIS_PROFILE", "ci")) + + +# --------------------------------------------------------------------------- # +# Candide detection +# --------------------------------------------------------------------------- # + +# Candide compute / login nodes are named c01, c03, n22..n36, etc. The login +# host this suite is most often driven from is ``c03``. We match the candide +# node-name families rather than a fixed list so new nodes are covered, and +# allow an explicit override for CI or odd hostnames. +_CANDIDE_HOST_RE = re.compile(r"^(c\d|n\d{2})", re.IGNORECASE) + + +def on_candide(): + """Return True when running on a candide node. + + The check is, in order: an explicit ``SHAPEPIPE_ON_CANDIDE`` override + (``1``/``0``), then the hostname against the candide node-name families + (``c0x`` login, ``nXX`` compute). Cheap, import-safe, no cluster calls. + """ + override = os.environ.get("SHAPEPIPE_ON_CANDIDE") + if override is not None: + return override == "1" + return bool(_CANDIDE_HOST_RE.match(socket.gethostname())) + + +# --------------------------------------------------------------------------- # +# Marker registration + skip policy +# --------------------------------------------------------------------------- # + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "slow: heavy compute (minutes); excluded from the fast inner loop.", + ) + config.addinivalue_line( + "markers", + "candide: needs the candide cluster and/or its real data; " + "auto-skipped elsewhere.", + ) + + +def pytest_collection_modifyitems(config, items): + """Skip ``candide`` tests off-cluster. + + Collection still happens everywhere — so ``pytest --collect-only`` shows + the cluster tests exist — they are just marked skipped at run time when + not on candide. + """ + if on_candide(): + return + skip_candide = pytest.mark.skip( + reason="needs candide (set SHAPEPIPE_ON_CANDIDE=1 to force)" + ) + for item in items: + if "candide" in item.keywords: + item.add_marker(skip_candide) + + +# --------------------------------------------------------------------------- # +# Shared fixtures +# --------------------------------------------------------------------------- # + + +@pytest.fixture(scope="session") +def artifacts_dir(): + """Directory where guardrail tests drop plots + status summaries. + + The artifacts SEAM: a later GitHub Pages / status step publishes from + here. Created on demand so a clean checkout has nothing to commit until + a test actually emits. + """ + from pathlib import Path + + path = Path(__file__).parent / "tests" / "_artifacts" + path.mkdir(parents=True, exist_ok=True) + return path diff --git a/pyproject.toml b/pyproject.toml index d8c950140..362db2fb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,5 +103,13 @@ script-files = [ where = ["src"] [tool.pytest.ini_options] -addopts = "--verbose --cov=shapepipe --cov-report=term-missing" +# Two homes, one discovery root. `src/shapepipe/tests/` holds module-unit +# tests next to the code they cover (src-layout idiom); `tests/` holds the +# suite-level tiers — `unit/` (structural), `science/` (fast guardrails), +# `cluster/` (candide, marked `candide`+`slow`). See tests/README.md. +addopts = "--verbose --strict-markers --cov=shapepipe --cov-report=term-missing" testpaths = ["tests", "src/shapepipe/tests"] +markers = [ + "slow: heavy compute (minutes); excluded from the fast inner loop.", + "candide: needs the candide cluster and/or its real data; auto-skipped elsewhere.", +] diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..59ee43d30 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,90 @@ +# ShapePipe test suite + +Two homes, one discovery root, three tiers of guardrail. Everything is driven +by `pytest` from the repo root (in the dev container — see the project +`CLAUDE.md`); `pyproject.toml` `[tool.pytest.ini_options]` carries the config. + +## Where tests live + +| Location | Holds | Why here | +|----------|-------|----------| +| `src/shapepipe/tests/` | **module-unit tests** — the fitter, file handler, split-exp, vignetmaker, ngmix internals, the GalSim weight-validation suite | next to the code they cover (src-layout idiom); these import package internals directly | +| `tests/unit/` | **structural tests** — every submodule imports, configs parse, shell scripts lint, runner metadata is well-formed, console entry points respond to `-h` | suite-level checks on the *tree*, not any one module | +| `tests/science/` | **fast scientific guardrails** — controlled simulations with a known answer, runnable in the inner loop with nothing from the cluster | scientific correctness that must stay green on every commit | +| `tests/cluster/` | **candide guardrails** — read real on-disk catalogs / submit cluster jobs | need the cluster + real data; marked and auto-skipped off it | +| `tests/helpers/` | shared, non-test library code (cluster submission, artifact emission, the star-response R-function) | imported by tests; not collected as tests | +| `tests/_artifacts/` | plots + status JSON/markdown emitted by guardrail tests | the seam a later GitHub Pages step publishes from | + +Both `testpaths` are discovered together, so a bare `pytest` runs the whole +suite. `conftest.py` at the repo root is the single source of markers, +environment detection, and the candide skip policy — it applies everywhere. + +## Markers + +``` +slow heavy compute (minutes); excluded from the fast inner loop +candide needs the candide cluster and/or its real data; auto-skipped elsewhere +``` + +`--strict-markers` is on, so a typo'd marker is an error, not a silent no-op. + +A `candide`-marked test is **collected everywhere** (so `--collect-only` shows +it exists) but **skipped off-cluster** with a clear reason. Candide is detected +by hostname (`c0x` login, `nXX` compute nodes) via `on_candide()` in +`conftest.py`; override with `SHAPEPIPE_ON_CANDIDE=0/1`. + +## Running + +```bash +pytest # full suite (cluster tests skip off-candide) +pytest -m "not slow" # fast inner loop +pytest tests/science # just the fast scientific guardrails +pytest tests/science/test_mbias.py # the m-bias guardrail alone + +# cluster guardrails (on candide, or forced): +SHAPEPIPE_ON_CANDIDE=1 pytest tests/cluster +``` + +## The guardrail tests + +### m-bias (`tests/science/test_mbias.py`) — fast, local + +Injects a known shear `g1 = 0.02` through `make_ngmix_observation` / +`do_ngmix_metacal`, builds the per-object metacal response +`R11 = (g1_1p - g1_1m) / (2·step)`, and asserts the response-corrected +multiplicative bias `|m| < 5e-3` in the ideal (low-noise) limit. The controlled +path recovers `g1 ≈ 0.01996` vs 0.02 injected (`m ≈ 2e-3`); a broken response +correction or dropped deconvolution blows `|m|` past tolerance in seconds. + +### Star shear-response (`tests/cluster/test_star_shear_response.py`) — slow + candide + +Faithful reproduction of Fabian's R-function (handoff 2026-06-12). Reads ngmix +metacal catalogs at +`output_*/run_sp_tile_ngmix_Ng1u_*/ngmix_runner/output/ngmix-*.fits` +(HDU map `[2]=1p [1]=1m [4]=2p [3]=2m`), masks `20 < mag < 26`, computes +`R1 = (g1_1p − g1_1m)/0.02`, `R2 = (g2_2p − g2_2m)/0.02` with a 100-group +jackknife error, and asserts ``, `` within `0 ± 0.03` — deconvolution +should leave stars no net shear response. + +The outputs `base_dir` is parametrized (`SHAPEPIPE_STAR_GRID_OUTPUTS`, default +`/home/hervas/n25/SP_simu_fab/SP_1z2z_star_grid/outputs/`). By default it +evaluates an **existing** outputs dir (seconds). Regenerating those outputs is +a multi-hour SLURM job chain +(`tile_launcher → job_per_tile_newversion → job_sp_14`, through the container) +— wired via `tests/helpers/cluster.py` but **opt-in only** +(`SHAPEPIPE_REGENERATE_STAR_GRID=1` plus `SHAPEPIPE_STAR_GRID_TILES`). The test +never launches a heavy job on its own. + +Both R1/R2 assertions share a module-scoped fixture that also **emits +artifacts** (plot + JSON + markdown) to `tests/_artifacts/` — on pass *and* +fail — so the status surface always reflects the latest run. + +## Helpers + +- `tests/helpers/star_response.py` — Fabian's R-function as a library + (`compute_star_response`, `find_ngmix_files`, `tile_responses`, + `jackknife_error`). +- `tests/helpers/cluster.py` — `srun` / `sbatch` / `submit_star_grid_chain` / + `wait_for_jobs`, the SLURM submission seam. Import-safe off-cluster. +- `tests/helpers/artifacts.py` — `emit_star_response_artifacts`, the GitHub + Pages publish seam (emit only; the deploy is intentionally elsewhere). diff --git a/tests/_artifacts/.gitignore b/tests/_artifacts/.gitignore new file mode 100644 index 000000000..4f8ebce37 --- /dev/null +++ b/tests/_artifacts/.gitignore @@ -0,0 +1,7 @@ +# Guardrail tests emit plots + status (JSON/markdown) here at run time. +# This is the publish SEAM: a later GitHub Pages step reads this directory. +# The directory is tracked (so the seam exists on a clean checkout); the +# generated artifacts themselves are not. +* +!.gitignore +!README.md diff --git a/tests/_artifacts/README.md b/tests/_artifacts/README.md new file mode 100644 index 000000000..1a416005c --- /dev/null +++ b/tests/_artifacts/README.md @@ -0,0 +1,17 @@ +# tests/_artifacts — the publish seam + +Guardrail tests emit status artifacts here at run time: + +- `.png` — distribution plot (e.g. R1/R2 with jackknife band) +- `.json` — scalar status (metric values, errors, `PASS`/`FAIL`) +- `.md` — short human-readable summary + +A later GitHub Pages (or other status) step reads this directory and +publishes. **That deploy is intentionally not built here** — this is the emit +side of the seam only. The star shear-response test +(`tests/cluster/test_star_shear_response.py`) is the first producer, via +`tests/helpers/artifacts.py::emit_star_response_artifacts`. + +Generated files are git-ignored (see `.gitignore`); only this README and the +ignore rule are tracked, so the seam exists on a clean checkout with nothing +stale committed. diff --git a/tests/cluster/__init__.py b/tests/cluster/__init__.py new file mode 100644 index 000000000..8885c0606 --- /dev/null +++ b/tests/cluster/__init__.py @@ -0,0 +1 @@ +"""Candide cluster guardrail tests (marked ``candide``; auto-skipped off-cluster).""" diff --git a/tests/cluster/test_star_shear_response.py b/tests/cluster/test_star_shear_response.py new file mode 100644 index 000000000..f2885bb09 --- /dev/null +++ b/tests/cluster/test_star_shear_response.py @@ -0,0 +1,101 @@ +"""Headline guardrail: stars must carry no net metacal shear response. + +Metacal deconvolves the PSF before applying the shear-response derivative. +Done right, a point source has no shape to respond to, so the per-object +response ``R = dg/dgamma`` averages to zero. A non-zero ```` is a direct +readout of PSF leakage into the response — the failure under active debugging +(Fabian handoff, 2026-06-12). Below mag 20 it is badly broken (R ~ 1, large +R1/R2 asymmetry); above mag 20 it is ~ok, so the metric masks ``20 < mag < 26``. + +Target: ````, ```` within ``0 +/- 0.03``. + +Marked ``slow`` + ``candide``: it reads real ngmix metacal catalogs on +candide. The default evaluates an EXISTING outputs dir (cheap — seconds). A +full regeneration of those outputs (the SLURM job chain, +``tile_launcher -> job_per_tile_newversion -> job_sp_14`` through the +container) is multi-hour and is wired but NOT run by the suite; opt in with +``SHAPEPIPE_REGENERATE_STAR_GRID=1``. + +Run on demand: + + SHAPEPIPE_ON_CANDIDE=1 pytest tests/cluster/test_star_shear_response.py + +Emits to ``tests/_artifacts/``: an R1/R2 distribution plot, a status JSON, +and a markdown summary — the seam a later GitHub Pages step publishes from. +""" + +import os + +import pytest + +from tests.helpers.artifacts import emit_star_response_artifacts +from tests.helpers.star_response import DEFAULT_BASE_DIR, compute_star_response + + +pytestmark = [pytest.mark.slow, pytest.mark.candide] + + +def _base_dir(): + """Outputs dir to evaluate — overridable for alternate runs / regen.""" + return os.environ.get("SHAPEPIPE_STAR_GRID_OUTPUTS", DEFAULT_BASE_DIR) + + +@pytest.fixture(scope="module") +def star_response(artifacts_dir): + """Compute the metric (regenerating outputs first only if asked). + + Emits artifacts as a side effect — including on a FAIL — so the status + surface always reflects the latest run, not only green ones. + """ + base_dir = _base_dir() + + if os.environ.get("SHAPEPIPE_REGENERATE_STAR_GRID") == "1": + from tests.helpers.cluster import ( + slurm_available, + submit_star_grid_chain, + wait_for_jobs, + ) + + if not slurm_available(): + pytest.skip("regeneration requested but SLURM tools not on PATH") + tiles = os.environ.get("SHAPEPIPE_STAR_GRID_TILES", "").split() + if not tiles: + pytest.skip("set SHAPEPIPE_STAR_GRID_TILES to regenerate") + wait_for_jobs(submit_star_grid_chain(tiles)) + + summary = compute_star_response(base_dir) + status = ( + "PASS" + if abs(summary["R1_mean"]) < summary["tolerance"] + and abs(summary["R2_mean"]) < summary["tolerance"] + else "FAIL" + ) + emit_star_response_artifacts( + artifacts_dir, + {**summary, "status": status}, + R1=summary["R1"], + R2=summary["R2"], + ) + return summary + + +def test_star_response_r1_consistent_with_zero(star_response): + """```` within tolerance of zero (no net PSF leakage into R1).""" + assert abs(star_response["R1_mean"]) < star_response["tolerance"], ( + f" = {star_response['R1_mean']:.6f} " + f"+/- {star_response['R1_jk_err']:.6f} " + f"exceeds +/-{star_response['tolerance']} " + f"(N={star_response['n_obj']}, {star_response['n_tiles']} tiles): " + "stars carry a net metacal shear response in g1 -> PSF leakage." + ) + + +def test_star_response_r2_consistent_with_zero(star_response): + """```` within tolerance of zero (no net PSF leakage into R2).""" + assert abs(star_response["R2_mean"]) < star_response["tolerance"], ( + f" = {star_response['R2_mean']:.6f} " + f"+/- {star_response['R2_jk_err']:.6f} " + f"exceeds +/-{star_response['tolerance']} " + f"(N={star_response['n_obj']}, {star_response['n_tiles']} tiles): " + "stars carry a net metacal shear response in g2 -> PSF leakage." + ) diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 000000000..57b2ccf34 --- /dev/null +++ b/tests/helpers/__init__.py @@ -0,0 +1 @@ +"""Test helpers: cluster submission, artifact emission, candide data paths.""" diff --git a/tests/helpers/artifacts.py b/tests/helpers/artifacts.py new file mode 100644 index 000000000..a9e966fa0 --- /dev/null +++ b/tests/helpers/artifacts.py @@ -0,0 +1,129 @@ +"""Artifact emission for guardrail tests — the GitHub Pages publish SEAM. + +A guardrail test calls :func:`emit_star_response_artifacts` to drop three +files into ``tests/_artifacts/``: + +* ``.png`` — the R1/R2 distribution + jackknife-error figure. +* ``.json`` — machine-readable status (metric values, errors, pass/fail). +* ``.md`` — a short human-readable status summary. + +This module *only emits*. A later CI step reads ``tests/_artifacts/`` and +publishes to Pages — that deploy is deliberately NOT built here. Keeping the +emit and the publish separate means the test can run (and the artifacts are +inspectable) with zero web infrastructure. + +Plotting follows the repo's seaborn-first convention but degrades gracefully: +if seaborn/matplotlib are unavailable the figure is skipped and the JSON/MD +status still lands, so the seam never blocks a result from being recorded. +""" + +import json +from datetime import datetime, timezone +from pathlib import Path + + +def _plot_star_response(png_path, R1, R2, summary): + """R1/R2 histograms with jackknife mean +/- error bands. Best-effort.""" + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + import numpy as np + + try: + import seaborn as sns + + sns.set_theme(style="whitegrid") + sns.set_palette("husl", 2) + except ImportError: + pass + + fig, axes = plt.subplots(1, 2, figsize=(10, 4), sharey=True) + for ax, R, label, mean, err in ( + (axes[0], R1, "R1", summary["R1_mean"], summary["R1_jk_err"]), + (axes[1], R2, "R2", summary["R2_mean"], summary["R2_jk_err"]), + ): + R = np.asarray(R) + # Clip the view to the informative core: per-object R has heavy + # tails (fit failures reach |R| ~ 80) that would crush the bulk. + lo, hi = np.percentile(R, [1, 99]) + ax.hist(R, bins=80, range=(lo, hi), histtype="stepfilled", alpha=0.5) + ax.axvline(0.0, color="k", lw=1, ls="--", label="target 0") + ax.axvline(mean, color="C3", lw=2, label=f"mean {label} = {mean:.4f}") + ax.axvspan(mean - err, mean + err, color="C3", alpha=0.2, + label=f"jk +/- {err:.4f}") + ax.set_xlim(lo, hi) + ax.set_xlabel(label) + ax.set_title(f"mean {label} = {mean:.4f} +/- {err:.4f}") + ax.legend(fontsize=8) + axes[0].set_ylabel("count") + fig.suptitle( + f"Star shear response (N={summary['n_obj']}, " + f"tol={summary['tolerance']}) -- {summary['status']}" + ) + fig.tight_layout() + fig.savefig(png_path, dpi=120) + plt.close(fig) + return str(png_path) + except Exception as exc: # noqa: BLE001 — emit must never crash the test + return f"plot skipped: {exc!r}" + + +def emit_star_response_artifacts(artifacts_dir, summary, R1=None, R2=None, + name="star_shear_response"): + """Write JSON + markdown status and (when data given) the figure. + + Parameters + ---------- + artifacts_dir : Path + Target directory (the ``artifacts_dir`` fixture). + summary : dict + Must carry ``R1_mean``, ``R2_mean``, ``R1_jk_err``, ``R2_jk_err``, + ``n_obj``, ``tolerance``, ``status`` (``"PASS"``/``"FAIL"``), and + ``base_dir``. + R1, R2 : array-like, optional + Per-object responses for the histogram. Plot skipped if omitted. + name : str + Artifact basename. + + Returns + ------- + dict + Paths of the artifacts written (``json``, ``md``, ``png``). + """ + artifacts_dir = Path(artifacts_dir) + artifacts_dir.mkdir(parents=True, exist_ok=True) + summary = {**summary, "generated_utc": datetime.now(timezone.utc).isoformat()} + + # Status JSON is scalar-only: the per-object R1/R2 arrays are for the plot, + # not the machine-readable summary a Pages step ingests. + status_payload = { + k: v for k, v in summary.items() if k not in ("R1", "R2") + } + json_path = artifacts_dir / f"{name}.json" + json_path.write_text(json.dumps(status_payload, indent=2, default=str) + "\n") + + md_path = artifacts_dir / f"{name}.md" + md_path.write_text( + f"# Star shear-response guardrail — {summary['status']}\n\n" + f"- generated: `{summary['generated_utc']}`\n" + f"- outputs: `{summary['base_dir']}`\n" + f"- N objects: {summary['n_obj']}\n" + f"- tolerance: |R| < {summary['tolerance']}\n\n" + f"| metric | value | jackknife error |\n" + f"|--------|-------|-----------------|\n" + f"| `` | {summary['R1_mean']:.6f} | {summary['R1_jk_err']:.6f} |\n" + f"| `` | {summary['R2_mean']:.6f} | {summary['R2_jk_err']:.6f} |\n\n" + f"Target (Fabian, 2026-06-12): `R1 = R2 = 0 +/- 0.03`. " + f"Deconvolution should leave no net star shear response.\n" + ) + + png_path = artifacts_dir / f"{name}.png" + plot_result = ( + _plot_star_response(png_path, R1, R2, summary) + if R1 is not None and R2 is not None + else "plot skipped: no per-object data passed" + ) + + return {"json": str(json_path), "md": str(md_path), "png": plot_result} diff --git a/tests/helpers/cluster.py b/tests/helpers/cluster.py new file mode 100644 index 000000000..c7695fcbf --- /dev/null +++ b/tests/helpers/cluster.py @@ -0,0 +1,125 @@ +"""Small SLURM submission helper for candide cluster tests. + +Cluster guardrail tests have two phases: *regenerate* (submit the ShapePipe +job chain via SLURM, wait for the outputs) and *evaluate* (run the science +assertion against an outputs directory). This module owns the first phase so +the test bodies stay about the science. + +Nothing here imports shapepipe — it is pure ``subprocess`` around ``srun`` / +``sbatch`` / ``squeue`` / ``sacct``, so it is import-safe off-cluster and the +tests that use it are guarded by the ``candide`` marker anyway. + +The default star-grid job chain (per Fabian's handoff, 2026-06-12): + + tile_launcher.job + -> job_per_tile_newversion.job (sbatch, one per tile) + -> SP_1z2z_star_grid/job_sp_14.job (through the container) + +A full regeneration is a multi-hour, multi-job affair; this helper is the +seam to drive it on demand, NOT something the test suite runs by default. +""" + +import shutil +import subprocess +import time +from pathlib import Path + + +SP_SIMU_ROOT = Path("/home/hervas/n25/SP_simu_fab") +STAR_GRID_ROOT = SP_SIMU_ROOT / "SP_1z2z_star_grid" + + +def slurm_available(): + """True when the SLURM client tools are on PATH (i.e. on candide).""" + return shutil.which("sbatch") is not None and shutil.which("squeue") is not None + + +def srun(args, *, partition="comp", time_limit="00:30:00", cpus=4, extra=None): + """Run a command synchronously on a compute node via ``srun``. + + Blocks until the step finishes. Use for short, single-step cluster work + (e.g. running the R-function evaluation against existing outputs on a + node). Returns the ``CompletedProcess``; raises on non-zero exit. + + Parameters + ---------- + args : list of str + Command + arguments to execute on the node. + partition : str + SLURM partition (``comp`` / ``pscomp`` on candide). + time_limit : str + Wall-clock limit, ``HH:MM:SS``. + cpus : int + ``--cpus-per-task``. + extra : list of str, optional + Extra ``srun`` flags (e.g. ``["--exclude", "n09,n17,n36"]``). + """ + cmd = [ + "srun", + f"--partition={partition}", + f"--time={time_limit}", + f"--cpus-per-task={cpus}", + *(extra or ["--exclude", "n09,n17,n36"]), + *args, + ] + return subprocess.run(cmd, check=True, text=True, capture_output=True) + + +def sbatch(job_script, *job_args, dependency=None): + """Submit a batch job and return its job id (str). + + Parameters + ---------- + job_script : str or Path + Path to the ``.job`` / ``.sbatch`` script. + *job_args + Positional arguments appended after the script path. + dependency : str, optional + A SLURM dependency spec, e.g. ``afterok:12345``. + """ + cmd = ["sbatch", "--parsable"] + if dependency is not None: + cmd.append(f"--dependency={dependency}") + cmd.extend([str(job_script), *map(str, job_args)]) + out = subprocess.run(cmd, check=True, text=True, capture_output=True).stdout + # --parsable prints "" or ";" + return out.strip().split(";")[0] + + +def submit_star_grid_chain(tiles, *, launcher=None): + """Submit the star-grid job chain for a list of tile ids. + + Mirrors ``tile_launcher.job``: one ``job_per_tile_newversion.job`` per + tile id. Returns the list of submitted job ids. Does NOT wait — pair + with :func:`wait_for_jobs`. + + This is the heavy path (each tile is a multi-hour job). It is wired so a + cluster test can regenerate outputs on demand; the suite never calls it + automatically. + """ + launcher = Path(launcher) if launcher else SP_SIMU_ROOT / "job_per_tile_newversion.job" + if not launcher.exists(): + raise FileNotFoundError(f"launcher job script not found: {launcher}") + return [sbatch(launcher, tile) for tile in tiles] + + +def wait_for_jobs(job_ids, *, poll=60, timeout=4 * 3600): + """Block until every job id has left the SLURM queue. + + Polls ``squeue`` for the given ids. Returns when none remain pending or + running, or raises ``TimeoutError`` after ``timeout`` seconds. + """ + deadline = time.monotonic() + timeout + ids = set(map(str, job_ids)) + while time.monotonic() < deadline: + out = subprocess.run( + ["squeue", "--jobs", ",".join(ids), "--noheader", "--format=%i"], + check=False, + text=True, + capture_output=True, + ).stdout + still_running = {line.strip() for line in out.splitlines() if line.strip()} + if not (still_running & ids): + return + time.sleep(poll) + raise TimeoutError(f"jobs {ids} did not finish within {timeout}s") diff --git a/tests/helpers/star_response.py b/tests/helpers/star_response.py new file mode 100644 index 000000000..90893b352 --- /dev/null +++ b/tests/helpers/star_response.py @@ -0,0 +1,127 @@ +"""Star shear-response metric — Fabian's R-function, faithfully. + +The deconvolution step in metacalibration should leave stars with *no* net +shear response: applied to point sources, the per-object metacal response +``R = dg/dgamma`` must average to zero. A non-zero ```` means PSF handling +is leaking shape into the response — the very failure being debugged (see the +Fabian handoff, 2026-06-12). + +This module reproduces Fabian's exact computation as a library function so it +can be both unit-tested and run on demand: + +* ngmix metacal catalogs at + ``output_*/run_sp_tile_ngmix_Ng1u_*/ngmix_runner/output/ngmix-*.fits`` +* HDU map (load-bearing): ``[2]=1p, [1]=1m, [4]=2p, [3]=2m`` +* mask ``20 < mag < 26`` +* ``R1 = (g1_1p - g1_1m) / 0.02``, ``R2 = (g2_2p - g2_2m) / 0.02`` +* 100-group jackknife for the error on ```` + +The defaults match the handoff so a bare call reproduces Fabian's number. +""" + +import glob +import os + +import numpy as np +from astropy.io import fits + + +DEFAULT_BASE_DIR = "/home/hervas/n25/SP_simu_fab/SP_1z2z_star_grid/outputs/" +METACAL_STEP = 0.02 +MAG_LO, MAG_HI = 20.0, 26.0 +N_JACKKNIFE = 100 + +# HDU index -> metacal type. Load-bearing: this is the on-disk ordering of the +# ngmix_runner output (verified against ngmix-*.fits: [1]=1M [2]=1P [3]=2M [4]=2P). +HDU = {"1p": 2, "1m": 1, "2p": 4, "2m": 3} + + +def find_ngmix_files(base_dir=DEFAULT_BASE_DIR): + """Latest ngmix FITS per ``output_*`` tile dir, in Fabian's order. + + Returns ``[(tile_id, path), ...]``. Tiles with no run dir / no FITS are + skipped silently — matching the handoff's ``continue`` behaviour. + """ + found = [] + for output_dir in sorted(glob.glob(os.path.join(base_dir, "output_*"))): + run_dirs = sorted(glob.glob( + os.path.join(output_dir, "run_sp_tile_ngmix_Ng1u_*") + )) + if not run_dirs: + continue + ngmix_files = sorted(glob.glob( + os.path.join(run_dirs[-1], "ngmix_runner/output/ngmix-*.fits") + )) + if not ngmix_files: + continue + ngmix_file = ngmix_files[-1] + tile_id = os.path.basename(ngmix_file).replace("ngmix-", "").replace(".fits", "") + found.append((tile_id, ngmix_file)) + return found + + +def tile_responses(ngmix_file): + """Per-object ``(R1, R2)`` for one tile, masked to ``20 < mag < 26``.""" + with fits.open(ngmix_file) as cat: + c1p, c1m = cat[HDU["1p"]].data, cat[HDU["1m"]].data + c2p, c2m = cat[HDU["2p"]].data, cat[HDU["2m"]].data + mask = (c1m["mag"] < MAG_HI) & (c1m["mag"] > MAG_LO) + R1 = (c1p["g1"][mask] - c1m["g1"][mask]) / METACAL_STEP + R2 = (c2p["g2"][mask] - c2m["g2"][mask]) / METACAL_STEP + return R1, R2 + + +def jackknife_error(values, n_groups=N_JACKKNIFE): + """Delete-d jackknife std of the mean over ``n_groups`` contiguous groups.""" + n = len(values) + groups = np.array_split(np.arange(n), n_groups) + jk = np.empty(n_groups) + for i, idx in enumerate(groups): + keep = np.ones(n, dtype=bool) + keep[idx] = False + jk[i] = np.average(values[keep]) + return np.sqrt((n_groups - 1) / n_groups * np.sum((jk - jk.mean()) ** 2)) + + +def compute_star_response(base_dir=DEFAULT_BASE_DIR, n_jackknife=N_JACKKNIFE): + """Full star shear-response metric across all tiles under ``base_dir``. + + Returns + ------- + dict + ``R1`` / ``R2`` (per-object arrays), ``R1_mean`` / ``R2_mean``, + ``R1_jk_err`` / ``R2_jk_err``, ``n_obj``, ``n_tiles``, ``base_dir``, + ``tolerance``. Raises ``FileNotFoundError`` if no FITS are found and + ``ValueError`` if fewer objects than jackknife groups. + """ + files = find_ngmix_files(base_dir) + if not files: + raise FileNotFoundError( + f"no ngmix-*.fits under {base_dir}/output_*/run_sp_tile_ngmix_Ng1u_*" + ) + + R1_all = np.array([]) + R2_all = np.array([]) + for _tile_id, ngmix_file in files: + R1, R2 = tile_responses(ngmix_file) + R1_all = np.append(R1_all, R1) + R2_all = np.append(R2_all, R2) + + n_obj = len(R1_all) + if n_obj < n_jackknife: + raise ValueError( + f"only {n_obj} objects; need >= {n_jackknife} for jackknife" + ) + + return { + "R1": R1_all, + "R2": R2_all, + "R1_mean": float(np.average(R1_all)), + "R2_mean": float(np.average(R2_all)), + "R1_jk_err": float(jackknife_error(R1_all, n_jackknife)), + "R2_jk_err": float(jackknife_error(R2_all, n_jackknife)), + "n_obj": int(n_obj), + "n_tiles": len(files), + "base_dir": str(base_dir), + "tolerance": 0.03, + } diff --git a/tests/science/__init__.py b/tests/science/__init__.py new file mode 100644 index 000000000..4c9eef13a --- /dev/null +++ b/tests/science/__init__.py @@ -0,0 +1 @@ +"""Scientific guardrail tests — fast, local, no cluster or real-data deps.""" diff --git a/tests/science/test_mbias.py b/tests/science/test_mbias.py new file mode 100644 index 000000000..347bb5be0 --- /dev/null +++ b/tests/science/test_mbias.py @@ -0,0 +1,85 @@ +"""Multiplicative-bias guardrail: inject known shear, recover after R. + +The headline number for any shear pipeline is the multiplicative bias ``m``, +where the recovered shear relates to the truth as ``g_rec = (1 + m) g_true``. +This test exercises the full module shape-measurement path on a controlled +simulation: a galaxy with a *known* injected shear ``g1 = 0.02`` is pushed +through ``make_ngmix_observation`` and ``do_ngmix_metacal``, the per-object +metacal response ``R11`` is built from the ``1p``/``1m`` shifted images, and +the response-corrected shear is compared to truth. + +This is the ideal limit: tiny noise, Moffat PSF, exponential galaxy, the same +``make_data`` simulator the ngmix reproducibility test uses. With deconvolution +and response correction wired correctly the recovered ``g1`` lands at ~0.01996 +against the injected 0.02 — ``|m| ~ 2e-3``. The assertion holds ``|m|`` below a +few x 1e-3, so a regression that breaks the response correction (e.g. drops the +deconvolution, mis-scales the metacal step) shows up as an ``m`` blowout here, +in seconds, with nothing from the cluster. + +Fast + local: marked neither ``slow`` nor ``candide``; part of the inner loop. +""" + +import numpy as np +import pytest + + +INJECTED_G1 = 0.02 +METACAL_STEP = 0.01 # ngmix MetacalBootstrapper default shear step +M_TOL = 5e-3 # |m| in the ideal limit; recovered g1 ~ 0.01996 -> m ~ 2e-3 + + +def _recover_g1_with_response(seed=42): + """Inject g1=0.02, return response-corrected recovered g1. + + Builds the per-object metacal response ``R11 = (g1_1p - g1_1m)/(2*step)`` + from the shifted-image results and corrects the noshear estimate: + ``g1_corrected = g1_noshear / R11``. This is the same response correction + the catalog-level pipeline applies, exercised on one controlled stamp. + """ + from shapepipe.modules.ngmix_package.ngmix import ( + Postage_stamp, + do_ngmix_metacal, + get_prior, + ) + from shapepipe.testing.simulate import make_data + + rng = np.random.RandomState(seed) + prior = get_prior(0.1857, rng) + gals, psfs, _, weights, flags, jacobs = make_data( + rng=np.random.RandomState(123), + shear=(INJECTED_G1, 0.0), + noise=1e-4, + n_epochs=2, + img_size=51, + ) + stamp = Postage_stamp(bkg_sub=False, megacam_flip=False) + stamp.gals, stamp.psfs, stamp.weights, stamp.flags, stamp.jacobs = ( + gals, psfs, weights, flags, jacobs, + ) + res, _ = do_ngmix_metacal(stamp, prior, 1.0, rng) + + g1_noshear = res["noshear"]["g"][0] + R11 = (res["1p"]["g"][0] - res["1m"]["g"][0]) / (2 * METACAL_STEP) + return g1_noshear / R11, R11 + + +def test_mbias_ideal_limit_below_few_e3(): + """Response-corrected ``m`` is within a few x 1e-3 in the ideal limit. + + The controlled-shear path that already recovers g1 ~ 0.01996 vs 0.02 + injected, made into an explicit ``m`` assertion. A broken response + correction or deconvolution pushes ``|m|`` well past the tolerance. + """ + g1_recovered, R11 = _recover_g1_with_response() + m = g1_recovered / INJECTED_G1 - 1.0 + + assert np.isfinite(R11) and R11 > 0.1, ( + f"metacal response R11 = {R11:.4f} is degenerate; " + "response correction cannot be trusted" + ) + assert abs(m) < M_TOL, ( + f"multiplicative bias m = {m:.5f} " + f"(recovered g1 = {g1_recovered:.5f} vs injected {INJECTED_G1}) " + f"exceeds |m| < {M_TOL:.0e} in the ideal limit -> " + "response correction / deconvolution regression" + ) From 71f217bab3c141431bcaf4c3a265d0398d2bf5a8 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Fri, 12 Jun 2026 21:14:40 +0200 Subject: [PATCH 78/80] test: consolidate the suite under one discovery root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The suite lived in two trees — top-level `tests/` (unit/science/cluster/ helpers) and `src/shapepipe/tests/` (the per-module tests). Fold the module tests into `tests/module/` so there is a single canonical location, discovered from one `testpaths` root. - Relocate the 12 `src/shapepipe/tests/*.py` modules to `tests/module/` (pure moves — their imports are already absolute `from shapepipe...`, so no source edits and no packaging change were needed; nothing referenced `shapepipe.tests`). - Make `tests/` a package (`tests/__init__.py`, `tests/module/__init__.py`). The cluster guardrail imports shared code as `tests.helpers.*`; that only resolves once `tests` itself is importable, which also removes a latent collection error in the harness branch. - `pyproject.toml`: `testpaths = ["tests"]` (was `["tests", "src/shapepipe/tests"]`). - Document the single layout in `tests/README.md`; fix the stale two-tree description in `docs/source/testing.md` and the project `CLAUDE.md`. Full suite collects from one root with no errors; the fast tier (`-m "not slow and not candide"`) is green (276 passed, 5 skipped). Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 5 ++++- docs/source/testing.md | 22 +++++++++++-------- pyproject.toml | 11 +++++----- tests/README.md | 19 +++++++++------- tests/__init__.py | 6 +++++ tests/module/__init__.py | 7 ++++++ .../module}/test_file_handler.py | 0 .../tests => tests/module}/test_get_images.py | 0 .../tests => tests/module}/test_ngmix.py | 0 .../module}/test_ngmix_weight_validation.py | 0 .../tests => tests/module}/test_pipeline.py | 0 .../module}/test_psfex_interp.py | 0 .../tests => tests/module}/test_run.py | 0 .../module}/test_sextractor_post_process.py | 0 .../tests => tests/module}/test_split_exp.py | 0 .../module}/test_tpv_external_tools.py | 0 .../tests => tests/module}/test_utilities.py | 0 .../module}/test_vignetmaker.py | 0 18 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/module/__init__.py rename {src/shapepipe/tests => tests/module}/test_file_handler.py (100%) rename {src/shapepipe/tests => tests/module}/test_get_images.py (100%) rename {src/shapepipe/tests => tests/module}/test_ngmix.py (100%) rename {src/shapepipe/tests => tests/module}/test_ngmix_weight_validation.py (100%) rename {src/shapepipe/tests => tests/module}/test_pipeline.py (100%) rename {src/shapepipe/tests => tests/module}/test_psfex_interp.py (100%) rename {src/shapepipe/tests => tests/module}/test_run.py (100%) rename {src/shapepipe/tests => tests/module}/test_sextractor_post_process.py (100%) rename {src/shapepipe/tests => tests/module}/test_split_exp.py (100%) rename {src/shapepipe/tests => tests/module}/test_tpv_external_tools.py (100%) rename {src/shapepipe/tests => tests/module}/test_utilities.py (100%) rename {src/shapepipe/tests => tests/module}/test_vignetmaker.py (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 723439ace..5d1994716 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,7 +56,10 @@ Full detail: `docs/source/installation.md` and `docs/source/container.md`. I/O; `utilities/`; `canfar/` is CANFAR/cluster job orchestration. Console entry points (`shapepipe_run`, `summary_run`, `canfar_*`) are defined under `[project.scripts]`. -- `src/shapepipe/tests/` — unit tests; `tests/unit/` holds newer test scaffolding. +- `tests/` — the whole test suite, one discovery root: `module/` (per-module + unit/property/integration tests), `unit/` (structural), `science/` (fast + guardrails), `cluster/` (candide-only), `helpers/` (shared library code). + See `tests/README.md`. - `example/` — a runnable example pipeline (`example/config.ini`) on a single CFIS tile; doubles as the CI smoke test. - `scripts/` — shell / Python / notebook helpers (`sh/`, `python/`, `jupyter/`), diff --git a/docs/source/testing.md b/docs/source/testing.md index 6d04b023a..2e21549ef 100644 --- a/docs/source/testing.md +++ b/docs/source/testing.md @@ -6,22 +6,26 @@ the bundled example pipeline. ## The automated test suite -The test suite runs with [pytest](https://docs.pytest.org/) and lives in two -trees: +The test suite runs with [pytest](https://docs.pytest.org/) under a single +discovery root, `tests/`, with one tier per subdirectory: -- `tests/unit/` — **structural** tests, one parametrized case per file: every - shell script parses (`bash -n`), every example config parses, every - `shapepipe.*` submodule imports, every `[project.scripts]` entry handles - `-h`, and every `*_runner.py` carries its `@module_runner` metadata. Cheap, - broad, and good at catching the dumb-but-real regressions (a syntax error, a - broken import, a renamed entry point). -- `src/shapepipe/tests/` — **unit, property-based, and integration** tests for +- `tests/module/` — **unit, property-based, and integration** tests for the analytic surface: pure helpers and geometry (coordinate round-trips, postage-stamp shapes, the MegaCam CCD flip), tested both by example and with [Hypothesis](https://hypothesis.readthedocs.io/) property tests where a function has a clear invariant. Integration tests that need the astromatic binaries (Source Extractor, PSFEx) build synthetic FITS on the fly rather than requiring survey data. +- `tests/unit/` — **structural** tests, one parametrized case per file: every + shell script parses (`bash -n`), every example config parses, every + `shapepipe.*` submodule imports, every `[project.scripts]` entry handles + `-h`, and every `*_runner.py` carries its `@module_runner` metadata. Cheap, + broad, and good at catching the dumb-but-real regressions (a syntax error, a + broken import, a renamed entry point). +- `tests/science/` — **fast scientific guardrails** (controlled simulations + with a known answer) and `tests/cluster/` — **candide guardrails** that read + real on-disk catalogs; the latter are marked `candide` and auto-skip off the + cluster. See `tests/README.md` for the full layout and markers. **CI is the single gate.** On every pull request and every push to `develop`, `.github/workflows/deploy-image.yml` builds the dev image and runs the suite diff --git a/pyproject.toml b/pyproject.toml index 362db2fb8..0f495179e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,12 +103,13 @@ script-files = [ where = ["src"] [tool.pytest.ini_options] -# Two homes, one discovery root. `src/shapepipe/tests/` holds module-unit -# tests next to the code they cover (src-layout idiom); `tests/` holds the -# suite-level tiers — `unit/` (structural), `science/` (fast guardrails), -# `cluster/` (candide, marked `candide`+`slow`). See tests/README.md. +# One discovery root: everything lives under `tests/`. Tiers are +# `module/` (per-module unit tests, import package internals directly), +# `unit/` (structural), `science/` (fast guardrails), `cluster/` (candide, +# marked `candide`+`slow`), with shared library code in `helpers/`. See +# tests/README.md. addopts = "--verbose --strict-markers --cov=shapepipe --cov-report=term-missing" -testpaths = ["tests", "src/shapepipe/tests"] +testpaths = ["tests"] markers = [ "slow: heavy compute (minutes); excluded from the fast inner loop.", "candide: needs the candide cluster and/or its real data; auto-skipped elsewhere.", diff --git a/tests/README.md b/tests/README.md index 59ee43d30..82bd066d7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,23 +1,26 @@ # ShapePipe test suite -Two homes, one discovery root, three tiers of guardrail. Everything is driven -by `pytest` from the repo root (in the dev container — see the project -`CLAUDE.md`); `pyproject.toml` `[tool.pytest.ini_options]` carries the config. +One discovery root, tiered by subdirectory. Everything lives under `tests/` and +is driven by `pytest` from the repo root (in the dev container — see the project +`CLAUDE.md`); `pyproject.toml` `[tool.pytest.ini_options]` carries the config +(`testpaths = ["tests"]`). ## Where tests live | Location | Holds | Why here | |----------|-------|----------| -| `src/shapepipe/tests/` | **module-unit tests** — the fitter, file handler, split-exp, vignetmaker, ngmix internals, the GalSim weight-validation suite | next to the code they cover (src-layout idiom); these import package internals directly | +| `tests/module/` | **module-unit tests** — the fitter, file handler, split-exp, vignetmaker, ngmix internals, the GalSim weight-validation suite | per-module unit/property/integration tests; import package internals directly. (Relocated from `src/shapepipe/tests/` so the suite has one home.) | | `tests/unit/` | **structural tests** — every submodule imports, configs parse, shell scripts lint, runner metadata is well-formed, console entry points respond to `-h` | suite-level checks on the *tree*, not any one module | | `tests/science/` | **fast scientific guardrails** — controlled simulations with a known answer, runnable in the inner loop with nothing from the cluster | scientific correctness that must stay green on every commit | | `tests/cluster/` | **candide guardrails** — read real on-disk catalogs / submit cluster jobs | need the cluster + real data; marked and auto-skipped off it | -| `tests/helpers/` | shared, non-test library code (cluster submission, artifact emission, the star-response R-function) | imported by tests; not collected as tests | +| `tests/helpers/` | shared, non-test library code (cluster submission, artifact emission, the star-response R-function) | imported by tests as `tests.helpers.*`; not collected as tests | | `tests/_artifacts/` | plots + status JSON/markdown emitted by guardrail tests | the seam a later GitHub Pages step publishes from | -Both `testpaths` are discovered together, so a bare `pytest` runs the whole -suite. `conftest.py` at the repo root is the single source of markers, -environment detection, and the candide skip policy — it applies everywhere. +`tests/` is a Python package (each tier has an `__init__.py`), so the shared +helpers import as `tests.helpers.*` from any tier. A bare `pytest` discovers the +whole tree from the single `testpaths` root. `conftest.py` at the repo root is +the single source of markers, environment detection, and the candide skip +policy — it applies everywhere. ## Markers diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..4884bbc82 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,6 @@ +"""ShapePipe test suite. + +One discovery root. ``tests`` is a package so the suite-level helpers import +as ``tests.helpers.*`` (e.g. the star-response R-function, artifact emission) +regardless of which tier a test sits in. See ``tests/README.md`` for the layout. +""" diff --git a/tests/module/__init__.py b/tests/module/__init__.py new file mode 100644 index 000000000..2116e8378 --- /dev/null +++ b/tests/module/__init__.py @@ -0,0 +1,7 @@ +"""Module-unit tests. + +Per-module tests that import package internals directly — the fitter, file +handler, split-exp, vignetmaker, ngmix internals, the GalSim weight-validation +suite. Relocated here from ``src/shapepipe/tests/`` so the whole suite lives +under one discovery root. +""" diff --git a/src/shapepipe/tests/test_file_handler.py b/tests/module/test_file_handler.py similarity index 100% rename from src/shapepipe/tests/test_file_handler.py rename to tests/module/test_file_handler.py diff --git a/src/shapepipe/tests/test_get_images.py b/tests/module/test_get_images.py similarity index 100% rename from src/shapepipe/tests/test_get_images.py rename to tests/module/test_get_images.py diff --git a/src/shapepipe/tests/test_ngmix.py b/tests/module/test_ngmix.py similarity index 100% rename from src/shapepipe/tests/test_ngmix.py rename to tests/module/test_ngmix.py diff --git a/src/shapepipe/tests/test_ngmix_weight_validation.py b/tests/module/test_ngmix_weight_validation.py similarity index 100% rename from src/shapepipe/tests/test_ngmix_weight_validation.py rename to tests/module/test_ngmix_weight_validation.py diff --git a/src/shapepipe/tests/test_pipeline.py b/tests/module/test_pipeline.py similarity index 100% rename from src/shapepipe/tests/test_pipeline.py rename to tests/module/test_pipeline.py diff --git a/src/shapepipe/tests/test_psfex_interp.py b/tests/module/test_psfex_interp.py similarity index 100% rename from src/shapepipe/tests/test_psfex_interp.py rename to tests/module/test_psfex_interp.py diff --git a/src/shapepipe/tests/test_run.py b/tests/module/test_run.py similarity index 100% rename from src/shapepipe/tests/test_run.py rename to tests/module/test_run.py diff --git a/src/shapepipe/tests/test_sextractor_post_process.py b/tests/module/test_sextractor_post_process.py similarity index 100% rename from src/shapepipe/tests/test_sextractor_post_process.py rename to tests/module/test_sextractor_post_process.py diff --git a/src/shapepipe/tests/test_split_exp.py b/tests/module/test_split_exp.py similarity index 100% rename from src/shapepipe/tests/test_split_exp.py rename to tests/module/test_split_exp.py diff --git a/src/shapepipe/tests/test_tpv_external_tools.py b/tests/module/test_tpv_external_tools.py similarity index 100% rename from src/shapepipe/tests/test_tpv_external_tools.py rename to tests/module/test_tpv_external_tools.py diff --git a/src/shapepipe/tests/test_utilities.py b/tests/module/test_utilities.py similarity index 100% rename from src/shapepipe/tests/test_utilities.py rename to tests/module/test_utilities.py diff --git a/src/shapepipe/tests/test_vignetmaker.py b/tests/module/test_vignetmaker.py similarity index 100% rename from src/shapepipe/tests/test_vignetmaker.py rename to tests/module/test_vignetmaker.py From 0ee0e944ff0d65838e12ccd6b1994c807992aae0 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Fri, 12 Jun 2026 21:20:07 +0200 Subject: [PATCH 79/80] feat(ngmix): make the Jacobian centroid source configurable (hsm | wcs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The galaxy Jacobian origin sets where the centroid prior sits, so it must land on the object. Two ways to place it, now selectable per run: - `hsm` (DEFAULT, unchanged behavior): re-center on the HSM adaptive-moment centroid measured from the stamp. Robust for galaxies — it follows the light, so the centroid prior does not bias an object offset from the stamp center. - `wcs` (Fabian's): place the origin at the catalog sky position projected through the WCS to a sub-pixel offset, with no shape measurement. Better for stars, whose HSM moments are too noisy to re-center on. Wiring: - `make_ngmix_observation` gains `centroid_source` (+ `wcs_full`/`ra`/`dec` for the wcs branch); the hsm branch is the prior code verbatim. - `do_ngmix_metacal`, `Ngmix.__init__`, and `Ngmix.process` thread it through; `Postage_stamp` carries the per-epoch WCS + ra/dec, filled in `prepare_postage_stamps` (used only by the wcs branch). - `ngmix_runner` reads the optional `CENTROID_SOURCE` ini key (default `hsm`); documented in `example/cfis/config_tile_Ng_template.ini`. The star-response guardrail declares `CENTROID_SOURCE = wcs` and threads it into the regeneration chain (`SP_CENTROID_SOURCE`), so the star grid is built with the star-appropriate centroid. Default path verified unchanged: 26 ngmix/science tests green; the wcs branch reproduces the WCS-projected sub-pixel offset on a synthetic exposure. Co-Authored-By: Claude Opus 4.8 --- example/cfis/config_tile_Ng_template.ini | 6 ++ src/shapepipe/modules/ngmix_package/ngmix.py | 103 ++++++++++++++++--- src/shapepipe/modules/ngmix_runner.py | 9 ++ tests/cluster/test_star_shear_response.py | 11 +- tests/helpers/cluster.py | 10 +- 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/example/cfis/config_tile_Ng_template.ini b/example/cfis/config_tile_Ng_template.ini index 482abb305..6760813dd 100644 --- a/example/cfis/config_tile_Ng_template.ini +++ b/example/cfis/config_tile_Ng_template.ini @@ -76,5 +76,11 @@ MAG_ZP = 30.0 # Pixel scale in arcsec PIXEL_SCALE = 0.186 +# CENTROID_SOURCE (optional): how to place the galaxy Jacobian origin for the +# centroid prior. "hsm" (default) re-centers on the HSM adaptive-moment +# centroid — robust for galaxies. "wcs" uses the catalog sky position +# projected through the WCS — better for stars, whose HSM moments are noisy. +CENTROID_SOURCE = hsm + ID_OBJ_MIN = X ID_OBJ_MAX = X diff --git a/src/shapepipe/modules/ngmix_package/ngmix.py b/src/shapepipe/modules/ngmix_package/ngmix.py index 8f2a2d179..c2bf7005b 100644 --- a/src/shapepipe/modules/ngmix_package/ngmix.py +++ b/src/shapepipe/modules/ngmix_package/ngmix.py @@ -151,6 +151,11 @@ def __init__( self.flags = [] self.bkg_rms = [] self.jacobs = [] + # Per-epoch full WCS and the object's sky position, used only by the + # "wcs" centroid source (skipped for the default "hsm" path). + self.wcs = [] + self.ra = [] + self.dec = [] self.bkg_sub = bkg_sub self.megacam_flip = megacam_flip @@ -232,6 +237,12 @@ class Ngmix(object): id_obj_max : int, optional Last galaxy ID to process, not used if the value is set to ``-1``; the default is ``-1`` + centroid_source : {"hsm", "wcs"}, optional + How to place the galaxy Jacobian origin for the centroid prior. The + default ``"hsm"`` re-centers on the HSM adaptive-moment centroid + (robust for galaxies); ``"wcs"`` uses the catalog sky position + projected through the WCS (better for stars, whose HSM moments are + noisy). See :func:`make_ngmix_observation`. Raises ------ @@ -252,6 +263,7 @@ def __init__( save_batch=-1, id_obj_min=-1, id_obj_max=-1, + centroid_source="hsm", ): if len(input_file_list) not in {6, 7}: @@ -289,6 +301,7 @@ def __init__( self._save_batch = save_batch self._id_obj_min = id_obj_min self._id_obj_max = id_obj_max + self._centroid_source = centroid_source self._w_log = w_log @@ -654,6 +667,7 @@ def process(self): prior, flux_guess, self._rng, + centroid_source=self._centroid_source, ) except Exception as ee: self._w_log.info( @@ -773,8 +787,9 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): else None ) + epoch_wcs = vignet.f_wcs_file[exp_name][int(ccd_n)]['WCS'] jacob = get_galsim_jacobian( - vignet.f_wcs_file[exp_name][int(ccd_n)]['WCS'], + epoch_wcs, tile_cat.ra[i_tile], tile_cat.dec[i_tile] ) @@ -804,7 +819,11 @@ def prepare_postage_stamps(vignet, obj_id, i_tile, tile_cat): stamp.flags.append(flag_vign) stamp.bkg_rms.append(bkg_rms_vign_scaled) stamp.jacobs.append(jacob) - + # For the "wcs" centroid source (see make_ngmix_observation). + stamp.wcs.append(epoch_wcs) + stamp.ra.append(tile_cat.ra[i_tile]) + stamp.dec.append(tile_cat.dec[i_tile]) + return stamp def background_subtract(gal,bkg): @@ -997,11 +1016,25 @@ def prepare_ngmix_weights(gal, weight, flag, rng, bkg_rms=None): return gal_masked, weight_map, noise_img -def make_ngmix_observation(gal, weight, flag, psf, wcs, rng, bkg_rms=None): +def make_ngmix_observation( + gal, weight, flag, psf, wcs, rng, + bkg_rms=None, centroid_source="hsm", wcs_full=None, ra=None, dec=None, +): """Build an ngmix Observation for a single galaxy epoch. - The galaxy Jacobian is re-centered on the HSM centroid so that the - centroid prior (centered at the Jacobian origin) does not bias the fit. + The galaxy Jacobian origin sets where the centroid prior is centered, so + it must sit on the object. Two ways to place it, selected by + ``centroid_source``: + + * ``"hsm"`` (default) — re-center on the HSM adaptive-moment centroid + measured from the stamp. Robust for **galaxies**: it follows the actual + light and so the centroid prior (centered at the Jacobian origin) does + not bias an object that is offset from the stamp center. + * ``"wcs"`` — place the origin at the object's catalog sky position, + projected through the WCS to a sub-pixel pixel offset from the stamp + center, with no shape measurement. Better for **stars**: their HSM + moments are noisy, so trusting the astrometry is more stable than + re-measuring the centroid. Parameters ---------- @@ -1016,6 +1049,14 @@ def make_ngmix_observation(gal, weight, flag, psf, wcs, rng, bkg_rms=None): reproducibility). bkg_rms : numpy.ndarray, optional Per-pixel background RMS map. + centroid_source : {"hsm", "wcs"}, optional + How to place the galaxy Jacobian origin; the default is ``"hsm"``. + wcs_full : astropy.wcs.WCS, optional + Full exposure WCS for the object's CCD. Required for + ``centroid_source="wcs"`` (ignored for ``"hsm"``). + ra, dec : float, optional + Object sky position in degrees. Required for + ``centroid_source="wcs"`` (ignored for ``"hsm"``). Returns ------- @@ -1032,18 +1073,36 @@ def make_ngmix_observation(gal, weight, flag, psf, wcs, rng, bkg_rms=None): gal, weight, flag, rng, bkg_rms=bkg_rms ) - # Re-center Jacobian on HSM centroid (pixel offset from stamp center). - # Fixes: centroid prior biases fit when galaxy is offset from stamp center. - try: - _hsm = galsim.hsm.FindAdaptiveMom( - galsim.Image(gal, scale=1.0), strict=False + if centroid_source == "hsm": + # Re-center Jacobian on HSM centroid (pixel offset from stamp center). + # Fixes: centroid prior biases fit when galaxy is offset from stamp + # center. Robust for galaxies; noisy for stars (use "wcs" there). + try: + _hsm = galsim.hsm.FindAdaptiveMom( + galsim.Image(gal, scale=1.0), strict=False + ) + if _hsm.error_message != "": + raise galsim.hsm.GalSimHSMError(_hsm.error_message) + _cen = _hsm.moments_centroid - galsim.Image(gal, scale=1.0).center + cen_row, cen_col = _cen.y, _cen.x + except Exception: + cen_row, cen_col = 0.0, 0.0 + elif centroid_source == "wcs": + # Place the origin at the catalog sky position projected through the + # WCS — no shape measurement. Stars have noisy HSM moments, so trust + # the astrometry instead of re-measuring the centroid. + g_wcs = galsim.fitswcs.AstropyWCS(wcs=wcs_full) + world_pos = galsim.CelestialCoord( + ra * galsim.degrees, dec * galsim.degrees + ) + pos = g_wcs.toImage(world_pos) + cen_col = pos.x - np.round(pos.x).astype(int) + cen_row = pos.y - np.round(pos.y).astype(int) + else: + raise ValueError( + f"Unknown centroid_source '{centroid_source}'; expected" + + " 'hsm' or 'wcs'" ) - if _hsm.error_message != "": - raise galsim.hsm.GalSimHSMError(_hsm.error_message) - _cen = _hsm.moments_centroid - galsim.Image(gal, scale=1.0).center - cen_row, cen_col = _cen.y, _cen.x - except Exception: - cen_row, cen_col = 0.0, 0.0 gal_jacob = ngmix.Jacobian( row=(gal.shape[0] - 1) / 2 + cen_row, @@ -1144,7 +1203,7 @@ def make_runners(prior, flux_guess, rng): ) -def do_ngmix_metacal(stamp, prior, flux_guess, rng): +def do_ngmix_metacal(stamp, prior, flux_guess, rng, centroid_source="hsm"): """Do Ngmix Metacal. Performs metacalibration on a single multi-epoch object and returns the @@ -1160,6 +1219,12 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): Initial flux guess. rng : numpy.random.RandomState Random state for guesses and priors. + centroid_source : {"hsm", "wcs"}, optional + How to place the galaxy Jacobian origin; passed through to + :func:`make_ngmix_observation`. The default is ``"hsm"`` (HSM + adaptive-moment centroid); ``"wcs"`` uses the catalog sky position + projected through the WCS — see that function for the star-vs-galaxy + rationale. Returns ------- @@ -1182,6 +1247,10 @@ def do_ngmix_metacal(stamp, prior, flux_guess, rng): stamp.jacobs[n_e], rng, bkg_rms=bkg_rms, + centroid_source=centroid_source, + wcs_full=stamp.wcs[n_e] if n_e < len(stamp.wcs) else None, + ra=stamp.ra[n_e] if n_e < len(stamp.ra) else None, + dec=stamp.dec[n_e] if n_e < len(stamp.dec) else None, ) gal_obs_list.append(gal_obs) diff --git a/src/shapepipe/modules/ngmix_runner.py b/src/shapepipe/modules/ngmix_runner.py index bc0e6290f..f12892e3f 100644 --- a/src/shapepipe/modules/ngmix_runner.py +++ b/src/shapepipe/modules/ngmix_runner.py @@ -79,6 +79,14 @@ def ngmix_runner( id_obj_min = config.getint(module_config_sec, "ID_OBJ_MIN") id_obj_max = config.getint(module_config_sec, "ID_OBJ_MAX") + # Centroid source for the galaxy Jacobian origin: "hsm" (default, + # HSM adaptive-moment centroid; robust for galaxies) or "wcs" (catalog + # sky position through the WCS; better for stars). + if config.has_option(module_config_sec, "CENTROID_SOURCE"): + centroid_source = config.get(module_config_sec, "CENTROID_SOURCE") + else: + centroid_source = "hsm" + # Initialise class instance ngmix_inst = Ngmix( input_file_list, @@ -91,6 +99,7 @@ def ngmix_runner( save_batch=save_batch, id_obj_min=id_obj_min, id_obj_max=id_obj_max, + centroid_source=centroid_source, ) # Process ngmix shape measurement and metacalibration diff --git a/tests/cluster/test_star_shear_response.py b/tests/cluster/test_star_shear_response.py index f2885bb09..7ce7d9a4e 100644 --- a/tests/cluster/test_star_shear_response.py +++ b/tests/cluster/test_star_shear_response.py @@ -34,6 +34,13 @@ pytestmark = [pytest.mark.slow, pytest.mark.candide] +# The star grid uses the WCS centroid source: a star's HSM adaptive moments +# are too noisy to re-center on, so the galaxy Jacobian origin is placed at the +# catalog sky position projected through the WCS (ngmix_runner CENTROID_SOURCE +# = wcs). When regenerating, this is threaded into the job chain so the outputs +# this metric reads are produced with the star-appropriate centroid. +CENTROID_SOURCE = "wcs" + def _base_dir(): """Outputs dir to evaluate — overridable for alternate runs / regen.""" @@ -61,7 +68,9 @@ def star_response(artifacts_dir): tiles = os.environ.get("SHAPEPIPE_STAR_GRID_TILES", "").split() if not tiles: pytest.skip("set SHAPEPIPE_STAR_GRID_TILES to regenerate") - wait_for_jobs(submit_star_grid_chain(tiles)) + wait_for_jobs( + submit_star_grid_chain(tiles, centroid_source=CENTROID_SOURCE) + ) summary = compute_star_response(base_dir) status = ( diff --git a/tests/helpers/cluster.py b/tests/helpers/cluster.py index c7695fcbf..1a8addcc5 100644 --- a/tests/helpers/cluster.py +++ b/tests/helpers/cluster.py @@ -19,6 +19,7 @@ seam to drive it on demand, NOT something the test suite runs by default. """ +import os import shutil import subprocess import time @@ -86,13 +87,19 @@ def sbatch(job_script, *job_args, dependency=None): return out.strip().split(";")[0] -def submit_star_grid_chain(tiles, *, launcher=None): +def submit_star_grid_chain(tiles, *, launcher=None, centroid_source="wcs"): """Submit the star-grid job chain for a list of tile ids. Mirrors ``tile_launcher.job``: one ``job_per_tile_newversion.job`` per tile id. Returns the list of submitted job ids. Does NOT wait — pair with :func:`wait_for_jobs`. + ``centroid_source`` ("wcs" for the star grid) is exported as + ``SP_CENTROID_SOURCE`` so the job chain runs ngmix with the + star-appropriate centroid (the ngmix_runner ``CENTROID_SOURCE`` config + key). Stars have noisy HSM moments, so the grid trusts the WCS-projected + catalog position rather than re-measuring the centroid. + This is the heavy path (each tile is a multi-hour job). It is wired so a cluster test can regenerate outputs on demand; the suite never calls it automatically. @@ -100,6 +107,7 @@ def submit_star_grid_chain(tiles, *, launcher=None): launcher = Path(launcher) if launcher else SP_SIMU_ROOT / "job_per_tile_newversion.job" if not launcher.exists(): raise FileNotFoundError(f"launcher job script not found: {launcher}") + os.environ["SP_CENTROID_SOURCE"] = centroid_source return [sbatch(launcher, tile) for tile in tiles] From f912d08b7d9acc1f421740ce253a7d69b305d0fe Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Fri, 12 Jun 2026 21:23:32 +0200 Subject: [PATCH 80/80] ci: fast-test workflow on push and PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `.github/workflows/fast-tests.yml`: the quick inner-loop gate that runs the `tests/` suite minus the heavy and cluster tiers — `pytest -m "not slow and not candide"` — on every push and pull request. It complements `deploy-image.yml` (which builds the dev image and runs the *full* suite inside it) by skipping the Docker build, so it returns in minutes. Matches the repo's conventions: SHA-pinned actions (covered by the existing github-actions dependabot policy), Python 3.12, and the environment reproduced from `uv.lock` via `uv sync --frozen --extra test` — the same pinned set a developer's `.venv` carries, including the pinned ngmix git source. Tests that need the astromatic binaries self-skip via `shutil.which`, so they don't run here (the dev-image CI exercises them). Concurrency-cancels superseded runs. Verified the exact invocation locally: 276 passed, 5 skipped, 2 deselected. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/fast-tests.yml | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/fast-tests.yml diff --git a/.github/workflows/fast-tests.yml b/.github/workflows/fast-tests.yml new file mode 100644 index 000000000..6347d5c9b --- /dev/null +++ b/.github/workflows/fast-tests.yml @@ -0,0 +1,60 @@ +name: Fast tests + +# The fast inner-loop signal: the `tests/` suite minus the heavy and +# cluster-only tiers (`-m "not slow and not candide"`), run on every push and +# pull request. This is the quick gate that complements — does not replace — +# `deploy-image.yml`, which builds the dev image and runs the *full* suite +# inside it (the environment that ships). This job skips the Docker build, so +# it returns in a couple of minutes instead of waiting on the image. +# +# Environment is reproduced from `uv.lock` (the repo's single source of pinned +# versions) with `uv sync --frozen`, so CI installs exactly what a developer's +# `.venv` does — no parallel pin set. Tests that need the astromatic binaries +# (Source Extractor, PSFEx) self-skip via `shutil.which`, so they simply don't +# run here; the dev-image CI exercises them. +on: + push: + branches: + - '**' + pull_request: + workflow_dispatch: + +# A newer push to the same ref cancels an in-flight run — the fast loop only +# cares about the latest commit. +concurrency: + group: fast-tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + fast-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # uv is the project's package manager; pin the Python it provisions to the + # `requires-python` floor (3.12) so CI matches the container. + - name: Set up uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + python-version: "3.12" + enable-cache: true + + # `--frozen` installs the exact versions in uv.lock (incl. the pinned + # ngmix git source) — no resolution, no drift from what ships. + - name: Install (test extra, from lockfile) + run: uv sync --frozen --extra test + + # The fast tier: everything under the single `tests/` root except `slow` + # and `candide`. Coverage gates are dropped here (this job is about speed, + # not the coverage report) and the deterministic Hypothesis profile is + # pinned, matching the dev-image CI step. + - name: Run fast suite + env: + HYPOTHESIS_PROFILE: ci + SHAPEPIPE_ON_CANDIDE: "0" + run: >- + uv run --frozen + pytest -m "not slow and not candide" + -o addopts="--strict-markers" + -rs