emdbva 0.0.1.dev145__tar.gz → 0.0.1.dev147__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 (55) hide show
  1. {emdbva-0.0.1.dev145/emdbva.egg-info → emdbva-0.0.1.dev147}/PKG-INFO +1 -1
  2. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147/emdbva.egg-info}/PKG-INFO +1 -1
  3. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/resmap.py +7 -7
  4. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/prepareandrun_codon_airflow.py +78 -84
  5. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/misc.py +63 -37
  6. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/stars.py +373 -93
  7. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/validationanalysis.py +24 -13
  8. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/version.py +1 -1
  9. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/LICENSE +0 -0
  10. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/MANIFEST.in +0 -0
  11. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/README.rst +0 -0
  12. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/emdbva.egg-info/SOURCES.txt +0 -0
  13. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/emdbva.egg-info/dependency_links.txt +0 -0
  14. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/emdbva.egg-info/entry_points.txt +0 -0
  15. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/emdbva.egg-info/requires.txt +0 -0
  16. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/emdbva.egg-info/top_level.txt +0 -0
  17. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/setup.cfg +0 -0
  18. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/setup.py +0 -0
  19. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/PATHS_codon_airflow.py +0 -0
  20. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/__init__.py +0 -0
  21. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/audit/__init__.py +0 -0
  22. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/audit/cif_revision.py +0 -0
  23. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/mainva.py +0 -0
  24. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/__init__.py +0 -0
  25. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/bars.py +0 -0
  26. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/connected_percentage.py +0 -0
  27. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/contour_level_predicator.py +0 -0
  28. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/emda_mmcc.py +0 -0
  29. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/emringer.py +0 -0
  30. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/inclusion.py +0 -0
  31. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/map_data_validation.py +0 -0
  32. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/overlap_percentage.py +0 -0
  33. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/phaserandomization.py +0 -0
  34. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/phenix_cc.py +0 -0
  35. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/phenix_mm.py +0 -0
  36. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/projections.py +0 -0
  37. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/qscore.py +0 -0
  38. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/residue_locres.py +0 -0
  39. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/smoc.py +0 -0
  40. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/strudel.py +0 -0
  41. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/surfaces.py +0 -0
  42. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/metrics/threedfsc.py +0 -0
  43. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/preparation.py +0 -0
  44. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/qscores.csv +0 -0
  45. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/schemas/__init__.py +0 -0
  46. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/schemas/emdb_entry_full.schema.json +0 -0
  47. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/schemas/loader.py +0 -0
  48. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/Checker.py +0 -0
  49. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/ChimeraxViews.py +0 -0
  50. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/MapProcessor.py +0 -0
  51. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/Model.py +0 -0
  52. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/__init__.py +0 -0
  53. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/cl_weights.pth +0 -0
  54. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/log_utils.py +0 -0
  55. {emdbva-0.0.1.dev145 → emdbva-0.0.1.dev147}/va/utils/rescolor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: emdbva
3
- Version: 0.0.1.dev145
3
+ Version: 0.0.1.dev147
4
4
  Summary: CryoEM validation toolkit
5
5
  Home-page: https://test.pypi.org/project/va/
6
6
  Author: Zhe Wang
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: emdbva
3
- Version: 0.0.1.dev145
3
+ Version: 0.0.1.dev147
4
4
  Summary: CryoEM validation toolkit
5
5
  Home-page: https://test.pypi.org/project/va/
6
6
  Author: Zhe Wang
@@ -180,7 +180,7 @@ def resmap_filecheck(mapone, output_path):
180
180
  return check if check else False
181
181
 
182
182
 
183
- def resmap_chimerax(mapone, output_path):
183
+ def relion_local_res_chimerax(orgmap, mapone, output_path):
184
184
  """
185
185
  Generate chimerax cmd for ResMap results
186
186
 
@@ -188,10 +188,10 @@ def resmap_chimerax(mapone, output_path):
188
188
  None. ChimeraX command files may be written to the output directory.
189
189
  """
190
190
 
191
- mapname = os.path.basename(mapone)
192
191
  output_chimerax_file = '{}{}_chimerax.cxc'.format(output_path, os.path.basename(mapone))
193
- orgmap = '{}{}_ori.map'.format(output_path, os.path.splitext(mapname)[0])
194
- resmap = '{}{}_ori_resmap.map'.format(output_path, os.path.splitext(mapname)[0])
192
+ orgmap = orgmap
193
+ # resmap = '{}{}_ori_resmap.map'.format(output_path, os.path.splitext(mapname)[0])
194
+ resmap = mapone
195
195
  header = mrcfile.open(mapone, mode='r', header_only=True)
196
196
  voxsizes = header.voxel_size.tolist()
197
197
  if all(element == voxsizes[0] for element in voxsizes):
@@ -305,12 +305,12 @@ def run_resmap_chimerax(bindisplay, locCHIMERA, cxcfile):
305
305
  if not bindisplay:
306
306
  subprocess.check_call(locCHIMERA + " --offscreen --nogui " + cxcfile, cwd=os.path.dirname(cxcfile),
307
307
  shell=True)
308
- print('Animated PNG for ResMap result has been produced.')
308
+ print('Animated PNG for local resolution result has been produced.')
309
309
  else:
310
310
  subprocess.check_call(locCHIMERA + " " + cxcfile, cwd=os.path.dirname(cxcfile), shell=True)
311
- print('Animated PNG for ResMap result has been produced.')
311
+ print('Animated PNG for local resolution result has been produced.')
312
312
  except subprocess.CalledProcessError as suberr:
313
- err = 'Saving ResMap local resolution animated png error: {}.'.format(suberr)
313
+ err = 'Saving local resolution animated png error: {}.'.format(suberr)
314
314
  errlist.append(err)
