openflo 2.6.2__tar.gz → 2.7.0b2__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.
Files changed (566) hide show
  1. {openflo-2.6.2 → openflo-2.7.0b2}/CHANGELOG.md +917 -0
  2. {openflo-2.6.2/src/openflo.egg-info → openflo-2.7.0b2}/PKG-INFO +62 -19
  3. {openflo-2.6.2 → openflo-2.7.0b2}/README.md +58 -17
  4. {openflo-2.6.2 → openflo-2.7.0b2}/pyproject.toml +6 -4
  5. openflo-2.7.0b2/scripts/make_reference_values.py +243 -0
  6. openflo-2.7.0b2/scripts/preflight.py +232 -0
  7. openflo-2.7.0b2/scripts/prove_tests.py +1299 -0
  8. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/_console.py +1 -1
  9. openflo-2.7.0b2/src/openflo/acquisition.py +117 -0
  10. openflo-2.7.0b2/src/openflo/async_task.py +338 -0
  11. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/audit.py +17 -5
  12. openflo-2.7.0b2/src/openflo/batch_normalize.py +378 -0
  13. openflo-2.7.0b2/src/openflo/bead_tracking.py +163 -0
  14. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/calibration.py +33 -12
  15. openflo-2.7.0b2/src/openflo/capabilities.py +247 -0
  16. openflo-2.7.0b2/src/openflo/cellcycle_djf.py +124 -0
  17. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/cli.py +621 -301
  18. openflo-2.7.0b2/src/openflo/cli_args.py +259 -0
  19. openflo-2.7.0b2/src/openflo/clipboard_export.py +177 -0
  20. openflo-2.7.0b2/src/openflo/comp_spread.py +71 -0
  21. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/compare.py +166 -25
  22. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/density.py +4 -4
  23. openflo-2.7.0b2/src/openflo/derived.py +262 -0
  24. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/diagnostics.py +8 -2
  25. openflo-2.7.0b2/src/openflo/dialog_inputs.py +96 -0
  26. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/diffexp.py +225 -26
  27. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/dr_compare.py +11 -6
  28. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_analysis.py +169 -54
  29. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_audit.py +7 -3
  30. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_autoclean.py +4 -4
  31. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_autogate.py +21 -4
  32. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_chrome.py +141 -37
  33. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_clipboard.py +27 -12
  34. openflo-2.7.0b2/src/openflo/editor_combos.py +213 -0
  35. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_compute.py +106 -24
  36. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_console.py +5 -5
  37. openflo-2.7.0b2/src/openflo/editor_coords.py +139 -0
  38. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_dnd.py +186 -22
  39. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_downsample.py +47 -12
  40. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_export.py +227 -34
  41. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_figure.py +16 -13
  42. openflo-2.7.0b2/src/openflo/editor_fixes.py +156 -0
  43. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_gatetools.py +157 -26
  44. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_gating.py +371 -68
  45. openflo-2.7.0b2/src/openflo/editor_grouping.py +186 -0
  46. openflo-2.7.0b2/src/openflo/editor_help.py +391 -0
  47. openflo-2.7.0b2/src/openflo/editor_keyboard.py +152 -0
  48. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_lifecycle.py +295 -79
  49. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_load.py +205 -55
  50. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_loadpool.py +145 -48
  51. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_menu.py +167 -70
  52. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_mode.py +5 -3
  53. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_plot.py +328 -47
  54. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_plotcache.py +183 -32
  55. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_populations.py +205 -88
  56. openflo-2.7.0b2/src/openflo/editor_refit.py +102 -0
  57. openflo-2.7.0b2/src/openflo/editor_samples.py +83 -0
  58. openflo-2.7.0b2/src/openflo/editor_session.py +1734 -0
  59. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_slider.py +1 -1
  60. openflo-2.7.0b2/src/openflo/editor_specialist.py +49 -0
  61. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_stats.py +72 -8
  62. openflo-2.7.0b2/src/openflo/editor_tabs.py +699 -0
  63. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_template.py +93 -30
  64. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_tools.py +269 -207
  65. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_tree.py +265 -28
  66. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_undo.py +44 -2
  67. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_update.py +54 -26
  68. openflo-2.7.0b2/src/openflo/errors.py +82 -0
  69. openflo-2.7.0b2/src/openflo/export_stamp.py +156 -0
  70. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/fcs_export.py +94 -22
  71. openflo-2.7.0b2/src/openflo/fcs_read.py +68 -0
  72. openflo-2.7.0b2/src/openflo/fixes.py +620 -0
  73. openflo-2.7.0b2/src/openflo/fluor_catalog.py +29 -0
  74. openflo-2.7.0b2/src/openflo/fmo_sensitivity.py +98 -0
  75. openflo-2.7.0b2/src/openflo/gate_combos.py +235 -0
  76. openflo-2.7.0b2/src/openflo/gate_coords.py +280 -0
  77. openflo-2.7.0b2/src/openflo/gate_refit.py +226 -0
  78. openflo-2.7.0b2/src/openflo/gate_table.py +81 -0
  79. openflo-2.7.0b2/src/openflo/gating.py +628 -0
  80. openflo-2.7.0b2/src/openflo/gatingml.py +753 -0
  81. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/gpu_accel.py +34 -11
  82. openflo-2.7.0b2/src/openflo/group_test_choice.py +95 -0
  83. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/gui.py +540 -169
  84. openflo-2.7.0b2/src/openflo/guide/01-getting-started.md +31 -0
  85. openflo-2.7.0b2/src/openflo/guide/02-loading-samples.md +23 -0
  86. openflo-2.7.0b2/src/openflo/guide/03-gating.md +38 -0
  87. openflo-2.7.0b2/src/openflo/guide/04-compensation-and-scales.md +27 -0
  88. openflo-2.7.0b2/src/openflo/guide/05-statistics-and-figures.md +22 -0
  89. openflo-2.7.0b2/src/openflo/guide/06-analysis-tools.md +21 -0
  90. openflo-2.7.0b2/src/openflo/guide/07-spectral.md +13 -0
  91. openflo-2.7.0b2/src/openflo/guide/08-quality-control.md +18 -0
  92. openflo-2.7.0b2/src/openflo/guide/09-sharing.md +27 -0
  93. openflo-2.7.0b2/src/openflo/guide/10-troubleshooting.md +17 -0
  94. openflo-2.7.0b2/src/openflo/index_sort.py +90 -0
  95. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/inspect_fcs.py +4 -1
  96. openflo-2.7.0b2/src/openflo/kinetics.py +135 -0
  97. openflo-2.7.0b2/src/openflo/layout_page.py +610 -0
  98. openflo-2.7.0b2/src/openflo/layout_render.py +976 -0
  99. openflo-2.7.0b2/src/openflo/parallel.py +65 -0
  100. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/paths.py +22 -0
  101. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/pipeline.py +1523 -171
  102. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/plotcache.py +13 -4
  103. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/plotmath.py +2 -2
  104. openflo-2.7.0b2/src/openflo/pptx_export.py +212 -0
  105. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/prefs.py +29 -3
  106. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/preview.py +2 -2
  107. openflo-2.7.0b2/src/openflo/project_archive.py +169 -0
  108. openflo-2.7.0b2/src/openflo/proliferation.py +128 -0
  109. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/provenance.py +1 -1
  110. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/report.py +11 -3
  111. openflo-2.7.0b2/src/openflo/result_stamp.py +315 -0
  112. openflo-2.7.0b2/src/openflo/sample_meta.py +157 -0
  113. openflo-2.7.0b2/src/openflo/saturation.py +147 -0
  114. openflo-2.7.0b2/src/openflo/saved_stats.py +197 -0
  115. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/scales.py +283 -27
  116. openflo-2.7.0b2/src/openflo/session_compare.py +168 -0
  117. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/session_format.py +39 -4
  118. openflo-2.7.0b2/src/openflo/session_lock.py +189 -0
  119. openflo-2.7.0b2/src/openflo/session_sidecar.py +129 -0
  120. openflo-2.7.0b2/src/openflo/som_kernel.py +141 -0
  121. openflo-2.7.0b2/src/openflo/spectral_af.py +136 -0
  122. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/stats.py +266 -31
  123. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/synthetic.py +4 -2
  124. openflo-2.7.0b2/src/openflo/table_export.py +336 -0
  125. openflo-2.7.0b2/src/openflo/theme.py +480 -0
  126. openflo-2.7.0b2/src/openflo/tk_threads.py +104 -0
  127. openflo-2.7.0b2/src/openflo/tool_window.py +513 -0
  128. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_abscounts.py +14 -10
  129. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_annotation.py +16 -12
  130. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_audit.py +15 -15
  131. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_autogate.py +38 -26
  132. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_axis_config.py +117 -24
  133. openflo-2.7.0b2/src/openflo/ui_batch_normalize.py +473 -0
  134. openflo-2.7.0b2/src/openflo/ui_bead_tracking.py +248 -0
  135. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_calibration.py +39 -15
  136. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_cell_cycle.py +25 -3
  137. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_comp.py +112 -25
  138. openflo-2.7.0b2/src/openflo/ui_comp_spread.py +260 -0
  139. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_compare.py +26 -13
  140. openflo-2.7.0b2/src/openflo/ui_concatenate.py +122 -0
  141. openflo-2.7.0b2/src/openflo/ui_derived.py +177 -0
  142. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_diff.py +59 -15
  143. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_embedding.py +34 -12
  144. openflo-2.7.0b2/src/openflo/ui_errors.py +174 -0
  145. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_expression.py +128 -51
  146. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_figure_layout.py +9 -8
  147. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_figure_window.py +1 -1
  148. openflo-2.7.0b2/src/openflo/ui_fixes.py +304 -0
  149. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_flowsom_tree.py +9 -7
  150. openflo-2.7.0b2/src/openflo/ui_fmo.py +472 -0
  151. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_frequency.py +114 -42
  152. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_group_stats.py +26 -3
  153. openflo-2.7.0b2/src/openflo/ui_guide.py +149 -0
  154. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_inspect.py +13 -8
  155. openflo-2.7.0b2/src/openflo/ui_kinetics.py +358 -0
  156. openflo-2.7.0b2/src/openflo/ui_layout_editor.py +2448 -0
  157. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_logic.py +13 -5
  158. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_methods.py +31 -19
  159. openflo-2.7.0b2/src/openflo/ui_notice.py +77 -0
  160. openflo-2.7.0b2/src/openflo/ui_plate.py +262 -0
  161. openflo-2.7.0b2/src/openflo/ui_polyfunction.py +176 -0
  162. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_preferences.py +197 -20
  163. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_preview.py +16 -16
  164. openflo-2.7.0b2/src/openflo/ui_proliferation.py +269 -0
  165. openflo-2.7.0b2/src/openflo/ui_refit.py +104 -0
  166. openflo-2.7.0b2/src/openflo/ui_sample_groups.py +152 -0
  167. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_sample_qc.py +44 -24
  168. openflo-2.7.0b2/src/openflo/ui_saturation.py +182 -0
  169. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_spectral_qc.py +20 -12
  170. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_spectral_unmix.py +24 -8
  171. openflo-2.7.0b2/src/openflo/ui_statistics.py +662 -0
  172. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_synth.py +17 -13
  173. openflo-2.7.0b2/src/openflo/ui_table.py +122 -0
  174. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_tips.py +1 -1
  175. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_trajectory.py +74 -37
  176. openflo-2.7.0b2/src/openflo/ui_unmixed_pairs.py +51 -0
  177. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/ui_voltage.py +20 -12
  178. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/update.py +50 -28
  179. openflo-2.7.0b2/src/openflo/user_guide.py +114 -0
  180. openflo-2.7.0b2/src/openflo/view_tabs.py +315 -0
  181. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/workspace.py +298 -101
  182. {openflo-2.6.2 → openflo-2.7.0b2/src/openflo.egg-info}/PKG-INFO +62 -19
  183. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo.egg-info/SOURCES.txt +165 -6
  184. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo.egg-info/entry_points.txt +1 -1
  185. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo.egg-info/requires.txt +3 -1
  186. openflo-2.7.0b2/tests/_fcs_writer.py +59 -0
  187. {openflo-2.6.2 → openflo-2.7.0b2}/tests/conftest.py +144 -21
  188. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_apply_region_gates.py +61 -30
  189. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autoclean_counts.py +38 -10
  190. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_axis_intensity_ticks.py +4 -1
  191. openflo-2.7.0b2/tests/test_batch_d_responsive.py +745 -0
  192. openflo-2.7.0b2/tests/test_batch_e_quick_features.py +599 -0
  193. openflo-2.7.0b2/tests/test_batch_g_cli_sharding.py +248 -0
  194. openflo-2.7.0b2/tests/test_batch_h_consistent_dialogs.py +560 -0
  195. openflo-2.7.0b2/tests/test_batch_i_menus_fonts_colours.py +608 -0
  196. openflo-2.7.0b2/tests/test_batch_j_data_handling.py +382 -0
  197. openflo-2.7.0b2/tests/test_batch_k_specialist.py +740 -0
  198. openflo-2.7.0b2/tests/test_batch_l_offline_help.py +187 -0
  199. openflo-2.7.0b2/tests/test_batch_n_exports_access.py +582 -0
  200. openflo-2.7.0b2/tests/test_batch_normalize.py +323 -0
  201. openflo-2.7.0b2/tests/test_batch_normalize_dialog.py +203 -0
  202. openflo-2.7.0b2/tests/test_beta_look.py +364 -0
  203. openflo-2.7.0b2/tests/test_beta_release.py +21 -0
  204. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_boolean_gate_fails_closed.py +57 -0
  205. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_boolean_gate_tools.py +53 -0
  206. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_calibration_dialog.py +3 -0
  207. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_capabilities.py +29 -8
  208. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cell_cycle_dialog.py +10 -2
  209. openflo-2.7.0b2/tests/test_ci_full_matrix_events.py +107 -0
  210. openflo-2.7.0b2/tests/test_ci_macos_job.py +44 -0
  211. openflo-2.7.0b2/tests/test_ci_privacy_gate_on_mirror.py +54 -0
  212. openflo-2.7.0b2/tests/test_clean_install_round2.py +121 -0
  213. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_cluster.py +3 -3
  214. openflo-2.7.0b2/tests/test_cli_reproducibility.py +270 -0
  215. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_run.py +79 -8
  216. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_shared_clusters.py +142 -2
  217. openflo-2.7.0b2/tests/test_close_save.py +418 -0
  218. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cluster_dialog.py +48 -8
  219. openflo-2.7.0b2/tests/test_cluster_recovery_highdim.py +142 -0
  220. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_clustering_known_defects.py +44 -14
  221. openflo-2.7.0b2/tests/test_combination_gates.py +323 -0
  222. openflo-2.7.0b2/tests/test_comp_apply_keeps_computed.py +254 -0
  223. openflo-2.7.0b2/tests/test_comp_apply_to_batch.py +149 -0
  224. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compare_flowjo_semantics.py +13 -1
  225. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compensation_editor_apply.py +7 -6
  226. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_console_encoding.py +12 -0
  227. openflo-2.7.0b2/tests/test_copy_gates_dialog.py +123 -0
  228. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cytonorm_qc_undefined.py +19 -9
  229. openflo-2.7.0b2/tests/test_derived_kinetics.py +403 -0
  230. openflo-2.7.0b2/tests/test_dialog_actions.py +282 -0
  231. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_dialog_tooltips.py +132 -12
  232. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_diffexp.py +48 -0
  233. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_editor_tools.py +3 -3
  234. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ellipse_tool.py +3 -3
  235. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ellipsoid_quadrant.py +26 -11
  236. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_embedding_dialog.py +1 -1
  237. openflo-2.7.0b2/tests/test_engine_availability.py +155 -0
  238. openflo-2.7.0b2/tests/test_error_handling.py +279 -0
  239. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_exit_autosave.py +92 -17
  240. openflo-2.7.0b2/tests/test_export_audit.py +282 -0
  241. openflo-2.7.0b2/tests/test_export_stamp.py +288 -0
  242. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_expression_window.py +30 -4
  243. openflo-2.7.0b2/tests/test_fcs_speed.py +226 -0
  244. openflo-2.7.0b2/tests/test_fcs_stored_values.py +82 -0
  245. openflo-2.7.0b2/tests/test_fcs_writer_quirks.py +127 -0
  246. openflo-2.7.0b2/tests/test_fixes.py +713 -0
  247. openflo-2.7.0b2/tests/test_fmo_gating_dialog.py +308 -0
  248. openflo-2.7.0b2/tests/test_fmo_histogram_overlay.py +85 -0
  249. openflo-2.7.0b2/tests/test_fmo_threshold_compare.py +116 -0
  250. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_frequency_missing_populations.py +2 -2
  251. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_frequency_window.py +36 -10
  252. openflo-2.7.0b2/tests/test_gate_coordinates.py +320 -0
  253. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_editor_helpers.py +27 -6
  254. openflo-2.7.0b2/tests/test_gate_on_missing_channel.py +87 -0
  255. openflo-2.7.0b2/tests/test_gate_refit.py +282 -0
  256. openflo-2.7.0b2/tests/test_gate_table.py +71 -0
  257. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_text_intensity.py +8 -3
  258. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_tools_axis_ends.py +7 -6
  259. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gating.py +7 -6
  260. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gating_helpers.py +8 -4
  261. openflo-2.7.0b2/tests/test_gating_strategy_page.py +107 -0
  262. openflo-2.7.0b2/tests/test_gatingml.py +367 -0
  263. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gui_smoke.py +17 -11
  264. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gui_toolings.py +6 -4
  265. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gui_ux.py +28 -19
  266. openflo-2.7.0b2/tests/test_highlighted_sample_is_plotted.py +105 -0
  267. openflo-2.7.0b2/tests/test_independent_reference.py +112 -0
  268. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_inspect_fcs.py +0 -4
  269. openflo-2.7.0b2/tests/test_install_leaves_no_files.py +126 -0
  270. openflo-2.7.0b2/tests/test_knn_threads.py +93 -0
  271. openflo-2.7.0b2/tests/test_layout_editor.py +935 -0
  272. openflo-2.7.0b2/tests/test_layout_exploded.py +463 -0
  273. openflo-2.7.0b2/tests/test_load_reliability.py +391 -0
  274. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_no_plausible_fills.py +5 -1
  275. openflo-2.7.0b2/tests/test_parallel_load_blas.py +125 -0
  276. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_parse_ellipsoid.py +17 -2
  277. openflo-2.7.0b2/tests/test_pipeline_missing_embedding.py +97 -0
  278. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_plot_cache.py +17 -0
  279. openflo-2.7.0b2/tests/test_plot_defaults.py +405 -0
  280. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_polygon_gating.py +81 -0
  281. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_polygon_rect_tools.py +38 -14
  282. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_population_stats_geomean.py +1 -1
  283. openflo-2.7.0b2/tests/test_population_stats_speed.py +315 -0
  284. openflo-2.7.0b2/tests/test_pptx_export.py +128 -0
  285. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_preexisting_data_defects.py +6 -0
  286. openflo-2.7.0b2/tests/test_public_links.py +59 -0
  287. openflo-2.7.0b2/tests/test_python_readiness.py +363 -0
  288. openflo-2.7.0b2/tests/test_quadrant_tool.py +306 -0
  289. openflo-2.7.0b2/tests/test_result_stamps.py +355 -0
  290. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_sample_qc_window_values.py +2 -0
  291. openflo-2.7.0b2/tests/test_saved_stats.py +454 -0
  292. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_session.py +15 -6
  293. openflo-2.7.0b2/tests/test_session_compare.py +147 -0
  294. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_session_continuity.py +19 -0
  295. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_session_json_is_valid.py +4 -5
  296. openflo-2.7.0b2/tests/test_session_lock.py +386 -0
  297. openflo-2.7.0b2/tests/test_session_missing_samples.py +155 -0
  298. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_session_restore_transforms.py +41 -6
  299. openflo-2.7.0b2/tests/test_session_safety.py +400 -0
  300. openflo-2.7.0b2/tests/test_session_saves_label_populations.py +82 -0
  301. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_session_sidecar_collision.py +15 -14
  302. openflo-2.7.0b2/tests/test_session_sidecar_cut_short.py +118 -0
  303. openflo-2.7.0b2/tests/test_setup_installer.py +299 -0
  304. openflo-2.7.0b2/tests/test_small_n_statistics.py +464 -0
  305. openflo-2.7.0b2/tests/test_som_kernel.py +190 -0
  306. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral_qc_window.py +2 -3
  307. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral_unmix_gui.py +2 -2
  308. openflo-2.7.0b2/tests/test_stat_forms.py +115 -0
  309. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_statistics_export.py +10 -4
  310. openflo-2.7.0b2/tests/test_statistics_population_column.py +134 -0
  311. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_stats.py +4 -2
  312. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_theme.py +9 -0
  313. openflo-2.7.0b2/tests/test_tk_main_thread.py +296 -0
  314. openflo-2.7.0b2/tests/test_tk_threads.py +89 -0
  315. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_trajectory_window.py +6 -0
  316. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_transform_records.py +58 -1
  317. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_typecheck_config.py +31 -6
  318. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_audit_sign_verify.py +3 -1
  319. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_logic.py +6 -5
  320. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_undo_redo.py +1 -0
  321. openflo-2.7.0b2/tests/test_unknown_population_is_missing.py +144 -0
  322. openflo-2.7.0b2/tests/test_unknown_scale_export.py +164 -0
  323. openflo-2.7.0b2/tests/test_unknown_scale_import_is_missing.py +87 -0
  324. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_update.py +28 -0
  325. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_update_flow.py +35 -4
  326. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_v261_sessions.py +102 -4
  327. openflo-2.7.0b2/tests/test_view_tabs.py +491 -0
  328. openflo-2.7.0b2/tests/test_window_fit.py +460 -0
  329. openflo-2.7.0b2/tests/test_windows_nothing_hidden.py +386 -0
  330. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_workspace.py +16 -7
  331. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_workspace_prep_offthread.py +16 -1
  332. openflo-2.7.0b2/tests/test_wsp_boolean_gates.py +152 -0
  333. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_export_golden.py +4 -1
  334. openflo-2.7.0b2/tests/test_wsp_extract.py +100 -0
  335. openflo-2.7.0b2/tests/test_wsp_groups_subfolders.py +154 -0
  336. openflo-2.7.0b2/tests/test_wsp_time_edge_check.py +76 -0
  337. openflo-2.7.0b2/tests/test_wsp_unnamed_population_names.py +89 -0
  338. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_writer.py +26 -19
  339. openflo-2.7.0b2/tests/tk_main_thread.py +208 -0
  340. openflo-2.6.2/scripts/bench_accelerators.py +0 -294
  341. openflo-2.6.2/scripts/bench_louvain_restarts.py +0 -120
  342. openflo-2.6.2/scripts/bench_ui_responsiveness.py +0 -174
  343. openflo-2.6.2/scripts/bench_umap_knn.py +0 -177
  344. openflo-2.6.2/scripts/mutate_controls.py +0 -283
  345. openflo-2.6.2/scripts/preflight.py +0 -72
  346. openflo-2.6.2/src/openflo/async_task.py +0 -78
  347. openflo-2.6.2/src/openflo/capabilities.py +0 -116
  348. openflo-2.6.2/src/openflo/editor_grouping.py +0 -87
  349. openflo-2.6.2/src/openflo/editor_help.py +0 -237
  350. openflo-2.6.2/src/openflo/editor_session.py +0 -848
  351. openflo-2.6.2/src/openflo/gating.py +0 -360
  352. openflo-2.6.2/src/openflo/theme.py +0 -230
  353. openflo-2.6.2/src/openflo/tool_window.py +0 -160
  354. openflo-2.6.2/src/openflo/ui_fmo.py +0 -174
  355. openflo-2.6.2/src/openflo/ui_statistics.py +0 -382
  356. openflo-2.6.2/tests/test_fmo_gating_dialog.py +0 -157
  357. openflo-2.6.2/tests/test_quadrant_tool.py +0 -171
  358. openflo-2.6.2/tests/test_wsp_extract.py +0 -71
  359. {openflo-2.6.2 → openflo-2.7.0b2}/LICENSE.txt +0 -0
  360. {openflo-2.6.2 → openflo-2.7.0b2}/MANIFEST.in +0 -0
  361. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/bench_import_cost.py +0 -0
  362. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/bench_snn_prune.py +0 -0
  363. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/lowest_direct_requirements.py +0 -0
  364. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/make_synthetic_dataset.py +0 -0
  365. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/migrate_session.py +0 -0
  366. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/run_analyses.py +0 -0
  367. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/smoke_test.py +0 -0
  368. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/theme_audit.py +0 -0
  369. {openflo-2.6.2 → openflo-2.7.0b2}/scripts/typecheck.py +0 -0
  370. {openflo-2.6.2 → openflo-2.7.0b2}/setup.cfg +0 -0
  371. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/__init__.py +0 -0
  372. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/_golden.json +0 -0
  373. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/annotate.py +0 -0
  374. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/comp_qc.py +0 -0
  375. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/compliance.py +0 -0
  376. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_base.py +0 -0
  377. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/editor_channels.py +0 -0
  378. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/gatetree.py +0 -0
  379. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/gating_helpers.py +0 -0
  380. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/interop.py +0 -0
  381. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/py.typed +0 -0
  382. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/selftest.py +0 -0
  383. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/selftest_controls.py +0 -0
  384. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/spectral.py +0 -0
  385. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/template_library/README.md +0 -0
  386. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/template_library/cleanup_acquisition_qc.json +0 -0
  387. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/template_library/cleanup_minimal.json +0 -0
  388. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/template_library/cleanup_standard.json +0 -0
  389. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/template_library/cleanup_strict.json +0 -0
  390. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/template_library/example_panel.json +0 -0
  391. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/trajectory.py +0 -0
  392. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/tree_ids.py +0 -0
  393. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/voltage.py +0 -0
  394. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo/warmup.py +0 -0
  395. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo.egg-info/dependency_links.txt +0 -0
  396. {openflo-2.6.2 → openflo-2.7.0b2}/src/openflo.egg-info/top_level.txt +0 -0
  397. {openflo-2.6.2 → openflo-2.7.0b2}/templates/testtemplate.json +0 -0
  398. {openflo-2.6.2 → openflo-2.7.0b2}/tests/__init__.py +0 -0
  399. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_abscounts.py +0 -0
  400. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_acquisition_qc_ground_truth.py +0 -0
  401. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_annotate.py +0 -0
  402. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_annotate_clusters.py +0 -0
  403. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_annotate_rename_dialogs.py +0 -0
  404. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_annotation_window.py +0 -0
  405. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_async_task.py +0 -0
  406. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_audit.py +0 -0
  407. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autoclean_doublets_scale.py +0 -0
  408. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autoclean_editor_actions.py +0 -0
  409. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autoclean_methods.py +0 -0
  410. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autogate.py +0 -0
  411. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autogate_dialog.py +0 -0
  412. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_autogate_reproducible.py +0 -0
  413. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_calibration.py +0 -0
  414. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cell_cycle.py +0 -0
  415. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_channel_classification.py +0 -0
  416. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_e2e.py +0 -0
  417. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_export_wsp.py +0 -0
  418. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_run_options.py +0 -0
  419. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cli_unmix.py +0 -0
  420. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_clipboard_move_marker.py +0 -0
  421. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cluster_gpu_fallback.py +0 -0
  422. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cluster_heatmap.py +0 -0
  423. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cluster_labels.py +0 -0
  424. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cluster_reproducible.py +0 -0
  425. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_clustering_reproducible.py +0 -0
  426. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_comp_editor.py +0 -0
  427. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_comp_matrix_csv_quoting.py +0 -0
  428. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_comp_qc.py +0 -0
  429. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compare.py +0 -0
  430. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compare_all.py +0 -0
  431. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compare_conditions_absent_clusters.py +0 -0
  432. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compare_conditions_joint_clustering.py +0 -0
  433. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compare_dialog.py +0 -0
  434. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compensation.py +0 -0
  435. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compensation_applied_once.py +0 -0
  436. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compensation_fcs_spill.py +0 -0
  437. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compensation_matrix_reading.py +0 -0
  438. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_compliance.py +0 -0
  439. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_cytonorm.py +0 -0
  440. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_degenerate_scatter_gating.py +0 -0
  441. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_density.py +0 -0
  442. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_diagnostics.py +0 -0
  443. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_diagnostics_selftest_gate.py +0 -0
  444. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_diff_abundance_small_n.py +0 -0
  445. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_diffabundance.py +0 -0
  446. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_diffabundance_calibration.py +0 -0
  447. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_doctor_tk_check.py +0 -0
  448. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_dr_compare.py +0 -0
  449. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_drag_tree_refresh.py +0 -0
  450. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_editor_mixins.py +0 -0
  451. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ellipse_params_geometry.py +0 -0
  452. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_embedding.py +0 -0
  453. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_embeddings.py +0 -0
  454. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_exceptions.py +0 -0
  455. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_fast_graph.py +0 -0
  456. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_fcs_export.py +0 -0
  457. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_figure_layout.py +0 -0
  458. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_flowsom.py +0 -0
  459. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_flowsom_mst.py +0 -0
  460. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_flowsom_tree_window.py +0 -0
  461. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_fmo_threshold_log.py +0 -0
  462. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_fold_change_direction.py +0 -0
  463. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_impl_parity.py +0 -0
  464. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_import_fixes.py +0 -0
  465. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_mask_cache.py +0 -0
  466. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_nonfinite_events.py +0 -0
  467. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_partition.py +0 -0
  468. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gate_replication_parent.py +0 -0
  469. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gatetree.py +0 -0
  470. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gmm_ellipse_info.py +0 -0
  471. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_golden_regression.py +0 -0
  472. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gpu_accel.py +0 -0
  473. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_group_stats.py +0 -0
  474. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_group_stats_window.py +0 -0
  475. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_group_summary_sd.py +0 -0
  476. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_grouping.py +0 -0
  477. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_gui_guard.py +0 -0
  478. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_histogram_offscale_note.py +0 -0
  479. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_histogram_resolution.py +0 -0
  480. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_import_is_lazy.py +0 -0
  481. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_interop.py +0 -0
  482. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_interop_h5ad_roundtrip.py +0 -0
  483. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_label_align.py +0 -0
  484. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_legend_placement.py +0 -0
  485. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_leiden.py +0 -0
  486. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_load_no_copy.py +0 -0
  487. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_loader_queue.py +0 -0
  488. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_log_histogram_range.py +0 -0
  489. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_log_transform_nonpositive.py +0 -0
  490. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_mem_tiny_clusters.py +0 -0
  491. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_normalise_groups_idempotent.py +0 -0
  492. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_not_gate_excludes_unmeasured.py +0 -0
  493. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_optimize_compensation_truth.py +0 -0
  494. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_panel_pairs.py +0 -0
  495. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_paths.py +0 -0
  496. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_phenograph_pool_cap.py +0 -0
  497. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_plot_density.py +0 -0
  498. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_plotmath.py +0 -0
  499. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_preferences_wiring.py +0 -0
  500. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_prefs.py +0 -0
  501. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_prepare_unit_tags.py +0 -0
  502. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_preview_quadrants.py +0 -0
  503. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_propagate_downsample_order.py +0 -0
  504. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_provenance.py +0 -0
  505. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_provenance_unreadable_entries.py +0 -0
  506. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_qc.py +0 -0
  507. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_qc_ignores_derived_columns.py +0 -0
  508. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_qc_integer_channels.py +0 -0
  509. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_qc_removed_visible.py +0 -0
  510. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_quality_correctness.py +0 -0
  511. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_quick_preview_dialog.py +0 -0
  512. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_raw_not_aliased.py +0 -0
  513. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_report.py +0 -0
  514. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_resume_and_transfer_hygiene.py +0 -0
  515. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_run_louvain.py +0 -0
  516. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_scales.py +0 -0
  517. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_scales_logicle.py +0 -0
  518. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_seed_phrases.py +0 -0
  519. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_selftest.py +0 -0
  520. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_selftest_cli.py +0 -0
  521. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_selftest_controls.py +0 -0
  522. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_session_migration_machinery.py +0 -0
  523. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral.py +0 -0
  524. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral_degenerate_reference.py +0 -0
  525. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral_matched_af.py +0 -0
  526. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral_measured_and_spread.py +0 -0
  527. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_spectral_unmix_nonfinite.py +0 -0
  528. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_stats_multigroup.py +0 -0
  529. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_survivor_boundaries.py +0 -0
  530. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_synthetic.py +0 -0
  531. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_template_apply.py +0 -0
  532. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_template_one_sample.py +0 -0
  533. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_templates.py +0 -0
  534. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_threshold_inclusive.py +0 -0
  535. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_tool_window.py +0 -0
  536. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_trajectory.py +0 -0
  537. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_trajectory_nonfinite.py +0 -0
  538. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_transform_consistency.py +0 -0
  539. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_transform_nonfinite.py +0 -0
  540. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_transform_record_writes.py +0 -0
  541. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_transforms.py +0 -0
  542. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_tree_ids.py +0 -0
  543. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_compare.py +0 -0
  544. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_dialogs_smoke.py +0 -0
  545. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_inspect.py +0 -0
  546. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_preview.py +0 -0
  547. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_sample_qc.py +0 -0
  548. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_synth.py +0 -0
  549. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_ui_voltage.py +0 -0
  550. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_umap_embedding.py +0 -0
  551. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_voltage.py +0 -0
  552. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_voltage_cli.py +0 -0
  553. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_voltage_titration_dialog.py +0 -0
  554. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_warmup.py +0 -0
  555. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_workspace_batch_folder.py +0 -0
  556. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_workspace_job_driver.py +0 -0
  557. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_write_fcs.py +0 -0
  558. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_axis_floor.py +0 -0
  559. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_gate_units.py +0 -0
  560. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_partial_matrix.py +0 -0
  561. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_population_names.py +0 -0
  562. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_quadrant_export.py +0 -0
  563. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_sample_compensation.py +0 -0
  564. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_sample_keywords.py +0 -0
  565. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_time_units.py +0 -0
  566. {openflo-2.6.2 → openflo-2.7.0b2}/tests/test_wsp_writer_structure.py +0 -0
