I faced a scenario where the same dataset (merge of two data collections) yielded one high resolution structure (1M particles, 2.4 GSFSC) and one low resolution structure coming from a minority conformation (0.1M particles, 3.8 GSFSC). Because the particles are on the same micrographs, I reasoned that the global CTF must be shared.
So, I determined the global CTF from the high resolution structure, and then copied that over to the low res structure. As per this post, I copied the odd terms. I also copied the shared even terms, accepting that defocus and phase shift would be wrong / off.
Then, I performed local ctf refinement on the low res structure twice in series, and on the second iteration the graph of per-particle shift showed no shift at all.
Am I correct in assuming that the local CTF correction should fix the issue with the defocus and phase shift being wrong from copying the global CTF?
Btw, this is the code I used (each block is a jupyter cell - note manual intervention is required to link the target particles to the job)
Code licence in case you want to reuse: CC BY 4.0 (Licence link)
Attribute: Andrea Murachelli a.murachelli@nki.nl
#
import numpy as np
from cryosparc.tools import CryoSPARC
# Connect to the CryoSPARC instance; change as needed.
cs = CryoSPARC(
license="mylicence",
host="myhost",
base_port=39000,
email="em@example.com",
password="password",
)
assert cs.test_connection()
myproject = "P287"
project = cs.find_project(myproject)
# change these parameters as needed
target_workspace = "W14"
source_global_CTF_job = "J724" #the global CTF job you want to copy from
# create a new external job with the global CTF job as input
# and a slot to connect our target particles
particle_slots = [
"blob",
"alignments3D",
"location",
"pick_stats",
"alignments2D",
"ctf",
]
job = project.create_external_job(target_workspace, title="Global CTF settings")
job.add_input(
type="particle",
name="CTF_corrected_particles",
min=1,
slots=particle_slots,
title="CTF corrected particles",
)
job.add_input(
type="particle",
name="target_particles",
min=1,
slots=particle_slots,
title="Particles to CTF correct",
)
job.connect("CTF_corrected_particles", source_global_CTF_job, "particles")
job.add_output(
type="particle",
name="CTF_updated_particles",
slots=particle_slots,
title="Particles with updated CTF",
)
## connect the target particles to the job, then execute the next cell to submit the job
ctf = job.load_input("CTF_corrected_particles", slots=["ctf"])
particles = job.load_input("target_particles", slots=particle_slots)
# determine the CTF values for each exposure group
# these are the changing values for each exposure group
# defocus value and defocus angle are changed by global CTF, but we ignore that for now
# local CTF will take care of that
field_names = [
"ctf/cs_mm",
"ctf/amp_contrast",
"ctf/phase_shift_rad",
"ctf/shift_A",
"ctf/tilt_A",
"ctf/trefoil_A",
"ctf/tetra_A",
"ctf/anisomag",
]
exposure_groups = list(np.unique(ctf["ctf/exp_group_id"]))
ctf_values = {group: {} for group in exposure_groups}
for group in exposure_groups:
for field in field_names:
ctf_values[group][field] = ctf[field][ctf["ctf/exp_group_id"] == group][0]
# Make sure that all exposure groups in the particles exist in the CTF table
target_exposure_groups = list(np.unique(particles["ctf/exp_group_id"]))
assert set(target_exposure_groups) <= set(exposure_groups)
# copy over the CTF values to the target particles
ptcls_copy = particles.copy()
for row in ptcls_copy[:]:
group = row["ctf/exp_group_id"]
for key in field_names:
row[key] = ctf_values[group][key]
# visual sanity check and show changes.
changes = {group: {} for group in exposure_groups}
for i in range(len(particles)):
current_group = particles["ctf/exp_group_id"][i]
if not changes[current_group]: # default value {} is falsy
for f in field_names:
old = particles[f][i]
new = ptcls_copy[f][i]
changes[current_group][f] = (old, new)
else:
continue
# no point in checking all particles, just one per group is enough
all_done = all(list(changes.values()))
if all_done:
break
for group in exposure_groups:
print(f"Group {group}")
for f in field_names:
print(f"{f}: Original: {changes[group][f][0]}\t New: {changes[group][f][1]}")
print("\n")
# commit changes and close job
particles = ptcls_copy
job.print_input_spec()
job.save_output("CTF_updated_particles", particles)
job.stop()