315
315
  sys.stderr.write(err + '\n')
316
316
 
@@ -627,7 +627,14 @@ class prepareandrun():
627
627
  logging.info('Validation command completed.')
628
628
  self.data_sync()
629
629
  else:
630
- logging.info('Validation command failed; skipping data sync.')
630
+ logging.error(
631
+ 'Validation command failed with exit code %s; skipping data sync. '
632
+ 'See stdout: %s and stderr: %s',
633
+ return_code,
634
+ stdout_path,
635
+ stderr_path,
636
+ )
637
+ raise subprocess.CalledProcessError(return_code, orgcmd)
631
638
 
632
639
  # predmem = self.memmsg(self.mapsize)
633
640
  # bsub -M predmem .....
@@ -665,7 +672,7 @@ class prepareandrun():
665
672
 
666
673
  def data_sync(self):
667
674
  """
668
- sync data at corresponding folder to staging and production folders
675
+ sync data at corresponding folder to public production folders
669
676
 
670
677
  :return: None
671
678
  """
@@ -673,94 +680,82 @@ class prepareandrun():
673
680
  result_folder = '{}{}/va/'.format(MAP_SERVER_PATH, sub_dir)
674
681
  id = list(self.idsubdirs.keys())[0]
675
682
  prod_folder = '{}/{}/va'.format(VA_PROD_PATH, sub_dir)
683
+ fallback_prod_folder = prod_folder.replace('/nfs/public/', '/nfs/ebi/public/', 1)
676
684
  logging.info(prod_folder)
685
+ logging.info(fallback_prod_folder)
677
686
  alljsonfile = glob.glob('{}emd_{}_all.json'.format(result_folder, id))
678
- if alljsonfile:
687
+ if not alljsonfile:
688
+ logging.info('No all json output, job failed to the end')
689
+ return None
690
+
691
+ rsync_filters = (
692
+ "--include '*/' "
693
+ "--include '*_rawmap.map' "
694
+ "--include '*_mask.mrc' "
695
+ "--exclude '*_locres.mrc' "
696
+ "--exclude '*.map' "
697
+ "--exclude '*.mrc' "
698
+ "--exclude '*.gz' "
699
+ "--exclude 'emd-*.cif' "
700
+ "--include '*' "
701
+ )
702
+
703
+ def sync_command(target_folder):
679
704
  quoted_result = shlex.quote(result_folder)
