pycistem 0.6.1__cp310-cp310-manylinux_2_28_x86_64.whl

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 (84) hide show
  1. pycistem/__init__.py +9 -0
  2. pycistem/config.py +20 -0
  3. pycistem/core/__init__.py +1 -0
  4. pycistem/core/core.cpp +1070 -0
  5. pycistem/core/core.cpython-310-x86_64-linux-gnu.so +0 -0
  6. pycistem/core/database.cpp +408 -0
  7. pycistem/core/euler_search.cpp +72 -0
  8. pycistem/core/run_profiles.cpp +114 -0
  9. pycistem/database/__init__.py +301 -0
  10. pycistem/programs/__init__.py +10 -0
  11. pycistem/programs/_cistem_constants.py +23 -0
  12. pycistem/programs/apply_ctf.py +59 -0
  13. pycistem/programs/cistem_program.py +277 -0
  14. pycistem/programs/ctffind.py +254 -0
  15. pycistem/programs/estimate_beamtilt.py +60 -0
  16. pycistem/programs/match_template.py +325 -0
  17. pycistem/programs/reconstruct3d.py +79 -0
  18. pycistem/programs/refine_ctf.py +106 -0
  19. pycistem/programs/refine_template.py +157 -0
  20. pycistem/programs/refine_template_dev.py +35 -0
  21. pycistem/programs/refine_template_niko.py +124 -0
  22. pycistem/programs/resample.py +30 -0
  23. pycistem/programs/run_profile.py +17 -0
  24. pycistem/programs/unblur.py +247 -0
  25. pycistem/programs/unblur_patch.py +204 -0
  26. pycistem/utils/__init__.py +1 -0
  27. pycistem/utils/extract_particles.py +36 -0
  28. pycistem/utils/move_class_into_unbinned_particle_stack.py +34 -0
  29. pycistem/utils/order_by_class_occupancy.py +26 -0
  30. pycistem/utils/plot_class_occupancy.py +51 -0
  31. pycistem/utils/plot_classification_fsc_movie.py +90 -0
  32. pycistem/utils/plot_occupancy_by_condition.py +129 -0
  33. pycistem-0.6.1.dist-info/METADATA +258 -0
  34. pycistem-0.6.1.dist-info/RECORD +84 -0
  35. pycistem-0.6.1.dist-info/WHEEL +5 -0
  36. pycistem-0.6.1.dist-info/licenses/LICENSE +201 -0
  37. pycistem-0.6.1.dist-info/sboms/auditwheel.cdx.json +1 -0
  38. pycistem.libs/libXau-154567c4.so.6.0.0 +0 -0
  39. pycistem.libs/libXcomposite-9a78b2b5.so.1.0.0 +0 -0
  40. pycistem.libs/libXcursor-557eab0f.so.1.0.2 +0 -0
  41. pycistem.libs/libXdamage-45f20f14.so.1.1.0 +0 -0
  42. pycistem.libs/libXfixes-e3b7d94c.so.3.1.0 +0 -0
  43. pycistem.libs/libXi-9178a6bd.so.6.1.0 +0 -0
  44. pycistem.libs/libXinerama-6a3f4a3e.so.1.0.0 +0 -0
  45. pycistem.libs/libXrandr-9f75043e.so.2.2.0 +0 -0
  46. pycistem.libs/libatk-1-3e086f29.0.so.0.22810.1 +0 -0
  47. pycistem.libs/libblkid-a9167753.so.1.1.0 +0 -0
  48. pycistem.libs/libbz2-a1e77c99.so.1.0.6 +0 -0
  49. pycistem.libs/libcairo-dfbea965.so.2.11512.0 +0 -0
  50. pycistem.libs/libdatrie-584ecbbd.so.1.3.2 +0 -0
  51. pycistem.libs/libffi-3a37023a.so.6.0.2 +0 -0
  52. pycistem.libs/libfontconfig-dcb2ce6c.so.1.12.0 +0 -0
  53. pycistem.libs/libfreetype-2f3b32b6.so.6.16.1 +0 -0
  54. pycistem.libs/libfribidi-a2ddad26.so.0.4.0 +0 -0
  55. pycistem.libs/libgdk-x11-2-ae2f4865.0.so.0.2400.32 +0 -0
  56. pycistem.libs/libgdk_pixbuf-2-e875edac.0.so.0.3612.0 +0 -0
  57. pycistem.libs/libgio-2-54f4f0a9.0.so.0.5600.4 +0 -0
  58. pycistem.libs/libgmodule-2-a8eef785.0.so.0.5600.4 +0 -0
  59. pycistem.libs/libgmp-d944b113.so.10.3.2 +0 -0
  60. pycistem.libs/libgnutls-e5fc1c5f.so.30.28.2 +0 -0
  61. pycistem.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
  62. pycistem.libs/libgraphite2-a2b39163.so.3.0.1 +0 -0
  63. pycistem.libs/libgtk-x11-2-76f42ab1.0.so.0.2400.32 +0 -0
  64. pycistem.libs/libharfbuzz-26b3d829.so.0.10705.0 +0 -0
  65. pycistem.libs/libhogweed-cd4c53be.so.4.5 +0 -0
  66. pycistem.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  67. pycistem.libs/libmount-ec61bd71.so.1.1.0 +0 -0
  68. pycistem.libs/libnettle-37944285.so.6.5 +0 -0
  69. pycistem.libs/libp11-kit-ac9dcd7e.so.0.3.0 +0 -0
  70. pycistem.libs/libpango-1-e80d1584.0.so.0.4200.3 +0 -0
  71. pycistem.libs/libpangocairo-1-e0e0fbcf.0.so.0.4200.3 +0 -0
  72. pycistem.libs/libpangoft2-1-1fa1613e.0.so.0.4200.3 +0 -0
  73. pycistem.libs/libpcre-0dd207b5.so.1.2.10 +0 -0
  74. pycistem.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  75. pycistem.libs/libpixman-1-06469c37.so.0.38.4 +0 -0
  76. pycistem.libs/libpng16-748299c7.so.16.34.0 +0 -0
  77. pycistem.libs/libselinux-d0805dcb.so.1 +0 -0
  78. pycistem.libs/libtasn1-564de53e.so.6.5.5 +0 -0
  79. pycistem.libs/libthai-cd935638.so.0.3.0 +0 -0
  80. pycistem.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  81. pycistem.libs/libuuid-95b83d40.so.1.3.0 +0 -0
  82. pycistem.libs/libxcb-5ddf6756.so.1.1.0 +0 -0
  83. pycistem.libs/libxcb-render-161c0eb5.so.0.0.0 +0 -0
  84. pycistem.libs/libxcb-shm-0be6dfbf.so.0.0.0 +0 -0
