######## snakemake preamble start (automatically inserted, do not edit) ########
import sys;sys.path.extend(['/fh/fast/bloom_j/software/miniforge3/envs/seqneut-pipeline/lib/python3.13/site-packages', '/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025/seqneut-pipeline', '/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025', '/fh/fast/bloom_j/software/miniforge3/envs/seqneut-pipeline/bin', '/fh/fast/bloom_j/software/miniforge3/envs/seqneut-pipeline/lib/python3.13', '/fh/fast/bloom_j/software/miniforge3/envs/seqneut-pipeline/lib/python3.13/lib-dynload', '/fh/fast/bloom_j/software/miniforge3/envs/seqneut-pipeline/lib/python3.13/site-packages', '/home/jbloom/.cache/snakemake/snakemake/source-cache/runtime-cache/tmpfro_n8p6/file/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025/seqneut-pipeline/notebooks', '/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025/seqneut-pipeline/notebooks']);import pickle;from snakemake import script;script.snakemake = pickle.loads(b'\x80\x04\x95\xfcU\x00\x00\x00\x00\x00\x00\x8c\x10snakemake.script\x94\x8c\tSnakemake\x94\x93\x94)\x81\x94}\x94(\x8c\x05input\x94\x8c\x0csnakemake.io\x94\x8c\nInputFiles\x94\x93\x94)\x81\x94(\x8c\'results/plates/plate17/curvefits.pickle\x94\x8c\xbb/home/jbloom/.cache/snakemake/snakemake/source-cache/runtime-cache/tmpfro_n8p6/file/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025/seqneut-pipeline/notebook_funcs.py\x94e}\x94(\x8c\x06_names\x94}\x94(\x8c\x07pickles\x94K\x00K\x01\x86\x94\x8c\x0enotebook_funcs\x94K\x01N\x86\x94u\x8c\x12_allowed_overrides\x94]\x94(\x8c\x05index\x94\x8c\x04sort\x94eh\x15h\x06\x8c\x0eAttributeGuard\x94\x93\x94)\x81\x94}\x94\x8c\x04name\x94h\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sbh\x0fh\x06\x8c\tNamedlist\x94\x93\x94)\x81\x94h\na}\x94(h\r}\x94h\x13]\x94(h\x15h\x16eh\x15h\x18)\x81\x94}\x94h\x1bh\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sbubh\x11h\x0bub\x8c\x06output\x94h\x06\x8c\x0bOutputFiles\x94\x93\x94)\x81\x94(\x8c2results/sera/NIID_NIID_19/titers_per_replicate.csv\x94\x8c$results/sera/NIID_NIID_19/titers.csv\x94\x8c$results/sera/NIID_NIID_19/curves.pdf\x94\x8c*results/sera/NIID_NIID_19/curvefits.pickle\x94\x8c&results/sera/NIID_NIID_19/qc_drops.yml\x94e}\x94(h\r}\x94(\x8c\x0eper_rep_titers\x94K\x00N\x86\x94\x8c\x06titers\x94K\x01N\x86\x94\x8c\ncurves_pdf\x94K\x02N\x86\x94\x8c\x06pickle\x94K\x03N\x86\x94\x8c\x08qc_drops\x94K\x04N\x86\x94uh\x13]\x94(h\x15h\x16eh\x15h\x18)\x81\x94}\x94h\x1bh\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sbh3h,h5h-h7h.h9h/h;h0ub\x8c\r_params_store\x94h\x06\x8c\x06Params\x94\x93\x94)\x81\x94(]\x94(\x8c\x16A/Vermont/10/2025_H1N1\x94\x8c\x13A/Utah/39/2025_H1N1\x94\x8c\x1bA/Hawaii/ISC-1140/2025_H1N1\x94\x8c\x18A/Colorado/218/2024_H1N1\x94\x8c"A/Massachusetts/ISC-1679/2025_H1N1\x94\x8c\x18A/Bangkok/P176/2025_H1N1\x94\x8c\x16A/Vermont/05/2025_H1N1\x94\x8c\x1bA/Santiago/101713/2024_H1N1\x94\x8c\x16A/Oregon/261/2024_H1N1\x94\x8c\x14A/Ohio/259/2024_H1N1\x94\x8c\x19A/Minnesota/131/2024_H1N1\x94\x8c\x18A/Tennessee/04/2025_H1N1\x94\x8c\x17A/Qatar/83328/2024_H1N1\x94\x8c\x15A/Oregon/11/2025_H1N1\x94\x8c\x19A/Maldives/2132/2024_H1N1\x94\x8c\x17A/Ufa/CRIE/47/2024_H1N1\x94\x8c\x1aA/Zacapa/FLU-012/2025_H1N1\x94\x8c"A/Vladimir/RII-MH223382S/2024_H1N1\x94\x8c\x15A/Busan/277/2025_H1N1\x94\x8c\x17A/Maryland/64/2024_H1N1\x94\x8c\x14A/Iowa/123/2024_H1N1\x94\x8c\x17A/Illinois/65/2024_H1N1\x94\x8c\x19A/Tambov/160-1V/2024_H1N1\x94\x8c\x15A/Ulsan/492/2025_H1N1\x94\x8c\x1dA/Singapore/MOH0547/2024_H1N1\x94\x8c\x1bA/KANAGAWA/AC2408/2025_H1N1\x94\x8c\x18A/Wisconsin/30/2025_H1N1\x94\x8c\x18A/Pakistan/306/2024_H1N1\x94\x8c%A/NovaScotia/ET1801CP00018S/2025_H1N1\x94\x8c\x1bA/TOKYO/EIS11-277/2024_H1N1\x94\x8c"A/Wisconsin/NIRC-IS-1111/2025_H1N1\x94\x8c\x1fA/Queensland/IN000684/2024_H1N1\x94\x8c&A/Qinghai-Chengzhong/SWL1410/2024_H1N1\x94\x8c#A/Saint-Petersburg/RII-04/2025_H1N1\x94\x8c\x1fA/Uganda/UVRI_KIS6850_2024_H1N1\x94\x8c%A/Shanghai-Huangpu/SWL12109/2024_H1N1\x94\x8c\x19A/Victoria/3599/2024_H1N1\x94\x8c\x16A/Lisboa/188/2023_H1N1\x94\x8c\x1cA/Madagascar/00003/2025_H1N1\x94\x8c!A/Victoria/4897/2022_IVR-238_H1N1\x94\x8c\x18A/Wisconsin/67/2022_H1N1\x94\x8c\x19A/Wisconsin/588/2019_H1N1\x94\x8c\x15A/Hawaii/70/2019_H1N1\x94\x8c\x17A/Brisbane/02/2018_H1N1\x94\x8c\x17A/Michigan/45/2015_H1N1\x94\x8c\x19A/California/07/2009_H1N1\x94\x8c#A/France/IDF-IPP29542/2023-egg_H3N2\x94\x8c\x1cA/BurkinaFaso/3131/2023_H3N2\x94\x8c\x18A/Norway/12374/2023_H3N2\x94\x8c"A/Massachusetts/ISC-1684/2025_H3N2\x94\x8c!A/DE/DE-DHSS-901/2025_(H3N2)_H3N2\x94\x8c#A/Michigan/UM-10062069629/2025_H3N2\x94\x8c\x17A/Kentucky/57/2024_H3N2\x94\x8c\x1aA/Washington/284/2024_H3N2\x94\x8c\x19A/Colombia/1851/2024_H3N2\x94\x8c,A/France/ARA-RELAB-HCL025017178801/2025_H3N2\x94\x8c\x18A/Canberra/613/2024_H3N2\x94\x8c\x1aA/Texas/ISC-1342/2025_H3N2\x94\x8c\x1cA/Manitoba/RV04865/2024_H3N2\x94\x8c\x18A/Tasmania/790/2024_H3N2\x94\x8c\x18A/Tasmania/788/2024_H3N2\x94\x8c\x19A/Minnesota/126/2024_H3N2\x94\x8c\x18A/Minnesota/97/2024_H3N2\x94\x8c\x18A/Tasmania/836/2024_H3N2\x94\x8c\x1fA/Queensland/IN000692/2024_H3N2\x94\x8c\x1dA/Netherlands/10563/2023_H3N2\x94\x8c\x1fA/Queensland/IN000803/2024_H3N2\x94\x8c\x19A/Maldives/2186/2024_H3N2\x94\x8c\x19A/Victoria/3480/2024_H3N2\x94\x8c\x16A/Lisboa/216/2023_H3N2\x94\x8c\x19A/Minnesota/133/2024_H3N2\x94\x8c\x1dA/CoteD\'Ivoire/4448/2024_H3N2\x94\x8c\x1dA/Switzerland/47775/2024_H3N2\x94\x8c$A/Switzerland/860423897313/2023_H3N2\x94\x8c,A/France/PAC-RELAB-HCL024172122101/2024_H3N2\x94\x8c"A/New_York/GKISBBBE61555/2025_H3N2\x94\x8c\x1cA/Badajoz/18680568/2025_H3N2\x94\x8c\x1cA/Massachusetts/93/2024_H3N2\x94\x8c\x1fA/Washington/UW-25728/2024_H3N2\x94\x8c\x1eA/Punta_Arenas/83659/2024_H3N2\x94\x8c\x1aA/Texas/ISC-1322/2025_H3N2\x94\x8c\x1fA/France/BRE-IPP01880/2025_H3N2\x94\x8c\x18A/Ecuador/1385/2024_H3N2\x94\x8c\x16A/Vermont/13/2025_H3N2\x94\x8c"A/Wisconsin/NIRC-IS-1028/2024_H3N2\x94\x8c\x18A/Tennessee/99/2024_H3N2\x94\x8c#A/Sao_Paulo/358026766-IAL/2024_H3N2\x94\x8c"A/Mato_Grosso_do_Sul/518/2025_H3N2\x94\x8c\x1aA/Texas/ISC-1148/2025_H3N2\x94\x8c\x1cA/Washington/15245/2025_H3N2\x94\x8c\x1bA/Rhode_Island/66/2024_H3N2\x94\x8c\x1cA/Amapa/021563-IEC/2024_H3N2\x94\x8c\x17A/Texas/15550/2024_H3N2\x94\x8c\x18A/New_York/191/2024_H3N2\x94\x8c A/Saskatchewan/RV04835/2024_H3N2\x94\x8c\x1eA/Santa_Catarina/333/2025_H3N2\x94\x8c\x1cA/Ghana/FS-25-0256/2025_H3N2\x94\x8c\x17A/Victoria/96/2025_H3N2\x94\x8c\x16A/Oregon/265/2024_H3N2\x94\x8c\x18A/Michigan/120/2024_H3N2\x94\x8c\x1eA/Rhode_Island/15446/2025_H3N2\x94\x8c\x13A/Utah/87/2024_H3N2\x94\x8c!A/India/Pune-NIV24_3439/2024_H3N2\x94\x8c\x16A/Nevada/216/2024_H3N2\x94\x8c\x19A/Colombia/7681/2024_H3N2\x94\x8c\x1aA/Texas/ISC-1274/2025_H3N2\x94\x8c\x15A/Busan/461/2025_H3N2\x94\x8c\x17A/Slovenia/49/2024_H3N2\x94\x8c\x15A/Sydney/43/2025_H3N2\x94\x8c\x1dA/Netherlands/01502/2025_H3N2\x94\x8c\x13A/Utah/94/2024_H3N2\x94\x8c\x1dA/Colorado/ISC-1416/2024_H3N2\x94\x8c\x17A/Victoria/46/2024_H3N2\x94\x8c\x1cA/Florida/ISC-1241/2025_H3N2\x94\x8c\x16A/Indiana/46/2024_H3N2\x94\x8c\x19A/Wisconsin/172/2024_H3N2\x94\x8c\x17A/New_York/39/2025_H3N2\x94\x8c#A/Michigan/UM-10062100736/2025_H3N2\x94\x8c&A/Massachusetts/BI_MGH-23147/2025_H3N2\x94\x8c\x19A/Victoria/3482/2024_H3N2\x94\x8c\x19A/Maldives/2147/2024_H3N2\x94\x8c\x1cA/Pennsylvania/288/2024_H3N2\x94\x8c\x1fA/Croatia/10136RV/2023-egg_H3N2\x94\x8c!A/DistrictOfColumbia/27/2023_H3N2\x94\x8c\x16A/Thailand/8/2022_H3N2\x94\x8c\x1cA/Massachusetts/18/2022_H3N2\x94\x8c\x14A/Darwin/9/2021_H3N2\x94\x8c\x14A/Darwin/6/2021_H3N2\x94\x8c A/Cambodia/e0826360/2020egg_H3N2\x94\x8c\x1dA/Cambodia/e0826360/2020_H3N2\x94\x8c\x19A/HongKong/2671/2019_H3N2\x94\x8c\x17A/HongKong/45/2019_H3N2\x94\x8c\x15A/Kansas/14/2017_H3N2\x94\x8c*A/Singapore/INFIMH-16-0019/2016X-307A_H3N2\x94\x8c$A/Singapore/INFIMH-16-0019/2016_H3N2\x94\x8c\x1cA/HongKong/4801/2014egg_H3N2\x94\x8c\x19A/HongKong/4801/2014_H3N2\x94\x8c%A/Switzerland/9715293/2013NIB-88_H3N2\x94\x8c\x1fA/Switzerland/9715293/2013_H3N2\x94\x8c\x1aA/Texas/50/2012X-223A_H3N2\x94\x8c\x14A/Texas/50/2012_H3N2\x94e\x8c\x08midpoint\x94}\x94(\x8c\x0emin_replicates\x94K\x01\x8c\x1bmax_fold_change_from_median\x94K\x06\x8c\x11viruses_ignore_qc\x94]\x94u\x8c\x04png8\x94e}\x94(h\r}\x94(\x8c\x17viral_strain_plot_order\x94K\x00N\x86\x94\x8c\x0eserum_titer_as\x94K\x01N\x86\x94\x8c\rqc_thresholds\x94K\x02N\x86\x94\x8c\x14curve_display_method\x94K\x03N\x86\x94uh\x13]\x94(h\x15h\x16eh\x15h\x18)\x81\x94}\x94h\x1bh\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sbh\xddhFh\xdfh\xd4h\xe1h\xd5h\xe3h\xdaub\x8c\r_params_types\x94}\x94\x8c\twildcards\x94h\x06\x8c\tWildcards\x94\x93\x94)\x81\x94(\x8c\x04NIID\x94\x8c\x07NIID_19\x94e}\x94(h\r}\x94(\x8c\x05group\x94K\x00N\x86\x94\x8c\x05serum\x94K\x01N\x86\x94uh\x13]\x94(h\x15h\x16eh\x15h\x18)\x81\x94}\x94h\x1bh\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sb\x8c\x05group\x94h\xf0\x8c\x05serum\x94h\xf1ub\x8c\x07threads\x94K\x01\x8c\tresources\x94h\x06\x8c\tResources\x94\x93\x94)\x81\x94(K\x01K\x01\x8c\x15/loc/scratch/30805658\x94e}\x94(h\r}\x94(\x8c\x06_cores\x94K\x00N\x86\x94\x8c\x06_nodes\x94K\x01N\x86\x94\x8c\x06tmpdir\x94K\x02N\x86\x94uh\x13]\x94(h\x15h\x16eh\x15h\x18)\x81\x94}\x94h\x1bh\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sbj\x07\x01\x00\x00K\x01j\t\x01\x00\x00K\x01j\x0b\x01\x00\x00j\x04\x01\x00\x00ub\x8c\x03log\x94h\x06\x8c\x03Log\x94\x93\x94)\x81\x94\x8c3results/sera/NIID_NIID_19/NIID_NIID_19_titers.ipynb\x94a}\x94(h\r}\x94\x8c\x08notebook\x94K\x00N\x86\x94sh\x13]\x94(h\x15h\x16eh\x15h\x18)\x81\x94}\x94h\x1bh\x15sbh\x16h\x18)\x81\x94}\x94h\x1bh\x16sbj\x19\x01\x00\x00j\x16\x01\x00\x00ub\x8c\x06config\x94}\x94(\x8c\x16recent_vaccine_strains\x94}\x94(\x8c\x1fA/Croatia/10136RV/2023-egg_H3N2\x94\x8c\x1b2025-2026 egg-based vaccine\x94\x8c!A/DistrictOfColumbia/27/2023_H3N2\x94\x8c\x1c2025-2026 cell-based vaccine\x94\x8c!A/Victoria/4897/2022_IVR-238_H1N1\x94\x8c\x1b2025-2026 egg-based vaccine\x94\x8c\x18A/Wisconsin/67/2022_H1N1\x94\x8c\x1c2025-2026 cell-based vaccine\x94\x8c\x16A/Thailand/8/2022_H3N2\x94\x8c\x1b2024-2025 egg-based vaccine\x94\x8c\x1cA/Massachusetts/18/2022_H3N2\x94\x8c\x1c2024-2025 cell-based vaccine\x94u\x8c\x1chuman_sera_groups_to_exclude\x94]\x94\x8c\x03FCI\x94a\x8c\x15human_sera_to_exclude\x94]\x94(\x8c\x06SCH_19\x94\x8c\x06SCH_22\x94\x8c\x06SCH_26\x94e\x8c\x17human_sera_plots_params\x94}\x94(\x8c\x0ctiter_cutoff\x94K\x8c\x8c\x11titer_lower_limit\x94K(\x8c\x10min_frac_strains\x94G?\xec\xcc\xcc\xcc\xcc\xcc\xcd\x8c\rmin_frac_sera\x94G?\xe8\x00\x00\x00\x00\x00\x00\x8c\x0fmin_frac_action\x94\x8c\x05raise\x94u\x8c\x10seqneut-pipeline\x94\x8c\x10seqneut-pipeline\x94\x8c\x04docs\x94\x8c\x04docs\x94\x8c\x0bdescription\x94X\x1b\x01\x00\x00# Sequencing-based neutralization assays using human serum samples collected in late 2024-2025 and combined pdmH1N1 and H3N2 influenza library\n\nThe numerical data and computer code are at [https://github.com/jbloomlab/flu-seqneut-2025](https://github.com/jbloomlab/flu-seqneut-2025)\n\x94\x8c\x0fviral_libraries\x94}\x94(\x8c!flu-seqneut-2025_library_designed\x94\x8cDdata/viral_libraries/flu-seqneut-2025-barcode-to-strain_designed.csv\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8cBdata/viral_libraries/flu-seqneut-2025-barcode-to-strain_actual.csv\x94u\x8c\x17viral_strain_plot_order\x94\x8c4data/viral_libraries/flu-seqneut-2025_plot_order.csv\x94\x8c\x12neut_standard_sets\x94}\x94\x8c\x08loes2023\x94\x8c3data/neut_standard_sets/loes2023_neut_standards.csv\x94s\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\x08upstream\x94\x8c\x1cCCTACAATGTCGGATTTGTATTTAATAG\x94\x8c\ndownstream\x94\x8c\x00\x94\x8c\x04minq\x94K\x14\x8c\x11upstream_mismatch\x94K\x04\x8c\x0ebc_orientation\x94\x8c\x02R2\x94u\x8c#default_process_plate_qc_thresholds\x94}\x94(\x8c\x1bavg_barcode_counts_per_well\x94M\xf4\x01\x8c\x1fmin_neut_standard_frac_per_well\x94G?tz\xe1G\xae\x14{\x8c"no_serum_per_viral_barcode_filters\x94}\x94(\x8c\x08min_frac\x94G?\x1a6\xe2\xeb\x1cC-\x8c\x0fmax_fold_change\x94K\x04\x8c\tmax_wells\x94K\x02u\x8c!per_neut_standard_barcode_filters\x94}\x94(\x8c\x08min_frac\x94G?tz\xe1G\xae\x14{\x8c\x0fmax_fold_change\x94K\x04\x8c\tmax_wells\x94K\x02u\x8c min_neut_standard_count_per_well\x94M\xe8\x03\x8c)min_no_serum_count_per_viral_barcode_well\x94Kd\x8c+max_frac_infectivity_per_viral_barcode_well\x94K\x03\x8c)min_dilutions_per_barcode_serum_replicate\x94K\x06u\x8c%default_process_plate_curvefit_params\x94}\x94(\x8c\x18frac_infectivity_ceiling\x94K\x01\x8c\x06fixtop\x94]\x94(G?\xe3333333K\x01e\x8c\tfixbottom\x94K\x00\x8c\x08fixslope\x94]\x94(G?\xe9\x99\x99\x99\x99\x99\x9aK\neu\x8c!default_process_plate_curvefit_qc\x94}\x94(\x8c\x1dmax_frac_infectivity_at_least\x94G\x00\x00\x00\x00\x00\x00\x00\x00\x8c\x0fgoodness_of_fit\x94}\x94(\x8c\x06min_R2\x94G?\xe0\x00\x00\x00\x00\x00\x00\x8c\x08max_RMSD\x94G?\xc3333333u\x8c#serum_replicates_ignore_curvefit_qc\x94]\x94\x8c+barcode_serum_replicates_ignore_curvefit_qc\x94]\x94u\x8c\x16default_serum_titer_as\x94h\xd4\x8c\x1bdefault_serum_qc_thresholds\x94h\xd5\x8c\x16sera_override_defaults\x94}\x94\x8c\x06plates\x94}\x94(\x8c\x08plate1-2\x94}\x94(\x8c\x05group\x94\x8c\x04UWMC\x94\x8c\x04date\x94\x8c\x08datetime\x94\x8c\x04date\x94\x93\x94C\x04\x07\xe9\x08\x07\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c#data/plates/2025-08-11_plate1-2.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\x05wells\x94]\x94(\x8c\x02H1\x94\x8c\x02H2\x94\x8c\x02H3\x94\x8c\x02H4\x94\x8c\x02H5\x94\x8c\x02H6\x94\x8c\x02H7\x94\x8c\x02H8\x94\x8c\x02H9\x94\x8c\x03H10\x94\x8c\x03H11\x94\x8c\x03H12\x94es\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06ATCGAT\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x08plate2-2\x94}\x94(\x8c\x05group\x94\x8c\x04UWMC\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x07\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c#data/plates/2025-08-11_plate2-2.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06TGACGC\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x06plate3\x94}\x94(\x8c\x05group\x94\x8c\x04UWMC\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x07\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c!data/plates/2025-08-11_plate3.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06CAGTTG\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x06plate4\x94}\x94(\x8c\x05group\x94\x8c\x04UWMC\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x07\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c!data/plates/2025-08-11_plate4.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GTCTAA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x06plate6\x94}\x94(\x8c\x05group\x94\x8c\x03FCI\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x0c\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c!data/plates/2025-08-12_plate6.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06ATCGAT\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\nplate7_FCI\x94}\x94(\x8c\x05group\x94\x8c\x03FCI\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x0c\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c%data/plates/2025-08-12_plate7_FCI.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06TGACGC\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\nplate7_SCH\x94}\x94(\x8c\x05group\x94\x8c\x03SCH\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x0c\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c%data/plates/2025-08-12_plate7_SCH.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06TGACGC\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x06plate8\x94}\x94(\x8c\x05group\x94\x8c\x03SCH\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x0c\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c!data/plates/2025-08-12_plate8.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06CAGTTG\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x06plate9\x94}\x94(\x8c\x05group\x94\x8c\x03SCH\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\r\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c!data/plates/2025-08-13_plate9.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GTCTAA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate10\x94}\x94(\x8c\x05group\x94\x8c\x03SCH\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\r\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-13_plate10.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06ACGCTG\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate11\x94}\x94(\x8c\x05group\x94\x8c\x03SCH\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\r\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-13_plate11.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06TATAGC\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x0bplate12_SCH\x94}\x94(\x8c\x05group\x94\x8c\x03SCH\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\r\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c&data/plates/2025-08-13_plate12_SCH.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06CGAGCT\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\rplate12_EPIHK\x94}\x94(\x8c\x05group\x94\x8c\x05EPIHK\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\r\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c(data/plates/2025-08-13_plate12_EPIHK.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06CGAGCT\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate13\x94}\x94(\x8c\x05group\x94\x8c\x05EPIHK\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x12\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-18_plate13.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GCTACA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate14\x94}\x94(\x8c\x05group\x94\x8c\x05EPIHK\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x12\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-18_plate14.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06ATCGAT\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate15\x94}\x94(\x8c\x05group\x94\x8c\x05EPIHK\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x12\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-18_plate15.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06TGACGC\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate16\x94}\x94(\x8c\x05group\x94\x8c\x04NIID\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x12\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-18_plate16.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06CAGTTG\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate17\x94}\x94(\x8c\x05group\x94\x8c\x04NIID\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x14\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-20_plate17.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GTCTAA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate18\x94}\x94(\x8c\x05group\x94\x8c\x04NIID\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x14\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-20_plate18.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06ACGCTG\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate19\x94}\x94(\x8c\x05group\x94\x8c\x04NIID\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x14\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-20_plate19.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06TATAGC\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x07plate20\x94}\x94(\x8c\x05group\x94\x8c\x04NIID\x94\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x14\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c\x1fflu-seqneut-2025_library_actual\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c"data/plates/2025-08-20_plate20.csv\x94\x8c\x0cmanual_drops\x94}\x94\x8c\rqc_thresholds\x94}\x94(j^\x01\x00\x00M\xf4\x01j_\x01\x00\x00G?tz\xe1G\xae\x14{j`\x01\x00\x00}\x94(jb\x01\x00\x00G?\x1a6\xe2\xeb\x1cC-jc\x01\x00\x00K\x04jd\x01\x00\x00K\x02uje\x01\x00\x00}\x94(jg\x01\x00\x00G?tz\xe1G\xae\x14{jh\x01\x00\x00K\x04ji\x01\x00\x00K\x02ujj\x01\x00\x00M\xe8\x03jk\x01\x00\x00Kdjl\x01\x00\x00K\x03jm\x01\x00\x00K\x06u\x8c\x0fcurvefit_params\x94}\x94(jp\x01\x00\x00K\x01jq\x01\x00\x00jr\x01\x00\x00js\x01\x00\x00K\x00jt\x01\x00\x00ju\x01\x00\x00u\x8c\x0bcurvefit_qc\x94}\x94(jx\x01\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00jy\x01\x00\x00}\x94(j{\x01\x00\x00G?\xe0\x00\x00\x00\x00\x00\x00j|\x01\x00\x00G?\xc3333333uj}\x01\x00\x00j~\x01\x00\x00j\x7f\x01\x00\x00j\x80\x01\x00\x00u\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06CGAGCT\x94\x8c\x12upstream2_mismatch\x94K\x01uuu\x8c\x14miscellaneous_plates\x94}\x94(\x8c\x1520250716_initial_pool\x94}\x94(\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x07\x10\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c!flu-seqneut-2025_library_designed\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c5data/miscellaneous_plates/2025-07-16_initial_pool.csv\x94\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GCTACA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x1620250723_balanced_pool\x94}\x94(\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x07\x17\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c!flu-seqneut-2025_library_designed\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c8data/miscellaneous_plates/2025-07-23_balanced_repool.csv\x94\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GCTACA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c(20250723_H3_and_partial_H1_balanced_pool\x94}\x94(\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x07\x17\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c!flu-seqneut-2025_library_designed\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8cJdata/miscellaneous_plates/2025-07-23_H3_and_partial_H1_balanced_repool.csv\x94\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GCTACA\x94\x8c\x12upstream2_mismatch\x94K\x01uu\x8c\x1620250807_balanced_pool\x94}\x94(\x8c\x04date\x94j\x8e\x01\x00\x00C\x04\x07\xe9\x08\x07\x94\x85\x94R\x94\x8c\rviral_library\x94\x8c!flu-seqneut-2025_library_designed\x94\x8c\x11neut_standard_set\x94\x8c\x08loes2023\x94\x8c\x0bsamples_csv\x94\x8c8data/miscellaneous_plates/2025-08-07_balanced_repool.csv\x94\x8c\x1eillumina_barcode_parser_params\x94}\x94(\x8c\tupstream2\x94\x8c\x06GCTACA\x94\x8c\x12upstream2_mismatch\x94K\x01uuuu\x8c\x04rule\x94\x8c\x12group_serum_titers\x94\x8c\x0fbench_iteration\x94N\x8c\tscriptdir\x94\x8c`/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025/seqneut-pipeline/notebooks\x94ub.');del script;from snakemake.logging import logger;from snakemake.script import snakemake;import os; os.chdir(r'/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025');
######## snakemake preamble end #########
Titers for a serum in a group¶
Analyze titers for a serum assigned to a group, aggregating replicates which may be across multiple plates.
import pickle
import sys
import altair as alt
import matplotlib
import matplotlib.pyplot as plt
import neutcurve
import numpy
import pandas as pd
import ruamel.yaml as yaml
_ = alt.data_transformers.disable_max_rows()
# faster plotting of neut curves
matplotlib.style.use("fast")
Get variables from snakemake
:
pickle_fits = snakemake.input.pickles
notebook_funcs = snakemake.input.notebook_funcs
per_rep_titers_csv = snakemake.output.per_rep_titers
titers_csv = snakemake.output.titers
curves_pdf = snakemake.output.curves_pdf
output_pickle = snakemake.output.pickle
qc_drops_file = snakemake.output.qc_drops
viral_strain_plot_order = snakemake.params.viral_strain_plot_order
serum_titer_as = snakemake.params.serum_titer_as
qc_thresholds = snakemake.params.qc_thresholds
serum = snakemake.wildcards.serum
group = snakemake.wildcards.group
curve_display_method = snakemake.params.curve_display_method
print(f"Processing {group=}, {serum=}")
Processing group='NIID', serum='NIID_19'
Load the notebook funcs:
print(f"Loading {notebook_funcs=}")
%run {notebook_funcs}
Loading notebook_funcs='/home/jbloom/.cache/snakemake/snakemake/source-cache/runtime-cache/tmpfro_n8p6/file/fh/fast/bloom_j/computational_notebooks/jbloom/2025/flu-seqneut-2025/seqneut-pipeline/notebook_funcs.py'
Get all titers for this plate¶
Combine all the pickled neutcurve.CurveFits
from plates for this serum into a single neutcurve.CurveFits
:
print(f"Combining the curve fits for {group=}, {serum=} from {pickle_fits=}")
fits_to_combine = []
for fname in pickle_fits:
with open(fname, "rb") as f:
fits_to_combine.append(pickle.load(f))
fits_noqc = neutcurve.CurveFits.combineCurveFits(fits_to_combine, sera=[serum])
Combining the curve fits for group='NIID', serum='NIID_19' from pickle_fits=['results/plates/plate17/curvefits.pickle']
Indicate how we are calculating the titer:
print(f"Calculating with {serum_titer_as=}")
assert serum_titer_as in {"nt50", "midpoint"}
Calculating with serum_titer_as='midpoint'
Get all the per-replicate fit params with the titers. We also convert the IC50 to NT50, and take inverse of midpoint to get it on same scale as NT50s:
per_rep_titers = fits_noqc.fitParams(average_only=False, no_average=True).assign(
group=group,
nt50=lambda x: 1 / x["ic50"],
midpoint=lambda x: 1 / x["midpoint_bound"],
titer=lambda x: x["midpoint"] if serum_titer_as == "midpoint" else x["nt50"],
titer_bound=lambda x: (
x["midpoint_bound_type"] if serum_titer_as == "midpoint" else x["ic50_bound"]
).map({"lower": "upper", "upper": "lower", "interpolated": "interpolated"}),
titer_as=serum_titer_as,
)[
[
"group",
"serum",
"virus",
"replicate",
"titer",
"titer_bound",
"titer_as",
"nt50",
"midpoint",
"top",
"bottom",
"slope",
]
]
assert per_rep_titers.notnull().all().all()
if len(invalid_titer_as := per_rep_titers.query("(titer_as == 'nt50') and top <= 0.5")):
raise ValueError(
f"There are titers computed as nt50 when curve top <= 0.5:\n{invalid_titer_as}"
)
assert len(per_rep_titers) == per_rep_titers["replicate"].nunique()
# get viruses in the order to plot them
viruses = sorted(per_rep_titers["virus"].unique())
if viral_strain_plot_order is not None:
if not set(viruses).issubset(viral_strain_plot_order):
raise ValueError(
"`viral_strain_plot_order` lacks some viruses with titers:\n"
+ str(set(viruses) - set(viral_strain_plot_order))
)
viruses = [v for v in viral_strain_plot_order if v in viruses]
print(f"{serum=} has titers for a total of {len(viruses)} viruses")
serum='NIID_19' has titers for a total of 140 viruses
Correlate NT50s with midpoints of curves¶
Plot the correlation of the NT50s with the midpoint (this is an interactive plot, mouse over points for details).
This plot can help you determine if you made the correct choice of serum_titer_as
when choosing to use the midpoint or NT50 for the titer.
For titers where they are well correlated it should not matter which you chose.
But if there are titers far from the correlation line, you should look at those measurements and curves to make sure you made the correct choice of calculating the titer as the NT50 versus midpoint:
virus_selection = alt.selection_point(fields=["virus"], on="mouseover", empty=False)
midpoint_vs_nt50_chart = (
alt.Chart(per_rep_titers)
.add_params(virus_selection)
.encode(
alt.X("nt50", scale=alt.Scale(type="log", nice=False, padding=8)),
alt.Y("midpoint", scale=alt.Scale(type="log", nice=False, padding=8)),
alt.Color("titer_bound"),
strokeWidth=alt.condition(virus_selection, alt.value(3), alt.value(0)),
size=alt.condition(virus_selection, alt.value(100), alt.value(60)),
tooltip=[
alt.Tooltip(c, format=".2g") if per_rep_titers[c].dtype == float else c
for c in per_rep_titers.columns
if c not in {"group", "serum", "titer_as"}
],
)
.mark_circle(stroke="black", fillOpacity=0.45, color="black")
.properties(
width=350,
height=350,
title=f"NT50 versus midpoint for {group} {serum}",
)
.configure_axis(grid=False)
)
midpoint_vs_nt50_chart
Write the individual per-replicate titers to a file, this is before any QC has been applied:
print(f"Writing per-replicate titers (without QC filtering) to {per_rep_titers_csv=}")
per_rep_titers.to_csv(per_rep_titers_csv, index=False, float_format="%.4g")
Writing per-replicate titers (without QC filtering) to per_rep_titers_csv='results/sera/NIID_NIID_19/titers_per_replicate.csv'
Plot median titers and determine if they pass QC¶
Get the median titers for each virus across replicates, then add these median titers to the per-replicate titers and calculate the fold-change in titer between each replicate and its median. Finally, for each virus indicates whether it passes the QC:
print(f"Using the following {qc_thresholds=}")
def get_median_bound(s):
"""Get the bound on titer when taking median."""
s = list(s)
if len(s) % 2:
return s[len(s) // 2]
else:
bounds = s[len(s) // 2 - 1 : len(s) // 2 + 1]
assert len(bounds) == 2
if len(set(bounds)) == 1:
return bounds[0]
elif "interpolated" in bounds:
return [b for b in bounds if b != "interpolated"][0]
else:
return "inconsistent"
median_titers_noqc = (
per_rep_titers.sort_values("titer") # for getting median bound
.groupby(["group", "serum", "virus", "titer_as"], as_index=False)
.aggregate(
titer=pd.NamedAgg("titer", "median"),
n_replicates=pd.NamedAgg("replicate", "count"),
titer_sem=pd.NamedAgg("titer", "sem"),
titer_bound=pd.NamedAgg("titer_bound", get_median_bound),
)
)
per_rep_titers_w_fc = (
per_rep_titers.merge(
median_titers_noqc[["group", "serum", "virus", "titer"]].rename(
columns={"titer": "median_titer"}
),
validate="many_to_one",
on=["group", "serum", "virus"],
)
.assign(
fc_from_median=lambda x: numpy.where(
x["titer"] > x["median_titer"],
x["titer"] / x["median_titer"],
x["median_titer"] / x["titer"],
),
)
.drop(columns=["group", "serum", "titer_as", "median_titer"])
)
median_titers_noqc = median_titers_noqc.merge(
per_rep_titers_w_fc.groupby("virus", as_index=False).aggregate(
max_fc_from_median=pd.NamedAgg("fc_from_median", "max")
),
on="virus",
validate="one_to_one",
).assign(
fails_min_reps=lambda x: x["n_replicates"] < qc_thresholds["min_replicates"],
fails_max_fc=lambda x: (
x["max_fc_from_median"] >= qc_thresholds["max_fold_change_from_median"]
),
fails_qc=lambda x: x["fails_min_reps"] | x["fails_max_fc"],
fails_qc_reason=lambda x: (
x.apply(
lambda r: ", ".join(
(["min_replicates"] if r["fails_min_reps"] else [])
+ (["max_fold_change_from_median"] if r["fails_max_fc"] else [])
),
axis=1,
)
),
)
# get viruses failing QC in order to plot
viruses_failing_qc = (
median_titers_noqc.query("fails_qc").set_index("virus")["fails_qc_reason"].to_dict()
)
viruses_failing_qc = {
v: viruses_failing_qc[v] for v in viruses if v in viruses_failing_qc
}
median_titers_noqc = median_titers_noqc.drop(
columns=["fails_min_reps", "fails_max_fc", "fails_qc_reason"]
)
per_rep_titers_w_fc = per_rep_titers_w_fc.merge(
median_titers_noqc[["virus", "fails_qc"]],
on="virus",
validate="many_to_one",
)
Using the following qc_thresholds={'min_replicates': 1, 'max_fold_change_from_median': 6, 'viruses_ignore_qc': []}
Now plot the per-replicate and median titers, indicating any viruses that failed QC.
Note that potentially some of these titers may still be retained if the viruses in question are specified in viruses_ignore_qc
of qc_thresholds
.
virus_selection = alt.selection_point(fields=["virus"], on="mouseover", empty=False)
per_rep_chart = (
alt.Chart(per_rep_titers_w_fc)
.encode(
alt.X("titer", scale=alt.Scale(nice=False, padding=5, type="log")),
alt.Y("virus", sort=viruses),
alt.Fill(
"fails_qc",
title=f"fails {qc_thresholds['min_replicates']=}, {qc_thresholds['max_fold_change_from_median']=}",
legend=alt.Legend(titleLimit=500),
),
alt.Shape("titer_bound"),
strokeWidth=alt.condition(virus_selection, alt.value(2), alt.value(0)),
tooltip=[
alt.Tooltip(c, format=".3g") if per_rep_titers_w_fc[c].dtype == float else c
for c in per_rep_titers_w_fc
],
)
.mark_point(
size=35,
filled=True,
fillOpacity=0.5,
strokeOpacity=1,
stroke="black",
)
)
median_chart = (
alt.Chart(median_titers_noqc)
.encode(
alt.X("titer", scale=alt.Scale(nice=False, padding=5, type="log")),
alt.Y("virus", sort=viruses),
alt.Fill("fails_qc"),
alt.Shape("titer_bound"),
strokeWidth=alt.condition(virus_selection, alt.value(2), alt.value(0.5)),
tooltip=[
alt.Tooltip(c, format=".3g") if median_titers_noqc[c].dtype == float else c
for c in median_titers_noqc
],
)
.mark_point(
size=75,
filled=True,
fillOpacity=0.9,
strokeOpacity=1,
stroke="black",
)
)
titer_chart = (
(per_rep_chart + median_chart)
.add_params(virus_selection)
.properties(
height=alt.Step(11),
width=250,
title=f"{group} {serum} median (large points) and per-replicate (small points) titers",
)
.configure_axis(grid=False)
)
titer_chart
Plot individual curves for any viruses failing QC¶
Plot individual curves for viruses failing QC.
Note that potentially some of these titers may still be retained if the viruses in question are specified in viruses_ignore_qc
of qc_thresholds
.
print(f"Neutralization curves for the {len(viruses_failing_qc)} viruses failing QC:")
if len(viruses_failing_qc):
fig, _ = fits_noqc.plotReplicates(
viruses=viruses_failing_qc,
attempt_shared_legend=False,
legendfontsize=8,
titlesize=12,
ncol=4,
heightscale=1.2,
widthscale=1.2,
subplot_titles="{virus}",
draw_in_bounds=True,
)
_ = fig.suptitle(
f"neutralization curves for viruses failing QC for {group} {serum}",
y=1,
fontsize=18,
fontweight="bold",
)
fig.tight_layout()
display_curve_fig(fig, curve_display_method)
plt.close(fig)
Neutralization curves for the 0 viruses failing QC:
Get the viruses to drop for QC failures¶
Drop any viruses that fail QC and are not specified in viruses_ignore_qc
of qc_thresholds
:
viruses_to_drop = {
v: reason
for (v, reason) in viruses_failing_qc.items()
if v not in qc_thresholds["viruses_ignore_qc"]
}
print(f"Dropping {len(viruses_to_drop)} viruses for failing QC:")
yaml.YAML(typ="rt").dump(viruses_to_drop, sys.stdout)
if nkept := (len(viruses_failing_qc) - len(viruses_to_drop)):
print(
f"\nRetaining {nkept} viruses that fail QC because they are in `viruses_ignore_qc`:"
)
print(
{
v: reason
for (v, reason) in viruses_failing_qc.items()
if v in qc_thresholds["viruses_ignore_qc"]
}
)
print(f"\nWriting QC drops to {qc_drops_file}")
with open(qc_drops_file, "w") as f:
yaml.YAML(typ="rt").dump(viruses_to_drop, f)
Dropping 0 viruses for failing QC: {}
Writing QC drops to results/sera/NIID_NIID_19/qc_drops.yml
Get and plot the neutralization curves for all retained viruses¶
First, get the CurveFits
for just those retained viruses (dropping ones that fail QC), and plot:
fits_qc = neutcurve.CurveFits.combineCurveFits(
[fits_noqc],
viruses=[v for v in viruses if v not in viruses_to_drop],
)
assert len(viruses) == len(fits_qc.viruses[serum]) + len(viruses_to_drop)
fig, _ = fits_qc.plotReplicates(
attempt_shared_legend=False,
legendfontsize=8,
titlesize=12,
ncol=4,
heightscale=1.2,
widthscale=1.2,
subplot_titles="{virus}",
viruses=[v for v in viruses if v not in viruses_to_drop],
draw_in_bounds=True,
)
_ = fig.suptitle(
f"neutralization curves for retained viruses for {group} {serum}",
y=1,
fontsize=18,
fontweight="bold",
)
fig.tight_layout()
display_curve_fig(fig, curve_display_method)
print(f"Saving to plot of curves to {curves_pdf}")
fig.savefig(curves_pdf)
plt.close(fig)
Saving to plot of curves to results/sera/NIID_NIID_19/curves.pdf
Save the CurveFits
to a pickle file:
with open(output_pickle, "wb") as f:
pickle.dump(fits_qc, f)
Write the titers (excluding QC dropped viruses) to a CSV:
print(f"Writing titers to {titers_csv}")
(
median_titers_noqc.query("virus not in @viruses_to_drop")[
[
"group",
"serum",
"virus",
"titer",
"titer_bound",
"titer_sem",
"n_replicates",
"titer_as",
]
].to_csv(titers_csv, index=False, float_format="%.4g")
)
Writing titers to results/sera/NIID_NIID_19/titers.csv