680
- quoted_prod = shlex.quote(prod_folder)
681
-
682
- rsync_filters = (
683
- "--include '*/' "
684
- "--include '*_rawmap.map' "
685
- "--include '*_mask.mrc' "
686
- "--exclude '*_locres.mrc' "
687
- "--exclude '*.map' "
688
- "--exclude '*.mrc' "
689
- "--exclude '*.gz' "
690
- "--exclude 'emd-*.cif' "
691
- "--include '*' "
705
+ quoted_target = shlex.quote(target_folder.rstrip('/'))
706
+ return (
707
+ 'mkdir -p {target} && '
708
+ 'rsync -avzrhP {filters}{source} {target}/'
709
+ ).format(
710
+ target=quoted_target,
711
+ filters=rsync_filters,
712
+ source=quoted_result,
692
713
  )
693
714
 
694
- sync_cmd = (
695
- f"rsync -avzrhP {rsync_filters}"
696
- f"{quoted_result} {quoted_prod}"
715
+ def sbatch_command(job_name, stdout_name, stderr_name, target_folder):
716
+ wrap_cmd = sync_command(target_folder)
717
+ return (
718
+ 'sbatch --wait -p datamover --mem=10G --time 12:00:00 '
719
+ '-J {job_name} --mail-user=pdb_em@ebi.ac.uk '
720
+ '--mail-type=END --mail-type=FAIL '
721
+ '-o {stdout_path} -e {stderr_path} --wrap="{wrap_cmd}"'
722
+ ).format(
723
+ job_name=shlex.quote(job_name),
724
+ stdout_path=shlex.quote('{}{}'.format(result_folder, stdout_name)),
725
+ stderr_path=shlex.quote('{}{}'.format(result_folder, stderr_name)),
726
+ wrap_cmd=wrap_cmd,
697
727
  )
698
- prod_stdout = f"{result_folder}{id}_prodsync_stdout.txt"
699
- prod_stderr = f"{result_folder}{id}_prodsync_stderr.txt"
700
-
701
- # for codon staging, production and dev
702
- try:
703
- subprocess.check_call(['ssh', 'wp-p1m2-e1',
704
- 'ls ' + prod_folder],
705
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
706
- prod_rc = self.run_shell_command(sync_cmd, prod_stdout, prod_stderr)
707
- if prod_rc == 0:
708
- logging.info('Target folder exists and data sync to production has completed')
709
- else:
710
- logging.info('Target folder exists but data sync to production failed')
711
-
712
- # sync data to HL
713
- fallback_prod_folder = prod_folder
714
- quoted_fallback_prod_folder = shlex.quote(fallback_prod_folder)
715
- quoted_fallback_source = shlex.quote(fallback_prod_folder.rstrip('/') + '/')
716
- quoted_fallback_dest = 'hl2-ws-login:' + shlex.quote(fallback_prod_folder.rstrip('/') + '/')
717
-
718
- fallback_sync_cmd = (
719
- f"ssh hl2-ws-login 'mkdir -p {quoted_fallback_prod_folder}' && "
720
- f"rsync -avzrhP {rsync_filters}"
721
- f"{quoted_fallback_source} {quoted_fallback_dest}"
722
- )
723
- fallback_stdout = f"{result_folder}{id}_fallbacksync_stdout.txt"
724
- fallback_stderr = f"{result_folder}{id}_fallbacksync_stderr.txt"
725
- fallback_rc = self.run_shell_command(fallback_sync_cmd, fallback_stdout, fallback_stderr)
726
- if fallback_rc == 0:
727
- logging.info("Fallback validation_analysis sync has completed")
728
- else:
729
- logging.info("Fallback validation_analysis sync failed")
730
728
 
731
- except subprocess.CalledProcessError as e:
732
- try:
733
- subprocess.check_call(['ssh wp-p1m2-e1 "mkdir -p ' + prod_folder + '"'], stdout=subprocess.PIPE,
734
- stderr=subprocess.PIPE, shell=True)
735
- prod_rc = self.run_shell_command(sync_cmd, prod_stdout, prod_stderr)
736
- if prod_rc == 0:
737
- logging.info('Target folder was created and data sync to production has completed')
738
- else:
739
- logging.info('Target folder was created but data sync to production failed')
740
-
741
- # sync data to HL
742
- fallback_prod_folder = prod_folder
743
- quoted_fallback_prod_folder = shlex.quote(fallback_prod_folder)
744
- quoted_fallback_source = shlex.quote(fallback_prod_folder.rstrip('/') + '/')
745
- quoted_fallback_dest = 'hl2-ws-login:' + shlex.quote(fallback_prod_folder.rstrip('/') + '/')
746
-
747
- fallback_sync_cmd = (
748
- f"ssh hl2-ws-login 'mkdir -p {quoted_fallback_prod_folder}' && "
749
- f"rsync -avzrhP {rsync_filters}"
750
- f"{quoted_fallback_source} {quoted_fallback_dest}"
751
- )
752
- fallback_stdout = f"{result_folder}{id}_fallbacksync_stdout.txt"
753
- fallback_stderr = f"{result_folder}{id}_fallbacksync_stderr.txt"
754
- fallback_rc = self.run_shell_command(fallback_sync_cmd, fallback_stdout, fallback_stderr)
755
- if fallback_rc == 0:
756
- logging.info("Fallback validation_analysis sync has completed")
757
- else:
758
- logging.info("Fallback validation_analysis sync failed")
759
-
760
- except subprocess.CalledProcessError as e:
761
- logging.info('Target folder not properly produced.')
762
- else:
763
- logging.info('No all json output, job failed to the end')
729
+ sync_jobs = [
730
+ (
731
+ 'production',
732
+ sbatch_command(
733
+ 'vasync_{}'.format(id),
734
+ '{}_prodsync_stdout.txt'.format(id),
735
+ '{}_prodsync_stderr.txt'.format(id),
736
+ prod_folder,
737
+ ),
738
+ ),
739
+ (
740
+ 'fallback',
741
+ sbatch_command(
742
+ 'vafallbacksync_{}'.format(id),
743
+ '{}_fallbacksync_stdout.txt'.format(id),
744
+ '{}_fallbacksync_stderr.txt'.format(id),
745
+ fallback_prod_folder,
746
+ ),
747
+ ),
748
+ ]
749
+
750
+ for label, job_cmd in sync_jobs:
751
+ logging.info('Submitting %s sync job...', label)
752
+ logging.info(job_cmd)
753
+ subprocess.check_call(
754
+ ['ssh', 'codon-slurm-login', job_cmd],
755
+ stdout=subprocess.PIPE,
756
+ stderr=subprocess.PIPE,
757
+ )
758
+ logging.info('%s sync job has been submitted', label)
764
759
 
765
760
  return None
766
761
 
@@ -1069,7 +1064,6 @@ def main():
1069
1064
 
1070
1065
  if not myobj.onlycopy and not myobj.onlysync:
1071
1066
  myobj.runcmd()
1072
- myobj.data_sync()
1073
1067
  else:
1074
1068
  logging.info('Only copy files. Please make sure use --update t/Ture/1')
1075
1069
 
@@ -142,51 +142,74 @@ def interpolated_intercept(x, y1, y2):
142
142
 
143
143
 
144
144
  def interpolated_intercepts_general(x, y1, y2):
145
- """Find the intercepts of two curves, given by the same x data"""
145
+ """
146
+ Find intersections between two curves sharing the same x-axis.
146
147
 
147
- def intercept(point1, point2, point3, point4):
148
- """Find the intersection between two lines.
148
+ This version avoids nan/inf intersections and avoids divide-by-zero
149
+ from the general line-intersection method.
150
+ """
149
151
 
150
- The first line is defined by the line between point1 and point2.
151
- The second line is defined by the line between point3 and point4.
152
- Each point is an (x, y) tuple.
152
+ x = np.asarray(x, dtype=float)
153
+ y1 = np.asarray(y1, dtype=float)
154
+ y2 = np.asarray(y2, dtype=float)
153
155
 
154
- Returns: the intercept, in (x, y) format.
155
- """
156
+ min_len = min(len(x), len(y1), len(y2))
157
+ x = x[:min_len]
158
+ y1 = y1[:min_len]
159
+ y2 = y2[:min_len]
156
160
 
157
- def line(p1, p2):
158
- A = (p1[1] - p2[1])
159
- B = (p2[0] - p1[0])
160
- C = (p1[0] * p2[1] - p2[0] * p1[1])
161
- return A, B, -C
161
+ valid = np.isfinite(x) & np.isfinite(y1) & np.isfinite(y2)
162
162
 
163
- def intersection(L1, L2):
164
- D = L1[0] * L2[1] - L1[1] * L2[0]
165
- Dx = L1[2] * L2[1] - L1[1] * L2[2]
166
- Dy = L1[0] * L2[2] - L1[2] * L2[0]
167
- x = Dx / D
168
- y = Dy / D
169
- return x, y
170
-
171
- L1 = line([point1[0], point1[1]], [point2[0], point2[1]])
172
- L2 = line([point3[0], point3[1]], [point4[0], point4[1]])
163
+ x = x[valid]
164
+ y1 = y1[valid]
165
+ y2 = y2[valid]
173
166
 
174
- return intersection(L1, L2)
167
+ if len(x) < 2:
168
+ return [], []
175
169
 
176
- idxs = np.argwhere(np.diff(np.sign(y1 - y2)) != 0)
170
+ order = np.argsort(x)
171
+ x = x[order]
172
+ y1 = y1[order]
173
+ y2 = y2[order]
177
174
 
175
+ diff = y1 - y2
178
176
 
179
177
  xcs = []
180
178
  ycs = []
181
179
 
182
- for idx in idxs:
183
- xc, yc = intercept((x[idx], y1[idx]), (x[idx + 1], y1[idx + 1]), (x[idx], y2[idx]), (x[idx + 1], y2[idx + 1]))
180
+ for i in range(len(x) - 1):
181
+ x0 = x[i]
182
+ x1 = x[i + 1]
183
+ d0 = diff[i]
184
+ d1 = diff[i + 1]
185
+
186
+ if not np.isfinite(d0) or not np.isfinite(d1):
187
+ continue
188
+
189
+ if x1 == x0:
190
+ continue
191
+
192
+ # Exact crossing at the first point.
193
+ if d0 == 0:
194
+ xc = x0
195
+ yc = y1[i]
184
196
 
185
- txc = xc[0].tolist()
186
- tyc = yc[0].tolist()
197
+ # Crossing between two adjacent points.
198
+ elif d0 * d1 < 0:
199
+ denom = d0 - d1
200
+ if denom == 0:
201
+ continue
202
+
203
+ t = d0 / denom
204
+ xc = x0 + t * (x1 - x0)
205
+ yc = y1[i] + t * (y1[i + 1] - y1[i])
206
+
207
+ else:
208
+ continue
187
209
 
188
- xcs.append(txc)
189
- ycs.append(tyc)
210
+ if np.isfinite(xc) and np.isfinite(yc):
211
+ xcs.append(float(xc))
212
+ ycs.append(float(yc))
190
213
 
191
214
  return xcs, ycs
192
215
 
@@ -198,12 +221,15 @@ def remove_duplicate_intersections(xs, ys):
198
221
  :param ys: list of y values
199
222
  """
200
223
 
201
- rxs = [round(val, 5) for val in xs]
202
- rys = [round(val, 5) for val in ys]
203
- # round xs and ys to 4-5 decimal potins and then do the following
204
- pairs = list(zip(rxs, rys))
205
- unique_pairs = set(pairs)
206
- unique_pairs_list = sorted(list(unique_pairs), key=lambda x: x[0])
224
+ pairs = []
225
+
226
+ for x, y in zip(xs, ys):
227
+ if not np.isfinite(x) or not np.isfinite(y):
228
+ continue
229
+
230
+ pairs.append((round(float(x), 5), round(float(y), 5)))
231
+
232
+ unique_pairs_list = sorted(set(pairs), key=lambda pair: pair[0])
207
233
 
208
234
  return unique_pairs_list
209
235
 
@@ -335,31 +335,61 @@ class GetStars:
335
335
 
336
336
  return final_curves
337
337
 
338
-
339
338
  @staticmethod
340
339
  def intersections_into_curve(intersections, levels, corrected_curve, phase_rand_curve):
341
340
  """
342
- After getting the intersections and insert them into these curves
343
-
344
- :param intersections: list of data pairs containing intersections
345
- :param levels: list of x value of two intersect curves
346
- :param corrected_curve: list of value of correction curve correlation values
347
- :param phase_rand_curve: list of value of phase rand curve correlation
341
+ Insert valid intersection x-values into the curve x-axis and interpolate
342
+ corrected and phase-randomized curves onto the new x-axis.
348
343
  """
349
344
 
350
- for a, b in intersections:
345
+ levels = np.asarray(levels, dtype=float)
346
+ corrected_curve = np.asarray(corrected_curve, dtype=float)
347
+ phase_rand_curve = np.asarray(phase_rand_curve, dtype=float)
351
348
 
352
- exists = np.any(levels == a)
353
- if not exists:
354
- index = np.searchsorted(levels, a)
355
- levels = np.insert(levels, index, a)
356
- corrected_curve = np.insert(corrected_curve, index, b)
357
- phase_rand_curve = np.insert(phase_rand_curve, index, b)
358
- else:
359
- print(a, b)
360
- print('Intersection point on curve data')
349
+ valid = (
350
+ np.isfinite(levels)
351
+ & np.isfinite(corrected_curve)
352
+ & np.isfinite(phase_rand_curve)
353
+ )
354
+
355
+ levels = levels[valid]
356
+ corrected_curve = corrected_curve[valid]
357
+ phase_rand_curve = phase_rand_curve[valid]
358
+
359
+ if len(levels) < 2:
360
+ return levels, corrected_curve, phase_rand_curve
361
+
362
+ order = np.argsort(levels)
363
+ levels = levels[order]
364
+ corrected_curve = corrected_curve[order]
365
+ phase_rand_curve = phase_rand_curve[order]
366
+
367
+ # Remove duplicated x values because np.interp needs increasing x.
368
+ unique_levels, unique_idx = np.unique(levels, return_index=True)
369
+ levels = unique_levels
370
+ corrected_curve = corrected_curve[unique_idx]
371
+ phase_rand_curve = phase_rand_curve[unique_idx]
372
+
373
+ intersection_xs = []
374
+
375
+ for x, y in intersections:
376
+ if not np.isfinite(x) or not np.isfinite(y):
377
+ continue
361
378
 
362
- return levels, corrected_curve, phase_rand_curve
379
+ if levels[0] <= x <= levels[-1]:
380
+ intersection_xs.append(float(x))
381
+
382
+ if intersection_xs:
383
+ new_levels = np.unique(
384
+ np.concatenate([levels, np.asarray(intersection_xs, dtype=float)])
385
+ )
386
+ else:
387
+ new_levels = levels
388
+
389
+ new_corrected_curve = np.interp(new_levels, levels, corrected_curve)
390
+ new_phase_rand_curve = np.interp(new_levels, levels, phase_rand_curve)
391
+
392
+ return new_levels, new_corrected_curve, new_phase_rand_curve
363
393
 
364
394
  def plot_fsc(self, data_curves, other_curves=None, other_curves_lable='Provided FSC'):
365
395
  """
@@ -413,48 +443,213 @@ class GetStars:
413
443
 
414
444
  def plot_feature_zone(self, intersections, levels, corrected_curve, phase_rand_curve):
415
445
  """
416
- Plot feature zone and save to image
417
-
418
- :param intersections: list of tuples containing intersections in (x,y) coordinates
419
- :param levels: list of frequency values
420
- :param corrected_curve: list of corrected FSC value from Relion Star file
421
- :param phase_rand_curve: list of phase randomized FSC value from Relion
446
+ Plot feature zone and save to image.
422
447
  """
423
448
 
449
+ levels = np.asarray(levels, dtype=float)
450
+ corrected_curve = np.asarray(corrected_curve, dtype=float)
451
+ phase_rand_curve = np.asarray(phase_rand_curve, dtype=float)
452
+
453
+ valid = (
454
+ np.isfinite(levels)
455
+ & np.isfinite(corrected_curve)
456
+ & np.isfinite(phase_rand_curve)
457
+ )
458
+
459
+ levels = levels[valid]
460
+ corrected_curve = corrected_curve[valid]
461
+ phase_rand_curve = phase_rand_curve[valid]
462
+
463
+ if len(levels) < 2:
464
+ return 0.0, 0.0
465
+
466
+ order = np.argsort(levels)
467
+ levels = levels[order]
468
+ corrected_curve = corrected_curve[order]
469
+ phase_rand_curve = phase_rand_curve[order]
470
+
471
+ valid_intersections = [
472
+ (float(x), float(y))
473
+ for x, y in intersections
474
+ if np.isfinite(x) and np.isfinite(y)
475
+ ]
476
+
477
+ valid_intersections = sorted(set(valid_intersections), key=lambda p: p[0])
478
+
479
+ plt.plot(levels, corrected_curve, color='red', label='FSC corrected')
480
+ plt.plot(levels, phase_rand_curve, color='blue', label='Phase randomized')
481
+
424
482
  frequency = 1 / self.randomise_from()
425
- plt.plot(levels, corrected_curve, color='red', label='t')
426
- plt.plot(levels, phase_rand_curve, color='blue', label='correlation')
427
- feature_area = 0
428
- for i in range(len(intersections)):
429
- if i == len(intersections) - 1:
430
- idx_start = np.where(levels == intersections[i][0])[0][0]
431
- idx_end = len(levels)
432
- else:
433
- idx_start = np.where(levels == intersections[i][0])[0][0]
434
- idx_end = np.where(levels == intersections[i + 1][0])[0][0]
435
- print(
436
- f'plt.fill_between(levels[{idx_start}:{idx_end}], corrected_curve[{idx_start}:{idx_end}], phase_rand_curve[{idx_start}:{idx_end}], where=(corrected_curve[{idx_start}:{idx_end}] >= phase_rand_curve[{idx_start}:{idx_end}]), color="gray", alpha=0.5)')
437
- plt.fill_between(levels[idx_start:idx_end], corrected_curve[idx_start:idx_end], phase_rand_curve[idx_start:idx_end],
438
- where=(corrected_curve[idx_start:idx_end] >= phase_rand_curve[idx_start:idx_end]), color='pink', alpha=0.5)
439
-
440
- status = all(x >= y for x, y in zip(corrected_curve[idx_start:idx_end], phase_rand_curve[idx_start:idx_end]))
441
-
442
- if status:
443
- cur_area = np.trapz(corrected_curve[idx_start:idx_end] - phase_rand_curve[idx_start:idx_end], levels[idx_start:idx_end])
444
- feature_area += cur_area
445
- # validation area
446
- # area_one = np.trapz(corrected_curve[idx_start:idx_end], levels[idx_start:idx_end])
447
- # area_two = np.trapz(phase_rand_curve[idx_start:idx_end], levels[idx_start:idx_end])
448
- # print(area_one - area_two)
449
- # print(cur_area)
450
-
451
- closest_idx = min(range(len(levels)), key=lambda t: abs(levels[t] - frequency))
452
- y_values = [0] * (len(levels) - closest_idx)
453
- overfit_area = np.trapz(phase_rand_curve[closest_idx:], levels[closest_idx:])
454
- plt.fill_between(levels[closest_idx:], phase_rand_curve[closest_idx:],
455
- y_values,
456
- where=(phase_rand_curve[closest_idx:] >= 0),
457
- color='blue', alpha=0.5)
483
+
484
+ # ============================================================
485
+ # FEATURE ZONE (PINK)
486
+ # ============================================================
487
+ # Feature area is independent of the randomization frequency.
488
+ #
489
+ # Count ALL area between the two curves wherever:
490
+ #
491
+ # corrected FSC > phase-randomized FSC
492
+ #
493
+ # over the entire available frequency range.
494
+
495
+ feature_delta = corrected_curve - phase_rand_curve
496
+
497
+ feature_area = np.trapz(
498
+ np.clip(feature_delta, 0, None),
499
+ levels
500
+ )
501
+
502
+ plt.fill_between(
503
+ levels,
504
+ corrected_curve,
505
+ phase_rand_curve,
506
+ where=(corrected_curve >= phase_rand_curve),
507
+ interpolate=True,
508
+ color='pink',
509
+ alpha=0.5,
510
+ )
511
+
512
+ # ============================================================
513
+ # OVERFIT ZONE (BLUE)
514
+ # ============================================================
515
+ # Blue area DOES depend on randomization frequency.
516
+ # It starts exactly at:
517
+ #
518
+ # frequency = 1 / self.randomise_from()
519
+ #
520
+ # and is the positive area between phase-randomized FSC
521
+ # and the x-axis.
522
+
523
+ overfit_area = 0.0
524
+
525
+ if levels[0] <= frequency < levels[-1]:
526
+ # Find the first original data point AFTER the exact
527
+ # randomization frequency.
528
+ start_idx = np.searchsorted(
529
+ levels,
530
+ frequency,
531
+ side='right'
532
+ )
533
+
534
+ # Interpolate the phase-randomized FSC at the exact
535
+ # randomization frequency.
536
+ phase_at_frequency = np.interp(
537
+ frequency,
538
+ levels,
539
+ phase_rand_curve
540
+ )
541
+
542
+ # Explicitly insert the exact randomization frequency
543
+ # as the first point.
544
+ overfit_x = np.concatenate(
545
+ ([frequency], levels[start_idx:])
546
+ )
547
+
548
+ overfit_phase = np.concatenate(
549
+ ([phase_at_frequency], phase_rand_curve[start_idx:])
550
+ )
551
+
552
+ # Count only positive phase-randomized FSC as blue area.
553
+ overfit_area = np.trapz(
554
+ np.clip(overfit_phase, 0, None),
555
+ overfit_x
556
+ )
557
+
558
+ # Draw blue area starting exactly at randomization frequency.
559
+ plt.fill_between(
560
+ overfit_x,
561
+ overfit_phase,
562
+ 0,
563
+ where=(overfit_phase >= 0),
564
+ interpolate=True,
565
+ color='blue',
566
+ alpha=0.5,
567
+ )
568
+
569
+ # feature_area = 0.0
570
+ #
571
+ # if valid_intersections:
572
+ # intersection_xs = [x for x, y in valid_intersections]
573
+ #
574
+ # # Use searchsorted instead of exact equality.
575
+ # boundaries = [np.searchsorted(levels, x) for x in intersection_xs]
576
+ # boundaries = [max(0, min(idx, len(levels))) for idx in boundaries]
577
+ # boundaries = sorted(set(boundaries))
578
+ #
579
+ # for i in range(len(boundaries)):
580
+ # idx_start = boundaries[i]
581
+ #
582
+ # if i == len(boundaries) - 1:
583
+ # idx_end = len(levels)
584
+ # else:
585
+ # idx_end = boundaries[i + 1]
586
+ #
587
+ # if idx_end <= idx_start:
588
+ # continue
589
+ #
590
+ # seg_levels = levels[idx_start:idx_end]
591
+ # seg_corrected = corrected_curve[idx_start:idx_end]
592
+ # seg_phase = phase_rand_curve[idx_start:idx_end]
593
+ #
594
+ # if len(seg_levels) < 2:
595
+ # continue
596
+ #
597
+ # finite_segment = (
598
+ # np.all(np.isfinite(seg_levels))
599
+ # and np.all(np.isfinite(seg_corrected))
600
+ # and np.all(np.isfinite(seg_phase))
601
+ # )
602
+ #
603
+ # if not finite_segment:
604
+ # continue
605
+ #
606
+ # above = seg_corrected >= seg_phase
607
+ #
608
+ # plt.fill_between(
609
+ # seg_levels,
610
+ # seg_corrected,
611
+ # seg_phase,
612
+ # where=above,
613
+ # color='pink',
614
+ # alpha=0.5,
615
+ # )
616
+ #
617
+ # if np.all(above):
618
+ # cur_area = np.trapz(
619
+ # seg_corrected - seg_phase,
620
+ # seg_levels,
621
+ # )
622
+ # feature_area += cur_area
623
+ #
624
+ # frequency = 1 / self.randomise_from()
625
+ #
626
+ # closest_idx = int(np.argmin(np.abs(levels - frequency)))
627
+ #
628
+ # overfit_x = levels[closest_idx:]
629
+ # overfit_y = phase_rand_curve[closest_idx:]
630
+ #
631
+ # valid_overfit = np.isfinite(overfit_x) & np.isfinite(overfit_y)
632
+ #
633
+ # overfit_x = overfit_x[valid_overfit]
634
+ # overfit_y = overfit_y[valid_overfit]
635
+ #
636
+ # if len(overfit_x) >= 2:
637
+ # y_values = np.zeros(len(overfit_x))
638
+ # overfit_area = np.trapz(overfit_y, overfit_x)
639
+ #
640
+ # plt.fill_between(
641
+ # overfit_x,
642
+ # overfit_y,
643
+ # y_values,
644
+ # where=(overfit_y >= 0),
645
+ # color='blue',
646
+ # alpha=0.5,
647
+ # )
648
+ # else:
649
+ # overfit_area = 0.0
650
+
651
+ plt.legend(loc='best', fontsize='x-small')
652
+
458
653
  zone_image = f'{self.va_dir}/feature_zone.png'
459
654
  plt.savefig(zone_image)
460
655
  plt.close()
@@ -564,56 +759,141 @@ class GetStars:
564
759
 
565
760
  def feature_zone(self, data_curves):
566
761
  """
567
- By given two curves get the feature zone based on the intersections
568
- return quantified area of that zone
569
- :param data_curves: dictionary contains all curves
762
+ Given two curves, calculate feature zone and overfit zone.
570
763
  """
571
764
 
572
765
  zones = {}
573
766
  result = {}
574
- all_curves = None
575
- if 'curves' in data_curves.keys():
576
- all_curves = data_curves['curves']
577
767
 
578
- levels = None
579
- corrected_curve = None
580
- phase_rand_curve = None
581
- masked_curve = None
582
- unmasked_curve = None
583
- if all_curves:
584
- if 'level' in all_curves.keys():
585
- levels = np.array(all_curves['level'])
586
- if 'fsc_corrected' in all_curves.keys():
587
- corrected_curve = np.array(all_curves['fsc_corrected'])
588
- if 'phaserandmization' in all_curves.keys():
589
- phase_rand_curve = np.array(all_curves['phaserandmization'])
590
- if 'phaserandomization' in all_curves.keys():
591
- phase_rand_curve = np.array(all_curves['phaserandomization'])
592
- if 'fsc_masked' in all_curves.keys():
593
- masked_curve = np.array(all_curves['fsc_masked'])
594
- if 'fsc' in all_curves.keys():
595
- unmasked_curve = np.array(all_curves['fsc'])
768
+ all_curves = data_curves.get('curves')
769
+ if not all_curves:
770
+ return result
771
+
772
+ levels = all_curves.get('level')
773
+ corrected_curve = all_curves.get('fsc_corrected')
774
+
775
+ phase_rand_curve = all_curves.get('phaserandomization')
776
+ if phase_rand_curve is None:
777
+ phase_rand_curve = all_curves.get('phaserandmization')
778
+
779
+ masked_curve = all_curves.get('fsc_masked')
780
+ unmasked_curve = all_curves.get('fsc')
781
+
782
+ if levels is None or corrected_curve is None or phase_rand_curve is None:
783
+ return result
784
+
785
+ try:
786
+ levels = np.asarray(levels, dtype=float)
787
+ corrected_curve = np.asarray(corrected_curve, dtype=float)
788
+ phase_rand_curve = np.asarray(phase_rand_curve, dtype=float)
789
+
790
+ min_len = min(len(levels), len(corrected_curve), len(phase_rand_curve))
791
+
792
+ levels = levels[:min_len]
793
+ corrected_curve = corrected_curve[:min_len]
794
+ phase_rand_curve = phase_rand_curve[:min_len]
795
+
796
+ valid = (
797
+ np.isfinite(levels)
798
+ & np.isfinite(corrected_curve)
799
+ & np.isfinite(phase_rand_curve)
800
+ )
801
+
802
+ levels = levels[valid]
803
+ corrected_curve = corrected_curve[valid]
804
+ phase_rand_curve = phase_rand_curve[valid]
805
+
806
+ if len(levels) < 2:
807
+ return result
808
+
809
+ order = np.argsort(levels)
810
+ levels = levels[order]
811
+ corrected_curve = corrected_curve[order]
812
+ phase_rand_curve = phase_rand_curve[order]
813
+
814
+ xs, ys = interpolated_intercepts_general(
815
+ levels,
816
+ corrected_curve,
817
+ phase_rand_curve,
818
+ )
596
819
 
597
- if isinstance(levels, np.ndarray) and isinstance(corrected_curve, np.ndarray) and isinstance(phase_rand_curve, np.ndarray):
598
- xs, ys = interpolated_intercepts_general(levels, corrected_curve, phase_rand_curve)
599
820
  intersections = remove_duplicate_intersections(xs, ys)
600
- nlevels, ncorrected_curve, phase_rand_curve = self.intersections_into_curve(intersections, levels, corrected_curve, phase_rand_curve)
601
- feature_area, overfit_area = self.plot_feature_zone(intersections, nlevels, ncorrected_curve, phase_rand_curve)
602
- masking_area = self.area_difference(masked_curve, corrected_curve, levels)
603
- corrected_unmasked_difference = sum(corrected_curve - unmasked_curve)
604
- corrected_masked_difference = sum(masked_curve - corrected_curve)
605
- if corrected_unmasked_difference == 0:
606
- print('Corrected is the same as the unmasked.')
607
- masking_area_ratio = 999
821
+
822
+ nlevels, ncorrected_curve, nphase_rand_curve = self.intersections_into_curve(
823
+ intersections,
824
+ levels,
825
+ corrected_curve,
826
+ phase_rand_curve,
827
+ )
828
+
829
+ feature_area, overfit_area = self.plot_feature_zone(
830
+ intersections,
831
+ nlevels,
832
+ ncorrected_curve,
833
+ nphase_rand_curve,
834
+ )
835
+
836
+ if masked_curve is not None and unmasked_curve is not None:
837
+ masked_curve = np.asarray(masked_curve, dtype=float)[:min_len][valid]
838
+ unmasked_curve = np.asarray(unmasked_curve, dtype=float)[:min_len][valid]
839
+
840
+ masked_curve = masked_curve[order]
841
+ unmasked_curve = unmasked_curve[order]
842
+
843
+ masking_area = self.area_difference(
844
+ masked_curve,
845
+ corrected_curve,
846
+ levels,
847
+ )
848
+
849
+ corrected_unmasked_difference = np.nansum(
850
+ corrected_curve - unmasked_curve
851
+ )
852
+ corrected_masked_difference = np.nansum(
853
+ masked_curve - corrected_curve
854
+ )
855
+
856
+ if corrected_unmasked_difference == 0:
857
+ masking_area_ratio = 999
858
+ else:
859
+ masking_area_ratio = abs(
860
+ corrected_masked_difference / corrected_unmasked_difference
861
+ )
608
862
  else:
609
- masking_area_ratio = abs(corrected_masked_difference / corrected_unmasked_difference)
863
+ masking_area = 0.0
864
+ masking_area_ratio = 0.0
865
+
866
+ feature_area = float(feature_area or 0.0)
867
+ overfit_area = float(overfit_area or 0.0)
868
+ masking_area = float(masking_area or 0.0)
869
+ masking_area_ratio = float(masking_area_ratio or 0.0)
870
+
610
871
  zones['feature_zone'] = keep_three_significant_digits(feature_area)
611
872
  zones['overfit_zone'] = keep_three_significant_digits(overfit_area)
612
873
  zones['masking_area'] = keep_three_significant_digits(masking_area)
613
874
  zones['masking_area_ratio'] = keep_three_significant_digits(masking_area_ratio)
614
- zones['feature_zone_ratio'] = keep_three_significant_digits(zones['feature_zone'] / (zones['feature_zone'] + zones['overfit_zone']))
875
+
876
+ total_area = feature_area + overfit_area
877
+ if total_area == 0:
878
+ zones['feature_zone_ratio'] = 0
879
+ else:
880
+ zones['feature_zone_ratio'] = keep_three_significant_digits(
881
+ feature_area / total_area
882
+ )
883
+
615
884
  result['feature_zones'] = zones
616
885
 
886
+ except Exception as exc:
887
+ print(f'Feature zone calculation failed: {exc}')
888
+ result['feature_zones'] = {
889
+ 'feature_zone': 0,
890
+ 'overfit_zone': 0,
891
+ 'masking_area': 0,
892
+ 'masking_area_ratio': 0,
893
+ 'feature_zone_ratio': 0,
894
+ 'error': str(exc),
895
+ }
896
+
617
897
  return result
618
898
 
619
899
 
@@ -6888,10 +6888,9 @@ class ValidationAnalysis:
6888
6888
  start = timeit.default_timer()
6889
6889
  errlist = []
6890
6890
  result_dict = {}
6891
- # if strudelapp and self.models:
6892
6891
  if self.hmeven and self.hmodd:
6893
- even = self.hmeven.fullname
6894
- odd = self.hmodd.fullname
6892
+ even = self.hmeven._iostream.name
6893
+ odd = self.hmodd._iostream.name
6895
6894
  full_map = '{}{}'.format(self.workdir, self.mapname)
6896
6895
  out_path = '{}_relion'.format(full_map)
6897
6896
  print(out_path)
@@ -6911,16 +6910,26 @@ class ValidationAnalysis:
6911
6910
  check = locres_filecheck(odd, even, out_path)
6912
6911
  if check:
6913
6912
  try:
6914
- resmap_chimerax_file = resmap_chimerax(odd, self.workdir)
6915
- if os.path.isfile(resmap_chimerax_file):
6916
- vtkpack, chimeraapp = self.surface_envcheck()
6917
- bindisplay = os.getenv('DISPLAY')
6918
- run_resmap_chimerax(bindisplay, chimeraapp, resmap_chimerax_file)
6919
- # resmap = '{}_ori_resmap.map'.format(odd[:-4])
6920
- # output_json = '{}{}_resmap.json'.format(self.workdir, self.mapname)
6921
- # save_imagestojson(resmap, output_json)
6913
+ map_basename = os.path.splitext(os.path.basename(odd))[0]
6914
+ orgmap = self.rawmap._iostream.name
6915
+ resmap = '{}/{}_relion/{}_{}_locres.mrc'.format(self.workdir, self.mapname, os.path.basename(odd), os.path.basename(even))
6916
+ missing_view_inputs = resmap if not os.path.isfile(resmap) else None
6917
+
6918
+ print(missing_view_inputs)
6919
+ if missing_view_inputs:
6920
+ err = 'Skipping local resolution views; missing input files: {}.'.format(
6921
+ ', '.join(missing_view_inputs)
6922
+ )
6923
+ errlist.append(err)
6924
+ sys.stderr.write(err + '\n')
6925
+ else:
6926
+ local_res_chimerax_file = relion_local_res_chimerax(orgmap, resmap, self.workdir)
6927
+ if os.path.isfile(local_res_chimerax_file):
6928
+ vtkpack, chimeraapp = self.surface_envcheck()
6929
+ bindisplay = os.getenv('DISPLAY')
6930
+ run_resmap_chimerax(bindisplay, chimeraapp, local_res_chimerax_file)
6922
6931
  except:
6923
- err = 'ResMap views error: {}.'.format(sys.exc_info()[1])
6932
+ err = 'Local resolution views error: {}.'.format(sys.exc_info()[1])
6924
6933
  errlist.append(err)
6925
6934
  sys.stderr.write(err + '\n')
6926
6935
  else:
@@ -6928,6 +6937,8 @@ class ValidationAnalysis:
6928
6937
 
6929
6938
  if errlist:
6930
6939
  result_dict['err'] = errlist
6940
+ output_json = '{}{}_resmap.json'.format(self.workdir, self.mapname)
6941
+ out_json(result_dict, output_json)
6931
6942
  else:
6932
6943
  try:
6933
6944
  resmap = '{}_ori_resmap.map'.format(odd[:-4])
@@ -6960,7 +6971,7 @@ class ValidationAnalysis:
6960
6971
  except (TypeError, ValueError):
6961
6972
  res_val = None
6962
6973
  if res_val is not None:
6963
- local_res = localres_histogram(self.hmodd.fullname, self.mapname, res_val)
6974
+ local_res = localres_histogram(self.hmodd._iostream.name, self.mapname, res_val)
6964
6975
  else:
6965
6976
  local_res = None
6966
6977
 
@@ -18,5 +18,5 @@ under the License.
18
18
 
19
19
  """
20
20
 
21
- __version__ = '0.0.1.dev145'
21
+ __version__ = '0.0.1.dev147'
22
22
  __em_statistics_version__ = '202505.v01'
File without changes
File without changes
File without changes
File without changes
File without changes