@@ -0,0 +1,254 @@
1
+ import asyncio
2
+ import datetime
3
+ import sqlite3
4
+ import struct
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import List, Union
8
+
9
+ import mrcfile
10
+ import pandas as pd
11
+
12
+ from pycistem.database import datetime_to_msdos, get_image_info_from_db
13
+ from pycistem.programs import cistem_program
14
+ from pycistem.programs._cistem_constants import socket_send_next_job
15
+
16
+
17
+ @dataclass
18
+ class CtffindParameters:
19
+ input_filename : str
20
+ input_is_a_movie: bool = True
21
+ number_of_frames_to_average : int = 3
22
+ output_diagnostic_filename : str = "diagnostic.mrc"
23
+ pixel_size_of_input_image : float = 1.0
24
+ acceleration_voltage : float = 300.0
25
+ spherical_aberration : float = 2.7
26
+ amplitude_contrast: float = 0.07
27
+ box_size: int = 512
28
+ minimum_resolution: float = 30.0
29
+ maximum_resolution: float = 5.0
30
+ minimum_defocus: float = 3000.0
31
+ maximum_defocus: float = 30000.0
32
+ defocus_search_step: float = 100.0
33
+ slower_search: bool = False
34
+ astigmatism_tolerance: float = -100.0
35
+ find_additional_phase_shift: bool = False
36
+ minimum_additional_phase_shift: float = 0.0
37
+ maximum_additional_phase_shift: float = 0.0
38
+ additional_phase_shift_search_step: float = 0.0
39
+ astigmatism_is_known: bool = False
40
+ known_astigmatism: float = 0.0
41
+ known_astigmatism_angle: float = 0.0
42
+ resample_if_pixel_too_small: bool = True
43
+ movie_is_gain_corrected: bool = False
44
+ gain_filename: str = "gain.mrc"
45
+ movie_is_dark_corrected: bool = True
46
+ dark_filename: str = "dark.mrc"
47
+ correct_movie_mag_distortion: bool = False
48
+ movie_mag_distortion_angle: float = 0.0
49
+ movie_mag_distortion_major_scale: float = 1.0
50
+ movie_mag_distortion_minor_scale: float = 1.0
51
+ defocus_is_known: bool = False
52
+ known_defocus_1: float = 0.0
53
+ known_defocus_2: float = 0.0
54
+ known_phase_shift: float = 0.0
55
+ determine_tilt: bool = False
56
+ desired_number_of_threads: int = 1
57
+ eer_frames_per_image: int = 0
58
+ eer_super_res_factor: int = 1
59
+ filter_lowres_signal: bool = True
60
+ fit_nodes: bool = False
61
+ fit_nodes_1D_brute_force: bool = True
62
+ fit_nodes_2D_refine: bool = True
63
+ fit_nodes_low_resolution_limit: float = 30.0
64
+ fit_nodes_high_resolution_limit: float = 4.0
65
+ target_pixel_size_after_resampling: float = 1.4
66
+ fit_nodes_use_rounded_square: bool = False
67
+ fit_nodes_downweight_nodes: bool = False
68
+
69
+ def parameters_from_database(database, decolace=False, **kwargs):
70
+ image_info = get_image_info_from_db(database,get_ctf=False)
71
+ ProjectDirectory = Path(database).parent
72
+ par = [CtffindParameters(
73
+ input_filename = image["movie_filename"],
74
+ pixel_size_of_input_image = image["movie_pixel_size"],
75
+ gain_filename=image["GAIN_FILENAME"],
76
+ output_diagnostic_filename=(ProjectDirectory / "Assets" / "CTF" / f"{Path(image['FILENAME']).stem}_{image['MOVIE_ASSET_ID']}_auto.mrc").as_posix(),
77
+ fit_nodes=decolace
78
+ ) for i,image in image_info.iterrows()]
79
+ return((par, image_info))
80
+
81
+
82
+ def write_results_to_database(database, parameters: list[CtffindParameters], results, image_info):
83
+ conn = sqlite3.connect(database, isolation_level=None)
84
+ cur = conn.cursor()
85
+ results = sorted(results, key=lambda x: x["parameter_index"])
86
+ ESTIMATED_CTF_PARAMETERS_LIST = []
87
+
88
+ max_ctf_estimation_id= cur.execute("SELECT MAX(CTF_ESTIMATION_ID) FROM ESTIMATED_CTF_PARAMETERS").fetchone()[0]
89
+ if max_ctf_estimation_id is None:
90
+ max_ctf_estimation_id = 0
91
+ ctf_estimation_job_id= cur.execute("SELECT MAX(CTF_ESTIMATION_JOB_ID) FROM ESTIMATED_CTF_PARAMETERS").fetchone()[0]
92
+ if ctf_estimation_job_id is None:
93
+ ctf_estimation_job_id = 1
94
+ else:
95
+ ctf_estimation_job_id += 1
96
+
97
+ for result in results:
98
+
99
+ ESTIMATED_CTF_PARAMETERS_LIST.append({
100
+ "CTF_ESTIMATION_ID": max_ctf_estimation_id + 1,
101
+ "CTF_ESTIMATION_JOB_ID": ctf_estimation_job_id,
102
+ "DATETIME_OF_RUN": datetime_to_msdos(datetime.datetime.now()),
103
+ "IMAGE_ASSET_ID": image_info.loc[result["parameter_index"]]["IMAGE_ASSET_ID"],
104
+ "ESTIMATED_ON_MOVIE_FRAMES": True,
105
+ "VOLTAGE": parameters[result["parameter_index"]].acceleration_voltage,
106
+ "SPHERICAL_ABERRATION": parameters[result["parameter_index"]].spherical_aberration,
107
+ "PIXEL_SIZE": parameters[result["parameter_index"]].pixel_size_of_input_image,
108
+ "AMPLITUDE_CONTRAST": parameters[result["parameter_index"]].amplitude_contrast,
109
+ "BOX_SIZE": parameters[result["parameter_index"]].box_size,
110
+ "MIN_RESOLUTION": parameters[result["parameter_index"]].minimum_resolution,
111
+ "MAX_RESOLUTION": parameters[result["parameter_index"]].maximum_resolution,
112
+ "MIN_DEFOCUS": parameters[result["parameter_index"]].minimum_defocus,
113
+ "MAX_DEFOCUS": parameters[result["parameter_index"]].maximum_defocus,
114
+ "DEFOCUS_STEP": parameters[result["parameter_index"]].defocus_search_step,
115
+ "RESTRAIN_ASTIGMATISM": parameters[result["parameter_index"]].astigmatism_tolerance > 0,
116
+ "TOLERATED_ASTIGMATISM": parameters[result["parameter_index"]].astigmatism_tolerance,
117
+ "FIND_ADDITIONAL_PHASE_SHIFT": parameters[result["parameter_index"]].find_additional_phase_shift,
118
+ "MIN_PHASE_SHIFT": parameters[result["parameter_index"]].minimum_additional_phase_shift,
119
+ "MAX_PHASE_SHIFT": parameters[result["parameter_index"]].maximum_additional_phase_shift,
120
+ "PHASE_SHIFT_STEP": parameters[result["parameter_index"]].additional_phase_shift_search_step,
121
+ "DEFOCUS1": result["defocus1"],
122
+ "DEFOCUS2": result["defocus2"],
123
+ "DEFOCUS_ANGLE": result["astigmatism_angle"],
124
+ "ADDITIONAL_PHASE_SHIFT": result["phase_shift"],
125
+ "SCORE": result["score"],
126
+ "DETECTED_RING_RESOLUTION": result["fit_resolution"],
127
+ "DETECTED_ALIAS_RESOLUTION": result["aliasing_resolution"],
128
+ "OUTPUT_DIAGNOSTIC_FILE": parameters[result["parameter_index"]].output_diagnostic_filename,
129
+ "NUMBER_OF_FRAMES_AVERAGED": parameters[result["parameter_index"]].number_of_frames_to_average,
130
+ "LARGE_ASTIGMATISM_EXPECTED": parameters[result["parameter_index"]].slower_search,
131
+ "ICINESS": result["iciness"],
132
+ "TILT_ANGLE": result["tilt_angle"],
133
+ "TILT_AXIS": result["tilt_axis"],
134
+ "SAMPLE_THICKNESS": result["sample_thickness"],
135
+ "SAMPLE_THICKNESS_JSON": "",
136
+ "DETERMINE_TILT": parameters[result["parameter_index"]].determine_tilt,
137
+ "FIT_NODES": parameters[result["parameter_index"]].fit_nodes,
138
+ "FIT_NODES_1D": parameters[result["parameter_index"]].fit_nodes_1D_brute_force,
139
+ "FIT_NODES_2D": parameters[result["parameter_index"]].fit_nodes_2D_refine,
140
+ "FIT_NODES_LOW_LIMIT": parameters[result["parameter_index"]].fit_nodes_low_resolution_limit,
141
+ "FIT_NODES_HIGH_LIMIT": parameters[result["parameter_index"]].fit_nodes_high_resolution_limit,
142
+ "FIT_NODES_ROUNDED_SQUARE": parameters[result["parameter_index"]].fit_nodes_use_rounded_square,
143
+ "FIT_NODES_DOWNWEIGHT_NODES": parameters[result["parameter_index"]].fit_nodes_downweight_nodes,
144
+ "RESAMPLE_IF_NESCESSARY": parameters[result["parameter_index"]].resample_if_pixel_too_small,
145
+ "TARGET_PIXEL_SIZE": parameters[result["parameter_index"]].target_pixel_size_after_resampling
146
+ })
147
+ #print("UPDATE IMAGE_ASSETS SET CTF_ESTIMATION_ID = ? WHERE IMAGE_ASSET_ID = ?", (max_ctf_estimation_id + 1, image_info.loc[result["parameter_index"]]["IMAGE_ASSET_ID"]))
148
+ #cur.execute("UPDATE IMAGE_ASSETS SET CTF_ESTIMATION_ID = ? WHERE IMAGE_ASSET_ID = ?", (max_ctf_estimation_id + 1, image_info.loc[result["parameter_index"]]["IMAGE_ASSET_ID"]))
149
+ max_ctf_estimation_id += 1
150
+
151
+ ESTIMATED_CTF_PARAMETERS_LIST = pd.DataFrame(ESTIMATED_CTF_PARAMETERS_LIST)
152
+ ESTIMATED_CTF_PARAMETERS_LIST.to_sql("ESTIMATED_CTF_PARAMETERS", conn, if_exists="append", index=False)
153
+
154
+ cur = conn.cursor()
155
+ cur.executemany("UPDATE IMAGE_ASSETS SET CTF_ESTIMATION_ID = ? WHERE IMAGE_ASSET_ID = ?",[
156
+ (row["CTF_ESTIMATION_ID"], row["IMAGE_ASSET_ID"])
157
+ for i, row in ESTIMATED_CTF_PARAMETERS_LIST.iterrows()
158
+ ])
159
+ conn.commit()
160
+ # Update CTF_ESTIMATION_ID in IMAGE_ASSETS table
161
+
162
+ conn.close()
163
+
164
+ async def handle_results(reader, writer, logger):
165
+ #logger.info("Handling results")
166
+ await reader.read(4)
167
+ length = await reader.readexactly(4)
168
+ print(f"CTFFIND results length {length}")
169
+ number_of_bytes = int.from_bytes(length, byteorder="little")
170
+ results = await reader.readexactly(number_of_bytes*4)
171
+ print(f"CTFFIND results {results}")
172
+ return(results)
173
+
174
+ signal_handlers = {
175
+ socket_send_next_job : handle_results
176
+ }
177
+
178
+ def run(parameters: Union[CtffindParameters,list[CtffindParameters]],**kwargs):
179
+
180
+ if not isinstance(parameters, list):
181
+ parameters = [parameters]
182
+
183
+ byte_results = asyncio.run(cistem_program.run("ctffind", parameters, signal_handlers=signal_handlers,**kwargs))
184
+ result_ctf = []
185
+
186
+ for parameter_index,byte_result in byte_results:
187
+ defocus1 = struct.unpack("<f",byte_result[0:4])[0]
188
+ defocus2 = struct.unpack("<f",byte_result[4:8])[0]
189
+ astigmatism_angle = struct.unpack("<f",byte_result[8:12])[0]
190
+ phase_shift = struct.unpack("<f",byte_result[12:16])[0]
191
+ score = struct.unpack("<f",byte_result[16:20])[0]
192
+ fit_resolution = struct.unpack("<f",byte_result[20:24])[0]
193
+ aliasing_resolution = struct.unpack("<f",byte_result[24:28])[0]
194
+ iciness = struct.unpack("<f",byte_result[28:32])[0]
195
+ tilt_angle = struct.unpack("<f",byte_result[32:36])[0]
196
+ tilt_axis = struct.unpack("<f",byte_result[36:40])[0]
197
+ sample_thickness = struct.unpack("<f",byte_result[40:44])[0]
198
+
199
+ result_ctf.append({
200
+ "parameter_index" : parameter_index,
201
+ "defocus1" : defocus1,
202
+ "defocus2" : defocus2,
203
+ "astigmatism_angle" : astigmatism_angle,
204
+ "phase_shift" : phase_shift,
205
+ "score" : score,
206
+ "fit_resolution" : fit_resolution,
207
+ "aliasing_resolution" : aliasing_resolution,
208
+ "iciness" : iciness,
209
+ "tilt_angle" : tilt_angle,
210
+ "tilt_axis" : tilt_axis,
211
+ "sample_thickness" : sample_thickness
212
+ })
213
+
214
+
215
+ return(result_ctf)
216
+
217
+ async def run_async(parameters: Union[CtffindParameters,list[CtffindParameters]],**kwargs):
218
+
219
+ if not isinstance(parameters, list):
220
+ parameters = [parameters]
221
+
222
+ byte_results = await cistem_program.run("ctffind", parameters, signal_handlers=signal_handlers,**kwargs)
223
+ result_ctf = []
224
+
225
+ for parameter_index,byte_result in byte_results:
226
+ defocus1 = struct.unpack("<f",byte_result[0:4])[0]
227
+ defocus2 = struct.unpack("<f",byte_result[4:8])[0]
228
+ astigmatism_angle = struct.unpack("<f",byte_result[8:12])[0]
229
+ phase_shift = struct.unpack("<f",byte_result[12:16])[0]
230
+ score = struct.unpack("<f",byte_result[16:20])[0]
231
+ fit_resolution = struct.unpack("<f",byte_result[20:24])[0]
232
+ aliasing_resolution = struct.unpack("<f",byte_result[24:28])[0]
233
+ iciness = struct.unpack("<f",byte_result[28:32])[0]
234
+ tilt_angle = struct.unpack("<f",byte_result[32:36])[0]
235
+ tilt_axis = struct.unpack("<f",byte_result[36:40])[0]
236
+ sample_thickness = struct.unpack("<f",byte_result[40:44])[0]
237
+
238
+ result_ctf.append({
239
+ "parameter_index" : parameter_index,
240
+ "defocus1" : defocus1,
241
+ "defocus2" : defocus2,
242
+ "astigmatism_angle" : astigmatism_angle,
243
+ "phase_shift" : phase_shift,
244
+ "score" : score,
245
+ "fit_resolution" : fit_resolution,
246
+ "aliasing_resolution" : aliasing_resolution,
247
+ "iciness" : iciness,
248
+ "tilt_angle" : tilt_angle,
249
+ "tilt_axis" : tilt_axis,
250
+ "sample_thickness" : sample_thickness
251
+ })
252
+
253
+
254
+ return(result_ctf)
@@ -0,0 +1,60 @@
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+ from typing import Union
4
+ import numpy as np
5
+ import struct
6
+ import mrcfile
7
+ from functools import partial
8
+ import pandas as pd
9
+
10
+
11
+ from pycistem.programs import cistem_program
12
+ from pycistem.programs._cistem_constants import socket_send_next_job, socket_job_result_queue
13
+ #from pycistem.core import Image
14
+
15
+ @dataclass
16
+ class EstimateBeamtiltParameters:
17
+ input_phase_difference_image: str
18
+ pixel_size: float = 1.0
19
+ voltage_kV: float = 300.0
20
+ spherical_aberration_mm: float = 2.7
21
+ first_position_to_search: int = 0
22
+ last_position_to_search: int = 0
23
+
24
+ async def handle_results(reader, writer, logger):
25
+ #logger.info("Handling results")
26
+ await reader.read(4)
27
+ length = await reader.read(4)
28
+ number_of_bytes = int.from_bytes(length, byteorder="little")
29
+ results = await reader.read(number_of_bytes*4)
30
+ score = struct.unpack("<f",results[0:4])[0]
31
+ beam_tilt_x = struct.unpack("<f",results[4:8])[0]
32
+ beam_tilt_y = struct.unpack("<f",results[8:12])[0]
33
+ particle_shift_x = struct.unpack("<f",results[12:16])[0]
34
+ particle_shift_y = struct.unpack("<f",results[16:20])[0]
35
+ return(score,beam_tilt_x,beam_tilt_y,particle_shift_x,particle_shift_y)
36
+
37
+ async def handle_job_result_queue(reader, writer, logger):
38
+
39
+ length = await reader.readexactly(4)
40
+ number_of_bytes = int.from_bytes(length, byteorder="little")
41
+ results = await reader.readexactly(number_of_bytes)
42
+ return(results)
43
+
44
+ def run(parameters: Union[EstimateBeamtiltParameters,list[EstimateBeamtiltParameters]],**kwargs) -> pd.DataFrame:
45
+
46
+ if not isinstance(parameters, list):
47
+ parameters = [parameters]
48
+ signal_handlers = {
49
+ socket_send_next_job : handle_results,
50
+ socket_job_result_queue : handle_job_result_queue,
51
+
52
+ }
53
+ result = asyncio.run(cistem_program.run("estimate_beamtilt", parameters, signal_handlers=signal_handlers,**kwargs))
54
+ result = pd.DataFrame([a[1] for a in result],
55
+ index = [a[0] for a in result],
56
+ columns=["score","beam_tilt_x","beam_tilt_y","particle_shift_x","particle_shift_y"])
57
+ return(result)
58
+
59
+
60
+
@@ -0,0 +1,325 @@
1
+ import asyncio
2
+ import sqlite3
3
+ import struct
4
+ from dataclasses import dataclass
5
+ from functools import partial
6
+ from pathlib import Path
7
+ from typing import List, Tuple, Union, Optional
8
+
9
+ import mrcfile
10
+ import numpy as np
11
+ import pandas as pd
12
+ from skimage.feature import peak_local_max
13
+ import datetime
14
+
15
+ from pycistem.core import EulerSearch, ParameterMap
16
+ from pycistem.database import datetime_to_msdos, ensure_template_is_a_volume_asset, get_image_info_from_db, create_peak_lists, get_max_match_template_job_id
17
+ from pycistem.programs import cistem_program
18
+ from pycistem.programs._cistem_constants import socket_job_result_queue, socket_program_defined_result, socket_i_have_info
19
+
20
+
21
+ @dataclass
22
+ class MatchTemplateParameters:
23
+ input_search_images_filename: str #0
24
+ input_reconstruction_filename: str #1
25
+ pixel_size: float = 1.0 #2
26
+ voltage_kV: float = 300.0 #3
27
+ spherical_aberration_mm: float = 2.7
28
+ amplitude_contrast: float = 0.07
29
+ defocus1: float = 10000.0
30
+ defocus2: float = 10000.0
31
+ defocus_angle: float = 0.0
32
+ low_resolution_limit: float = 30.0
33
+ high_resolution_limit_search: float = 3.0
34
+ angular_step: float = 3.0 #10
35
+ best_parameters_to_keep: int = 1
36
+ defocus_search_range: float = 1000.0
37
+ defocus_step: float = 200.0
38
+ pixel_size_search_range: float = 0.0
39
+ pixel_size_step: float = 0.0
40
+ padding: float = 1.0
41
+ ctf_refinement: bool = False
42
+ particle_radius_angstroms: float = 0.0
43
+ phase_shift: float = 0.0
44
+ mip_output_file: str = "/dev/null"
45
+ best_psi_output_file: str = "best_psi.mrc"
46
+ best_theta_output_file: str = "best_theta.mrc"
47
+ best_phi_output_file: str = "best_phi.mrc"
48
+ best_defocus_output_file: str = "best_defocus.mrc"
49
+ best_pixel_size_output_file: str = "best_pixel_size.mrc"
50
+ scaled_mip_output_file: str = "scaled_mip.mrc"
51
+ correlation_avg_output_file : str = "correlation_avg.mrc"
52
+ my_symmetry: str = "C1"
53
+ in_plane_angular_step: float = 2.0
54
+ output_histogram_file: str = "histogram.txt"
55
+ first_search_position: int = 0
56
+ last_search_position: int = 100
57
+ image_number_for_gui: int = 0
58
+ number_of_jobs_per_image_in_gui: int = 1
59
+ correlation_std_output_file: str = "correlation_std.mrc"
60
+ directory_for_results: str = "/dev/null"
61
+ result_output_filename: str = "/dev/null"
62
+ min_peak_radius: float = 10.0
63
+ use_gpu: bool = True
64
+ max_threads: int = 4
65
+
66
+ # TODO
67
+ # 1. Write correct threshold into db
68
+ # 2. Get correct border_exclude value
69
+ # 3. Maybe do projection?
70
+ async def handle_socket_i_have_info(reader, writer, logger):
71
+ data = await reader.readexactly(4)
72
+ length = int.from_bytes(data, byteorder="little")
73
+ data = await reader.readexactly(length)
74
+ print(f"Info: {data.decode('utf-8')}")
75
+
76
+ def get_np_arrays(bytes,o,i,x,y,numpix):
77
+ array = np.frombuffer(bytes,offset=o+i*numpix*4, count=numpix,dtype=np.float32).copy()
78
+ array = array.reshape((y,-1))
79
+ array = array[:,:x]
80
+ return array
81
+
82
+ def parameters_from_database(database, template_filename: str, match_template_job_id: Optional[int] = None,**kwargs) :
83
+ image_info = get_image_info_from_db(database,get_ctf=True)
84
+ if image_info is None:
85
+ return []
86
+ if match_template_job_id is None:
87
+ match_template_job_id = get_max_match_template_job_id(database) + 1
88
+ ProjectDirectory = Path(database).parent
89
+ par = [MatchTemplateParameters(
90
+ input_search_images_filename = image["FILENAME"],
91
+ input_reconstruction_filename = template_filename,
92
+ pixel_size = image["image_pixel_size"],
93
+ defocus1=image["DEFOCUS1"],
94
+ defocus2=image["DEFOCUS2"],
95
+ defocus_angle=image["DEFOCUS_ANGLE"],
96
+ scaled_mip_output_file=(ProjectDirectory / "Assets" / "TemplateMatching" / f"{Path(image['FILENAME']).stem}_auto_{match_template_job_id}_scaled_mip.mrc").as_posix(),
97
+ output_histogram_file=(ProjectDirectory / "Assets" / "TemplateMatching" / f"{Path(image['FILENAME']).stem}_auto_{match_template_job_id}_histogram.txt").as_posix(),
98
+ ) for i,image in image_info.iterrows()]
99
+ image_info["MATCH_TEMPLATE_JOB_ID"] = match_template_job_id
100
+ image_info["PARAMETERS"] = par
101
+ image_info["DATABASE"] = database
102
+ image_info["THRESHOLD"] = 0.0
103
+ return(image_info)
104
+
105
+ def write_results_to_database(database, parameters: list[MatchTemplateParameters], results: list[tuple[int, pd.DataFrame]], image_info):
106
+ # Ensure Volume assets
107
+ template_vol_ids = {}
108
+ for par in parameters:
109
+ template_vol_ids[par.input_reconstruction_filename] = -1
110
+ for template in template_vol_ids.keys():
111
+ template_vol_ids[template] = ensure_template_is_a_volume_asset(database, template, parameters[0].pixel_size)
112
+
113
+ conn = sqlite3.connect(database, isolation_level=None)
114
+ cur = conn.cursor()
115
+ results = sorted(results, key=lambda x: x[0])
116
+ # Add results to TEMPLATE_MATCH_LIST
117
+ template_match_result_list = []
118
+
119
+ max_template_match_id = cur.execute("SELECT MAX(TEMPLATE_MATCH_ID) FROM TEMPLATE_MATCH_LIST").fetchone()[0]
120
+ if max_template_match_id is None:
121
+ max_template_match_id = 0
122
+ template_match_id = max_template_match_id + 1
123
+ #template_match_job_id = cur.execute("SELECT MAX(TEMPLATE_MATCH_JOB_ID) FROM TEMPLATE_MATCH_LIST").fetchone()[0]
124
+ #if template_match_job_id is None:
125
+ # template_match_job_id = 1
126
+ #else:
127
+ # template_match_job_id += 1
128
+
129
+ for result in results:
130
+ template_match_job_id = image_info.iloc[result[0]]["MATCH_TEMPLATE_JOB_ID"]
131
+ # CHeck if THRESHOLD column exists in image_info
132
+ if "THRESHOLD" in image_info.columns:
133
+ threshold = image_info.iloc[result[0]]["THRESHOLD"]
134
+ else:
135
+ threshold = 7.0
136
+ template_match_result_list.append({
137
+ "TEMPLATE_MATCH_ID": template_match_id,
138
+ "JOB_NAME": f"auto_{template_match_job_id}_{Path(parameters[result[0]].input_reconstruction_filename).stem}",
139
+ "DATETIME_OF_RUN": datetime_to_msdos(datetime.datetime.now()),
140
+ "TEMPLATE_MATCH_JOB_ID": template_match_job_id,
141
+ "JOB_TYPE_CODE": 0,
142
+ "INPUT_TEMPLATE_MATCH_ID": 0,
143
+ "IMAGE_ASSET_ID": image_info.iloc[result[0]]["IMAGE_ASSET_ID"],
144
+ "REFERENCE_VOLUME_ASSET_ID": template_vol_ids[parameters[result[0]].input_reconstruction_filename],
145
+ "IS_ACTIVE": 1,
146
+ "USED_SYMMETRY": parameters[result[0]].my_symmetry,
147
+ "USED_PIXEL_SIZE": parameters[result[0]].pixel_size,
148
+ "USED_VOLTAGE": parameters[result[0]].voltage_kV,
149
+ "USED_SPHERICAL_ABERRATION": parameters[result[0]].spherical_aberration_mm,
150
+ "USED_AMPLITUDE_CONTRAST": parameters[result[0]].amplitude_contrast,
151
+ "USED_DEFOCUS1": parameters[result[0]].defocus1,
152
+ "USED_DEFOCUS2": parameters[result[0]].defocus2,
153
+ "USED_DEFOCUS_ANGLE": parameters[result[0]].defocus_angle,
154
+ "USED_PHASE_SHIFT": parameters[result[0]].phase_shift,
155
+ "LOW_RESOLUTION_LIMIT": parameters[result[0]].low_resolution_limit,
156
+ "HIGH_RESOLUTION_LIMIT": parameters[result[0]].high_resolution_limit_search,
157
+ "OUT_OF_PLANE_ANGULAR_STEP": parameters[result[0]].angular_step,
158
+ "IN_PLANE_ANGULAR_STEP": parameters[result[0]].in_plane_angular_step,
159
+ "DEFOCUS_SEARCH_RANGE": parameters[result[0]].defocus_search_range,
160
+ "DEFOCUS_STEP": parameters[result[0]].defocus_step,
161
+ "PIXEL_SIZE_SEARCH_RANGE": parameters[result[0]].pixel_size_search_range,
162
+ "PIXEL_SIZE_STEP": parameters[result[0]].pixel_size_step,
163
+ "REFINEMENT_THRESHOLD": 0.0,
164
+ "USED_THRESHOLD": threshold,
165
+ "REF_BOX_SIZE_IN_ANGSTROMS": 0,
166
+ "MASK_RADIUS": 0.0,
167
+ "MIN_PEAK_RADIUS": parameters[result[0]].min_peak_radius,
168
+ "XY_CHANGE_THRESHOLD": 0.0,
169
+ "EXCLUDE_ABOVE_XY_THRESHOLD": False,
170
+ "MIP_OUTPUT_FILE": "/dev/null",
171
+ "SCALED_MIP_OUTPUT_FILE": parameters[result[0]].scaled_mip_output_file,
172
+ "AVG_OUTPUT_FILE": "/dev/null",
173
+ "STD_OUTPUT_FILE": "/dev/null",
174
+ "PSI_OUTPUT_FILE": "/dev/null",
175
+ "THETA_OUTPUT_FILE": "/dev/null",
176
+ "PHI_OUTPUT_FILE": "/dev/null",
177
+ "DEFOCUS_OUTPUT_FILE": "/dev/null",
178
+ "PIXEL_SIZE_OUTPUT_FILE": "/dev/null",
179
+ "HISTOGRAM_OUTPUT_FILE": parameters[result[0]].output_histogram_file,
180
+ "PROJECTION_RESULT_OUTPUT_FILE": parameters[result[0]].scaled_mip_output_file,
181
+ })
182
+ create_peak_lists(conn, template_match_id)
183
+ result[1].to_sql(f"TEMPLATE_MATCH_PEAK_LIST_{template_match_id}", conn, if_exists="append", index=False)
184
+ conn.commit()
185
+ result[1]['ORIGINAL_PEAK_NUMBER'] = 0
186
+ result[1]['NEW_PEAK_NUMBER'] = 0
187
+ print(result[1])
188
+ result[1].to_sql(f"TEMPLATE_MATCH_PEAK_CHANGE_LIST_{template_match_id}", conn, if_exists="append", index=False)
189
+ template_match_id += 1
190
+ template_match_result_list = pd.DataFrame(template_match_result_list)
191
+ template_match_result_list.to_sql("TEMPLATE_MATCH_LIST", conn, if_exists="append", index=False)
192
+
193
+
194
+ conn.close()
195
+
196
+
197
+ async def handle_results(reader, writer, logger, parameters, write_directly_to_db, image_info):
198
+ logger.debug("Handling results")
199
+ size_of_array= await reader.readexactly(4)
200
+ result_number= await reader.readexactly(4)
201
+ number_of_expected_results= await reader.readexactly(4)
202
+ number_of_floats = int.from_bytes(size_of_array, byteorder="little")
203
+ result_number = int.from_bytes(result_number, byteorder="little")
204
+ number_of_expected_results = int.from_bytes(number_of_expected_results, byteorder="little")
205
+ results = await reader.readexactly(number_of_floats*4)
206
+ x_dim = int(struct.unpack("<f",results[0:4])[0])
207
+ y_dim = int(struct.unpack("<f",results[4:8])[0])
208
+ num_pixels = int(struct.unpack("<f",results[8:12])[0])
209
+ num_histogram_points = int(struct.unpack("<f",results[16:20])[0])
210
+ print(f"Got number of histogram points: {num_histogram_points}, but replacing with hardcoded 512")
211
+ num_histogram_points = 512
212
+ num_ccs = struct.unpack("<f",results[12:16])[0]
213
+ struct.unpack("<f",results[20:24])[0]
214
+ struct.unpack("<f",results[24:28])[0]
215
+ print(f"Result number: {result_number} Number of expected results: {number_of_expected_results} Number of pixels: {num_pixels} X dim: {x_dim} Y dim: {y_dim} Num histogram points: {num_histogram_points} Num ccs: {num_ccs} Num float {number_of_floats}")
216
+ mip = get_np_arrays(results,28,0,x_dim,y_dim,num_pixels)
217
+ psi = get_np_arrays(results,28,1,x_dim,y_dim,num_pixels)
218
+ theta = get_np_arrays(results,28,2,x_dim,y_dim,num_pixels)
219
+ phi = get_np_arrays(results,28,3,x_dim,y_dim,num_pixels)
220
+ defocus = get_np_arrays(results,28,4,x_dim,y_dim,num_pixels)
221
+ get_np_arrays(results,28,5,x_dim,y_dim,num_pixels)
222
+ sum = get_np_arrays(results,28,6,x_dim,y_dim,num_pixels)
223
+
224
+ sum = sum / num_ccs
225
+ sum_squares = get_np_arrays(results,28,7,x_dim,y_dim,num_pixels)
226
+ sum_squares = np.sqrt(sum_squares/num_ccs - sum**2)
227
+ scaled_mip = np.divide(mip - sum, sum_squares, out=np.zeros_like(mip), where=sum_squares!=0)
228
+ par = parameters[result_number]
229
+ histogram = np.frombuffer(results,offset=28+8*num_pixels*4, count=num_histogram_points,dtype=np.int64).copy()
230
+ survival_histogram = np.zeros(num_histogram_points, dtype=np.float32)
231
+ survival_histogram[-1] = histogram[-1]
232
+ for line_counter in range(num_histogram_points - 2, -1, -1):
233
+ survival_histogram[line_counter] = survival_histogram[line_counter + 1] + histogram[line_counter]
234
+
235
+
236
+ # Calculate expected threshold
237
+ from scipy.special import erfcinv,erfc
238
+ expected_threshold = np.sqrt(2.0) * erfcinv(2.0/(x_dim*y_dim*num_ccs)) * 1
239
+
240
+ histogram_min = -12.5
241
+ histogram_max = 22.5
242
+ histogram_step = (histogram_max - histogram_min) / num_histogram_points
243
+ temp_float = histogram_min + (histogram_step / 2.0)
244
+
245
+ expected_survival_histogram = np.zeros(num_histogram_points, dtype=np.float32)
246
+ for line_counter in range(num_histogram_points):
247
+ expected_survival_histogram[line_counter] = (erfc((temp_float + histogram_step * float(line_counter)) / np.sqrt(2.0)) / 2.0) * (x_dim*y_dim*num_ccs)
248
+
249
+ survival_histogram_float = survival_histogram * (expected_survival_histogram.sum() / survival_histogram.sum())
250
+ with open(par.output_histogram_file, 'w') as f:
251
+ f.write(f"# Expected threshold = {expected_threshold:.2f}\n")
252
+ f.write("# histogram, expected histogram, survival histogram, expected survival histogram\n")
253
+ for line_counter in range(num_histogram_points):
254
+ temp_double_array = [
255
+ temp_float + histogram_step * float(line_counter),
256
+ histogram[line_counter],
257
+ survival_histogram[line_counter],
258
+ expected_survival_histogram[line_counter]
259
+ ]
260
+ f.write(" ".join(str(x) for x in temp_double_array) + "\n")
261
+ mrcfile.write(par.scaled_mip_output_file, scaled_mip.astype(np.float32), overwrite=True)
262
+ if par.mip_output_file != "/dev/null":
263
+ mrcfile.write(par.mip_output_file, mip.astype(np.float32), overwrite=True)
264
+ peak_coordinates = peak_local_max(scaled_mip, min_distance=int(par.min_peak_radius), exclude_border=50, threshold_abs=expected_threshold)
265
+ result = pd.DataFrame({
266
+ "X_POSITION": peak_coordinates[:,1] * par.pixel_size,
267
+ "Y_POSITION": peak_coordinates[:,0] * par.pixel_size,
268
+ "PSI": psi[tuple(peak_coordinates.T)],
269
+ "THETA": theta[tuple(peak_coordinates.T)],
270
+ "PHI": phi[tuple(peak_coordinates.T)],
271
+ "DEFOCUS": defocus[tuple(peak_coordinates.T)],
272
+ "PEAK_HEIGHT": scaled_mip[tuple(peak_coordinates.T)]
273
+ })
274
+ if(write_directly_to_db):
275
+ image_info["THRESHOLD"].iat[result_number] = expected_threshold
276
+ write_results_to_database(image_info.iloc[result_number]["DATABASE"], parameters, [(result_number, result)], image_info)
277
+ print("Wrote results to database")
278
+ print(f"{par.input_search_images_filename}: {len(result)} peaks found. Median {result['PEAK_HEIGHT'].median()} Max {result['PEAK_HEIGHT'].max()} Threshold {expected_threshold}")
279
+ return(result)
280
+
281
+ async def handle_job_result_queue(reader, writer, logger):
282
+ #logger.info("Handling results")
283
+ #await reader.read(4)
284
+ length = await reader.readexactly(4)
285
+ number_of_bytes = int.from_bytes(length, byteorder="little")
286
+ results = await reader.readexactly(number_of_bytes)
287
+ number_of_jobs = int.from_bytes(results[0:4], byteorder="little")
288
+ # print(f"Number of bytes: {number_of_bytes} Number of jobs: {number_of_jobs}")
289
+ #for i in range(number_of_jobs):
290
+ # job_number = int.from_bytes(results[4+i*4:8+i*4], byteorder="little")
291
+ # result_size = int.from_bytes(results[8+i*4:12+i*4], byteorder="little")
292
+
293
+ # logger.info(f"Job {job_number} finished with result {result_number}")
294
+ return(results)
295
+
296
+
297
+
298
+ def run(parameters: Union[MatchTemplateParameters,list[MatchTemplateParameters],pd.DataFrame],write_directly_to_db=False,image_info=None,**kwargs):
299
+
300
+ if isinstance(parameters, pd.DataFrame):
301
+ image_info = parameters
302
+ parameters = image_info["PARAMETERS"].tolist()
303
+ if not isinstance(parameters, list):
304
+ parameters = [parameters]
305
+
306
+ signal_handlers = {
307
+ socket_program_defined_result : partial(handle_results, parameters = parameters, write_directly_to_db=write_directly_to_db,image_info=image_info),
308
+ socket_job_result_queue : handle_job_result_queue,
309
+ socket_i_have_info: handle_socket_i_have_info,
310
+ }
311
+ for i, par in enumerate(parameters):
312
+ par.image_number_for_gui = i
313
+ global_euler_search = EulerSearch()
314
+ parameter_map = ParameterMap()
315
+ parameter_map.SetAllTrue( )
316
+ global_euler_search.InitGrid(par.my_symmetry,par.angular_step, 0.0, 0.0, 360.0, par.in_plane_angular_step, 0.0, 1.0 / 2.0, parameter_map, 10)
317
+ if par.my_symmetry.startswith("C") and global_euler_search.test_mirror:
318
+ global_euler_search.theta_max = 180.0
319
+ global_euler_search.CalculateGridSearchPositions(False)
320
+ par.first_search_position = 0
321
+ par.last_search_position = global_euler_search.number_of_search_positions - 1
322
+
323
+ results = asyncio.run(cistem_program.run("match_template_gpu", parameters, signal_handlers=signal_handlers,num_threads=parameters[0].max_threads,**kwargs))
324
+
325
+ return(results)