openflo 2.6.0__tar.gz → 2.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openflo-2.6.0 → openflo-2.6.2}/CHANGELOG.md +322 -13
- {openflo-2.6.0 → openflo-2.6.2}/MANIFEST.in +3 -0
- {openflo-2.6.0/src/openflo.egg-info → openflo-2.6.2}/PKG-INFO +102 -4
- {openflo-2.6.0 → openflo-2.6.2}/README.md +101 -3
- {openflo-2.6.0 → openflo-2.6.2}/pyproject.toml +11 -1
- openflo-2.6.2/scripts/bench_import_cost.py +79 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/make_synthetic_dataset.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/scripts/run_analyses.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/scripts/smoke_test.py +3 -3
- openflo-2.6.2/scripts/typecheck.py +275 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/__init__.py +10 -5
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/_golden.json +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/calibration.py +53 -17
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/cli.py +1327 -276
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/comp_qc.py +48 -19
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/compare.py +147 -18
- openflo-2.6.2/src/openflo/diffexp.py +406 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_analysis.py +40 -12
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_autoclean.py +39 -11
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_autogate.py +9 -1
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_channels.py +32 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_chrome.py +6 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_clipboard.py +71 -11
- openflo-2.6.2/src/openflo/editor_compute.py +583 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_dnd.py +26 -9
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_downsample.py +1 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_export.py +26 -14
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_figure.py +2 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_gatetools.py +227 -16
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_gating.py +323 -49
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_grouping.py +4 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_lifecycle.py +6 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_load.py +168 -21
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_loadpool.py +222 -4
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_plot.py +188 -146
- openflo-2.6.2/src/openflo/editor_plotcache.py +1050 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_populations.py +58 -28
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_session.py +76 -4
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_slider.py +46 -10
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_stats.py +35 -10
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_template.py +59 -9
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_tools.py +132 -17
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_tree.py +31 -6
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_undo.py +11 -1
- openflo-2.6.2/src/openflo/fcs_export.py +182 -0
- openflo-2.6.2/src/openflo/gating.py +360 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/gating_helpers.py +3 -3
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/gui.py +94 -41
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/interop.py +46 -6
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/pipeline.py +3323 -504
- openflo-2.6.2/src/openflo/plotcache.py +405 -0
- openflo-2.6.2/src/openflo/plotmath.py +393 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/prefs.py +8 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/preview.py +19 -19
- openflo-2.6.2/src/openflo/scales.py +396 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/selftest.py +3 -3
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/selftest_controls.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/session_format.py +40 -0
- openflo-2.6.2/src/openflo/spectral.py +927 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/stats.py +39 -5
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/synthetic.py +32 -32
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/template_library/README.md +17 -10
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/template_library/cleanup_minimal.json +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/template_library/cleanup_standard.json +2 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/template_library/cleanup_strict.json +1 -1
- openflo-2.6.2/src/openflo/template_library/example_panel.json +15 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/trajectory.py +4 -4
- openflo-2.6.2/src/openflo/ui_abscounts.py +144 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_axis_config.py +15 -3
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_calibration.py +27 -7
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_cell_cycle.py +5 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_comp.py +172 -87
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_compare.py +6 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_diff.py +81 -24
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_expression.py +124 -23
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_figure_layout.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_fmo.py +38 -6
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_frequency.py +136 -21
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_group_stats.py +32 -7
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_preferences.py +19 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_sample_qc.py +14 -3
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_spectral_qc.py +46 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_statistics.py +42 -9
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_trajectory.py +35 -8
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/update.py +29 -4
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/voltage.py +2 -2
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/workspace.py +235 -26
- {openflo-2.6.0 → openflo-2.6.2/src/openflo.egg-info}/PKG-INFO +102 -4
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo.egg-info/SOURCES.txt +90 -2
- {openflo-2.6.0 → openflo-2.6.2}/templates/testtemplate.json +2 -2
- openflo-2.6.2/tests/conftest.py +449 -0
- openflo-2.6.2/tests/test_abscounts.py +148 -0
- openflo-2.6.2/tests/test_acquisition_qc_ground_truth.py +462 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_annotate.py +6 -6
- openflo-2.6.2/tests/test_annotate_clusters.py +137 -0
- openflo-2.6.2/tests/test_annotate_rename_dialogs.py +103 -0
- openflo-2.6.2/tests/test_annotation_window.py +107 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_apply_region_gates.py +3 -3
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_autoclean_counts.py +15 -3
- openflo-2.6.2/tests/test_autoclean_editor_actions.py +330 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_autoclean_methods.py +289 -15
- openflo-2.6.2/tests/test_autogate_dialog.py +201 -0
- openflo-2.6.2/tests/test_axis_intensity_ticks.py +175 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_boolean_gate_fails_closed.py +78 -0
- openflo-2.6.2/tests/test_boolean_gate_tools.py +698 -0
- openflo-2.6.2/tests/test_calibration_dialog.py +155 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cell_cycle.py +5 -5
- openflo-2.6.2/tests/test_cell_cycle_dialog.py +181 -0
- openflo-2.6.2/tests/test_channel_classification.py +93 -0
- openflo-2.6.2/tests/test_cli_export_wsp.py +53 -0
- openflo-2.6.2/tests/test_cli_run.py +1215 -0
- openflo-2.6.2/tests/test_cli_run_options.py +143 -0
- openflo-2.6.2/tests/test_cli_shared_clusters.py +316 -0
- openflo-2.6.2/tests/test_cluster_dialog.py +629 -0
- openflo-2.6.2/tests/test_cluster_gpu_fallback.py +170 -0
- openflo-2.6.2/tests/test_cluster_heatmap.py +72 -0
- openflo-2.6.2/tests/test_clustering_known_defects.py +157 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_comp_matrix_csv_quoting.py +12 -12
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_comp_qc.py +64 -6
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_compare.py +17 -12
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_compare_conditions_absent_clusters.py +5 -0
- openflo-2.6.2/tests/test_compare_conditions_joint_clustering.py +103 -0
- openflo-2.6.2/tests/test_compare_dialog.py +134 -0
- openflo-2.6.2/tests/test_compare_flowjo_semantics.py +219 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_compensation.py +1 -1
- openflo-2.6.2/tests/test_compensation_applied_once.py +352 -0
- openflo-2.6.2/tests/test_compensation_editor_apply.py +442 -0
- openflo-2.6.2/tests/test_compensation_fcs_spill.py +178 -0
- openflo-2.6.2/tests/test_compensation_matrix_reading.py +258 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_diff_abundance_small_n.py +24 -13
- openflo-2.6.2/tests/test_diffabundance_calibration.py +196 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_diffexp.py +5 -4
- openflo-2.6.2/tests/test_editor_tools.py +381 -0
- openflo-2.6.2/tests/test_ellipse_params_geometry.py +62 -0
- openflo-2.6.2/tests/test_ellipse_tool.py +392 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ellipsoid_quadrant.py +120 -46
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_embedding.py +19 -19
- openflo-2.6.2/tests/test_embedding_dialog.py +104 -0
- openflo-2.6.2/tests/test_expression_window.py +259 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_fcs_export.py +8 -8
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_figure_layout.py +12 -12
- openflo-2.6.2/tests/test_flowsom_tree_window.py +94 -0
- openflo-2.6.2/tests/test_fmo_gating_dialog.py +157 -0
- openflo-2.6.2/tests/test_fmo_threshold_log.py +35 -0
- openflo-2.6.2/tests/test_fold_change_direction.py +92 -0
- openflo-2.6.2/tests/test_frequency_missing_populations.py +260 -0
- openflo-2.6.2/tests/test_frequency_window.py +327 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_editor_helpers.py +13 -6
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_import_fixes.py +16 -12
- openflo-2.6.2/tests/test_gate_text_intensity.py +467 -0
- openflo-2.6.2/tests/test_gate_tools_axis_ends.py +307 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gating.py +2 -2
- openflo-2.6.2/tests/test_gmm_ellipse_info.py +62 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_golden_regression.py +12 -12
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gpu_accel.py +17 -0
- openflo-2.6.2/tests/test_group_stats_window.py +147 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_grouping.py +2 -2
- openflo-2.6.2/tests/test_gui_guard.py +99 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gui_smoke.py +87 -15
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gui_ux.py +134 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_import_is_lazy.py +39 -11
- openflo-2.6.2/tests/test_inspect_fcs.py +65 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_interop_h5ad_roundtrip.py +43 -0
- openflo-2.6.2/tests/test_label_align.py +148 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_no_plausible_fills.py +2 -10
- openflo-2.6.2/tests/test_optimize_compensation_truth.py +129 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_panel_pairs.py +22 -22
- openflo-2.6.2/tests/test_parse_ellipsoid.py +909 -0
- openflo-2.6.2/tests/test_phenograph_pool_cap.py +82 -0
- openflo-2.6.2/tests/test_plot_cache.py +693 -0
- openflo-2.6.2/tests/test_plotmath.py +281 -0
- openflo-2.6.2/tests/test_polygon_rect_tools.py +422 -0
- openflo-2.6.2/tests/test_population_stats_geomean.py +171 -0
- openflo-2.6.2/tests/test_preexisting_data_defects.py +314 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_preview_quadrants.py +6 -6
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_provenance.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_provenance_unreadable_entries.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_qc.py +18 -13
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_qc_ignores_derived_columns.py +82 -7
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_qc_integer_channels.py +4 -10
- openflo-2.6.2/tests/test_qc_removed_visible.py +156 -0
- openflo-2.6.2/tests/test_quadrant_tool.py +171 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_quality_correctness.py +5 -5
- openflo-2.6.2/tests/test_quick_preview_dialog.py +109 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_report.py +2 -2
- openflo-2.6.2/tests/test_run_louvain.py +98 -0
- openflo-2.6.2/tests/test_sample_qc_window_values.py +107 -0
- openflo-2.6.2/tests/test_selftest_cli.py +75 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_session.py +29 -21
- openflo-2.6.2/tests/test_session_restore_transforms.py +215 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_spectral_degenerate_reference.py +2 -2
- openflo-2.6.2/tests/test_spectral_matched_af.py +339 -0
- openflo-2.6.2/tests/test_spectral_measured_and_spread.py +275 -0
- openflo-2.6.2/tests/test_spectral_qc_window.py +191 -0
- openflo-2.6.2/tests/test_spectral_unmix_gui.py +242 -0
- openflo-2.6.2/tests/test_statistics_export.py +232 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_stats.py +76 -64
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_survivor_boundaries.py +1 -1
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_synthetic.py +27 -27
- openflo-2.6.2/tests/test_template_apply.py +177 -0
- openflo-2.6.2/tests/test_template_one_sample.py +66 -0
- openflo-2.6.2/tests/test_threshold_inclusive.py +96 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_trajectory.py +1 -1
- openflo-2.6.2/tests/test_trajectory_window.py +131 -0
- openflo-2.6.2/tests/test_transform_consistency.py +71 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_transform_nonfinite.py +3 -3
- openflo-2.6.2/tests/test_transform_record_writes.py +140 -0
- openflo-2.6.2/tests/test_transform_records.py +590 -0
- openflo-2.6.2/tests/test_typecheck_config.py +143 -0
- openflo-2.6.2/tests/test_ui_audit_sign_verify.py +134 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_dialogs_smoke.py +3 -3
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_logic.py +5 -5
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_voltage.py +2 -2
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_undo_redo.py +2 -2
- openflo-2.6.2/tests/test_update_flow.py +225 -0
- openflo-2.6.2/tests/test_v261_sessions.py +276 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_voltage.py +21 -21
- openflo-2.6.2/tests/test_voltage_cli.py +103 -0
- openflo-2.6.2/tests/test_voltage_titration_dialog.py +134 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_workspace.py +50 -22
- openflo-2.6.2/tests/test_workspace_batch_folder.py +111 -0
- openflo-2.6.2/tests/test_workspace_job_driver.py +194 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_write_fcs.py +4 -4
- openflo-2.6.2/tests/test_wsp_axis_floor.py +444 -0
- openflo-2.6.2/tests/test_wsp_export_golden.py +119 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_wsp_extract.py +4 -2
- openflo-2.6.2/tests/test_wsp_gate_units.py +287 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_wsp_partial_matrix.py +3 -3
- openflo-2.6.2/tests/test_wsp_quadrant_export.py +110 -0
- openflo-2.6.2/tests/test_wsp_sample_compensation.py +129 -0
- openflo-2.6.2/tests/test_wsp_sample_keywords.py +91 -0
- openflo-2.6.2/tests/test_wsp_time_units.py +410 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_wsp_writer.py +25 -25
- openflo-2.6.2/tests/test_wsp_writer_structure.py +156 -0
- openflo-2.6.0/scripts/publish_public.py +0 -198
- openflo-2.6.0/src/openflo/diffexp.py +0 -255
- openflo-2.6.0/src/openflo/editor_compute.py +0 -218
- openflo-2.6.0/src/openflo/fcs_export.py +0 -87
- openflo-2.6.0/src/openflo/gating.py +0 -165
- openflo-2.6.0/src/openflo/plotmath.py +0 -191
- openflo-2.6.0/src/openflo/scales.py +0 -85
- openflo-2.6.0/src/openflo/spectral.py +0 -305
- openflo-2.6.0/src/openflo/template_library/example_panel.json +0 -15
- openflo-2.6.0/src/openflo/ui_abscounts.py +0 -74
- openflo-2.6.0/tests/conftest.py +0 -201
- openflo-2.6.0/tests/test_abscounts.py +0 -47
- openflo-2.6.0/tests/test_label_align.py +0 -148
- openflo-2.6.0/tests/test_plotmath.py +0 -102
- openflo-2.6.0/tests/test_population_stats_geomean.py +0 -101
- {openflo-2.6.0 → openflo-2.6.2}/LICENSE.txt +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/bench_accelerators.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/bench_louvain_restarts.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/bench_snn_prune.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/bench_ui_responsiveness.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/bench_umap_knn.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/lowest_direct_requirements.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/migrate_session.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/mutate_controls.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/preflight.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/scripts/theme_audit.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/setup.cfg +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/_console.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/annotate.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/async_task.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/audit.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/capabilities.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/compliance.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/density.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/diagnostics.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/dr_compare.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_audit.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_base.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_console.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_help.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_menu.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_mode.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/editor_update.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/gatetree.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/gpu_accel.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/inspect_fcs.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/paths.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/provenance.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/py.typed +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/report.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/template_library/cleanup_acquisition_qc.json +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/theme.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/tool_window.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/tree_ids.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_annotation.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_audit.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_autogate.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_embedding.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_figure_window.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_flowsom_tree.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_inspect.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_logic.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_methods.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_preview.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_spectral_unmix.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_synth.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_tips.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/ui_voltage.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo/warmup.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo.egg-info/dependency_links.txt +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo.egg-info/entry_points.txt +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo.egg-info/requires.txt +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/src/openflo.egg-info/top_level.txt +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/__init__.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_async_task.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_audit.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_autoclean_doublets_scale.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_autogate.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_autogate_reproducible.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_calibration.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_capabilities.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cli_cluster.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cli_e2e.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cli_unmix.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_clipboard_move_marker.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cluster_labels.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cluster_reproducible.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_clustering_reproducible.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_comp_editor.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_compare_all.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_compliance.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_console_encoding.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cytonorm.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_cytonorm_qc_undefined.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_degenerate_scatter_gating.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_density.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_diagnostics.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_diagnostics_selftest_gate.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_dialog_tooltips.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_diffabundance.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_doctor_tk_check.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_dr_compare.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_drag_tree_refresh.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_editor_mixins.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_embeddings.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_exceptions.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_exit_autosave.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_fast_graph.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_flowsom.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_flowsom_mst.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_impl_parity.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_mask_cache.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_nonfinite_events.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_partition.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gate_replication_parent.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gatetree.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gating_helpers.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_group_stats.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_group_summary_sd.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_gui_toolings.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_histogram_offscale_note.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_histogram_resolution.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_interop.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_legend_placement.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_leiden.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_load_no_copy.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_loader_queue.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_log_histogram_range.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_log_transform_nonpositive.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_mem_tiny_clusters.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_normalise_groups_idempotent.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_not_gate_excludes_unmeasured.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_paths.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_plot_density.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_polygon_gating.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_preferences_wiring.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_prefs.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_prepare_unit_tags.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_propagate_downsample_order.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_raw_not_aliased.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_resume_and_transfer_hygiene.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_scales.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_scales_logicle.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_seed_phrases.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_selftest.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_selftest_controls.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_session_continuity.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_session_json_is_valid.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_session_migration_machinery.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_session_sidecar_collision.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_spectral.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_spectral_unmix_nonfinite.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_stats_multigroup.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_templates.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_theme.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_tool_window.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_trajectory_nonfinite.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_transforms.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_tree_ids.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_compare.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_inspect.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_preview.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_sample_qc.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_ui_synth.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_umap_embedding.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_update.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_warmup.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_workspace_prep_offthread.py +0 -0
- {openflo-2.6.0 → openflo-2.6.2}/tests/test_wsp_population_names.py +0 -0
|
@@ -6,6 +6,315 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.6.2] - 2026-10-09
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Example markers and cutoffs are generic too.** The example panel,
|
|
13
|
+
synthetic data, CLI defaults, docs and tests now use the markers `CD901b`,
|
|
14
|
+
`CD902` and `CD903` (they still parse as CD markers) and round example
|
|
15
|
+
cutoffs (0.5 / 0.4 / 0.45). The privacy gate blocks the real markers.
|
|
16
|
+
- **Examples, fixtures and docs use generic detector names.** Channels in
|
|
17
|
+
the example panel, synthetic data, CLI defaults, README, tests and golden
|
|
18
|
+
fixtures are now `FL1-A`, `FL2-A`, ... instead of real fluorochromes, and
|
|
19
|
+
the comp-control matcher tests use generic dyes (`FLB`, `FLB-Cy7`, ...)
|
|
20
|
+
with the same tandem structure. The privacy gate now blocks the three
|
|
21
|
+
most common real ones, except in a functional fluor catalog
|
|
22
|
+
(`src/openflo/fluor_catalog.py`), which names dyes without revealing a
|
|
23
|
+
panel. Note: a session or template saved against the old example channel
|
|
24
|
+
names will not match the renamed example panel.
|
|
25
|
+
- **Repo tidy for the beta.** Removed four empty files at the repo root
|
|
26
|
+
(`UMAP`, `data`, `events`, `i'`) left by stray shell redirects, and
|
|
27
|
+
CONTRIBUTING.md now points to `src/openflo/pipeline.py` for
|
|
28
|
+
`OpenFloError` instead of the nonexistent `flow_pipeline.py`.
|
|
29
|
+
- **The privacy gate now runs on every PR and before every PyPI upload.**
|
|
30
|
+
It used to run only when the public mirror was refreshed, and the sdist
|
|
31
|
+
is built from this repo, so nothing scrubbed what PyPI would get. That
|
|
32
|
+
gap was real: the sdist shipped `scripts/publish_public.py`, whose
|
|
33
|
+
pattern list names the private tokens the gate exists to block.
|
|
34
|
+
MANIFEST.in now excludes it, `publish_public.py` gains `--check` and
|
|
35
|
+
`--check-dist DIR`, and CI's new `privacy gate` job and the release
|
|
36
|
+
workflow run them.
|
|
37
|
+
- **The side panel is compact, and the plot's controls fit the default
|
|
38
|
+
window.** At 1500×840 the Samples & Gates panel took 747px, half the
|
|
39
|
+
window, and the plot's control bar was cut off ("Color" showed one
|
|
40
|
+
letter).
|
|
41
|
+
- The panel is now 372px and the plot pane 1116px.
|
|
42
|
+
- The action buttons use tighter padding and sit in three columns.
|
|
43
|
+
Undo and Redo share one cell.
|
|
44
|
+
- The Pipeline button reads "Pipeline (beta)".
|
|
45
|
+
- A "Layout (to be added)" button marks the place of a FlowJo-style
|
|
46
|
+
layout editor, which is not built yet. Clicking it says so and offers
|
|
47
|
+
File → Figure layout…, which builds multi-panel figures from preset
|
|
48
|
+
arrangements.
|
|
49
|
+
- Showing the log/console no longer widens the panel.
|
|
50
|
+
- The tree's "All" heading is no longer clipped.
|
|
51
|
+
- **Acquisition QC on load is off by default.** It is a new Preferences
|
|
52
|
+
option: Loading → "Run acquisition QC on load". QC removes events from
|
|
53
|
+
time bins where the signal drifts or the flow rate jumps, and events piled
|
|
54
|
+
at a channel's ceiling, before any gate. FlowJo has no such step, so with
|
|
55
|
+
QC on, the same gates count fewer events in OpenFlo than in FlowJo.
|
|
56
|
+
- Turning the option on applies to files loaded afterwards.
|
|
57
|
+
- A sample keeps the choice it was loaded with when it is re-compensated,
|
|
58
|
+
reopened from a session, moved to another window or reloaded by the
|
|
59
|
+
Workspace.
|
|
60
|
+
- Sessions saved before this change reload their samples with QC, as they
|
|
61
|
+
were loaded.
|
|
62
|
+
- `openflo-run` is unchanged.
|
|
63
|
+
- **The type check gives the same answer locally and in CI** (development
|
|
64
|
+
only; nothing a user runs changes).
|
|
65
|
+
- CI and the pre-push hook now run the same `scripts/typecheck.py`, with
|
|
66
|
+
the same packages: CI also installs the optional backends
|
|
67
|
+
(`requirements-typecheck.txt`), which it used to leave unresolved.
|
|
68
|
+
- The script refuses to run (exit 2) when pyright would read a different
|
|
69
|
+
environment than CI: wrong package versions, a different pyright, or
|
|
70
|
+
pyright resolving imports from another interpreter.
|
|
71
|
+
- In a git worktree it uses the main checkout's `.venv`; no junction needed.
|
|
72
|
+
- A release tag is built only after the same type check passes.
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- **Reference spectra of dim dyes on autofluorescent cells.** The positive
|
|
76
|
+
population of a dim single-stain control was picked by total brightness.
|
|
77
|
+
It then included bright-AF cells, which mixed autofluorescence into the
|
|
78
|
+
dye's spectrum. Example: a dye at 400 on cells where 30% have 10x AF read
|
|
79
|
+
cosine 0.53–0.98 to its true spectrum, and unmixing error was 0.38–0.72.
|
|
80
|
+
- Positives are now picked by the dye's own excess in its primary
|
|
81
|
+
detector. Each event's autofluorescence is subtracted using its nearest
|
|
82
|
+
unstained neighbours in the detectors where AF dominates. The same case
|
|
83
|
+
now reads 0.9999 and unmixing error 0.044–0.061, which matches the
|
|
84
|
+
true-spectrum floor.
|
|
85
|
+
- Guardrail: the old estimate is kept unless the two estimates disagree
|
|
86
|
+
(cosine < 0.99) or the control is poorly separated. Over a 576-cell
|
|
87
|
+
simulation grid, no cell got worse than before. Every bright dye
|
|
88
|
+
(≥10k) and every control without AF variation gives a bit-identical
|
|
89
|
+
result.
|
|
90
|
+
- Each spectrum now says how it was built. Controls too dim to trust are
|
|
91
|
+
named with a recommendation (beads or a brighter fluor), in the CLI
|
|
92
|
+
report, the editor status bar and the Spectral QC window.
|
|
93
|
+
- New `method='auto'|'total'|'matched'` and `beads=` options, and the CLI
|
|
94
|
+
flags `--unmix-spectra` and `--unmix-beads`.
|
|
95
|
+
- **Selecting a sample or gate on large files is fast.** At 20M events,
|
|
96
|
+
clicking a sample row took 1.4 s and clicking a gate row 13 s. Every replot
|
|
97
|
+
shuffled all events to pick the drawn subsample, re-scanned them for
|
|
98
|
+
missing values, and recounted each gate's chain twice.
|
|
99
|
+
- Now 0.2 s for either once a sample has been prepared. The first view of
|
|
100
|
+
a sample takes 0.4 s, and 1.8 s for a gate row.
|
|
101
|
+
- Filter mode went from 8.1 s to 0.18 s, Highlight from 3.0 s to 0.27 s,
|
|
102
|
+
and Histogram from 1.1 s to 0.17 s.
|
|
103
|
+
- After a change, the displayed sample is drawn first. The other loaded
|
|
104
|
+
samples are prepared in the background, at about 1.7 s per 20M sample,
|
|
105
|
+
without blocking the window.
|
|
106
|
+
- No count, percentage or histogram value changes. The drawn subsample is
|
|
107
|
+
still fixed per sample, axes and cap, and now also across restarts.
|
|
108
|
+
- Caches are dropped whenever a sample's data or a gate on its chain
|
|
109
|
+
changes, and are bounded at 192 MB.
|
|
110
|
+
- **Gate bounds are shown as intensities, not stored logicle values.** A cut
|
|
111
|
+
at an intensity of about 1,000 read `CD3-A >= 0.453` and now reads
|
|
112
|
+
`CD3-A >= 987`, in the same style as the axis ticks.
|
|
113
|
+
- Applies to the gate list, unnamed population names in Statistics,
|
|
114
|
+
Frequencies and their CSVs, the histogram slider, the auto-threshold
|
|
115
|
+
status, the backgate legend, the Expression, FMO and Workspace windows,
|
|
116
|
+
and the `openflo-run` log.
|
|
117
|
+
- Open sides are said as open (`CD3-A < 2.5K`) instead of printing the
|
|
118
|
+
±1e12 sentinel. A channel of unknown scale keeps its stored value,
|
|
119
|
+
marked "(stored, scale unknown)".
|
|
120
|
+
- Colour bars and the CLI scatter PNGs are ticked at round intensities
|
|
121
|
+
(`0, 100, 10^3, 10^4`) instead of `0.2 … 1.0`.
|
|
122
|
+
- Populations are still matched across samples by gate structure, so
|
|
123
|
+
Frequencies is unaffected by the new names.
|
|
124
|
+
- **Frequencies: a population missing from a sample is missing, not 0.**
|
|
125
|
+
- Populations were matched across samples by their displayed path. An
|
|
126
|
+
unnamed gate's path includes its coordinates, so a cut nudged in one
|
|
127
|
+
sample, or a marker on another detector, split one population into
|
|
128
|
+
several.
|
|
129
|
+
- Differential abundance then counted each gap as 0 events. A gate drawn
|
|
130
|
+
only in one group's samples read as a population that had vanished from
|
|
131
|
+
the other group.
|
|
132
|
+
- Populations now match by gate name, or by kind of gate, markers and
|
|
133
|
+
position among siblings.
|
|
134
|
+
- A sample that still lacks a population shows it as missing in the
|
|
135
|
+
table, the tests, the overview and the exports. Differential abundance
|
|
136
|
+
fits it on the samples that have it and reports `n_missing`.
|
|
137
|
+
- %Parent of an empty parent is NaN, not 0%.
|
|
138
|
+
- **Compensation is applied once, with each sample's own matrix.** Reported
|
|
139
|
+
as "either ignored or applied twice"; both happened.
|
|
140
|
+
- **Applied twice.** A second compensation call (`auto_compensate` then
|
|
141
|
+
`compensate_from_wsp`, the same call twice, or `FMOGater.prepare` run
|
|
142
|
+
twice) compensated values that were already compensated: a channel with
|
|
143
|
+
0.30 spillover moved by about 15,000 units. A new matrix now replaces the
|
|
144
|
+
one applied before. Compensating a channel that is already transformed
|
|
145
|
+
raises `CompensationError` instead of mixing logicle values.
|
|
146
|
+
- **Wrong matrix or none.** `compensate_from_wsp` applied the workspace's
|
|
147
|
+
first matrix to every file, and raised on a workspace with no matrix.
|
|
148
|
+
That affected Compare workspace, `FMOGater` and `Experiment.run_all`. It
|
|
149
|
+
now uses the matrix inside the file's own `<Sample>`, or the file's
|
|
150
|
+
`$SPILL` (FlowJo's Acquisition-defined) when the workspace gives it none.
|
|
151
|
+
Several same-named matrices are no longer collapsed into the last one.
|
|
152
|
+
- **Editor.** Opening a `.wsp` applied each FCS's `$SPILL` instead of the
|
|
153
|
+
workspace's matrix for that sample. A reopened session, a sample moved
|
|
154
|
+
to another window and a workspace re-run silently went back to `$SPILL`,
|
|
155
|
+
discarding a matrix the user had applied. Each now uses the matrix the
|
|
156
|
+
sample was compensated with. A sample restored from a processed CSV keeps
|
|
157
|
+
its matrix and the link to its FCS.
|
|
158
|
+
- **FlowJo gate coordinates are converted to and from OpenFlo's display
|
|
159
|
+
scale.** A `.wsp` holds gates in linear intensity, while the editor and
|
|
160
|
+
`openflo-run` keep gates in the stored (e.g. logicle) coordinates. Nothing
|
|
161
|
+
converted between them:
|
|
162
|
+
- an imported `FL1-A > 1000` selected 0 events where FlowJo counts 7,653;
|
|
163
|
+
- an exported logicle threshold of 0.454 reached FlowJo as an intensity of
|
|
164
|
+
0.454, so FlowJo counted 14,087 events where OpenFlo counts 7,653.
|
|
165
|
+
|
|
166
|
+
Import (workspace and template) and export (editor and `--export-wsp`) now
|
|
167
|
+
convert per channel. Bounds convert exactly. Polygon edges are subdivided,
|
|
168
|
+
and ellipses are traced as polygons. A channel of unknown scale is left
|
|
169
|
+
unconverted, with a warning. Importing also keeps sibling gates on one
|
|
170
|
+
channel: one workspace's 5 gates arrived as 3, with a child moved under
|
|
171
|
+
the wrong parent.
|
|
172
|
+
- **GeoMean and rCV follow FlowJo's definitions.** These change the
|
|
173
|
+
statistics table and its CSV.
|
|
174
|
+
- rCV is now 100 × ½ (P84.13 − P15.87) / median. The MAD form used before
|
|
175
|
+
read 74% where FlowJo reports 89% on a skewed population.
|
|
176
|
+
- GeoMean on a transformed channel is the mean in display space,
|
|
177
|
+
back-transformed. It used to drop every non-positive event, so a dim
|
|
178
|
+
population read 212 where FlowJo reports 84.
|
|
179
|
+
- **Differential abundance is calibrated.** The negative-binomial test gave
|
|
180
|
+
a false positive in 24% of no-difference 3-vs-3 comparisons at the 5%
|
|
181
|
+
level, and in 64–84% for nested gates. It solved the Poisson score and
|
|
182
|
+
used one dispersion for every population, and nested parent gates pulled
|
|
183
|
+
that dispersion to almost zero. Each population now has its own NB fit
|
|
184
|
+
and dispersion, with a likelihood-ratio test on an F(1, df) reference.
|
|
185
|
+
Simulations give 5–6% false positives, and rows gain a `df` column.
|
|
186
|
+
One sample per group now gives no p-value.
|
|
187
|
+
- **One fold-change direction: log2(B / A) everywhere.** `differential_test`
|
|
188
|
+
alone gave A over B, so one comparison showed opposite signs in two
|
|
189
|
+
windows. A group mean of 0 now gives ±inf, not ±29.2 from a 1e-9
|
|
190
|
+
pseudo-count.
|
|
191
|
+
- **Absolute counts use the bead and sample volumes and the dilution.**
|
|
192
|
+
50 µL of beads at 1,000/µL in 100 µL of blood read 2,000 cells/µL where
|
|
193
|
+
1,000 is right. With no volumes entered, the result is unchanged.
|
|
194
|
+
- **`compare_conditions` compares populations, not cluster numbers.**
|
|
195
|
+
Samples clustered one by one number their clusters independently, so
|
|
196
|
+
cluster 0 could be CD4 cells in one sample and CD8 cells in another: a
|
|
197
|
+
CD4 split of 80% vs 20% read 80% vs 80%. The compared samples are now
|
|
198
|
+
clustered together first. `openflo-run` already did this.
|
|
199
|
+
- **Compensation matrix files.**
|
|
200
|
+
- A `$SPILL` that names its channels by parameter number now compensates.
|
|
201
|
+
Before, nothing matched and the sample stayed uncompensated.
|
|
202
|
+
- A matrix in percent (diagonal ≈100) is converted, with a warning, from
|
|
203
|
+
any source. Before, it left every channel at 0.01×.
|
|
204
|
+
- CSV rows are matched to the header by their labels.
|
|
205
|
+
- Header-only CSVs from FlowJo now read.
|
|
206
|
+
- A `.wsp` with no matrix returns `(None, None)`, as documented.
|
|
207
|
+
- **Optimize compensation is unbiased.** It now uses the difference of the
|
|
208
|
+
positive and negative medians and drops saturated events. On bead peaks
|
|
209
|
+
the estimate was 0.158 for a true 0.20, and 0.63 with 3% of events
|
|
210
|
+
saturated; it is now 0.200.
|
|
211
|
+
- **Compensation QC** ranks spillover by magnitude relative to each source's
|
|
212
|
+
own signal. A −0.35 entry ranked below +0.04.
|
|
213
|
+
- **Spectral unmixing.**
|
|
214
|
+
- The editor unmixes what the detectors measured, i.e. the values before
|
|
215
|
+
compensation and transform. It used to read compensated logicle values
|
|
216
|
+
(median abundance error 36–54%).
|
|
217
|
+
- The spillover spread matrix uses the standard formula, net of the
|
|
218
|
+
negative population. It overstated spread 4–6× when the control was
|
|
219
|
+
mostly negative, and understated it at 0.58× with non-negative
|
|
220
|
+
unmixing.
|
|
221
|
+
- `run_qc` drops the same events from `raw`, so `--unmix` no longer
|
|
222
|
+
unmixes events that QC removed.
|
|
223
|
+
- **Values reported on the linear scale.** Several outputs carried logicle
|
|
224
|
+
coordinates as intensities:
|
|
225
|
+
- CLI `<sample>_stats.csv` and `compare_shared_clusters.csv` medians read
|
|
226
|
+
0.43 / 0.75 where 796 / 20,104 is right;
|
|
227
|
+
- the trajectory trends export;
|
|
228
|
+
- the AnnData export, which keeps X and adds a `linear` layer and each
|
|
229
|
+
marker's transform;
|
|
230
|
+
- the FMO and auto-gate status lines, which now give the linear cut;
|
|
231
|
+
- the heatmaps, which now say they are on the display scale.
|
|
232
|
+
- **GPU logicle** no longer clamps values above ~7.4×10⁵ (error 0.16 →
|
|
233
|
+
2×10⁻⁸).
|
|
234
|
+
- **Scatter, Time and instrument channels are recognised in any case**
|
|
235
|
+
(`TIME`, `fsc-a`, Beckman `FS INT`/`SS INT`, Sony `BSC-A`, CyTOF
|
|
236
|
+
`Event_length`…). They were logicle-transformed and clustered as markers.
|
|
237
|
+
- **Switching a channel to 'log'** now asks first and names how many events
|
|
238
|
+
at or below zero it will empty.
|
|
239
|
+
- **Gating.**
|
|
240
|
+
- A threshold counts `x >= value`, as Gating-ML and FlowJo do. On integer
|
|
241
|
+
data events exactly on the cut were lost: 50 vs FlowJo's 60. This
|
|
242
|
+
includes FMO `_pos` flags.
|
|
243
|
+
- A gate drawn to the end of an axis is open there, so the negative
|
|
244
|
+
events piled on the axis floor are inside. A rectangle from the corner
|
|
245
|
+
counted 271 of 2,543 events.
|
|
246
|
+
- Axis ticks and the range dialog are in intensity, not stored logicle
|
|
247
|
+
units.
|
|
248
|
+
- A `.wsp` template applies one sample's gate tree, not every sample's.
|
|
249
|
+
- **Compare workspace.**
|
|
250
|
+
- A gate on a bare (uncompensated) parameter is counted on uncompensated
|
|
251
|
+
values, as FlowJo does: 5,216 → 8,413.
|
|
252
|
+
- A population under a gate OpenFlo cannot read is reported as an error.
|
|
253
|
+
It used to be re-rooted and counted.
|
|
254
|
+
- **Cancel stops a Workspace run's whole process tree on Linux and macOS.**
|
|
255
|
+
Before, it stopped only the job process, so PhenoGraph's worker pool and
|
|
256
|
+
the Louvain binaries kept running. Windows already killed the tree.
|
|
257
|
+
- **Events removed by the editor's load QC are reported** in the load status
|
|
258
|
+
and the `.wsp` export warning. FlowJo has no such step, so its counts
|
|
259
|
+
include them.
|
|
260
|
+
- **Ellipses drawn in FlowJo are imported.** FlowJo stores such an ellipse
|
|
261
|
+
as foci and edge points in its display space. The import (workspace,
|
|
262
|
+
template, Compare workspace) now converts it with the sample's own axes:
|
|
263
|
+
exactly on linear axes, and as a 64-vertex polygon on logicle and biex axes
|
|
264
|
+
(FlowJo's biex lookup table). Before, it was dropped and each population
|
|
265
|
+
under it counted every event the ellipse excluded.
|
|
266
|
+
- An ellipse on any other axis (log, arcsinh, a linear gain other than 1)
|
|
267
|
+
becomes a new gate kind, `flowjo_ellipse`. It counts no events, its
|
|
268
|
+
populations stay under it, the gate list and status bar flag it, and an
|
|
269
|
+
export to FlowJo writes it back as FlowJo drew it, with its axes.
|
|
270
|
+
- **Session files:** the `.flowsession` keys and schema version are
|
|
271
|
+
unchanged (v1), and older sessions load as before. A session can now
|
|
272
|
+
contain a `flowjo_ellipse` gate. An older OpenFlo does not know the kind
|
|
273
|
+
and lets every event through it, so open such sessions in this version
|
|
274
|
+
or later.
|
|
275
|
+
- **Single-stain auto-detect** (Optimize compensation). A marker name in the
|
|
276
|
+
channel no longer decides the match (`CD4 FL3-A` took `FL5.fcs`), `-A` and
|
|
277
|
+
`-H` channels of one fluor share its control, and joined names match
|
|
278
|
+
(`AF647`, `CD3FLA`, `eF450`, `compFLA`). A file that two fluors fit
|
|
279
|
+
equally is reported, not assigned.
|
|
280
|
+
|
|
281
|
+
## [2.6.1] - 2026-10-02
|
|
282
|
+
|
|
283
|
+
### Fixed
|
|
284
|
+
- **FlowJo round trip, measured against FlowJo 10.10.2.** FlowJo opened,
|
|
285
|
+
computed and saved synthetic probe workspaces and OpenFlo exports with no
|
|
286
|
+
manual steps; each FlowJo behaviour below comes from those saved files.
|
|
287
|
+
- **Ellipse gates.** A workspace with an ellipse gate lost every sample that
|
|
288
|
+
carried it, with no message. Ellipses are now exported as 64-vertex
|
|
289
|
+
polygons.
|
|
290
|
+
- **Quadrants.** A quadrant was exported as a QuadrantGate, which FlowJo
|
|
291
|
+
counts as the whole parent. It is now four rectangle gates in FlowJo's own
|
|
292
|
+
form, with each open side left unbounded, and a child gate stays under its
|
|
293
|
+
own sector. Importing FlowJo's own quadrants also failed: their sectors
|
|
294
|
+
have one bound per side, and the reader skipped every such rectangle.
|
|
295
|
+
- **Fluorescence gates were applied to uncompensated data in FlowJo.** They
|
|
296
|
+
were exported under the raw channel name, which FlowJo reads as the raw
|
|
297
|
+
parameter. They are now written as `Comp-<channel>` for each channel the
|
|
298
|
+
sample's own matrix compensates.
|
|
299
|
+
- **Per-sample `<Keywords>`.** Without the FCS TEXT in each sample, FlowJo
|
|
300
|
+
keeps the sample but computes none of its populations.
|
|
301
|
+
- **Time gates were 100× off** across the `.wsp` boundary. FlowJo works in
|
|
302
|
+
seconds: ticks × the FCS file's own `$TIMESTEP`, or ($ETIM − $BTIM) / last
|
|
303
|
+
tick without one, even when the workspace says otherwise. Export and
|
|
304
|
+
import now convert the same way.
|
|
305
|
+
- **Compensation.** `read_compensation_matrix` found no spillover in any FCS
|
|
306
|
+
file, because flowio lower-cases keywords. It now reads SPILL, and prefers
|
|
307
|
+
it over `$SPILLOVER`, as FlowJo does. Each exported sample carries its own
|
|
308
|
+
matrix, so a run that mixes files with different spillover no longer gets
|
|
309
|
+
one file's matrix applied to all.
|
|
310
|
+
- **Import of a lower edge near the bottom of an axis.** FlowJo treats such
|
|
311
|
+
an edge as open. Imported gates now do the same, so an imported gate no
|
|
312
|
+
longer undercounts by the events below the axis. The writer warns about
|
|
313
|
+
each exported edge FlowJo will open.
|
|
314
|
+
- Polygon counts still differ: by about 2% for scatter polygons and 3-7%
|
|
315
|
+
for exported ellipses. See "Where FlowJo and OpenFlo count differently" in
|
|
316
|
+
the README.
|
|
317
|
+
|
|
9
318
|
## [2.6.0] - 2026-09-10
|
|
10
319
|
|
|
11
320
|
### Added
|
|
@@ -503,7 +812,7 @@ filename, and the loader looks for both.
|
|
|
503
812
|
40-population workspace arrived fully unnamed while its geometry survived
|
|
504
813
|
intact, which is why a round trip looked like it worked. A compensation
|
|
505
814
|
matrix whose *channel name contained a comma* (a `$PnS` description like
|
|
506
|
-
"CD4
|
|
815
|
+
"CD4 FL3, clone SK3" is ordinary) wrote a file this same module then
|
|
507
816
|
refused to read. Standards-conformant Gating-ML `-INF` bounds reached the
|
|
508
817
|
session file as `-Infinity`, which *RFC 8259 forbids* — valid to Python,
|
|
509
818
|
rejected by `JSON.parse` and `jq`. An *unusable reference spectrum* (a
|
|
@@ -736,7 +1045,7 @@ filename, and the loader looks for both.
|
|
|
736
1045
|
- **The golden baseline's compensation metric never ran compensation.** It
|
|
737
1046
|
wrote the generator's own planted spillover matrix to a CSV, read it straight
|
|
738
1047
|
back, and asserted the value it had just written — a metric labelled
|
|
739
|
-
"Compensation
|
|
1048
|
+
"Compensation FL2 -> FL8 spill" that could not observe compensation
|
|
740
1049
|
being broken, and which therefore stayed green through the 2.2.1 transpose
|
|
741
1050
|
bug that corrupted every real compensated analysis. It now applies
|
|
742
1051
|
compensation to data with a known spillover baked in and reports two numbers
|
|
@@ -744,7 +1053,7 @@ filename, and the loader looks for both.
|
|
|
744
1053
|
error against the true signal, relative to signal scale) and
|
|
745
1054
|
`signal_retained` (which a "zero everything" implementation would fail while
|
|
746
1055
|
flattering the residual). Verified to catch a transposed matrix, a zeroing
|
|
747
|
-
implementation, and a no-op. **Baseline keys changed**: `compensation.
|
|
1056
|
+
implementation, and a no-op. **Baseline keys changed**: `compensation.fl2_leak`
|
|
748
1057
|
is replaced by `compensation.residual_err` + `compensation.signal_retained`.
|
|
749
1058
|
- **`--update` could bless a bug as the baseline.** Re-pinning rewrites every
|
|
750
1059
|
golden value from the current run, so running it while a bug was live
|
|
@@ -1051,7 +1360,7 @@ filename, and the loader looks for both.
|
|
|
1051
1360
|
sample. Malformed or incomplete gate dicts are reported rather than discarded
|
|
1052
1361
|
in silence.
|
|
1053
1362
|
- **CLI `--gates` help documented a form the parser rejects.** Copying the
|
|
1054
|
-
documented `{"
|
|
1363
|
+
documented `{"FL1-A":0.5}` example ran the analysis **ungated**; the help now
|
|
1055
1364
|
shows the accepted JSON-list form.
|
|
1056
1365
|
- **`--export-wsp` resolved zero samples** for a comma-string or per-sample-dict
|
|
1057
1366
|
`samples` spec (it iterated the string or dict). It now normalises groups
|
|
@@ -1190,7 +1499,7 @@ filename, and the loader looks for both.
|
|
|
1190
1499
|
- Session restore: a corrupt per-channel display range is skipped instead of
|
|
1191
1500
|
aborting the whole load; auto-saved sessions are schema-migrated on resume
|
|
1192
1501
|
(same as a manual open).
|
|
1193
|
-
- FlowJo import: gates on natively-compensated channels (FlowJo's `<
|
|
1502
|
+
- FlowJo import: gates on natively-compensated channels (FlowJo's `<FL5-A>`
|
|
1194
1503
|
bracket form) now match the data instead of selecting everything; the
|
|
1195
1504
|
region/CLI gate filter evaluates all gate kinds (ellipsoid / cluster /
|
|
1196
1505
|
category / boolean), not just lines and polygons.
|
|
@@ -1819,10 +2128,10 @@ filename, and the loader looks for both.
|
|
|
1819
2128
|
cells along a differentiation trajectory: a symmetric kNN graph over the
|
|
1820
2129
|
enabled samples' shared fluor channels (concatenated, so a day-series becomes
|
|
1821
2130
|
one continuous trajectory), with pseudotime = geodesic distance from a root
|
|
1822
|
-
cell chosen at the extreme of a marker (e.g.
|
|
2131
|
+
cell chosen at the extreme of a marker (e.g. CD902-high progenitors as t=0).
|
|
1823
2132
|
It writes a ``pseudotime`` column to every sample (selectable as a plot
|
|
1824
2133
|
colour) and draws each marker's mean expression along pseudotime — the
|
|
1825
|
-
|
|
2134
|
+
CD902-down / CD901b-up maturation curve — with CSV / Prism XY / figure export.
|
|
1826
2135
|
Backend (`openflo.trajectory`: `compute_pseudotime`, `robust_root`,
|
|
1827
2136
|
`pseudotime_trends`) is pure (numpy/scipy/sklearn), subsamples large data for
|
|
1828
2137
|
the graph and propagates by nearest neighbour, and is exported.
|
|
@@ -1883,7 +2192,7 @@ filename, and the loader looks for both.
|
|
|
1883
2192
|
the current plot into a publication-style small-multiples figure: one panel
|
|
1884
2193
|
per sample (current channels), one panel per channel pair (samples overlaid),
|
|
1885
2194
|
a samples × pairs grid, or a single panel. Channel pairs accept marker labels
|
|
1886
|
-
or channel names (e.g. `
|
|
2195
|
+
or channel names (e.g. `CD902/CD901b, CD901b/CD903`). Each panel reuses the live
|
|
1887
2196
|
rendering pipeline (mode, density/colour, axis scales, gates) via an
|
|
1888
2197
|
axes-swap (`_render_into`), so panels match the on-screen plot exactly. A
|
|
1889
2198
|
preview window saves to PNG / PDF / SVG / TIFF at 300 dpi, with a
|
|
@@ -1918,7 +2227,7 @@ filename, and the loader looks for both.
|
|
|
1918
2227
|
reads a CD↔fluorophore sheet and maps each fluorophore to its detector
|
|
1919
2228
|
channel, merged with `--labels`. New `read_staining_panel` / `find_panel_xlsx`.
|
|
1920
2229
|
- **Per-group marker-pair scatters (CLI).** Every group now emits, for each
|
|
1921
|
-
pair in `--pairs` (default
|
|
2230
|
+
pair in `--pairs` (default CD902/CD901b, CD901b/CD903, CD902/CD903), an *overlay*
|
|
1922
2231
|
(all samples on one axes, coloured by sample) and a *grid* (one density panel
|
|
1923
2232
|
per sample, shared limits). New `save_group_pair_scatters`.
|
|
1924
2233
|
- **Adjustable plot point cap.** A **Max points** control (presets + free
|
|
@@ -2188,7 +2497,7 @@ First public release.
|
|
|
2188
2497
|
stamps each gate's channel with its antibody label; applying that
|
|
2189
2498
|
template to a sample where the marker sits on a *different* detector
|
|
2190
2499
|
retargets the gate to that sample's detector (`relabel_gate_for_sample`).
|
|
2191
|
-
So a
|
|
2500
|
+
So a CD901b gate applies wherever CD901b lives in each sample, across
|
|
2192
2501
|
panels — compensation is unaffected (only which column the gate
|
|
2193
2502
|
reads changes).
|
|
2194
2503
|
- **Per-sample FMO override in the config GUI.** A group's samples
|
|
@@ -2206,7 +2515,7 @@ First public release.
|
|
|
2206
2515
|
the common label set, renaming each sample's fluors to labels). The
|
|
2207
2516
|
statistics table now names per-channel columns by **each sample's own**
|
|
2208
2517
|
label, so a marker on different fluors merges into one column
|
|
2209
|
-
(`Median
|
|
2518
|
+
(`Median CD901b`) across samples. The editor flags a non-common fluor
|
|
2210
2519
|
panel on sample load and in the Statistics window; non-common labels
|
|
2211
2520
|
are simply blank where absent.
|
|
2212
2521
|
- **Population statistics table (FlowJo-style).** A "Statistics…" window
|
|
@@ -2214,7 +2523,7 @@ First public release.
|
|
|
2214
2523
|
as the cumulative gate chain): Count, %Parent, %Total, and per-channel
|
|
2215
2524
|
Median / Mean / CV. Columns are modular (checkbox toggles); the table
|
|
2216
2525
|
exports to analysis-ready CSV. Populations show a FlowJo-style path
|
|
2217
|
-
(`Cells/Singlets/
|
|
2526
|
+
(`Cells/Singlets/CD901b+`). Computed on full sample data, not the plot
|
|
2218
2527
|
downsample.
|
|
2219
2528
|
- **Full FlowJo gate parity — ellipsoid + quadrant.** `WspReader`
|
|
2220
2529
|
parses `EllipsoidGate` (mean + covariance + distanceSquare) and
|
|
@@ -2345,7 +2654,7 @@ First public release.
|
|
|
2345
2654
|
for single-stain control files now uses an ordered candidate
|
|
2346
2655
|
tokenizer (joined form first, then each dash-separated part
|
|
2347
2656
|
longest-first) instead of the naïve "first dash-separated token"
|
|
2348
|
-
heuristic. `
|
|
2657
|
+
heuristic. `FL3-A` now produces tokens `['fl3', 'cy7']`
|
|
2349
2658
|
instead of just `['pe']`. Ambiguous and unmatched channels surface
|
|
2350
2659
|
in the status bar.
|
|
2351
2660
|
|
|
@@ -7,3 +7,6 @@ recursive-include templates *.json *.md
|
|
|
7
7
|
recursive-include src/openflo/template_library *.json *.md
|
|
8
8
|
include CHANGELOG.md
|
|
9
9
|
include src/openflo/_golden.json
|
|
10
|
+
# The public-mirror publisher lists the private tokens its scrub gate blocks,
|
|
11
|
+
# so it must never ship (same reason it is export-ignore in .gitattributes).
|
|
12
|
+
exclude scripts/publish_public.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openflo
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.2
|
|
4
4
|
Summary: Flow cytometry analysis pipeline + Tk GUI with FlowJo .wsp round-trip
|
|
5
5
|
Author: Skyler Niedzielski
|
|
6
6
|
License-Expression: MIT
|
|
@@ -69,8 +69,8 @@ FlowJo `.wsp` that round-trips back to the desktop tool. A themed (light/dark)
|
|
|
69
69
|
editor with resizable, pop-out panels drives it all interactively.
|
|
70
70
|
|
|
71
71
|
> Panel-agnostic — point it at any FCS set and pass `--channels`. The
|
|
72
|
-
> examples throughout use a generic 3-colour panel (
|
|
73
|
-
>
|
|
72
|
+
> examples throughout use a generic 3-colour panel (FL1=CD901b, FL2=CD902,
|
|
73
|
+
> FL3=CD903); substitute your own markers and controls.
|
|
74
74
|
|
|
75
75
|
> ### 📣 Free to use — the one ask is a citation
|
|
76
76
|
> OpenFlo is free (MIT). If it contributes to research you **publish or
|
|
@@ -266,7 +266,7 @@ openflo-run \
|
|
|
266
266
|
--trials trials/day1,trials/day2 \
|
|
267
267
|
--groups '[{"name":"Day1","samples":["m1","m2"],"fmo_set":"standard"},
|
|
268
268
|
{"name":"Day2","samples":["m1","m2"],"fmo_set":"standard"}]' \
|
|
269
|
-
--fmo-sets '{"standard":{"
|
|
269
|
+
--fmo-sets '{"standard":{"FL1-A":"fl1-fmo","FL2-A":"fl2-fmo","FL3-A":"fl3-fmo"}}' \
|
|
270
270
|
--k 30 \
|
|
271
271
|
--workers 4 \
|
|
272
272
|
--seed 42 \
|
|
@@ -278,6 +278,75 @@ The result: `outputs/Day1/`, `outputs/Day2/`, each with cluster
|
|
|
278
278
|
heatmaps + UMAP + per-sample stats; plus a single `analysis.wsp` you
|
|
279
279
|
can open in FlowJo to validate the gates.
|
|
280
280
|
|
|
281
|
+
#### What `openflo-run` writes
|
|
282
|
+
|
|
283
|
+
With several `--trials` folders in the default `independent` mode, each trial
|
|
284
|
+
gets its own sub-folder holding all of the below.
|
|
285
|
+
|
|
286
|
+
- **Per sample**, in `<out>/<group>/`: `<name>_stats.csv` and the sample's
|
|
287
|
+
plots (`<name>_heatmap.png`, `_scatter.png`, `_fsc_ssc.png`).
|
|
288
|
+
`<name>_stats.csv` has one row per cluster of that sample's **own**
|
|
289
|
+
clustering: `cluster`, `population`, `count`, `pct_total` and a
|
|
290
|
+
`median_<marker>` per marker. For every threshold gate applied to the
|
|
291
|
+
sample (its FMO set, or a `--gates` threshold) it also has a
|
|
292
|
+
`pct_pos_<marker>` column: the % of the cluster's events above the cut.
|
|
293
|
+
- **Per group**, in `<out>/`: `<group>_<trial>_umap.png` (`<group>_umap.png`
|
|
294
|
+
with `--batch-mode concatenate`) and the marker-pair scatters
|
|
295
|
+
(`..._<X>_<Y>_overlay.png`, `_grid.png`).
|
|
296
|
+
- **Per pair of groups**, in `<out>/`: `compare_<A>_vs_<B>.csv` and `.png`,
|
|
297
|
+
each cluster's `mean_pct`, `sd_pct` and `n_samples` per group, and a
|
|
298
|
+
`cluster_ids` column saying where the ids come from (below).
|
|
299
|
+
- **`compare_shared_clusters.csv`**, in `<out>/`: what each comparison cluster
|
|
300
|
+
is. One row per sample and cluster: `group`, `sample`, `cluster`,
|
|
301
|
+
`population`, `count`, `pct_total`, `median_<marker>`.
|
|
302
|
+
|
|
303
|
+
**A comparison's cluster ids are not the `<name>_stats.csv` ids.**
|
|
304
|
+
PhenoGraph, Leiden and FlowSOM number clusters arbitrarily, so one id names
|
|
305
|
+
different populations in two separately clustered samples. The comparisons
|
|
306
|
+
therefore come from ONE clustering of every sample of an analysis together
|
|
307
|
+
(one per trial folder in `independent` mode). Read a comparison id's phenotype
|
|
308
|
+
in `compare_shared_clusters.csv`, never in a `<name>_stats.csv`. That shared
|
|
309
|
+
clustering:
|
|
310
|
+
|
|
311
|
+
- clusters at most `--max-events` events **IN TOTAL**, an equal share from
|
|
312
|
+
each sample (a smaller sample gives all it has). With no cap it clusters as
|
|
313
|
+
many events as the largest sample holds, or Leiden's 200,000 / FlowSOM's
|
|
314
|
+
50,000 when that is smaller. Every other event takes the label of its
|
|
315
|
+
nearest clustered event; with FlowSOM, its best-matching SOM node's.
|
|
316
|
+
- matches channels across samples by antibody label ($PnS, `--labels`,
|
|
317
|
+
`--panel`), so a marker that moved to another fluor on one day still lines
|
|
318
|
+
up. A channel with no label is matched by detector. The run names every
|
|
319
|
+
channel it leaves out or matches across different detectors.
|
|
320
|
+
- is written together with every comparison, or not at all. If any sample of
|
|
321
|
+
any group is missing (it failed, its file was not found, a trial folder of
|
|
322
|
+
a `concatenate` run lacks its file, or `--skip-existing` kept it but its
|
|
323
|
+
file is gone or cannot be reloaded), no comparison file is written, the
|
|
324
|
+
run names each missing sample and why, files from an earlier run are
|
|
325
|
+
left as they were, and the run exits with status 1. The same holds when
|
|
326
|
+
the shared clustering itself fails. A single pair whose comparison fails or has no
|
|
327
|
+
rows gets no file, and its file from an earlier run is removed (the run
|
|
328
|
+
says so), so no old file sits beside the new set. The files are written
|
|
329
|
+
to a new `.compare-staging-*` folder and moved into place together once
|
|
330
|
+
all exist, so a run killed while computing them leaves the earlier set
|
|
331
|
+
untouched. A run killed while the files are moved can leave part of the
|
|
332
|
+
earlier set, part of the new one, or none; the next run replaces it, as
|
|
333
|
+
every run recomputes the comparisons, and removes the staging folder.
|
|
334
|
+
If a comparison file is in use (open in Excel), nothing is
|
|
335
|
+
moved: the earlier set stays, the run names the file and the staging
|
|
336
|
+
folder holding the new set, goes on with any other trial folder, and
|
|
337
|
+
exits with status 1.
|
|
338
|
+
- is recomputed on every run. `--skip-existing` skips only the per-sample
|
|
339
|
+
work: the kept samples' events are reloaded (not re-clustered) and the
|
|
340
|
+
comparisons made again, so they always match the current groups, files,
|
|
341
|
+
settings and OpenFlo version. The comparison files of an earlier run
|
|
342
|
+
(`compare_shared_clusters.csv` and every `compare_<A>_vs_<B>.csv` /
|
|
343
|
+
`.png`) are replaced or removed; other files, such as an
|
|
344
|
+
`openflo-compare` report, are left alone.
|
|
345
|
+
|
|
346
|
+
**GUI, Pipeline Workspace → Batch folder…**: each file's `<label>_clusters.csv`
|
|
347
|
+
and `<label>_events.csv` come from at most the run bar's *max ev* events (a
|
|
348
|
+
random subsample, seeded by the run seed), as a **Run** does.
|
|
349
|
+
|
|
281
350
|
### Compare OpenFlo's gating against FlowJo
|
|
282
351
|
|
|
283
352
|
`openflo-compare` reads a FlowJo workspace, re-applies every Population
|
|
@@ -294,6 +363,35 @@ The HTML report colour-codes |Δ| > 5% rows for review. Use it to
|
|
|
294
363
|
validate a new OpenFlo version against an established FlowJo
|
|
295
364
|
analysis before switching the analysis pipeline over.
|
|
296
365
|
|
|
366
|
+
### Where FlowJo and OpenFlo count differently
|
|
367
|
+
|
|
368
|
+
Measured against FlowJo 10.10.2, which opened, computed and saved
|
|
369
|
+
OpenFlo-exported workspaces and synthetic probe workspaces:
|
|
370
|
+
|
|
371
|
+
- **Rectangle gates agree exactly.** That covers 1-D ranges, 2-D rectangles
|
|
372
|
+
and quadrant sectors.
|
|
373
|
+
- **Polygon gates do not.** FlowJo decides polygon membership on a display
|
|
374
|
+
grid whose size `gateResolution` sets (default 256). It is not an exact
|
|
375
|
+
point-in-polygon test, and its exact rule is not known. On real data
|
|
376
|
+
FlowJo's polygon populations, and everything gated below them, usually come
|
|
377
|
+
out higher than OpenFlo's: by about 2% for scatter polygons, and by 3-7% for
|
|
378
|
+
exported ellipses. The display axis matters too. With no axis settings in
|
|
379
|
+
the workspace, FlowJo uses each FCS file's `P<n>DISPLAY` keyword (LIN or
|
|
380
|
+
LOG), so the same polygon can count differently on two acquisitions.
|
|
381
|
+
OpenFlo keeps the exact test.
|
|
382
|
+
- **The bottom of an axis.** FlowJo treats a rectangle's lower edge at or near
|
|
383
|
+
the bottom of an axis as open, and also counts every event below the axis.
|
|
384
|
+
On linear scatter and Time axes, "near" means within about 20 units of the
|
|
385
|
+
axis minimum: a Time gate that starts at 10 s starts at 0 s in FlowJo. A
|
|
386
|
+
polygon whose bottom reaches the axis minimum is opened the same way. On
|
|
387
|
+
import (`openflo-compare`, the gate editor, templates) OpenFlo applies the
|
|
388
|
+
rule to rectangles. On export it cannot change FlowJo, so it lists each
|
|
389
|
+
affected gate as a warning.
|
|
390
|
+
- **Ellipse gates** are exported as 64-vertex polygons, with a warning.
|
|
391
|
+
FlowJo silently drops a whole sample that carries a Gating-ML ellipse.
|
|
392
|
+
- **Time gates** cross the `.wsp` boundary in seconds (ticks × `$TIMESTEP`),
|
|
393
|
+
the unit FlowJo uses.
|
|
394
|
+
|
|
297
395
|
---
|
|
298
396
|
|
|
299
397
|
## Synthetic data & self-test
|