@@ -6,6 +6,923 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ This becomes **2.7.0b2**, the first public beta on PyPI (the version in
10
+ pyproject.toml); 2.7.0b1 went only to the public source mirror. It is tagged
11
+ as a pre-release, so `pip install openflo` keeps installing 2.6.2 until
12
+ `--pre` is given, and the update check offers a beta only to someone already
13
+ on a pre-release.
14
+
15
+ ### Changed
16
+ - **No error disappears unseen any more.** An error in a background
17
+ thread, or in background work with no handler of its own, now opens the
18
+ same "Something went wrong" window and problem report as any other,
19
+ instead of only printing or only flashing in the status bar. The 418
20
+ places that caught every error and did nothing now catch only what they
21
+ expect, write a "[handled]" line to the log, or say why nothing can
22
+ matter there; a test keeps new ones out.
23
+ - **Dialogs say which setting is wrong.** Fifteen number settings
24
+ (Cluster, Cell cycle, Proliferation, Bead tracking, Auto-gate, Batch-norm,
25
+ Calibration, Embedding, MEM annotation, FMO, Spectral unmixing,
26
+ Trajectory, Figure layout and the layout editor's text size) refuse text
27
+ that is not a number in range with a message naming the setting.
28
+ **Behaviour change:** several used to swap a mistyped value for a default
29
+ without saying so, or clamp it into range; they now stop instead.
30
+ - **Re-fit per sample finds the valley before a bright population at the
31
+ top of the range.** It used to say "one population, no valley" and keep
32
+ the old cut when a tight population sat at the top of the data.
33
+ **Behaviour change:** such gates now move to the valley.
34
+ - **The layout editor's Layout and Export buttons** no longer show the menu
35
+ arrow twice.
36
+ - **The embedding warm-up no longer stalls.** Tk clean-up that a background
37
+ thread's garbage collection triggers is handed to the window's event loop
38
+ rather than waiting on it.
39
+ - **Group comparisons can come out significant at 3 to 5 samples a
40
+ group.** Mann-Whitney's smallest possible p at 3 vs 3 is 0.1, so two
41
+ groups that did not overlap at all read "not significant". Frequencies
42
+ and Marker expression now default to Welch's t-test (ANOVA for more
43
+ groups) on transformed values: percentages as log odds, counts as log,
44
+ intensities on the arcsinh scale (cofactor 150). The Test box keeps the
45
+ rank test and a t-test on raw values. **Behaviour change:** p-values in
46
+ those windows, Compare all, and the CLI's `compare_*.csv` (now Welch's t
47
+ on the logit of each sample's %) differ from earlier versions on purpose.
48
+ - **Paired by donor.** A tick in Frequencies and Marker expression matches
49
+ samples by their name with the group taken out (D1_Stim with D1_Ctrl):
50
+ two groups get a paired t-test (Wilcoxon signed-rank with the rank test),
51
+ more get a repeated-measures ANOVA (Friedman). Differential abundance
52
+ then models the donor as a term (`count ~ donor + group`, as diffcyt's
53
+ edgeR model), so a donor's own level is taken out before the groups are
54
+ compared. A sample with no partner is left out and named.
55
+ - **Saying when a test can't reach 0.05.** Every result carries the
56
+ smallest p its test could give at those group sizes; the summaries,
57
+ Compare all and Group comparison say plainly when that is above 0.05.
58
+ - **MESF calibration fits log-log by default.** Tools > Calibration fits
59
+ log MESF against log MFI, the standard for bead sets spanning decades;
60
+ Linear is one click away. `fit_mesf_calibration` and `apply_calibration`
61
+ take `model='loglog'` and keep linear as their default.
62
+ - **Faster `openflo-run` batches, same files.** With `--workers` above 1,
63
+ each sample's scatter plots and stats CSV are drawn in the worker that
64
+ clustered it, instead of one at a time in the main process while the
65
+ workers wait; group UMAPs are drawn in parallel; and `compare` takes
66
+ `--workers` (default up to 4) and compares samples in parallel, reporting
67
+ them in workspace order. The files are byte-identical to a one-worker run.
68
+ - **Every dialog behaves the same way.** Each window opened from the editor
69
+ stays in front of it (no taskbar entry of its own), Escape closes it, and
70
+ Return runs its main action, which sits beside Cancel or Close at the
71
+ right of the button row. The layout editor and a popped-out workspace stay
72
+ separate windows.
73
+ - **Errors in plain words.** An error says what failed and why ("The file
74
+ may be open in another program") instead of the exception's name; Show
75
+ details has the technical description and Copy details copies it for a
76
+ bug report. An unexpected error opens that window once instead of popping
77
+ the log and console open.
78
+ - **Keyboard shortcuts and About are proper windows**: shortcuts in two
79
+ columns by menu, and About with the version, licence, how to cite (with a
80
+ Copy citation button) and links.
81
+ - **Clearer wording.** Side-panel buttons say what they do (Delete gate,
82
+ Delete all, Copy gates…, Populations…, Undo, Redo, Fit, Apply to all
83
+ shown), beta tags moved into the tips, no arrows used as words (File >
84
+ Export FlowJo workspace…), sentence case, American spelling, the methods'
85
+ published names (PhenoGraph, t-SNE, TriMap), and the Pipeline's fields
86
+ named in full (Method, Neighbors, Resolution, Max events, Seed).
87
+ - **The title names the session**: "session name — OpenFlo", with * while
88
+ there are unsaved changes.
89
+ - **Menus regrouped, with their shortcuts.** Tools is in four groups
90
+ (compensation and scales, figures, utilities, templates); exports moved
91
+ to File, and Methods & provenance sits beside History / audit in Analyze.
92
+ Menus show Ctrl+Z, Ctrl+Y, Ctrl+F, the zoom keys and F5, and on macOS
93
+ the same keys answer to Command. F1 opens the documentation, and the main
94
+ window's File menu ends in Quit OpenFlo (Ctrl+Q).
95
+ - **No pop-up after an export.** The status bar says what was saved, with
96
+ an Open folder button, instead of a box to click away.
97
+ - **Theme: windows and plot background, chosen separately.** View > Theme
98
+ is Light or Dark windows, and View > Plot background is White or Black;
99
+ figures in analysis windows follow the plot background, so "Dark figures
100
+ in pop-ups" is gone. Sessions keep their theme.
101
+ - **Readable fonts and colours.** Windows use named fonts that follow the
102
+ window-width scaling and are never below 9 pt. Error, warning and success
103
+ text, the drag highlight and the log pane take their colours from the
104
+ theme, each readable at 4.5:1 or more; the log is light in the light
105
+ theme.
106
+ - **Cluster dialog**: only the picked method's settings show (FlowSOM's
107
+ grid and metaclusters, Leiden's resolution), and markers are listed as
108
+ "marker (channel)", as the axis menus show them.
109
+ - **Layout editor**: Ctrl+Z and Ctrl+Y undo and redo, Ctrl+S saves the
110
+ session (which carries the layout), and a change marks the session
111
+ unsaved, so closing asks to save it. The empty-page hint names the real
112
+ buttons and the status line wraps instead of running off the window.
113
+ - **Tools > Quick figure grid** is the old Figure layout, one name in the
114
+ menu and the window, so it is not mistaken for the layout editor.
115
+
116
+ ### Added
117
+ - **Results record the OpenFlo version that made them (session schema
118
+ v3).** A gate placed by Re-place on each sample or by Auto-gate, and each
119
+ column written by clustering, an embedding, cell cycle, calibration or
120
+ pseudotime, carries a stamp: the step, the OpenFlo version and what it ran
121
+ on. The `.flowsession` gains a top-level `app_version`, and each sample
122
+ entry an `app_version` and `result_stamps` (gate stamps travel on the gate
123
+ as `stamp`). Saving writes an older result's stamp back unchanged, so a
124
+ later fix can find the results made before it. Each audit-trail entry
125
+ also records the version (`version`). Version-2 sessions upgrade on open,
126
+ their results' versions unknown; a session saved now does not open in an
127
+ older OpenFlo.
128
+ - **Fixes reach saved sessions, once and only when you say so.** When a
129
+ session opens, OpenFlo lists the saved results made by a version with a
130
+ bug since fixed: each sample's gate or column, the version that made it,
131
+ and its numbers before and after (events and % of parent for a gate).
132
+ Tick the ones to correct and Apply (one undo step); nothing changes
133
+ otherwise. A corrected result is recomputed with this version, its stamp
134
+ moves to it, and its original stays in the session (a gate's
135
+ `original`, a column's values under `<column> [original <version>]`,
136
+ out of plots and statistics). Fixes not taken are recorded and not
137
+ offered again; Tools > Review fixes for this session… lists them all.
138
+ Gates and other results built on a corrected one are offered as a
139
+ follow-up re-run, never started by themselves; for clusters, embeddings,
140
+ trajectory, cell cycle and calibration, Open… opens that tool on the
141
+ sample. The same list names the files exported from a corrected sample
142
+ before the fix (when made, by which version, whether still there), with
143
+ Show in folder; they are not re-exported. Plot images, Gating-ML,
144
+ FlowJo .wsp, population FCS folders and the Statistics, Frequencies,
145
+ Kinetics, workspace-comparison and differential-abundance exports are
146
+ recorded for it too. A changed default is
147
+ flagged, not applied. The first fix listed is the re-fit valley fix
148
+ (#78), for gates re-placed per sample before 2.7.0b2. Exports name the
149
+ fixes applied. The session gains `fixes_applied` and `fixes_declined`,
150
+ and each sample entry `result_originals`.
151
+ - **`openflo-run check-sessions <folder>`** lists, for every `.flowsession`
152
+ under a folder, the fixes that would apply to it. It only reads.
153
+ - **A gate moved by hand is yours.** Dragging, typing coordinates, the
154
+ arrow keys or a slider takes the step's stamp off the gate (its stamp
155
+ records the geometry it was placed with), and a pasted or copied gate
156
+ carries none, so a later fix never overwrites a hand edit.
157
+ - **A reopened session shows its gate counts and statistics at once.** Each
158
+ sample entry saves the counts its gates had and the Statistics window's
159
+ last few tables (`saved_stats`, numbers only), and a reopen uses them
160
+ instead of computing them again, but only when the sample's data digest,
161
+ the gates (and their ancestors), the statistics chosen, the Preferences
162
+ that change a formula and the OpenFlo version are all the same; anything
163
+ else is computed again as before. Measured on 6 samples of 500,000
164
+ events with 10 gates each: the counts were all shown 0.8 s after the last
165
+ sample loaded, now as each one loads; the Statistics window was ready in
166
+ 1.1 s, now 0.25 s. Plots are not saved.
167
+ - **Normalize batches dialog.** Tools > Normalize batches (CytoNorm)
168
+ replaces the one-click batch-norm: batches by trial / day or by any FCS
169
+ keyword ($DATE by default), the mapping learnt from a reference sample
170
+ in every batch (named with a token such as "ref"; CytoNorm's standard
171
+ form) or from all samples (CytoNorm 2.0), 1 to 40 metaclusters, applied
172
+ to the loaded samples and/or written as normalized FCS copies. It shows
173
+ each batch before and after on a channel, the per-batch medians and a
174
+ methods sentence, which also goes into the audit trail. Compensation
175
+ controls and set-aside samples stay out of the fit. Restore original
176
+ values (in the dialog, or Tools > Undo batch normalization) puts the
177
+ loaded samples back on their pre-normalization values without reloading.
178
+ - **Excel workbooks and European CSV.** Statistics has Save as Excel: the
179
+ whole table on one sheet, then one sheet per statistic (populations
180
+ down, samples across), and an About sheet with the OpenFlo version.
181
+ Frequencies can save its tidy table as .xlsx, and `openflo-run --xlsx`
182
+ collects the run's CSV tables into one workbook. Preferences > Export >
183
+ CSV tables can follow this computer's list separator and decimal mark
184
+ (semicolon and decimal comma across most of Europe), so Excel opens the
185
+ CSV in columns; copied tables follow the same setting. The default CSV
186
+ is unchanged.
187
+ - **Set a sample aside.** Right-click a sample > Set aside from
188
+ comparisons, with a reason (failed QC, clogged, too few events). It stays
189
+ in the session and in the Statistics table (with a Set aside column),
190
+ is greyed with its reason in the tree, is left out of every group
191
+ comparison (Frequencies, Expression, Group stats, normalization) and is
192
+ listed with its reason in the methods text. A free-text note per sample
193
+ too. Both are saved in the session (additive keys an older OpenFlo
194
+ ignores).
195
+ - **Colour-blind-safe colours and keyboard gating.** Samples and groups
196
+ now take the Okabe-Ito colours, which stay distinct under the common
197
+ colour-vision deficiencies (tab10's red and green do not); View > Group
198
+ colors switches back to the classic palette, keeping colours you picked
199
+ by hand. The pipeline's `--palette auto` follows. The plot takes keyboard
200
+ focus (click it, or F6 from the gate list): the arrow keys move the gate
201
+ selected in the list, Shift for larger steps, one undo step per run of
202
+ presses.
203
+ - **Setup without Python.** `setup.bat` and `setup.sh` no longer stop when
204
+ no Python 3.11+ is installed (an older Python is now refused up front
205
+ instead of failing in pip). They use a `uv` on PATH, or download a pinned
206
+ uv release (Windows x86_64/ARM64, macOS Intel/Apple silicon, Linux
207
+ x86_64/aarch64) into `.tools/uv`, check its SHA-256 against the hash in
208
+ the script and refuse to continue on a mismatch, then let uv install
209
+ Python 3.12 and create `.venv`. An interactive Windows setup also adds
210
+ Desktop and Start-menu shortcuts. The scripts are not code-signed, so
211
+ SmartScreen or Gatekeeper may still warn.
212
+ - **Batch normalization helpers (library).** `openflo.batch_normalize`
213
+ wraps the existing CytoNorm model for acquisition-day batches: group
214
+ samples by any FCS keyword (`$DATE` by default in a dialog), fit on a
215
+ reference sample run in every batch ('controls') or on all samples
216
+ ('goal'), apply to new frames without touching the loaded ones, a
217
+ per-channel, per-batch median and robust-SD before/after table with a
218
+ methods-report sentence, and normalized FCS copies (compensated linear
219
+ values, source keywords kept, `OPENFLO_NORMALIZATION` / `OPENFLO_NORM_*`
220
+ provenance keywords added) written to a folder. Original files are never
221
+ modified.
222
+ - **Combination gates.** Edit > Combination gates… (also on a gate's
223
+ right-click menu) takes 2 to 6 gates and makes every combination of
224
+ them, all positive to all negative, as boolean populations under the
225
+ gates' common parent, on every sample that has those gates; a negative
226
+ means in the parent and outside the gate, and an event with no value is
227
+ in no combination. A polyfunctionality window shows SPICE-style bars
228
+ per combination (group means with a dot per sample), the +/- grid and
229
+ pies of how many gates each positive event is in, and exports the
230
+ frequency table (NA where a frequency cannot be computed). The
231
+ combinations are ordinary gates, so they reach statistics and the
232
+ FlowJo export.
233
+ - **Typed gate coordinates.** A gate's right-click > Edit coordinates…
234
+ types a threshold, interval, rectangle, quadrant divider, polygon's
235
+ vertices or ellipse's centre and shape in the axis units (intensity;
236
+ 1.5K reads as 1,500, a blank bound is open), optionally on the same gate
237
+ in every other sample. One undo step.
238
+ - **Gates that re-fit per sample.** A threshold or interval gate, or a
239
+ singlet or Gaussian-mixture gate from Auto-gate, can be re-placed on
240
+ each sample's own data (right-click > Re-place on each sample…): a cut
241
+ moves to the density valley nearest where it was drawn, and a proposal
242
+ is fitted again by its own rule. A grid shows every sample's old and
243
+ new placement before you accept; a sample with no landmark keeps its
244
+ gate and says why.
245
+ - **Compare FMO thresholds.** Edit > FMO gating has a Compare
246
+ thresholds button: every stained sample's % positive on each mapped
247
+ channel with the cutoff at each FMO percentile you list (99 and 99.5 by
248
+ default, i.e. 1 % and 0.5 % of FMO events above the gate), inside the
249
+ selected parent gate, without adding a gate. Copy or export the table.
250
+ - **FMO on histograms.** Once FMO gating has mapped a channel to its FMO
251
+ control, a histogram of that channel draws the FMO in grey behind the
252
+ sample, and the legend gives each one's % at or above the channel's
253
+ threshold gate. View > FMO on histograms turns it off. The mapping is
254
+ saved with the session.
255
+ - **Gating strategy page.** The layout editor's + Strategy (also on the
256
+ page menu) adds a page with one plot per gating step, each the parent
257
+ population on the gate's axes with its gates drawn; with a gate selected
258
+ in the main window, only the steps down to it. Batch export draws it for
259
+ every sample.
260
+ - **PowerPoint export.** The layout editor exports a page, or every
261
+ sample's pages in a batch, as a .pptx deck: one slide per page, the page
262
+ drawn at the export resolution and filling a slide of the page's size.
263
+ - **Compare with saved session.** File > Compare with saved session counts
264
+ every population with this session's gates and with a saved session's
265
+ gates, on the events loaded now, and highlights each count that moved
266
+ and each gate that changed.
267
+ - **Gate list.** File > Export gate list (CSV) writes every sample's gates
268
+ with their population path, kind, channels and coordinates, both as
269
+ stored and in linear units.
270
+ - **FlowJo export checks Time gates.** A Time gate whose upper edge ends
271
+ within the first `$TIMESTEP` fraction of the run (where an edge already
272
+ converted to seconds lands) now warns, with the events it cuts, since
273
+ OpenFlo keeps Time in ticks and converts it to seconds for FlowJo.
274
+ - **Derived channels.** Tools > Derived channels makes a channel computed
275
+ per event from others, such as the ratio of two detectors for a ratio
276
+ dye, FRET, or one marker over another: `[FL1-A] / [FL2-A]`,
277
+ `log10([FL1-A] / [FL2-A])`. Channels go in square brackets (a marker name
278
+ works too); + - * / ** and log, log10, log2, sqrt, exp, abs, asinh, min
279
+ and max can be used, and nothing else is run. It is computed on
280
+ compensated linear values, shows as `D:<name>` to plot, gate and
281
+ summarise, is saved with its formula (in the sample entry, so older
282
+ sessions open as before) and recomputed when a session reopens from the
283
+ FCS files. An event with no finite result (0 / 0) is NaN, not 0.
284
+ - **Kinetics.** Analyze > Kinetics follows a channel over acquisition time
285
+ (calcium flux, dye uptake): one binned median (or mean) trace per sample,
286
+ overlaid, and a table of each sample's baseline, peak after the
287
+ stimulus, time to peak, area over the baseline and peak / baseline, with
288
+ CSV and figure export. Time is in seconds when the file gives
289
+ `$TIMESTEP`, else in ticks, and the window says which. What can't be
290
+ computed (no baseline events, a baseline at 0 for the fold) reads NA.
291
+ - **Two windows can't overwrite one session silently.** Opening or saving a
292
+ session by name writes a small lock file beside it
293
+ (`name.flowsession.lock`) naming the computer and user. A second person,
294
+ or a window of another OpenFlo, is told the session is open and picks
295
+ Open read-only (Save then asks for a new name), Open anyway or Cancel. A
296
+ lock left by a crash on this computer is taken over without asking.
297
+ Before Save, or Save and close, writes over the file this window opened,
298
+ it checks the file is still the one it read or last saved; if someone
299
+ saved in between (or the file was deleted), it asks: Save as a copy,
300
+ Overwrite or Cancel. Synced folders copy lock files late, so this check
301
+ is what protects the data.
302
+ - **Every test proven to pass and to fail.** `scripts/prove_tests.py` runs
303
+ each test on the real code, then breaks the code it guards one change at
304
+ a time and records the break that made it fail. `docs/test-assurance/`
305
+ holds the result for all 3,420 tests (3,406 proven both ways) and the
306
+ method. Tests that needed a developer's own sample files now generate
307
+ their data, and tests that could never fail were strengthened.
308
+ - Hover help on the auto-gate method choices, the embedding method boxes and
309
+ the axis-settings parameter box.
310
+ - **Offline user guide.** Help > User guide (F1) opens a guide and
311
+ tutorial that ship with OpenFlo, so it works without a connection: ten
312
+ topics from a first analysis to troubleshooting, with search, and the
313
+ tutorial can load the example dataset. Help > Online documentation still
314
+ opens the web page. A test checks that every menu path the guide names
315
+ exists.
316
+ - **Dean-Jett-Fox cell cycle.** Analyze > Cell cycle fits G1 and G2/M peaks
317
+ and a polynomial S phase (Fox 1980) by default and reports their areas,
318
+ so S-phase cells under the peaks count as S (synthetic truth 56/30/14
319
+ read 55.8/30.5/13.6, against 53.4/29.3/17.4 by boundaries). The window
320
+ draws the fit and lists the boundaries result beside it; Boundaries stays
321
+ available, and `cell_cycle(method=...)` defaults to it.
322
+ - **Proliferation.** Analyze > Proliferation fits a dye-dilution histogram
323
+ as generations one halving apart and reports cells per generation,
324
+ precursor frequencies, percent divided and the division, proliferation
325
+ and expansion indices (Roederer 2011). The undivided peak is found, or
326
+ fixed at an unstimulated control's median. Too few cells read NA.
327
+ - **Spreading matrix for compensation.** Tools > Spreading matrix measures
328
+ the spillover spread of conventional compensation from compensated
329
+ single-stain controls, matched to their channels automatically; a
330
+ channel without a control reads NA. CSV and figure export.
331
+ - **Several autofluorescence signatures.** Spectral unmix can find up to
332
+ five autofluorescence spectra in the unstained control; each cell is
333
+ unmixed with the one that fits it best, and a U:AF index channel says
334
+ which. Spectral QC adds Unmixed pairs, an NxN plot of every pair of
335
+ unmixed fluors.
336
+ - **Bead tracking.** Tools > Bead tracking keeps a Levey-Jennings log of QC
337
+ bead runs (a CSV) and judges each run by the Westgard rules (1-3s, 2-2s,
338
+ R-4s, 4-1s and 10x reject; 1-2s warns).
339
+ - **Index-sort data.** Index-sort locations in the FCS keywords
340
+ (INDEX SORTING LOCATIONS, $INDEX and similar) are read, and the plate view
341
+ shows each sorted cell in its well, coloured by a channel.
342
+ - **Group samples by keyword or table.** Edit > Group samples puts the
343
+ samples into groups by an FCS keyword the files carry (tube, condition,
344
+ plate, ...) or by a sample-to-condition table (CSV or tab separated: the
345
+ sample or its file name, then its group), with a preview before Apply.
346
+ The groups are the ones the sample tree and the group statistics use.
347
+ - **Plate view.** Analyze > Plate view shows a 96-well plate heatmap of the
348
+ events, or a gate's count or percent of its parent, per well. The well
349
+ comes from the well keyword or the sample's name (Plate1_B07); a sample
350
+ without the gate reads NA, and samples without a well are listed.
351
+ - **Export populations, all samples.** File > Export populations, all
352
+ samples (FCS) writes every sample's gated populations, a folder per
353
+ sample.
354
+ - **Concatenate samples** from File: joins several samples with the same
355
+ channels and scales into one new sample, each event tagged with its
356
+ source in a `__sample__` column.
357
+ - **Downsampling the data can be undone.** Setting the downsample from
358
+ Display + data back to Display only or Off restores every event, except
359
+ in a sample whose data changed after the trim, which is named.
360
+ - **More statistics.** Analyze > Statistics adds %Grandparent, SD, robust
361
+ SD (half of P84.13 to P15.87), MAD, any percentiles you type (P5, P95,
362
+ ...) and the MFI ratio: a population's median over the same population's
363
+ median in a reference sample you pick, such as the unstained control.
364
+ The column ticks sit on three rows so they fit a laptop screen, and Copy
365
+ table puts the table on the clipboard, tab separated, for Excel or Prism.
366
+ - **Editable figure text.** PDF and SVG exports keep their labels as text
367
+ (TrueType in PDF, `<text>` in SVG), so Illustrator and Inkscape can edit
368
+ them; Preferences can switch it off. Preferences also set the export
369
+ resolution (150 to 1200 dpi, 300 by default) and the figure font, from the
370
+ fonts installed. File > Copy plot to clipboard, and right-click on the
371
+ figure in analysis windows, copy a figure as an image.
372
+ - **Saturation report.** Analyze > Saturation report lists, per sample,
373
+ the percent of events at the top of each channel's range (`$PnR` - 1, on
374
+ the raw detector values) and across any channel, flags channels at 1 %
375
+ or more, and exports or copies the table. A channel whose range the file
376
+ does not give reads NA.
377
+ - **Gating-ML 2.0 files.** File > Export Gating-ML 2.0 gates writes the
378
+ active sample's gate tree in the ISAC standard other analysis tools read,
379
+ with each channel's scale (logicle, hyperlog, asinh as `fasinh`, log as
380
+ `flog`) and the sample's compensation matrix, so polygons keep their
381
+ shape. Templates > From file… and `openflo-run --gates-from` read them;
382
+ a polygon stays straight on the axes the file names. Gates Gating-ML
383
+ cannot hold, or OpenFlo cannot read (quadrant gates, ratio dimensions),
384
+ are left out with what is under them, and named.
385
+ - **FlowJo groups come across.** Opening a `.wsp` puts each sample in an
386
+ OpenFlo group named after its FlowJo group (not 'All Samples' or
387
+ 'Compensation'), and the `.wsp` export writes OpenFlo's groups as FlowJo
388
+ groups. A workspace whose FCS files moved finds them in the subfolders
389
+ of its own folder or the FCS folder; two files of one name are told
390
+ apart by their folder, never guessed.
391
+ - **`openflo-run` reproducibility.** Each run writes `run_manifest.json`
392
+ (command, settings, software versions, gates, and inputs and outputs
393
+ with SHA-256). `--gates-from` gates every sample on one population
394
+ (`--gate-population`) of a GUI session, template, `.wsp` or Gating-ML
395
+ file. `--gate-units data` takes `--gates` in intensities instead of
396
+ logicle units. The comparison CSV gains `p` (Mann-Whitney U) and `p_adj`
397
+ (Benjamini-Hochberg), as the GUI's differential abundance has.
398
+ - **Project archives.** File > Export project archive saves one .zip with
399
+ the session, its saved data and every sample's FCS file, to send or move
400
+ as one file. Open session opens it directly: it unpacks beside the zip
401
+ and relinks the samples there. Samples whose FCS file was not found are
402
+ listed when exporting.
403
+ - **Save session as.** Save (Ctrl+S) now writes back to the session you
404
+ opened or last saved, and asks for a name only the first time; Save
405
+ session as picks a new one. The window title shows the session's name.
406
+ - **Exploded view of a zoom or gate in layouts.** A layout plot whose zoom
407
+ hides part of its data shows the whole plot with the zoomed region
408
+ outlined, beside a magnified copy joined to it by connector lines, on the
409
+ page and in Export page and Export batch. Edit… sets it per plot (Auto,
410
+ Always when zoomed, Off), chooses the copy's side, and can magnify one of
411
+ the plot's gates (its events, outline and %) instead of the zoom.
412
+
413
+ ### Changed
414
+ - **Nothing written inside the installed package.** PhenoGraph's scratch
415
+ files now go to a temporary folder per run that is removed afterwards, even
416
+ when the run fails (they used to collect in a `.phenograph_cache` folder
417
+ inside the package, which is read-only on a system Python). Saved gate
418
+ templates now go to `~/.openflo/templates`; templates saved by an older
419
+ OpenFlo inside the package still show in the Templates menu.
420
+ - **The editor window appears sooner.** It paints before matplotlib loads
421
+ and the plot area fills in right after, so the window shows in about
422
+ 0.3 s instead of 0.7 s on a warm start (more on a cold disk).
423
+ - **`openflo-run --help` answers at once.** Help and argument errors no
424
+ longer load the scientific stack (0.07 s instead of 0.8 s); the console
425
+ script's entry point is now `openflo.cli_args:main`.
426
+ - **The window keeps answering while OpenFlo works.** Dragging a gate
427
+ redraws only its outline, and its counts are made in the background
428
+ after you let go (they used to be redone on every mouse move with gates
429
+ applied). Zooming or changing axes no longer rebuilds the Samples & Gates
430
+ tree. Trajectory, Marker expression, FMO gating, cell cycle, population
431
+ export, Sample QC and compensation optimisation run in the background
432
+ with a progress bar and Cancel; layout batch export shows how far it got
433
+ and stops on Cancel. Results are unchanged.
434
+ - **Gate rows in the Samples & Gates tree show the population.** Each row
435
+ reads the gate's name, its event count and its frequency of the parent
436
+ ('Cells 2,796 · 69.9%') instead of its bounds, which were cut off at the
437
+ panel's width. An unnamed gate is named by its channels ('FSC-A x SSC-A')
438
+ or its cut ('FL1-A >= 1K'). The tree widens to its longest row and
439
+ scrolls sideways. A population that cannot be computed reads NA.
440
+ - **Colouring by cluster gives each cluster its own colour**, the colour of
441
+ its population in the tree, with a legend naming them. It used a
442
+ continuous scale, so neighbouring clusters looked alike.
443
+ - **Groups are named after the sample's own folder** when no 'Day N' folder
444
+ is above it: `synth/pbmc/` and `synth/compensation/` are two groups, no
445
+ longer both "synth", and the example data is no longer ".openflo". A
446
+ generic 'Blank Experiment with…' folder still defers to the one above it.
447
+ - **Samples are listed by name** once loading is done, not in the order
448
+ they finished loading.
449
+ - **Gates imported from a FlowJo workspace come in ticked**, like gates
450
+ drawn in OpenFlo. They came in unticked.
451
+ - **The Add FCS and Load CSV dialogs open in the folder last picked from,**
452
+ else the current folder, else the home folder; never the installed
453
+ package's own folder.
454
+ - **Plots are drawn the way flow papers draw them.** Scatter (FSC, SSC)
455
+ and Time open on a linear axis, so the cells no longer sit squeezed into
456
+ one corner of a log plot. Fluorescence opens on logicle, so events at or
457
+ below zero keep their own positions near 0 instead of piling up on the
458
+ axis. Plots open in pseudocolor, coloured with Turbo, a rainbow that is
459
+ close to perceptually uniform (View → Density colors switches back to the
460
+ classic rainbow or to Viridis). A channel whose scale a session already
461
+ saved keeps it.
462
+ - **Logicle width and arcsinh cofactor can be set per axis.** The axis
463
+ dialog (⚙ beside X and Y) offers Logicle, Arcsinh, Log and Linear, with
464
+ logicle's width W and arcsinh's cofactor. These change only how the axis
465
+ is drawn: gates select the same events, and the data is not
466
+ re-transformed. The setting is saved with the session, its tabs and
467
+ layout items.
468
+ - **Gates show their name and frequency on the plot.** Each gate is drawn
469
+ with a solid outline and labelled with its name (when it has one) and its
470
+ percent of the parent, one decimal. Quadrants are labelled in their outer
471
+ corners, and a population that cannot be computed reads NA. View → Gate
472
+ labels on plots turns the labels off.
473
+ - **One notation for axis ticks and percentages.** Every decade on an axis
474
+ reads as a power of ten (10², 10³, not "100" next to "10³"). Linear axes
475
+ read 50K and 100K. Frequencies in the Statistics table and the status bar
476
+ have one decimal (93.0, not 93 or 92.96). A gate whose parent is empty
477
+ reads NA instead of 0.00%.
478
+ - **No window hides its buttons.** Statistics, Methods & provenance,
479
+ Absolute counts, Preferences, FCS inspector, Expression, Frequencies,
480
+ Calibration and Voltage optimization opened at fixed sizes too small for
481
+ their content, and the button row (or Update, or Detect peaks) was simply
482
+ not shown. Every window now opens big enough for all its controls (on a
483
+ scaled display too), centred on the main window and on the screen; a
484
+ window made smaller shrinks its table or plot, not its buttons.
485
+ Preferences scrolls when the screen is too short and lists themes by
486
+ name; the layout editor's toolbar wraps onto two rows when narrow.
487
+ - **Statistics tells a parent from its child.** The Population column
488
+ shows each gate's own name, indented under its parent, in a column as
489
+ wide as the names; the exported CSV keeps the full path. Statistic
490
+ columns are as wide as their headings.
491
+ - **Sharp text on scaled Windows displays.** OpenFlo declares itself
492
+ DPI aware, so at 125 % or 150 % Windows no longer stretches a blurry
493
+ 96-DPI image of every window.
494
+ - **One answer to "is this engine available?".** Help → Environment,
495
+ Compare embeddings, the Cluster dialog and the Pipeline all test an
496
+ engine by importing it (in a separate process, once per session), so a
497
+ package installed without its own dependencies is shown as "fails to
498
+ load", with the reason, everywhere, instead of being offered and then
499
+ producing nothing.
500
+ - **No stray untitled box over a window.** It was a hover tip left behind
501
+ when its window closed, or was busy, under the pointer. Tips now close
502
+ with their window and are not shown once the pointer has left.
503
+
504
+ ### Added
505
+ - **Compensation can be applied to the whole acquisition batch.** Apply
506
+ reached only the highlighted sample, so a set of files took one Apply
507
+ each, and its tooltip said "every loaded sample". The Compensation window
508
+ now has "Apply to: this sample / this batch". The batch is the samples
509
+ acquired like the highlighted one, read from each file's keywords rather
510
+ than its folder: the same instrument (`$CYTSN`, else `$CYT`), acquisition
511
+ date (`$DATE`) and detector voltages (`$PnV`) on the matrix's channels,
512
+ and the same stored spillover matrix when both files carry one. The
513
+ window lists the batch, and hovering it says why each other sample was
514
+ left out (other voltages, another date, no keywords). A file without
515
+ these keywords is never guessed into a batch.
516
+ - **Tabs in the Samples & Gates tray (beta).** A tab is a saved view: its
517
+ samples and gates, their on/off states, and the plot's axes, type,
518
+ colouring, axis types and zoom. Selecting a tab shows and plots only its
519
+ members and restores its plot; "All" always holds every sample. + makes a
520
+ tab from the selected rows (or empty); right-click a tab to rename,
521
+ duplicate or delete it. In the tree's right-click menu, "Copy to tab" adds
522
+ the selected samples or gates to a tab (a gate brings its ancestors and
523
+ subtree), and "Apply to other tabs" (all, or chosen) pushes their on/off
524
+ state to other tabs. "Within a tab (choose samples)…" applies it to only
525
+ the samples you find and tick in one other tab, matching gates by
526
+ population name. Samples and gates can also be dragged onto a tab on the
527
+ strip: an arranged tab (samples from several groups, or its own order)
528
+ holds them in a Presort zone at the top of its tree, which grows as needed
529
+ and vanishes when empty; drag a row out of it to place it. Any other tab
530
+ takes them straight to the top of its stack. Items dragged back from the
531
+ Pipeline onto the tree or a tab join that tab. The main plot keeps your
532
+ zoom through every redraw of the same view (a gate edit, a toggle, a tab
533
+ switch) until Reset, and each tab keeps its own. Gates are shared: an edit shows in every tab holding
534
+ that sample. The tabs pop out with the tray. The layout editor's "last
535
+ displayed" view also reads the views saved in tabs.
536
+ - **Sessions save the tabs and the layout (schema v2).** `.flowsession`
537
+ gains `view_tabs` and `layout`; version-1 sessions upgrade on open (one
538
+ All tab, no layout). A session saved now does not open in an older
539
+ OpenFlo.
540
+ - **Layout editor (beta).** The side panel's Layout button (was "to be
541
+ added") opens a FlowJo-style editor of pages: plots of gated populations,
542
+ stats tables (Count, %Parent, %Total and per-channel Median / Mean /
543
+ GeoMean / CV / rCV), violin / box / bar charts across samples, and text
544
+ boxes. Drag to move, drag the corner to resize, double-click to edit; an
545
+ optional grid, and soft snapping to grid lines and to other items' edges,
546
+ corners and centres, with guides. Samples and gates dragged from the
547
+ Samples & Gates tree or the Pipeline (docked or popped out) land as plots,
548
+ shown as that population was last displayed (Preferences can make it the
549
+ current graph instead), or join the plot, table or chart they are dropped
550
+ on, so several samples and populations overlay in one plot. A plot keeps
551
+ the axis types and zoom it was taken with; when much of its data falls
552
+ outside, a Zoom to fit badge offers (never applies) a refit. Cluster
553
+ results (PhenoGraph, Leiden, FlowSOM) colour a plot by cluster and their
554
+ populations can be plotted and tabulated. A View zoom (Fit, 50-200%,
555
+ custom) sets the on-screen resolution; Export DPI defaults to 300 (150,
556
+ 600, 1200 or custom). Pages can be added, renamed, duplicated, items copied
557
+ or moved between them, and pages imported from or sent to another
558
+ project's layout file. The layout re-applies to every sample, matching
559
+ populations by name; Batch export… writes every page per sample. The
560
+ mouse wheel scrolls the page (Ctrl+wheel zooms it, Shift+wheel scrolls
561
+ sideways); over the selected plot it zooms that plot around the cursor.
562
+ Holding the middle
563
+ button drags its view, and right-click has Center zoom here, Zoom in,
564
+ Zoom out and Zoom to fit; the view is kept with the plot. A cross-page
565
+ host in the left margin keeps copies of items dragged onto it, to drag
566
+ onto any page or place on every page; each is listed by number, kind,
567
+ populations, axes and the page it came from, and can be renamed. Drag
568
+ the host's edge to widen it; pointing at an entry outlines the items on
569
+ the page that are still true duplicates of it (same populations, axes,
570
+ zoom, colours...) and marks (◆) the tabs of other pages that hold one.
571
+ Every item has a lasting id (P1, S2, C3, T4), shown in its corner on
572
+ screen and in the host, so look-alike plots can be told apart. The toolbar reads left to
573
+ right: insert, edit, the sample shown, Export (its dpi on the button, 300
574
+ by default) and the Layout menu; the row below holds the page tabs, page
575
+ size, grid, snap and the on-screen zoom. A plot dragged
576
+ onto a stats table or chart supplies its samples (and its populations, if
577
+ it has none). Each layer's colour can be set per item; a default colour
578
+ list (layer 1 red, 2 blue, ...) fills in, and an override toggle (off by
579
+ default) makes every layer use that list. A population that cannot be
580
+ computed reads NA in tables, gets no bar (NA instead) and no violin, and
581
+ its plot says so; a measured empty population still reads 0. Layouts
582
+ save to `.oflayout` files (version 2; version-1 files open). Also under
583
+ Tools → Layout editor (beta)….
584
+
585
+ ### Changed
586
+ - **Saving and reopening sessions is much faster.** A session keeps each
587
+ analysed sample's events beside it. They were CSV text, and formatting
588
+ every value as text was most of a save: about 10 s per 300,000-event,
589
+ 24-channel sample, holding the windows for the 5-minute autosave. They
590
+ are now binary (`<session>_data/<sample>.npz`, every value and type kept
591
+ exactly): four such samples save in 0.5 s instead of 44 s, reopen in
592
+ 0.6 s instead of about 6 s, and take 58 MB each instead of 136 MB.
593
+ Sessions with CSV sidecars still open, and are converted on their next
594
+ save. A column the binary format can't hold (a nullable or categorical
595
+ one) keeps that sample on CSV.
596
+ - **The 5-minute autosave no longer freezes the windows.** It now writes on
597
+ a background thread, as the close does; a close or Save started while it
598
+ is writing waits for it.
599
+ - **The first save after opening a session no longer rewrites every
600
+ sample.** The sidecars the session was opened from are remembered, so the
601
+ autosave and the close copy an unchanged one instead of encoding it again.
602
+ - **Faster analysis with identical results.** Each of these gives the same
603
+ numbers as before, bit for bit, checked against the previous code:
604
+ - FlowSOM training (and CytoNorm, which uses it) runs compiled with numba,
605
+ already installed with UMAP: about 14 s to 1.2 s per sample at the
606
+ defaults.
607
+ - The Statistics table splits large tables across CPU cores and computes
608
+ only the statistics asked for: 31 s to 10 s for 2 million events, 20
609
+ gates and 16 channels on 4 cores, faster with more.
610
+ - Leiden and Louvain neighbour searches run on several cores: 7.8 s to
611
+ 2.6 s for 50,000 events on 4 cores. Ties between equally near events
612
+ resolve exactly as before.
613
+ - Exporting a population as FCS converts the events in one step: 9.4 s to
614
+ 1.5 s for 2 million events and 16 channels, with about 1 GB less memory,
615
+ and a byte-identical file.
616
+ - Integer FCS files that store fewer bits than their words hold (10-bit
617
+ data in 16 bits, for example) load about 70x faster: 8.8 s to 0.12 s for
618
+ 2 million events and 20 channels.
619
+
620
+ ### Removed
621
+ - **Out-of-date developer files.** DESIGN_NOTES.md (last checked against the
622
+ code in July, before most of the current interface) and five one-off
623
+ scripts nothing used: four speed benchmarks and mutate_controls.py.
624
+
625
+ ### Fixed
626
+ - **The public mirror's CI no longer fails its privacy gate.** That job
627
+ and the release build's gate step ran scripts/publish_public.py, which is
628
+ kept out of the mirror because it holds the private token list. Both
629
+ now skip on the mirror; the gate still runs here before every snapshot
630
+ and every PyPI upload.
631
+ - **Compare embeddings shows Run and Cancel on a 150 % display.** The
632
+ window took a fixed 420x360 and packed its buttons last, so larger text
633
+ squeezed the button row to half height on Windows. It now takes the size
634
+ its content asks for.
635
+ - **New windows stay on a busy machine's screen.** On a loaded Windows
636
+ PC a window could be fitted on opening and then put back where Windows
637
+ first placed it, past the bottom of a laptop screen (seen with Plate
638
+ view at 1366x768). The editor checks the place again just after.
639
+ - **Help > Report a problem reaches the issue tracker.** It, and Help's
640
+ documentation link, opened the private development repository, a 404 for
641
+ everyone else. They open openflo-public now, and a test fails on any link
642
+ to the private repository in what ships.
643
+ - **FCS files with common writer quirks open.** A file whose $ENDDATA is
644
+ one byte off, whose HEADER and TEXT disagree on where the data starts, or
645
+ that chains a second data set with $NEXTDATA was refused and reported as
646
+ "cut short" or "damaged". Each now loads (the first data set, for a
647
+ chained file) with a load warning naming the quirk; the compensation
648
+ readers and the FCS inspector read them too. A file that is really
649
+ damaged still fails, with a message that names the quirk when one is the
650
+ cause.
651
+ - **Clustering and the FlowSOM tree repeat with their seed.** igraph has no
652
+ number seed: `layout_fruchterman_reingold(seed=)` takes starting
653
+ positions, so the tree layout was drawn differently every time. It now
654
+ runs on a generator seeded from `seed`. Louvain left igraph on a fresh,
655
+ unseeded generator after each run, so `random.seed()` stopped controlling
656
+ igraph for the rest of the session; it now hands back the default.
657
+ PhenoGraph's seeded Leiden backend stays the default, as before.
658
+ - **Leiden noise reads as noise.** Its unassigned bucket imported as
659
+ "leiden -1" in a palette colour and the Populations menu left Leiden out;
660
+ it is grey and named "Unassigned (noise)", and the menu lists it (other
661
+ Leiden populations keep their names, such as "leiden 3").
662
+ - **`python -m openflo.inspect_fcs` lists the channel keywords** again
663
+ (flowio 1.x returns them lower-case, without the `$`).
664
+ - **The update check never offers a beta to someone on a final release**,
665
+ and without the `packaging` module a beta no longer sorts above its own
666
+ final release.
667
+ - **The README's licence note** said OpenFlo does not import igraph and
668
+ leidenalg (both GPL) directly; it does, and the note says so.
669
+ - **The Cluster dialog starts FlowSOM at 20 metaclusters, and says why.**
670
+ Asked for exactly as many metaclusters as there are populations, FlowSOM
671
+ merged a rare (1.5%) population into a neighbour (F1 0.22; 0.99 with 12).
672
+ The dialog now starts at 20 (was 10), notes "set above the populations
673
+ you expect" beside the field, and its hover tip explains the reason and
674
+ how to join extra metaclusters with an OR gate. **Behaviour change:**
675
+ FlowSOM runs from the dialog at its default now make 20 metaclusters, not
676
+ 10. The CLI and the Python API keep 10.
677
+ - **A 30-channel recovery test for every clustering method.** Eight planted
678
+ populations in a lineage tree, one of them rare (1.5%): PhenoGraph, Leiden
679
+ and Louvain must recover all eight (ARI >= 0.95, each population F1 >=
680
+ 0.9), FlowSOM the seven common ones, and the rare one when asked for more
681
+ metaclusters than populations. The same checks must fail on data with no
682
+ structure and on random labels.
683
+ - **macOS is tested on the public mirror.** A macOS job in ci.yml runs only
684
+ on openflo-public, where GitHub's macOS runners are free (they bill at ten
685
+ times Linux on the private repository), so each mirror refresh runs the
686
+ suite with the GUI on macOS.
687
+ - **The PyPI release waits for the tests.** A version tag ran only the type
688
+ check before building and publishing; it now runs the whole CI workflow
689
+ (tests with the GUI, type check, privacy gate, install checks) first.
690
+ - **A status message stays put after the gate tree rebuilds.** On Windows,
691
+ adding FMO gates (or an FMO refusal) could have its message replaced by the
692
+ selected gate's count, because the tree re-selecting its own row was taken
693
+ for a click.
694
+ - **The editor opens on Tk 9.** The drag-and-drop package pinned at 0.4.3
695
+ stopped OpenFlo from starting on any Python that ships Tk 9 (uv's 3.14
696
+ and 3.15 builds, python.org's macOS installer from 3.14.5). It is now
697
+ 0.6.4, and if drag-and-drop still can't load, the editor opens without it
698
+ instead of not at all.
699
+ - **Identical groups keep p = 1 with SciPy 1.18.** SciPy 1.18 returns NaN
700
+ for a Mann-Whitney test where every value is the same, so a cluster or
701
+ population with the same share in both groups read NA in the comparison
702
+ CSV, differential abundance and group statistics. It is 1 again on every
703
+ SciPy.
704
+ - **Cluster populations of a sample reopened without its clustering read
705
+ NA, not 0.** A session whose processed data was not found reloads that
706
+ sample from its FCS file, without the cluster column: every cluster read
707
+ 0 events under a group holding all of them. They now read NA, and the
708
+ load says which results were not found.
709
+ - **A session keeps its previous save.** Saving over a session keeps the
710
+ earlier version as `name.backup.flowsession`. A session that can't be read
711
+ says so in plain words and offers that backup, and resuming after a crash
712
+ skips a damaged autosave for the newest one that reads.
713
+ - **Save and close updates the session you opened.** It used to write only
714
+ the autosave, so the file you opened was left as it was at your last
715
+ save.
716
+ - **Problems found while loading are shown.** A renamed channel, an event
717
+ count that disagrees with the data, a spillover matrix that could not be
718
+ used, or a workspace population that was not imported went only to the
719
+ log or to a status line the next sample overwrote. They now appear
720
+ together in one "Notes from loading" window and are kept in the history.
721
+ - **Damaged or unusual FCS files.** A parameter without `$PnN` is named
722
+ `Ch<n>`; one without `$PnR` takes the range its `$PnB` allows (as FlowJo
723
+ does); two channels with one name load as `FL1-A` and `FL1-A (2)`; and a
724
+ cut-short or non-FCS file says so in plain words instead of showing a
725
+ parser error.
726
+ - **Spillover keywords that name markers.** A `$SPILLOVER` listing marker
727
+ names (`$PnS`) instead of detectors is matched to the detectors when each
728
+ marker belongs to exactly one, instead of leaving the sample
729
+ uncompensated. Values past the end of the matrix are reported.
730
+ - **CSV files in common layouts.** Load CSV detects semicolon, tab and bar
731
+ separators and decimal commas, skips `#` comment lines at the top, keeps
732
+ the first event of a file with no header row (columns `Ch1`, `Ch2`, ...),
733
+ and refuses a file with no numeric column in plain words.
734
+ - **Undo after compensation or a transform change.** Undo can't reverse
735
+ those, and it used to undo the gate change made before them without a
736
+ word. It now says it can't, and the next Undo goes on to the gate change.
737
+ Undo no longer brings back the gates of a removed sample.
738
+ - **Exported FCS files keep their keywords.** Every keyword from the source
739
+ that does not describe the file's layout is carried over (`$SRC`, `$OP`,
740
+ tube and well names and more; the source's `$FIL` as
741
+ `OPENFLO_SOURCE_FIL`), and the matrix the values were compensated with is
742
+ recorded as `OPENFLO_APPLIED_MATRIX`. Keywords about compensation are not
743
+ carried, so no reader compensates the values twice.
744
+ - **A session sample whose FCS file is missing is no longer deleted by the
745
+ next save.** Opening a session skipped a sample whose file had moved, and
746
+ the save or autosave after it wrote the session without it and its gates.
747
+ The sample is now kept offline and saved back unchanged, and after opening
748
+ OpenFlo offers to look for the missing files in a folder (and its
749
+ subfolders) and relinks them by file name.
750
+ - **A session's saved data cut short no longer loads part of a sample.** The
751
+ per-sample data beside a session is written whole or not at all, and one
752
+ that is damaged or holds fewer rows than recorded is rebuilt from the
753
+ sample's FCS file, with a warning.
754
+ - **Log-amplified and gain-adjusted FCS files load as intensities**
755
+ (behaviour change). Integer data with `$PnE` log amplification was read as
756
+ raw channel numbers and `$PnG` was ignored; both are now applied on load
757
+ and recorded in the history. A session holding such a file now reads it
758
+ as intensities. Float data is left as stored.
759
+ - **A gate on a channel a sample lacks reads NA with the reason**
760
+ (behaviour change). It used to admit every event, so the population read
761
+ 100%. It now admits none and Statistics, the report and the tree show NA
762
+ ("channel X is not in this sample").
763
+ - **FlowJo AND / OR / NOT gates are imported from and written to .wsp.**
764
+ They were dropped on import without a word, with everything under them.
765
+ An operand that can't be found reads NA with a warning.
766
+ - **Pipeline (beta) says when an embedding can't run.** TriMap was ticked by
767
+ default, and without the `[embed]` extra it was skipped with nothing but a
768
+ log line: the run ended "Done: 4 ok" with no TriMap output. PHATE, TriMap
769
+ and PaCMAP are now unticked and disabled in the panel when not installed
770
+ (the tooltip says how to install them), and a run that asks for one anyway,
771
+ or whose embedding fails or makes no plot, lists it in the run's summary.
772
+ - **The highlighted sample is always one on the plot.** After loading
773
+ several files the first was plotted but the last to arrive was
774
+ highlighted, so a gate drawn with "all shown" went to both and
775
+ compensation went to a sample not on screen. Now the plotted sample is the
776
+ highlighted one after a load, highlighting an unticked sample ticks it,
777
+ clicking a ☑/☐ box no longer moves the highlight, and unticking the
778
+ highlighted sample moves the highlight to one still plotted.
779
+ - **The Copy gates dialog shows its Copy button.** Its four buttons sat in
780
+ one row about 560 px wide inside a 420 px dialog, so Copy, packed last,
781
+ got no room and only appeared if you widened the window. Select all /
782
+ Deselect all and Copy / Cancel now sit on two rows of a 460 px dialog.
783
+ - **Copying gates no longer stacks duplicates.** Copying appended every
784
+ gate, so a sample that already held it (copied before, or drawn there with
785
+ "all shown") got a second, identical-looking row. A gate the target
786
+ already has is now reused; the same population drawn differently (same
787
+ kind, axes and name, under the same parent, and only one such) takes the
788
+ copied geometry in place, keeping its id, on/off state and children; the
789
+ status line counts both. Anything less certain is appended as before.
790
+ - **A FlowJo export names each unnamed gate the same in every sample.**
791
+ An unnamed gate was written under the editor's per-sample gate id, so
792
+ the same gate could be `g2` in one sample and `g3` in the next (any
793
+ sample that had once had a gate deleted numbered on from there). FlowJo
794
+ matches populations across samples by name, so its tables and group gates
795
+ didn't line up. An unnamed gate is now named for its axes (`FSC-A x
796
+ SSC-A`, or the channel of a 1-D gate), and siblings with the same name get
797
+ `(2)`, `(3)`. Before: 6-sample export, populations `g1`/`g2`/`g3` with
798
+ ctrl_d1 off by one. After: `FSC-A x SSC-A` / `FL1-A x FL2-A` in all six.
799
+ - **Closing a large session no longer freezes OpenFlo.** "Save and close"
800
+ saved on the window thread, rewriting every sample's processed-data CSV:
801
+ about a minute for a large session, with every window marked "Not
802
+ Responding" and no message, and each extra click on ✕ queued another full
803
+ save. The save now runs in the background behind a "Saving session… n of
804
+ N samples" window, extra clicks only bring that window forward, and a
805
+ sample's CSV is rewritten only when its data changed since the last save,
806
+ which also makes the 5-minute autosave quick when nothing changed.
807
+ - **Closing asks whether to save the session.** ✕ autosaved every time,
808
+ and a large session (every sample's data rewritten) froze every window
809
+ for up to a minute with no message, so the windows looked as if they
810
+ would not close; each further ✕ queued another save. It now asks: Save
811
+ and close, Close without saving, or Cancel. A second ✕ while closing is
812
+ ignored. (A stop-gap: making the save itself fast is its own change.)
813
+ - **The update offer asks Update or Cancel.** It asked Yes / No / Cancel,
814
+ and No opened the release page: three ways to say yes. The release notes
815
+ link is now in the question's text.
816
+ - **Export to FlowJo keeps gates on a channel of unknown scale.** A column
817
+ of an older OpenFlo CSV with no transform record has no transform to undo,
818
+ so its gates reached FlowJo in display units with only a log line (an
819
+ asinh cut at 4.0 became an intensity of 4.0). When the sample's raw FCS
820
+ can be read, the channel is now matched to it rank for rank, as FlowJo
821
+ will gate it, and the gate keeps its events (a cut exactly, a polygon to
822
+ within its re-traced edges). Without the FCS the gate is still written,
823
+ as stored. Either way a warning after the export names each such gate.
824
+ - **A sample on the same transform with other settings is put on the
825
+ editor's.** A library export on asinh cofactor 5 loaded beside samples on
826
+ the editor's asinh (cofactor 150) kept cofactor 5: the same events plotted
827
+ at median 4.45 beside 1.16, its axis labels were wrong, and the Expression
828
+ window pooled both scales into one violin. The parameters are now compared,
829
+ not just the method, for the first sample too. A restored session's gates
830
+ move with the data: a cut drawn on cofactor 5 held 380 of 800 events, and
831
+ without converting it held none after reopening.
832
+ - **A population that cannot be computed reads NA, not 0.** A gate whose
833
+ region is unknown (a FlowJo ellipse not converted, a boolean that lost an
834
+ operand, an ellipse whose covariance describes no ellipse) selects
835
+ nothing, and so does every gate under it or built on it. Statistics,
836
+ Frequencies and its comparisons, the report and the gate's status line
837
+ showed that as a measured 0 events. They now show NA with the reason,
838
+ Frequencies leaves it out as missing, and the tidy CSV writes NA.
839
+ - NOT or OR over such an ellipse now admits no events too; NOT over it
840
+ admitted every event.
841
+ - **A FlowJo gate imported onto a channel of unknown scale reads NA.** Its
842
+ coordinates are in FlowJo's linear units and cannot be converted to a
843
+ channel whose display scale is unknown (an older OpenFlo CSV with no
844
+ transform record). It was kept as is, comparing a linear cut against
845
+ values on another scale, with only a log warning. It is now kept but
846
+ selects nothing, and its population, everything under it and every
847
+ boolean built on it read NA with the reason.
848
+ - **`openflo-run`'s shared clustering and comparison files.**
849
+ - A run with one group removes an earlier two-group run's comparison
850
+ files (`compare_<A>_vs_<B>.csv`/`.png`, `compare_shared_clusters.csv`)
851
+ and says so, instead of leaving them as if it had made them. A file
852
+ that cannot be removed is named and the run exits 1.
853
+ - Channel labels match across samples despite common real spellings: a
854
+ fluor name at either end ('CD3 FL5' on FL3-A), Greek letters
855
+ ('CD8α' = 'CD8a') and detector names that differ only in case
856
+ ('FL7-A' / 'fl7-a'). Such channels were dropped before.
857
+ - Pooled and unpooled events follow one noise rule: each finite event
858
+ takes the label of its nearest labelled pooled event, so a sample's
859
+ "Unclustered (noise)" share no longer scales with how much of it was
860
+ pooled. Only rows with a non-finite marker value stay noise.
861
+ - The shared PhenoGraph's RAM check applies with the default
862
+ `--workers 1` too; with too little free RAM it runs on one core and
863
+ says why.
864
+ - **The compensation editor's Apply keeps what was computed on the
865
+ sample.** Apply replaced the sample's data with a fresh load of its FCS.
866
+ Clusters, embeddings, U: abundances and _pos flags were lost, so their
867
+ populations selected no events and gates on them selected every event. A
868
+ downsampled sample came back with all its events.
869
+ - Apply now re-compensates the sample's own events in place: every event
870
+ and column stays, and each channel keeps its transform.
871
+ - The status line names what was computed before the change and asks
872
+ you to re-run it.
873
+ - A sample opened from a CSV with no record of its compensation and no
874
+ FCS behind it is refused, with the reason. It used to fail with "could
875
+ not parse FCS".
876
+ - **A session with Leiden or FlowSOM populations can be saved.** Saving,
877
+ and the autosaves, failed with "Object of type int64 is not JSON
878
+ serializable".
879
+ - **Gates that cannot be evaluated select nothing, and say so.**
880
+ - A boolean gate over a boolean that lost an operand (or over a gate
881
+ under one) now admits no events. With the inner boolean empty, NOT over
882
+ it admitted every event, and OR with it was the other operand alone.
883
+ Clear, Cut, session restore, .wsp ingest and template apply now also
884
+ name the booleans built on a broken one.
885
+ - An ellipse whose covariance is singular or not positive definite
886
+ admits no events instead of every event. Exported to FlowJo it is still
887
+ the all-pass rectangle, and the warning says FlowJo will count every
888
+ event.
889
+ - **Open-sided gates are drawn in full on every view.** A quadrant, an
890
+ imported max-only range, or a polygon opened at an axis end drew only part
891
+ of its outline on a fluorescence channel's log and symlog views (no
892
+ outline at all on symlog). An open interval bound no longer draws a line
893
+ or stretches a linear view to ±1e12.
894
+ - **Editing overlapping polygons grabs the nearest vertex.** A click on one
895
+ polygon's corner took the first-drawn polygon's corner ~9 px away; the
896
+ nearest vertex (then the nearest edge) of all polygons now wins.
897
+ - **FMO gates are placed at the FMO's linear cutoff.** The FMO dialog took
898
+ the percentile in the FMO tube's own display coordinates and used it as a
899
+ gate value on the stained sample. An FMO on asinh cofactor 5 beside a
900
+ stained sample on cofactor 150 called 5.36% positive where 50.52% is
901
+ right; an FMO of unknown scale gave 0.0%.
902
+ - The cutoff is the FMO's percentile on its linear values, mapped into the
903
+ stained sample's display coordinates. With a parent gate selected, the
904
+ FMO is read through it in the stained sample's coordinates.
905
+ - An FMO or stained channel of unknown scale is refused, and the status
906
+ names the channel, the FMO and why.
907
+ - **A logicle scale inferred from the value range is marked as a guess.**
908
+ A column of an older OpenFlo CSV with no transform record, whose values
909
+ all lie in the logicle range, was taken as logicle. A dim channel on asinh
910
+ falls in that range too: Statistics reported a linear median of 1793.94
911
+ where 80.56 is right.
912
+ - The entry is kept as logicle but marked guessed, through re-saves,
913
+ session restore, re-transforms and pooled Workspace runs.
914
+ - Its statistics are titled "(linear, guessed logicle)" in the Statistics
915
+ window and its CSV. Expression and Group comparison name the samples
916
+ whose medians rest on a guess.
917
+ - **`marker_expression` names the samples it could not use.** A sample of
918
+ unknown scale went into `differential_test` as NaN and was dropped
919
+ without a word. The groups now list omitted and guessed samples, and each
920
+ result row carries `omitted_a/b` and `guessed_a/b`.
921
+ - **Compensation-control names.** The rule took any word ending in "comp"
922
+ ("recomp", "precomp", "Decomp_1", "Autocomp", "TComp_1") as a compensation
923
+ control. It now names the bead brands written as one word (UltraComp,
924
+ OneComp, VersaComp, AbC, ArC). "Non-comp" and "No-comp" are samples.
925
+
9
926
  ## [2.6.2] - 2026-10-09
10
927
 
11
928
  ### Changed