From 052781eb312d1fbdb12a587b70026dab508c151e Mon Sep 17 00:00:00 2001 From: Hazen Babcock Date: Tue, 8 Sep 2026 15:38:02 -0400 Subject: [PATCH 1/6] Fix the test name left behind by 2a02fc1c That commit re-enabled the first drift correction test by renaming _test_drift_correction_1() to test_drift_correction_1(), and did not update the __main__ block, which still called the old name. Running the file directly has raised NameError ever since: NameError: name '_test_drift_correction_1' is not defined. Did you mean: 'test_drift_correction_1'? The full test suite stayed green throughout, because pytest collects test_* functions and never executes __main__. That is also why this was not caught: the verification for that commit ran pytest, which cannot reach the broken line. Also lists test_drift_correction_1_xy_failure(), added by the same commit and likewise never added to the block. Verified by running the file the way __main__ intends rather than through pytest, which exercises all eleven tests and now exits 0. Co-Authored-By: Claude Opus 5 --- storm_analysis/test/test_drift_correction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/storm_analysis/test/test_drift_correction.py b/storm_analysis/test/test_drift_correction.py index ccf257b0..3ef5c774 100644 --- a/storm_analysis/test/test_drift_correction.py +++ b/storm_analysis/test/test_drift_correction.py @@ -480,7 +480,8 @@ def test_drift_correction_10(): if (__name__ == "__main__"): - _test_drift_correction_1() + test_drift_correction_1() + test_drift_correction_1_xy_failure() test_drift_correction_2() test_drift_correction_3() test_drift_correction_4() From 46d9d6c68351120db55905a8d253b84dc41bca69 Mon Sep 17 00:00:00 2001 From: Hazen Babcock Date: Tue, 8 Sep 2026 15:43:13 -0400 Subject: [PATCH 2/6] Make aberrationOPD able to run at all Two names in it were wrong, and it has never been called, so neither showed up: sin_theta_2 = (n1/n2)*sin_theta_1 NameError, no n1 or n2 return self.appylNARestriction(pf_ab) AttributeError, appyl The first is what the linter found. The second it cannot see, since an attribute is not a bare name, and fixing only the first moves the failure four lines down rather than removing it. applyNARestriction is spelled correctly at the file's five other call sites. For Snell's law here, n1 is the immersion medium and n2 the sample, as the sibling aberration() does it eight lines above: sin_theta_2 = (self.imm_index/smp_index)*sin_theta_1 which also matches the Gibson-Lanni model this function cites. The test calls the function, which is all that is needed to catch both names, and then checks two things that are true of it by construction: everything beyond the NA is zero, which is what makes the second fix observable rather than assumed, and at zero defocus with the sample index matching the immersion index the aberration is identically 1. That test uses geo.r > 1.0 for "outside the NA", because that is what applyNARestriction() masks on. geo.r is a normalized radius while geo.r_max is the NA edge in grid units, and confusing the two, as I did at first, gives a mask that quietly includes part of the pupil. The geometry is chosen so that 691 of 1024 pixels fall outside the NA. At the geometry the other tests in this file use, nothing does, and the restriction check would pass without testing anything. This does not verify the physics, only that the function runs and obeys those two properties. Nothing calls it. Note that pupil_math.py has CRLF line endings, so it was edited in binary; its line ending count is unchanged at 765. Co-Authored-By: Claude Opus 5 --- storm_analysis/simulator/pupil_math.py | 4 +-- storm_analysis/test/test_pupil_math.py | 42 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/storm_analysis/simulator/pupil_math.py b/storm_analysis/simulator/pupil_math.py index f4b7f446..cb03e712 100644 --- a/storm_analysis/simulator/pupil_math.py +++ b/storm_analysis/simulator/pupil_math.py @@ -184,7 +184,7 @@ def aberrationOPD(self, z_stage, z_p, smp_index): sin_theta_1 = (self.wavelength/self.imm_index)*self.k theta_1 = numpy.arcsin(sin_theta_1 + 0j) - sin_theta_2 = (n1/n2)*sin_theta_1 + sin_theta_2 = (self.imm_index/smp_index)*sin_theta_1 theta_2 = numpy.arcsin(sin_theta_2 + 0j) z_o = smp_index/self.imm_index * z_stage @@ -206,7 +206,7 @@ def aberrationOPD(self, z_stage, z_p, smp_index): # decrease exponentially with increasing distance from coverslip. # pf_ab = numpy.exp(-1j * t4) - return self.appylNARestriction(pf_ab) + return self.applyNARestriction(pf_ab) def applyNARestriction(self, pupil_fn): """ diff --git a/storm_analysis/test/test_pupil_math.py b/storm_analysis/test/test_pupil_math.py index 2e3e8a97..a645630f 100644 --- a/storm_analysis/test/test_pupil_math.py +++ b/storm_analysis/test/test_pupil_math.py @@ -82,9 +82,51 @@ def test_pupil_math_5(): psf_c = geo_c.pfToPSF(pf, z_vals, scaling_factor = gsf) +def test_pupil_math_aberration_opd(): + """ + aberrationOPD() had two names wrong and could not run at all. + + sin_theta_2 was computed from n1 and n2, neither of which exists, and + the return statement called self.appylNARestriction(). The first + raised NameError, and fixing only that moved the failure to an + AttributeError from the second. + + Snell's law here is between the immersion medium and the sample, + which is what the sibling aberration() uses: + + sin_theta_2 = (self.imm_index/smp_index)*sin_theta_1 + + This geometry deliberately puts most of the grid outside the NA, so + that the restriction being applied is observable rather than vacuous. + """ + geo = pupilMath.Geometry(32, 0.05, 0.6, 1.5, 1.2) + + # applyNARestriction() masks on the normalized radius, geo.r > 1.0. + # geo.r_max is a different quantity, the NA edge in grid units. + outside = (geo.r > 1.0) + assert(numpy.count_nonzero(outside) > 0) + + ab = geo.aberrationOPD(1.0, 0.5, 1.33) + + assert(ab.shape == (32, 32)) + assert(numpy.all(numpy.isfinite(ab))) + + # applyNARestriction() zeros everything beyond the NA. + assert(numpy.all(ab[outside] == 0.0)) + assert(numpy.any(ab[~outside] != 0.0)) + + # At zero defocus and a matched sample index there is no aberration, + # so the function is identically 1 inside the NA. + flat = geo.aberrationOPD(0.0, 0.0, 1.5) + assert(numpy.allclose(flat[~outside], 1.0 + 0j)) + + if (__name__ == "__main__"): test_pupil_math_1() test_pupil_math_2() test_pupil_math_3() + test_pupil_math_4() + test_pupil_math_5() + test_pupil_math_aberration_opd() From 1f282060dedc0736c41187af763be83567214635 Mon Sep 17 00:00:00 2001 From: Hazen Babcock Date: Tue, 8 Sep 2026 15:47:17 -0400 Subject: [PATCH 3/6] Fix the check_mode PSF dump in MPPeakFinderDao Three names wrong in one four line block, all from copying it out of MPPeakFinderArb.setVariances(): print("psf max", numpy.max(psf)) no psf here filename = "psf_z0.0_c{1:d}.tif".format(j) no j, and no arg 1 tifffile.imwrite(filename, psf.astype(...)) In the version it came from, the loop runs over z and then channel, so the PSF is 'psf' and the channel index is 'j', and the filename carries both z and channel: "psf_z{0:.3f}_c{1:d}".format(mfilter_z, j). The Gaussian version has a single z, so it hardcodes z in the name and loops over channels with 'i', and its PSF is 'psf_norm'. So the block referenced two names that do not exist and kept a format string for two arguments while passing one. Fixing only the names would have moved the failure to IndexError on {1:d}. That is the same shape of defect as the Spliner check_mode bug fixed in the fitters branch: a debugging path that crashes the moment anyone turns it on. There was no test file for fitting_mp at all. This adds one that builds the finder, turns check_mode on and calls setVariances(), which is enough to reach the block. It checks that one image is written per channel and named by the channel index, and that the contents match a Gaussian PSF computed independently in the test rather than read back out of the finder. The mapping file and parameters the finder needs are built the way diagnostics/multiplane_dao/configure.py does it. Co-Authored-By: Claude Opus 5 --- storm_analysis/multi_plane/fitting_mp.py | 6 +- storm_analysis/test/test_fitting_mp.py | 98 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 storm_analysis/test/test_fitting_mp.py diff --git a/storm_analysis/multi_plane/fitting_mp.py b/storm_analysis/multi_plane/fitting_mp.py index 94ec6c06..520771a2 100644 --- a/storm_analysis/multi_plane/fitting_mp.py +++ b/storm_analysis/multi_plane/fitting_mp.py @@ -600,9 +600,9 @@ def setVariances(self, variances): # Save a pictures of the PSFs for debugging purposes. if self.check_mode: - print("psf max", numpy.max(psf)) - filename = "psf_z0.0_c{1:d}.tif".format(j) - tifffile.imwrite(filename, psf.astype(numpy.float32)) + print("psf max", numpy.max(psf_norm)) + filename = "psf_z0.0_c{0:d}.tif".format(i) + tifffile.imwrite(filename, psf_norm.astype(numpy.float32)) # This handles the rest of the initialization. # diff --git a/storm_analysis/test/test_fitting_mp.py b/storm_analysis/test/test_fitting_mp.py new file mode 100644 index 00000000..e53a4d54 --- /dev/null +++ b/storm_analysis/test/test_fitting_mp.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +""" +Tests of multi_plane.fitting_mp +""" +import glob +import numpy +import os +import pickle +import tifffile + +import storm_analysis +import storm_analysis.sa_library.fitting as fitting +import storm_analysis.sa_library.parameters as parameters + +import storm_analysis.multi_plane.fitting_mp as fittingMp + + +im_size = (20, 18) +n_channels = 2 + + +def configureTest(): + """ + Build the parameters and the mapping file that MPPeakFinderDao needs. + """ + map_name = storm_analysis.getPathOutputTest("test_fitting_mp.map") + + mappings = {} + for i in range(n_channels): + mappings["0_" + str(i) + "_x"] = numpy.array([0.0, 1.0, 0.0]) + mappings["0_" + str(i) + "_y"] = numpy.array([0.0, 0.0, 1.0]) + mappings[str(i) + "_0_x"] = numpy.array([0.0, 1.0, 0.0]) + mappings[str(i) + "_0_y"] = numpy.array([0.0, 0.0, 1.0]) + with open(map_name, "wb") as fp: + pickle.dump(mappings, fp) + + params = parameters.ParametersMultiplaneDao() + params.setAttr("background_sigma", "float", 8.0) + params.setAttr("find_max_radius", "int", 2) + params.setAttr("foreground_sigma", "float", 1.5) + params.setAttr("iterations", "int", 1) + params.setAttr("mapping", "filename", map_name) + params.setAttr("no_fitting", "int", 0) + params.setAttr("pixel_size", "float", 100.0) + params.setAttr("roi_size", "int", 10) + params.setAttr("sigma", "float", 1.5) + params.setAttr("threshold", "float", 6.0) + + return params + + +def test_fitting_mp_check_mode(): + """ + MPPeakFinderDao.setVariances() saves a picture of each channel's PSF + when check_mode is on. + + That block was copied from the arbitrary PSF version, where the PSF is + called 'psf' and the channel index is 'j'. Here the PSF is 'psf_norm' + and the index is 'i', so it raised NameError, and the filename kept + "{1:d}" from the two argument version, so it would have raised + IndexError once the names were fixed. + """ + params = configureTest() + + # check_mode writes into the working directory. + cwd = os.getcwd() + os.chdir(storm_analysis.getPathOutputTest("")) + try: + for name in glob.glob("psf_z0.0_c*.tif"): + os.remove(name) + + finder = fittingMp.MPPeakFinderDao(parameters = params, + n_channels = n_channels) + finder.check_mode = True + + variances = [numpy.ones(im_size) for i in range(n_channels)] + finder.setVariances(variances) + + # One image per channel, named by the channel index. + written = sorted(glob.glob("psf_z0.0_c*.tif")) + assert(written == ["psf_z0.0_c0.tif", "psf_z0.0_c1.tif"]) + + # And it is the normalized PSF that was saved, computed here + # independently of the finder. The variances are padded by the + # margin before the filters are built. + padded = (im_size[0] + 2*finder.margin, im_size[1] + 2*finder.margin) + expected = fitting.gaussianPSF(padded, 1.5).astype(numpy.float32) + + for name in written: + psf = tifffile.imread(name) + assert(psf.shape == padded) + assert(numpy.allclose(psf, expected)) + finally: + os.chdir(cwd) + + +if (__name__ == "__main__"): + test_fitting_mp_check_mode() From 17902a24d2f659290c582b47e7428d7d90d96d25 Mon Sep 17 00:00:00 2001 From: Hazen Babcock Date: Tue, 8 Sep 2026 15:50:37 -0400 Subject: [PATCH 4/6] Fix five names that do not resolve The rest of what the linter found, all of the same kind: a name used that is not defined, or defined twice. None of these paths runs in the test suite, which is why they have survived. multi_plane/analysis_io.py static_background is used but never imported. sa_library/analysis_io.py has the same block and does import it, as import storm_analysis.sa_library.\ static_background as static_background Guarded by static_background_estimate, with a FIXME saying it is untested. admm/admm_lasso_c.py def run(self, a_lamba, iterations) against a body that uses a_lambda. The parameter is the misspelt one. Nothing calls this method, so renaming it breaks no caller. multi_plane/mp_fit_arb_c.py super(MPSFFnFit, ...) inside class MPPSFFnFit. A dropped P. simulator/emitters_on_lines.py argls.nemitters for args.nemitters, in the argument list of the __main__ call, so the script fails for everyone who runs it. jupyter_examples/ __main__ calls sCMOSSinglePlaneXML(). multiplane_measure_psf.py The function is sCMOSSingleFrameXML(). And two things defined twice: rolling_ball_bgr/ imports numpy twice, six lines apart. rolling_ball_lib_c.py diagnostics/ imports spliner's settings module at spliner_configure_common.py the top and then shadows it with the settings parameter of configure(), which is the only function in the file. Every settings. reference in it is the parameter. Worse than unused: the module is meant to be parameterized, and fista_decon's configure.py calls it with its own settings. Removing the import is safer than renaming the parameter, which two callers pass positionally. Not touched, and still reported: unicode in loadclib.py, which sits in the else of a sys.version_info > (3,0) test and so is unreachable rather than broken, unicode twice in TSFProto_pb2.py, which is generated protobuf output and should be regenerated rather than hand edited, and raw_input in spline1D.py. Removing those means deciding to drop Python 2 from those files, which is a separate question. Co-Authored-By: Claude Opus 5 --- storm_analysis/admm/admm_lasso_c.py | 2 +- storm_analysis/diagnostics/spliner_configure_common.py | 2 -- storm_analysis/jupyter_examples/multiplane_measure_psf.py | 2 +- storm_analysis/multi_plane/analysis_io.py | 1 + storm_analysis/multi_plane/mp_fit_arb_c.py | 2 +- storm_analysis/rolling_ball_bgr/rolling_ball_lib_c.py | 1 - storm_analysis/simulator/emitters_on_lines.py | 2 +- 7 files changed, 5 insertions(+), 7 deletions(-) diff --git a/storm_analysis/admm/admm_lasso_c.py b/storm_analysis/admm/admm_lasso_c.py index 74123f5a..dd13c602 100644 --- a/storm_analysis/admm/admm_lasso_c.py +++ b/storm_analysis/admm/admm_lasso_c.py @@ -160,7 +160,7 @@ def newImage(self, image, background): c_image = numpy.ascontiguousarray(image_no_bg, dtype=numpy.float64) admm_lasso.newImage(self.c_admm_lasso, c_image) - def run(self, a_lamba, iterations): + def run(self, a_lambda, iterations): admm_lasso.run(self.c_admm_lasso, a_lambda, iterations) diff --git a/storm_analysis/diagnostics/spliner_configure_common.py b/storm_analysis/diagnostics/spliner_configure_common.py index bf5946a3..950e8e1a 100644 --- a/storm_analysis/diagnostics/spliner_configure_common.py +++ b/storm_analysis/diagnostics/spliner_configure_common.py @@ -23,8 +23,6 @@ import storm_analysis.spliner.measure_psf_utils as measurePSFUtils import storm_analysis.spliner.psf_to_spline as psfToSpline -import storm_analysis.diagnostics.spliner.settings as settings - def configure(settings, use_dh, no_splines): diff --git a/storm_analysis/jupyter_examples/multiplane_measure_psf.py b/storm_analysis/jupyter_examples/multiplane_measure_psf.py index ab5d5142..25791cd8 100644 --- a/storm_analysis/jupyter_examples/multiplane_measure_psf.py +++ b/storm_analysis/jupyter_examples/multiplane_measure_psf.py @@ -221,5 +221,5 @@ def sCMOSSingleFrameXML(): makeCMOSCalibration() makeMapping() makeSampleData() - sCMOSSinglePlaneXML() + sCMOSSingleFrameXML() diff --git a/storm_analysis/multi_plane/analysis_io.py b/storm_analysis/multi_plane/analysis_io.py index 080f9046..ad631846 100644 --- a/storm_analysis/multi_plane/analysis_io.py +++ b/storm_analysis/multi_plane/analysis_io.py @@ -12,6 +12,7 @@ import storm_analysis.sa_library.analysis_io as analysisIO import storm_analysis.sa_library.sa_h5py as saH5Py +import storm_analysis.sa_library.static_background as static_background import storm_analysis.multi_plane.mp_utilities as mpUtil diff --git a/storm_analysis/multi_plane/mp_fit_arb_c.py b/storm_analysis/multi_plane/mp_fit_arb_c.py index decc7a4c..391bfe75 100644 --- a/storm_analysis/multi_plane/mp_fit_arb_c.py +++ b/storm_analysis/multi_plane/mp_fit_arb_c.py @@ -103,7 +103,7 @@ def setWeights(self, weights, verbose = True): "x" : numpy.ones((2, self.n_channels))/float(self.n_channels), "y" : numpy.ones((2, self.n_channels))/float(self.n_channels), "z" : numpy.ones((2, self.n_channels))/float(self.n_channels)} - super(MPSFFnFit, self).setWeights(weights, 0.0, 0.0, verbose = verbose) + super(MPPSFFnFit, self).setWeights(weights, 0.0, 0.0, verbose = verbose) else: zmax = self.psf_objects[0].getZMax() * 1.0e-3 diff --git a/storm_analysis/rolling_ball_bgr/rolling_ball_lib_c.py b/storm_analysis/rolling_ball_bgr/rolling_ball_lib_c.py index f648d1c5..123d62d1 100644 --- a/storm_analysis/rolling_ball_bgr/rolling_ball_lib_c.py +++ b/storm_analysis/rolling_ball_bgr/rolling_ball_lib_c.py @@ -11,7 +11,6 @@ import scipy.ndimage import ctypes -import numpy from numpy.ctypeslib import ndpointer import storm_analysis.sa_library.loadclib as loadclib diff --git a/storm_analysis/simulator/emitters_on_lines.py b/storm_analysis/simulator/emitters_on_lines.py index 116cf0e9..7993a0f9 100644 --- a/storm_analysis/simulator/emitters_on_lines.py +++ b/storm_analysis/simulator/emitters_on_lines.py @@ -133,7 +133,7 @@ def emittersOnLines(h5_name, nlines, nemitters, sx = 256, sy = 256, maxl = 200, emittersOnLines(args.hdf5, args.nlines, - argls.nemitters, + args.nemitters, sx = args.sx, sy = args.sy, maxl = args.maxl, From 2e18c04673cb304d67db56e13a883d54e1266cfe Mon Sep 17 00:00:00 2001 From: Hazen Babcock Date: Tue, 8 Sep 2026 15:51:48 -0400 Subject: [PATCH 5/6] List the audit's new tests in their __main__ blocks Same omission as 052781eb, in the other three files where the audit added or re-enabled tests. pytest collects test_* functions and ignores __main__, so running the file directly ran a subset of it and nobody noticed. test_clusters_sa_h5py.py test_cl_sa_h5py_z_is_loaded and test_cl_sa_h5py_drops_invalid_z, added with the clustering z fix. test_fitz_c.py test_fitz_c_marker_survives_drift and test_fitz_c_marker_propagates_to_tracks, added with the -infinity marker. test_spliner.py test_psf_to_spline and test_psf_to_spline_2D were re-enabled by renaming them, and the block still had the old names commented out as _test_psf_to_spline(). Uncommented under the names they now have. Verified by running all three files directly rather than through pytest, which is the path that was wrong. All exit 0. Four files still do not list everything they define: test_clustering.py, test_dataio.py, test_matched_filter.py and test_measure_psf_utils.py. Those omissions predate this work and none of them raises, so they are left alone. Co-Authored-By: Claude Opus 5 --- storm_analysis/test/test_clusters_sa_h5py.py | 2 ++ storm_analysis/test/test_fitz_c.py | 2 ++ storm_analysis/test/test_spliner.py | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/storm_analysis/test/test_clusters_sa_h5py.py b/storm_analysis/test/test_clusters_sa_h5py.py index 626b1cdc..b1681447 100644 --- a/storm_analysis/test/test_clusters_sa_h5py.py +++ b/storm_analysis/test/test_clusters_sa_h5py.py @@ -343,6 +343,8 @@ def test_cl_sa_h5py_7(): test_cl_sa_h5py_5() test_cl_sa_h5py_6() test_cl_sa_h5py_7() + test_cl_sa_h5py_z_is_loaded() + test_cl_sa_h5py_drops_invalid_z() diff --git a/storm_analysis/test/test_fitz_c.py b/storm_analysis/test/test_fitz_c.py index 66b5aa61..760bf0bb 100644 --- a/storm_analysis/test/test_fitz_c.py +++ b/storm_analysis/test/test_fitz_c.py @@ -271,4 +271,6 @@ def test_fitz_c_5(): test_fitz_c_3() test_fitz_c_4() test_fitz_c_5() + test_fitz_c_marker_survives_drift() + test_fitz_c_marker_propagates_to_tracks() diff --git a/storm_analysis/test/test_spliner.py b/storm_analysis/test/test_spliner.py index eff9ad16..1431982d 100644 --- a/storm_analysis/test/test_spliner.py +++ b/storm_analysis/test/test_spliner.py @@ -194,8 +194,8 @@ def test_spliner_fista_non_square(): if (__name__ == "__main__"): test_measure_psf() test_measure_psf_2D() -# _test_psf_to_spline() -# _test_psf_to_spline_2D() + test_psf_to_spline() + test_psf_to_spline_2D() test_spliner_std() test_spliner_std_2D() test_spliner_std_non_square() From 32eec2f73e1e58879ddbc2c86c2122547965b3e8 Mon Sep 17 00:00:00 2001 From: Hazen Babcock Date: Tue, 8 Sep 2026 15:57:53 -0400 Subject: [PATCH 6/6] Drop the Python 2 compatibility code pyproject.toml already says requires-python = '>=3.10', and CI builds on 3.10, 3.11 and 3.12 only, so none of this has been a supported path for some time. It was dead code rather than a policy question. Twenty five sys.version_info guards, in two shapes. Five outside the tests, where the Python 2 branch is simply deleted and the Python 3 branch kept: sa_utilities/bin_to_hdf5.py ElementTree.tostring(..., 'unicode') sa_library/analysis_io.py rather than 'ISO-8859-1' multi_plane/analysis_io.py sa_library/loadclib.py SetDllDirectoryW without unicode() L1H/setup_A_matrix.py pickle.load(fp, encoding='latin1') The loadclib one is where the linter's "undefined name unicode" came from. It was never reachable, since it sits in the else of the version test, but it was the reason that file was flagged. Twenty in the tests, all of the form # Only test for Python3 due to pickle incompatibility issues. if (sys.version_info < (3, 0)): return which on any supported interpreter never fires. Two of those twenty were mine, copied from their neighbours when I added the py_f tests. Removing the guards left 'import sys' unused in seven files, so those go too. loadclib.py keeps its import, it still uses sys.platform. This is not a general unused-import cleanup, only the ones this commit made dead. Also fixes the two Python 2 idioms in spline1D.py's __main__ demo, range(x.size/10), which is a float in Python 3 and raises, and raw_input(). Not touched: sa_utilities/TSFProto_pb2.py, which is generated protobuf output, says "DO NOT EDIT" at the top, and uses unicode("", "utf-8") in two default values. It should be regenerated rather than hand edited, and it cannot run in any case: google.protobuf is not a dependency of this package, and its two consumers, read_tagged_spot_file.py and bin_to_tagged_spot_file.py, use the Python 2 implicit relative form "import TSFProto_pb2", which fails outright on Python 3. That corner is three files that do not run, and untangling it is a separate job. Those two names are the only F821 findings left in the tree. Co-Authored-By: Claude Opus 5 --- storm_analysis/L1H/setup_A_matrix.py | 9 +----- storm_analysis/multi_plane/analysis_io.py | 6 +--- storm_analysis/sa_library/analysis_io.py | 6 +--- storm_analysis/sa_library/loadclib.py | 5 +--- storm_analysis/sa_utilities/bin_to_hdf5.py | 6 +--- storm_analysis/spliner/spline1D.py | 4 +-- storm_analysis/test/test_simulator.py | 21 ------------- storm_analysis/test/test_spline.py | 35 ---------------------- storm_analysis/test/test_spliner.py | 24 --------------- 9 files changed, 7 insertions(+), 109 deletions(-) diff --git a/storm_analysis/L1H/setup_A_matrix.py b/storm_analysis/L1H/setup_A_matrix.py index 53d6e315..cc20a2d5 100644 --- a/storm_analysis/L1H/setup_A_matrix.py +++ b/storm_analysis/L1H/setup_A_matrix.py @@ -12,7 +12,6 @@ import pickle import scipy.io from scipy.interpolate import griddata -import sys def createCoordinates(inner_size, scale, boundary_scale, pad = [0,0]): @@ -232,13 +231,7 @@ def loadAMatrix(file_name): with open(file_name, 'rb') as fp: - # Python 3 - if (sys.version_info > (3, 0)): - matrix = pickle.load(fp, encoding='latin1') - - # Python 2 - else: - matrix = pickle.load(fp) + matrix = pickle.load(fp, encoding='latin1') return matrix diff --git a/storm_analysis/multi_plane/analysis_io.py b/storm_analysis/multi_plane/analysis_io.py index ad631846..261f9056 100644 --- a/storm_analysis/multi_plane/analysis_io.py +++ b/storm_analysis/multi_plane/analysis_io.py @@ -6,7 +6,6 @@ """ import numpy import os -import sys from xml.etree import ElementTree @@ -57,10 +56,7 @@ def __init__(self, parameters = None, sa_type = None, **kwds): # Save analysis parameters. etree = parameters.toXMLElementTree(False) - if (sys.version_info > (3, 0)): - self.h5.addMetadata(ElementTree.tostring(etree, 'unicode')) - else: - self.h5.addMetadata(ElementTree.tostring(etree, 'ISO-8859-1')) + self.h5.addMetadata(ElementTree.tostring(etree, 'unicode')) # Save pixel size. self.h5.setPixelSize(parameters.getAttr("pixel_size")) diff --git a/storm_analysis/sa_library/analysis_io.py b/storm_analysis/sa_library/analysis_io.py index b8c36526..dae3144d 100644 --- a/storm_analysis/sa_library/analysis_io.py +++ b/storm_analysis/sa_library/analysis_io.py @@ -7,7 +7,6 @@ """ import numpy import os -import sys from xml.etree import ElementTree @@ -156,10 +155,7 @@ def __init__(self, parameters = None, sa_type = None, **kwds): # Save analysis parameters. etree = parameters.toXMLElementTree(False) - if (sys.version_info > (3, 0)): - self.h5.addMetadata(ElementTree.tostring(etree, 'unicode')) - else: - self.h5.addMetadata(ElementTree.tostring(etree, 'ISO-8859-1')) + self.h5.addMetadata(ElementTree.tostring(etree, 'unicode')) # Save pixel size. self.h5.setPixelSize(parameters.getAttr("pixel_size")) diff --git a/storm_analysis/sa_library/loadclib.py b/storm_analysis/sa_library/loadclib.py index f799212e..1278be49 100644 --- a/storm_analysis/sa_library/loadclib.py +++ b/storm_analysis/sa_library/loadclib.py @@ -44,10 +44,7 @@ def loadCLibrary(library_filename): ctypes.windll.kernel32.SetErrorMode(0) # Push the storm-analysis directory into the DLL search path. - if (sys.version_info > (3, 0)): - ctypes.windll.kernel32.SetDllDirectoryW(c_lib_path) - else: - ctypes.windll.kernel32.SetDllDirectoryW(unicode(c_lib_path)) + ctypes.windll.kernel32.SetDllDirectoryW(c_lib_path) # Try to load the library. c_lib = ctypes.cdll.LoadLibrary(os.path.join(c_lib_path, library_filename)) diff --git a/storm_analysis/sa_utilities/bin_to_hdf5.py b/storm_analysis/sa_utilities/bin_to_hdf5.py index 88871adb..207a9d93 100644 --- a/storm_analysis/sa_utilities/bin_to_hdf5.py +++ b/storm_analysis/sa_utilities/bin_to_hdf5.py @@ -8,7 +8,6 @@ Hazen 12/17 """ import os -import sys from xml.etree import ElementTree @@ -55,10 +54,7 @@ def BinToHDF5(bin_name, hdf5_name): # Set metadata. if params_xml is not None: - if (sys.version_info > (3, 0)): - h5.addMetadata(ElementTree.tostring(params_xml, 'unicode')) - else: - h5.addMetadata(ElementTree.tostring(params_xml, 'ISO-8859-1')) + h5.addMetadata(ElementTree.tostring(params_xml, 'unicode')) # Convert data. i3 = readinsight3.I3Reader(bin_name) diff --git a/storm_analysis/spliner/spline1D.py b/storm_analysis/spliner/spline1D.py index 33d74bc8..23690e00 100644 --- a/storm_analysis/spliner/spline1D.py +++ b/storm_analysis/spliner/spline1D.py @@ -97,7 +97,7 @@ def f(self, x): xv = [] yv = [] - for i in range(x.size/10): + for i in range(x.size//10): xv.append(x[i*10]) yv.append(y[i*10]) xv.append(x[-1]) @@ -120,5 +120,5 @@ def f(self, x): pw.plot(x,ys) #pw.plot(x,dxs) - raw_input("return to continue") + input("return to continue") diff --git a/storm_analysis/test/test_simulator.py b/storm_analysis/test/test_simulator.py index 9f3e15ba..6bbaec72 100644 --- a/storm_analysis/test/test_simulator.py +++ b/storm_analysis/test/test_simulator.py @@ -1,6 +1,5 @@ #!/usr/bin/env python import numpy -import sys import tifffile import storm_analysis @@ -20,10 +19,6 @@ def test_psf_spline2D_1(): """ Test that spline PSF agrees with spliner (for 0.0 offset). """ - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_name = storm_analysis.getData("test/data/test_spliner_psf_2d.spline") psf_sp_2d = psf.Spline2D(spline_name) @@ -39,10 +34,6 @@ def test_psf_spline2D_2(): """ Test that spline PSF C and Python versions agree. """ - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_name = storm_analysis.getData("test/data/test_spliner_psf_2d.spline") psf_sp_2D = psf.Spline2D(spline_name) @@ -60,10 +51,6 @@ def test_psf_spline3D_1(): """ Test that spline PSF agrees with spliner (for 0.0 offset). """ - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_name = storm_analysis.getData("test/data/test_spliner_psf.spline") psf_sp_3d = psf.Spline3D(spline_name) @@ -79,10 +66,6 @@ def test_psf_spline3D_2(): """ Test that spline PSF C and Python versions agree. """ - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_name = storm_analysis.getData("test/data/test_spliner_psf.spline") psf_sp_3D = psf.Spline3D(spline_name) @@ -134,10 +117,6 @@ def test_simulate_3(): No photo-physics, spline PSF, sCMOS camera. """ - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - dax_name = storm_analysis.getPathOutputTest("test_sim3.dax") bin_name = storm_analysis.getData("test/data/test_sim.hdf5") cal_name = storm_analysis.getData("test/data/calib.npy") diff --git a/storm_analysis/test/test_spline.py b/storm_analysis/test/test_spline.py index 172bf7b5..b5c0eefe 100644 --- a/storm_analysis/test/test_spline.py +++ b/storm_analysis/test/test_spline.py @@ -5,7 +5,6 @@ import numpy import pickle import random -import sys import storm_analysis @@ -18,12 +17,6 @@ def test_psf_2D_f(): - # Only test for Python3 due to pickle incompatibility issues which I am tired - # of trying to deal with. - # - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf_2d.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -41,10 +34,6 @@ def test_psf_2D_f(): def test_psf_2D_dx(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf_2d.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -62,10 +51,6 @@ def test_psf_2D_dx(): def test_psf_2D_dy(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf_2d.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -84,10 +69,6 @@ def test_psf_2D_dy(): def test_psf_3D_f(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -106,10 +87,6 @@ def test_psf_3D_f(): def test_psf_3D_dx(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -128,10 +105,6 @@ def test_psf_3D_dx(): def test_psf_3D_dy(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -150,10 +123,6 @@ def test_psf_3D_dy(): def test_psf_3D_dz(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - spline_filename = storm_analysis.getData("test/data/test_spliner_psf.spline") with open(spline_filename, "rb") as fp: spline_data = pickle.load(fp) @@ -178,8 +147,6 @@ def test_psf_2D_py_f(): the opposite order, which evaluates the transposed point. A PSF spline is not symmetric in x and y, so the answers differ. """ - if (sys.version_info < (3, 0)): - return spline_filename = storm_analysis.getData("test/data/test_spliner_psf_2d.spline") with open(spline_filename, "rb") as fp: @@ -200,8 +167,6 @@ def test_psf_3D_py_f(): The same check on CSpline3D, which has always passed its arguments through in the right order. """ - if (sys.version_info < (3, 0)): - return spline_filename = storm_analysis.getData("test/data/test_spliner_psf.spline") with open(spline_filename, "rb") as fp: diff --git a/storm_analysis/test/test_spliner.py b/storm_analysis/test/test_spliner.py index 1431982d..ca3cb707 100644 --- a/storm_analysis/test/test_spliner.py +++ b/storm_analysis/test/test_spliner.py @@ -4,7 +4,6 @@ """ import numpy import pickle -import sys import storm_analysis @@ -73,10 +72,6 @@ def test_psf_to_spline_2D(): def test_spliner_std(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - movie_name = storm_analysis.getData("test/data/test_spliner.dax") settings = storm_analysis.getData("test/data/test_spliner_dh.xml") mlist = storm_analysis.getPathOutputTest("test_spliner_dh.hdf5") @@ -93,10 +88,6 @@ def test_spliner_std(): def test_spliner_std_2D(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - movie_name = storm_analysis.getData("test/data/test.dax") settings = storm_analysis.getData("test/data/test_spliner_2D.xml") mlist = storm_analysis.getPathOutputTest("test_spliner_2D.hdf5") @@ -113,10 +104,6 @@ def test_spliner_std_2D(): def test_spliner_std_non_square(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - movie_name = storm_analysis.getData("test/data/test_300x200_dh.dax") settings = storm_analysis.getData("test/data/test_spliner_dh.xml") mlist = storm_analysis.getPathOutputTest("test_spliner_dh.hdf5") @@ -133,9 +120,6 @@ def test_spliner_std_non_square(): def test_spliner_fista(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return movie_name = storm_analysis.getData("test/data/test_spliner.dax") settings = storm_analysis.getData("test/data/test_spliner_dh_fista.xml") @@ -153,10 +137,6 @@ def test_spliner_fista(): def test_spliner_fista_2D(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - movie_name = storm_analysis.getData("test/data/test.dax") settings = storm_analysis.getData("test/data/test_spliner_2D_fista.xml") mlist = storm_analysis.getPathOutputTest("test_spliner_2D_fista.hdf5") @@ -173,10 +153,6 @@ def test_spliner_fista_2D(): def test_spliner_fista_non_square(): - # Only test for Python3 due to pickle incompatibility issues. - if (sys.version_info < (3, 0)): - return - movie_name = storm_analysis.getData("test/data/test_300x200_dh.dax") settings = storm_analysis.getData("test/data/test_spliner_dh_fista.xml") mlist = storm_analysis.getPathOutputTest("test_spliner_dh_fista_ns.hdf5")