emdbva 0.0.1.dev147__tar.gz → 0.0.1.dev148__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.
- {emdbva-0.0.1.dev147/emdbva.egg-info → emdbva-0.0.1.dev148}/PKG-INFO +1 -1
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148/emdbva.egg-info}/PKG-INFO +1 -1
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/emdbva.egg-info/SOURCES.txt +1 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/mainva.py +4 -0
- emdbva-0.0.1.dev148/va/metrics/map_map_cc.py +139 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/phaserandomization.py +3 -3
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/preparation.py +2 -2
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/prepareandrun_codon_airflow.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/stars.py +536 -34
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/validationanalysis.py +39 -2
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/version.py +1 -1
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/LICENSE +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/MANIFEST.in +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/README.rst +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/emdbva.egg-info/dependency_links.txt +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/emdbva.egg-info/entry_points.txt +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/emdbva.egg-info/requires.txt +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/emdbva.egg-info/top_level.txt +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/setup.cfg +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/setup.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/PATHS_codon_airflow.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/__init__.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/audit/__init__.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/audit/cif_revision.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/__init__.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/bars.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/connected_percentage.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/contour_level_predicator.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/emda_mmcc.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/emringer.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/inclusion.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/map_data_validation.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/overlap_percentage.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/phenix_cc.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/phenix_mm.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/projections.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/qscore.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/residue_locres.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/resmap.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/smoc.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/strudel.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/surfaces.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/metrics/threedfsc.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/qscores.csv +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/schemas/__init__.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/schemas/emdb_entry_full.schema.json +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/schemas/loader.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/Checker.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/ChimeraxViews.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/MapProcessor.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/Model.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/__init__.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/cl_weights.pth +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/log_utils.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/misc.py +0 -0
- {emdbva-0.0.1.dev147 → emdbva-0.0.1.dev148}/va/utils/rescolor.py +0 -0
|
@@ -119,6 +119,10 @@ def allruns(validationobj, runs):
|
|
|
119
119
|
validationobj.fscs()
|
|
120
120
|
# validationobj.surface_ratios()
|
|
121
121
|
|
|
122
|
+
# PHENIX map-map correlation
|
|
123
|
+
if 'mapmapcc' in runs:
|
|
124
|
+
validationobj.map_map_cc()
|
|
125
|
+
|
|
122
126
|
# mmFSC
|
|
123
127
|
if 'mmfsc' in runs:
|
|
124
128
|
validationobj.phenix_mmfsc()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""PHENIX map-map correlation metric helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
from distutils.spawn import find_executable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_MAP_MAP_CC_PATTERN = re.compile(
|
|
10
|
+
r"Map-map CC is\s+([-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?)"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_map_map_cc_output(stdout_text):
|
|
15
|
+
"""Extract the map-map correlation value from PHENIX output.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
stdout_text (str): Combined standard output and error text from
|
|
19
|
+
``phenix.map_correlations``.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
float or None: The parsed correlation, or ``None`` when the expected
|
|
23
|
+
result line is absent or cannot be converted to a number.
|
|
24
|
+
"""
|
|
25
|
+
if not isinstance(stdout_text, str):
|
|
26
|
+
return None
|
|
27
|
+
match = _MAP_MAP_CC_PATTERN.search(stdout_text)
|
|
28
|
+
if match is None:
|
|
29
|
+
return None
|
|
30
|
+
try:
|
|
31
|
+
return float(match.group(1))
|
|
32
|
+
except (TypeError, ValueError):
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_map_map_cc(map1_path, map2_path, output_path, echo=True):
|
|
37
|
+
"""Run PHENIX map-map correlation for two map files.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
map1_path (str): Path to the first map.
|
|
41
|
+
map2_path (str): Path to the second map.
|
|
42
|
+
output_path (str): Directory for the PHENIX stdout diagnostic file.
|
|
43
|
+
echo (bool): Whether to print captured PHENIX output.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
tuple: ``(errlist, result, stdout_text)``. ``result`` is empty when
|
|
47
|
+
the executable, inputs, subprocess, or parser fails.
|
|
48
|
+
"""
|
|
49
|
+
errlist = []
|
|
50
|
+
result = {}
|
|
51
|
+
stdout_text = ""
|
|
52
|
+
|
|
53
|
+
phenix_path = find_executable("phenix.map_correlations")
|
|
54
|
+
if phenix_path is None:
|
|
55
|
+
return ["Phenix executable phenix.map_correlations is not available."], result, stdout_text
|
|
56
|
+
|
|
57
|
+
if not os.path.isfile(map1_path):
|
|
58
|
+
errlist.append("Map file does not exist: {}".format(map1_path))
|
|
59
|
+
if not os.path.isfile(map2_path):
|
|
60
|
+
errlist.append("Map file does not exist: {}".format(map2_path))
|
|
61
|
+
if errlist:
|
|
62
|
+
return errlist, result, stdout_text
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
os.makedirs(output_path, exist_ok=True)
|
|
66
|
+
except OSError as exc:
|
|
67
|
+
return ["Could not create output directory {}: {}".format(output_path, exc)], result, stdout_text
|
|
68
|
+
|
|
69
|
+
command = [phenix_path, map1_path, map2_path]
|
|
70
|
+
try:
|
|
71
|
+
completed = subprocess.run(
|
|
72
|
+
command,
|
|
73
|
+
cwd=output_path,
|
|
74
|
+
stdout=subprocess.PIPE,
|
|
75
|
+
stderr=subprocess.STDOUT,
|
|
76
|
+
text=True,
|
|
77
|
+
check=False,
|
|
78
|
+
)
|
|
79
|
+
stdout_text = completed.stdout or ""
|
|
80
|
+
except OSError as exc:
|
|
81
|
+
return ["Failed to run phenix.map_correlations: {}".format(exc)], result, stdout_text
|
|
82
|
+
finally:
|
|
83
|
+
log_path = os.path.join(output_path, "map_correlations.stdout")
|
|
84
|
+
try:
|
|
85
|
+
with open(log_path, "w", encoding="utf-8") as log_file:
|
|
86
|
+
log_file.write(stdout_text)
|
|
87
|
+
except OSError as exc:
|
|
88
|
+
errlist.append("Could not write {}: {}".format(log_path, exc))
|
|
89
|
+
|
|
90
|
+
if echo:
|
|
91
|
+
print(stdout_text, end="")
|
|
92
|
+
if completed.returncode != 0:
|
|
93
|
+
errlist.append("phenix.map_correlations exited with code {}".format(completed.returncode))
|
|
94
|
+
if "error" in stdout_text.lower():
|
|
95
|
+
errlist.extend(
|
|
96
|
+
line.strip() for line in stdout_text.splitlines() if "error" in line.lower()
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
correlation = parse_map_map_cc_output(stdout_text)
|
|
100
|
+
if correlation is None:
|
|
101
|
+
errlist.append("Could not parse 'Map-map CC is ...' from PHENIX output.")
|
|
102
|
+
|
|
103
|
+
if not errlist:
|
|
104
|
+
map1_name = os.path.basename(map1_path)
|
|
105
|
+
map2_name = os.path.basename(map2_path)
|
|
106
|
+
result = {"name": [map1_name, map2_name], "value": correlation}
|
|
107
|
+
return errlist, result, stdout_text
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def write_map_map_cc_json(map1_path, map2_path, correlation, output_path):
|
|
111
|
+
"""Write a successful map-map correlation result as a JSON fragment.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
map1_path (str): Path to the first map.
|
|
115
|
+
map2_path (str): Path to the second map.
|
|
116
|
+
correlation (float): Parsed map-map correlation value.
|
|
117
|
+
output_path (str): Directory in which to write the fragment.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
str: Path to the written JSON file.
|
|
121
|
+
"""
|
|
122
|
+
import json
|
|
123
|
+
|
|
124
|
+
os.makedirs(output_path, exist_ok=True)
|
|
125
|
+
map1_name = os.path.basename(map1_path)
|
|
126
|
+
map2_name = os.path.basename(map2_path)
|
|
127
|
+
pair_name = "{}_{}".format(map1_name, map2_name)
|
|
128
|
+
output_file = os.path.join(output_path, pair_name + "_cc.json")
|
|
129
|
+
data = {
|
|
130
|
+
"map_map_cc": {
|
|
131
|
+
pair_name: {
|
|
132
|
+
"names": [map1_name, map2_name],
|
|
133
|
+
"value": correlation,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
with open(output_file, "w", encoding="utf-8") as handle:
|
|
138
|
+
json.dump(data, handle, indent=2)
|
|
139
|
+
return output_file
|
|
@@ -89,7 +89,7 @@ def relion_mask(raw_map, out_dir, mapname=None):
|
|
|
89
89
|
print(f'Relion mask command: {relion_mask_cmd}')
|
|
90
90
|
# Produce my own mask to compare with relion mask and time
|
|
91
91
|
try:
|
|
92
|
-
ownmask_loose = os.path.join(out_dir, f'{mapname}
|
|
92
|
+
ownmask_loose = os.path.join(out_dir, f'{mapname}_mask.mrc')
|
|
93
93
|
relion_auto_mask(original_input_mrc, ownmask_loose, float(max_val_thirty), dilatepx, softpx)
|
|
94
94
|
print(f'Relion auto mask command: {relion_auto_mask.__name__}')
|
|
95
95
|
except Exception as e:
|
|
@@ -97,7 +97,7 @@ def relion_mask(raw_map, out_dir, mapname=None):
|
|
|
97
97
|
ownmask_loose = None
|
|
98
98
|
|
|
99
99
|
if angpix and max_val_thirty and dilatepx and softpx and input_mrc:
|
|
100
|
-
subprocess.run(relion_mask_cmd, shell=True)
|
|
100
|
+
# subprocess.run(relion_mask_cmd, shell=True)
|
|
101
101
|
if not MapProcessor.check_map_starts(mask_loose, original_input_mrc):
|
|
102
102
|
print('Relion mask does not have the same nstarts as the original map.')
|
|
103
103
|
MapProcessor.update_map_starts(original_input_mrc, mask_loose)
|
|
@@ -221,7 +221,7 @@ def relion_auto_mask_fast(data, threshold, extend_pixels, edge_width, verbose=Tr
|
|
|
221
221
|
steps = []
|
|
222
222
|
if verbose:
|
|
223
223
|
steps = tqdm(total=3, desc="Masking Steps", unit="step")
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
# Step 1: Initial binary mask
|
|
226
226
|
mask = (data >= threshold).astype(np.float32)
|
|
227
227
|
if verbose:
|
|
@@ -910,7 +910,7 @@ class PreParation:
|
|
|
910
910
|
'volume': False, 'fsc': False, 'raps': False, 'mapmodel': False, 'inclusion': False,
|
|
911
911
|
'largestvariance': False, 'mask': False, 'symmetry': False, 'rmmcc': False, 'smoc': False,
|
|
912
912
|
'resccc': False, 'emringer': False, 'strudel': False, '3dfsc': False, 'locres': False,
|
|
913
|
-
'phrand': False, 'predictcontour': False}
|
|
913
|
+
'phrand': False, 'predictcontour': False, 'mapmapcc': False}
|
|
914
914
|
for key in resdict.keys():
|
|
915
915
|
if key in runs:
|
|
916
916
|
resdict[key] = True
|
|
@@ -924,7 +924,7 @@ class PreParation:
|
|
|
924
924
|
if self.mapname is not None:
|
|
925
925
|
runlist.extend(['projection', 'central', 'surface', 'volume', 'density', 'raps', 'largestvariance',
|
|
926
926
|
'mask', 'fsc', 'mmfsc', 'rmmcc', 'symmetry', 'qscore', 'strudel', 'emringer', '3dfsc',
|
|
927
|
-
'smoc', 'resccc', 'locres', 'phrand', 'predictcontour',
|
|
927
|
+
'smoc', 'resccc', 'locres', 'phrand', 'predictcontour', 'mapmapcc',
|
|
928
928
|
])
|
|
929
929
|
|
|
930
930
|
if self.masks is None:
|
|
File without changes
|
|
@@ -757,9 +757,139 @@ class GetStars:
|
|
|
757
757
|
|
|
758
758
|
return area
|
|
759
759
|
|
|
760
|
+
@staticmethod
|
|
761
|
+
def curve_touches_or_crosses_zero(curve, atol=1e-12):
|
|
762
|
+
"""
|
|
763
|
+
Check whether a curve touches or crosses y=0.
|
|
764
|
+
|
|
765
|
+
Returns
|
|
766
|
+
-------
|
|
767
|
+
bool
|
|
768
|
+
True if:
|
|
769
|
+
- any sampled value is approximately zero, OR
|
|
770
|
+
- two adjacent values have opposite signs.
|
|
771
|
+
|
|
772
|
+
False if the entire curve stays strictly on one side of zero.
|
|
773
|
+
|
|
774
|
+
Parameters
|
|
775
|
+
----------
|
|
776
|
+
curve : array-like
|
|
777
|
+
Y values of the curve.
|
|
778
|
+
|
|
779
|
+
atol : float
|
|
780
|
+
Absolute tolerance used to consider a value equal to zero.
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
curve = np.asarray(curve, dtype=float)
|
|
784
|
+
|
|
785
|
+
# Ignore NaN / +/-inf values.
|
|
786
|
+
curve = curve[np.isfinite(curve)]
|
|
787
|
+
|
|
788
|
+
if curve.size == 0:
|
|
789
|
+
return False
|
|
790
|
+
|
|
791
|
+
# Does an actual sampled point touch zero?
|
|
792
|
+
if np.any(np.isclose(curve, 0.0, atol=atol, rtol=0.0)):
|
|
793
|
+
return True
|
|
794
|
+
|
|
795
|
+
# Does the curve cross zero between two adjacent samples?
|
|
796
|
+
crosses_zero = np.any(
|
|
797
|
+
curve[:-1] * curve[1:] < 0
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
return bool(crosses_zero)
|
|
801
|
+
|
|
760
802
|
def feature_zone(self, data_curves):
|
|
761
803
|
"""
|
|
762
|
-
|
|
804
|
+
Calculate feature-zone, overfit-zone, and masking diagnostics.
|
|
805
|
+
|
|
806
|
+
Masking notation
|
|
807
|
+
----------------
|
|
808
|
+
M = masked FSC
|
|
809
|
+
C = corrected FSC
|
|
810
|
+
U = unmasked FSC
|
|
811
|
+
|
|
812
|
+
The masking metrics are:
|
|
813
|
+
|
|
814
|
+
N = integral(M - C) dx
|
|
815
|
+
|
|
816
|
+
Signed Masked-Corrected area.
|
|
817
|
+
N > 0 : M is above C in the integrated sense.
|
|
818
|
+
N < 0 : C is above M in the integrated sense.
|
|
819
|
+
|
|
820
|
+
D = integral(M - U) dx
|
|
821
|
+
|
|
822
|
+
Signed Masked-Unmasked area.
|
|
823
|
+
D > 0 : M is above U in the integrated sense.
|
|
824
|
+
D < 0 : U is above M in the integrated sense.
|
|
825
|
+
|
|
826
|
+
R_signed = N / D
|
|
827
|
+
|
|
828
|
+
Signed relative position of C with respect to M and U.
|
|
829
|
+
This is useful for determining the integrated ordering
|
|
830
|
+
of the three curves, but can become unreliable when D
|
|
831
|
+
is close to zero because of curve crossing/cancellation.
|
|
832
|
+
|
|
833
|
+
MC_abs = integral(|M - C|) dx
|
|
834
|
+
|
|
835
|
+
Total absolute separation between Masked and Corrected.
|
|
836
|
+
|
|
837
|
+
MU_abs = integral(|M - U|) dx
|
|
838
|
+
|
|
839
|
+
Total absolute separation between Masked and Unmasked.
|
|
840
|
+
|
|
841
|
+
R_abs = MC_abs / MU_abs
|
|
842
|
+
|
|
843
|
+
Main masking discrepancy metric.
|
|
844
|
+
|
|
845
|
+
This value is stored using the existing key:
|
|
846
|
+
|
|
847
|
+
masking_area_ratio
|
|
848
|
+
|
|
849
|
+
for backwards compatibility.
|
|
850
|
+
|
|
851
|
+
R_abs ~= 0:
|
|
852
|
+
Masked and Corrected track very closely.
|
|
853
|
+
|
|
854
|
+
Increasing R_abs:
|
|
855
|
+
Increasing Masked-Corrected disagreement relative
|
|
856
|
+
to the overall Masked-Unmasked separation.
|
|
857
|
+
|
|
858
|
+
R_abs ~= 1:
|
|
859
|
+
Masked-Corrected disagreement is approximately as
|
|
860
|
+
large as Masked-Unmasked disagreement.
|
|
861
|
+
|
|
862
|
+
R_abs > 1:
|
|
863
|
+
Corrected is, in total absolute area, farther from
|
|
864
|
+
Masked than Unmasked is from Masked.
|
|
865
|
+
|
|
866
|
+
Q_mc = |N| / MC_abs
|
|
867
|
+
|
|
868
|
+
Directionality/cancellation metric for M-C.
|
|
869
|
+
|
|
870
|
+
Q_mc ~= 1:
|
|
871
|
+
M-C has mostly one sign. The sign of N is meaningful.
|
|
872
|
+
|
|
873
|
+
Q_mc ~= 0:
|
|
874
|
+
Strong crossing/cancellation between M and C.
|
|
875
|
+
The sign of N alone should not be interpreted as a
|
|
876
|
+
simple global curve ordering.
|
|
877
|
+
|
|
878
|
+
Q_mu = |D| / MU_abs
|
|
879
|
+
|
|
880
|
+
Directionality/cancellation metric for M-U.
|
|
881
|
+
|
|
882
|
+
Q_mu ~= 1:
|
|
883
|
+
M-U has mostly one sign. The sign of D is meaningful.
|
|
884
|
+
|
|
885
|
+
Q_mu ~= 0:
|
|
886
|
+
Strong crossing/cancellation between M and U.
|
|
887
|
+
R_signed may therefore be unstable or misleading.
|
|
888
|
+
|
|
889
|
+
Notes
|
|
890
|
+
-----
|
|
891
|
+
All masking metrics are calculated over the same complete valid
|
|
892
|
+
frequency range. No high-frequency-only restriction is applied.
|
|
763
893
|
"""
|
|
764
894
|
|
|
765
895
|
zones = {}
|
|
@@ -771,28 +901,42 @@ class GetStars:
|
|
|
771
901
|
|
|
772
902
|
levels = all_curves.get('level')
|
|
773
903
|
corrected_curve = all_curves.get('fsc_corrected')
|
|
774
|
-
|
|
775
904
|
phase_rand_curve = all_curves.get('phaserandomization')
|
|
905
|
+
|
|
776
906
|
if phase_rand_curve is None:
|
|
777
907
|
phase_rand_curve = all_curves.get('phaserandmization')
|
|
778
908
|
|
|
779
909
|
masked_curve = all_curves.get('fsc_masked')
|
|
780
910
|
unmasked_curve = all_curves.get('fsc')
|
|
781
911
|
|
|
782
|
-
if
|
|
912
|
+
if (
|
|
913
|
+
levels is None
|
|
914
|
+
or corrected_curve is None
|
|
915
|
+
or phase_rand_curve is None
|
|
916
|
+
):
|
|
783
917
|
return result
|
|
784
918
|
|
|
785
919
|
try:
|
|
920
|
+
# ----------------------------------------------------------
|
|
921
|
+
# Prepare feature-zone curves
|
|
922
|
+
# ----------------------------------------------------------
|
|
923
|
+
|
|
786
924
|
levels = np.asarray(levels, dtype=float)
|
|
787
925
|
corrected_curve = np.asarray(corrected_curve, dtype=float)
|
|
788
926
|
phase_rand_curve = np.asarray(phase_rand_curve, dtype=float)
|
|
789
927
|
|
|
790
|
-
min_len = min(
|
|
928
|
+
min_len = min(
|
|
929
|
+
len(levels),
|
|
930
|
+
len(corrected_curve),
|
|
931
|
+
len(phase_rand_curve),
|
|
932
|
+
)
|
|
791
933
|
|
|
792
934
|
levels = levels[:min_len]
|
|
793
935
|
corrected_curve = corrected_curve[:min_len]
|
|
794
936
|
phase_rand_curve = phase_rand_curve[:min_len]
|
|
795
937
|
|
|
938
|
+
# Remove points that cannot be used for the feature-zone
|
|
939
|
+
# calculation.
|
|
796
940
|
valid = (
|
|
797
941
|
np.isfinite(levels)
|
|
798
942
|
& np.isfinite(corrected_curve)
|
|
@@ -806,11 +950,38 @@ class GetStars:
|
|
|
806
950
|
if len(levels) < 2:
|
|
807
951
|
return result
|
|
808
952
|
|
|
953
|
+
# Integration and interpolation require an ordered x axis.
|
|
809
954
|
order = np.argsort(levels)
|
|
955
|
+
|
|
810
956
|
levels = levels[order]
|
|
811
957
|
corrected_curve = corrected_curve[order]
|
|
812
958
|
phase_rand_curve = phase_rand_curve[order]
|
|
813
959
|
|
|
960
|
+
# ----------------------------------------------------------
|
|
961
|
+
# Check whether corrected FSC and phase-randomized FSC
|
|
962
|
+
# touch or cross y=0.
|
|
963
|
+
# ----------------------------------------------------------
|
|
964
|
+
|
|
965
|
+
corrected_touches_zero = self.curve_touches_or_crosses_zero(
|
|
966
|
+
corrected_curve
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
phase_touches_zero = self.curve_touches_or_crosses_zero(
|
|
970
|
+
phase_rand_curve
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
feature_zone_corrected_fsc_nottouching_zero = (
|
|
974
|
+
not corrected_touches_zero
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
feature_zone_phase_fsc_nottouching_zero = (
|
|
978
|
+
not phase_touches_zero
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
# ----------------------------------------------------------
|
|
982
|
+
# Feature-zone / overfit-zone calculation
|
|
983
|
+
# ----------------------------------------------------------
|
|
984
|
+
|
|
814
985
|
xs, ys = interpolated_intercepts_general(
|
|
815
986
|
levels,
|
|
816
987
|
corrected_curve,
|
|
@@ -819,7 +990,11 @@ class GetStars:
|
|
|
819
990
|
|
|
820
991
|
intersections = remove_duplicate_intersections(xs, ys)
|
|
821
992
|
|
|
822
|
-
|
|
993
|
+
(
|
|
994
|
+
nlevels,
|
|
995
|
+
ncorrected_curve,
|
|
996
|
+
nphase_rand_curve,
|
|
997
|
+
) = self.intersections_into_curve(
|
|
823
998
|
intersections,
|
|
824
999
|
levels,
|
|
825
1000
|
corrected_curve,
|
|
@@ -833,63 +1008,390 @@ class GetStars:
|
|
|
833
1008
|
nphase_rand_curve,
|
|
834
1009
|
)
|
|
835
1010
|
|
|
1011
|
+
# ----------------------------------------------------------
|
|
1012
|
+
# Default masking values
|
|
1013
|
+
# ----------------------------------------------------------
|
|
1014
|
+
|
|
1015
|
+
masking_area = 0.0
|
|
1016
|
+
|
|
1017
|
+
# Existing indexed field.
|
|
1018
|
+
# This will now contain R_abs.
|
|
1019
|
+
masking_area_ratio = np.nan
|
|
1020
|
+
|
|
1021
|
+
masked_corrected_signed_area = np.nan # N
|
|
1022
|
+
masked_unmasked_signed_area = np.nan # D
|
|
1023
|
+
|
|
1024
|
+
masked_corrected_absolute_area = np.nan # MC_abs
|
|
1025
|
+
masked_unmasked_absolute_area = np.nan # MU_abs
|
|
1026
|
+
|
|
1027
|
+
masking_signed_ratio = np.nan # R_signed
|
|
1028
|
+
|
|
1029
|
+
masked_corrected_directionality = np.nan # Q_mc
|
|
1030
|
+
masked_unmasked_directionality = np.nan # Q_mu
|
|
1031
|
+
|
|
1032
|
+
# ----------------------------------------------------------
|
|
1033
|
+
# Masking calculations
|
|
1034
|
+
# ----------------------------------------------------------
|
|
1035
|
+
|
|
836
1036
|
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
1037
|
|
|
1038
|
+
# Apply exactly the same initial trimming and validity mask
|
|
1039
|
+
# that was used for levels/corrected/phase-randomized curves.
|
|
1040
|
+
masked_curve = np.asarray(
|
|
1041
|
+
masked_curve,
|
|
1042
|
+
dtype=float,
|
|
1043
|
+
)[:min_len][valid]
|
|
1044
|
+
|
|
1045
|
+
unmasked_curve = np.asarray(
|
|
1046
|
+
unmasked_curve,
|
|
1047
|
+
dtype=float,
|
|
1048
|
+
)[:min_len][valid]
|
|
1049
|
+
|
|
1050
|
+
# Apply the same sorting order.
|
|
840
1051
|
masked_curve = masked_curve[order]
|
|
841
1052
|
unmasked_curve = unmasked_curve[order]
|
|
842
1053
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1054
|
+
# ------------------------------------------------------
|
|
1055
|
+
# There may still be non-finite values in M or U even
|
|
1056
|
+
# though level/C/phase-randomized were valid.
|
|
1057
|
+
#
|
|
1058
|
+
# Use one common mask so M, C, U and x always remain
|
|
1059
|
+
# aligned.
|
|
1060
|
+
# ------------------------------------------------------
|
|
1061
|
+
|
|
1062
|
+
mask_valid = (
|
|
1063
|
+
np.isfinite(levels)
|
|
1064
|
+
& np.isfinite(masked_curve)
|
|
1065
|
+
& np.isfinite(corrected_curve)
|
|
1066
|
+
& np.isfinite(unmasked_curve)
|
|
847
1067
|
)
|
|
848
1068
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
)
|
|
1069
|
+
mask_levels = levels[mask_valid]
|
|
1070
|
+
mask_masked = masked_curve[mask_valid]
|
|
1071
|
+
mask_corrected = corrected_curve[mask_valid]
|
|
1072
|
+
mask_unmasked = unmasked_curve[mask_valid]
|
|
1073
|
+
|
|
1074
|
+
if len(mask_levels) >= 2:
|
|
1075
|
+
|
|
1076
|
+
# ==================================================
|
|
1077
|
+
# Signed areas
|
|
1078
|
+
# ==================================================
|
|
1079
|
+
|
|
1080
|
+
# N = integral(M - C) dx
|
|
1081
|
+
#
|
|
1082
|
+
# Positive:
|
|
1083
|
+
# Masked has more integrated area than Corrected.
|
|
1084
|
+
#
|
|
1085
|
+
# Negative:
|
|
1086
|
+
# Corrected has more integrated area than Masked.
|
|
1087
|
+
masked_corrected_signed_area = np.trapz(
|
|
1088
|
+
mask_masked - mask_corrected,
|
|
1089
|
+
mask_levels,
|
|
1090
|
+
)
|
|
855
1091
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1092
|
+
# D = integral(M - U) dx
|
|
1093
|
+
#
|
|
1094
|
+
# Positive:
|
|
1095
|
+
# Masked has more integrated area than Unmasked.
|
|
1096
|
+
#
|
|
1097
|
+
# Negative:
|
|
1098
|
+
# Unmasked has more integrated area than Masked.
|
|
1099
|
+
masked_unmasked_signed_area = np.trapz(
|
|
1100
|
+
mask_masked - mask_unmasked,
|
|
1101
|
+
mask_levels,
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
# ==================================================
|
|
1105
|
+
# Absolute areas
|
|
1106
|
+
# ==================================================
|
|
1107
|
+
|
|
1108
|
+
# Total geometrical separation between M and C.
|
|
1109
|
+
masked_corrected_absolute_area = np.trapz(
|
|
1110
|
+
np.abs(mask_masked - mask_corrected),
|
|
1111
|
+
mask_levels,
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
# Total geometrical separation between M and U.
|
|
1115
|
+
masked_unmasked_absolute_area = np.trapz(
|
|
1116
|
+
np.abs(mask_masked - mask_unmasked),
|
|
1117
|
+
mask_levels,
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
# Keep the historical "masking_area" field, but make
|
|
1121
|
+
# its definition consistent with the new masking
|
|
1122
|
+
# metric:
|
|
1123
|
+
#
|
|
1124
|
+
# masking_area = integral(|M-C|) dx
|
|
1125
|
+
#
|
|
1126
|
+
masking_area = masked_corrected_absolute_area
|
|
1127
|
+
|
|
1128
|
+
# ==================================================
|
|
1129
|
+
# R_signed = N / D
|
|
1130
|
+
# ==================================================
|
|
1131
|
+
|
|
1132
|
+
if not np.isclose(
|
|
1133
|
+
masked_unmasked_signed_area,
|
|
1134
|
+
0.0,
|
|
1135
|
+
rtol=1e-8,
|
|
1136
|
+
atol=1e-12,
|
|
1137
|
+
):
|
|
1138
|
+
masking_signed_ratio = (
|
|
1139
|
+
masked_corrected_signed_area
|
|
1140
|
+
/ masked_unmasked_signed_area
|
|
1141
|
+
)
|
|
1142
|
+
else:
|
|
1143
|
+
# D ~= 0 makes N/D undefined or numerically
|
|
1144
|
+
# unstable.
|
|
1145
|
+
masking_signed_ratio = np.nan
|
|
1146
|
+
|
|
1147
|
+
# ==================================================
|
|
1148
|
+
# R_abs = MC_abs / MU_abs
|
|
1149
|
+
#
|
|
1150
|
+
# THIS replaces the old masking_area_ratio.
|
|
1151
|
+
# ==================================================
|
|
1152
|
+
|
|
1153
|
+
if not np.isclose(
|
|
1154
|
+
masked_unmasked_absolute_area,
|
|
1155
|
+
0.0,
|
|
1156
|
+
rtol=1e-8,
|
|
1157
|
+
atol=1e-12,
|
|
1158
|
+
):
|
|
1159
|
+
masking_area_ratio = (
|
|
1160
|
+
masked_corrected_absolute_area
|
|
1161
|
+
/ masked_unmasked_absolute_area
|
|
1162
|
+
)
|
|
1163
|
+
else:
|
|
1164
|
+
# If M and U have essentially no absolute
|
|
1165
|
+
# separation, there is no meaningful denominator
|
|
1166
|
+
# against which to normalize M-C.
|
|
1167
|
+
masking_area_ratio = np.nan
|
|
1168
|
+
|
|
1169
|
+
# ==================================================
|
|
1170
|
+
# Q_mc
|
|
1171
|
+
#
|
|
1172
|
+
# |integral(M-C)| / integral(|M-C|)
|
|
1173
|
+
# ==================================================
|
|
1174
|
+
|
|
1175
|
+
if not np.isclose(
|
|
1176
|
+
masked_corrected_absolute_area,
|
|
1177
|
+
0.0,
|
|
1178
|
+
rtol=1e-8,
|
|
1179
|
+
atol=1e-12,
|
|
1180
|
+
):
|
|
1181
|
+
masked_corrected_directionality = (
|
|
1182
|
+
abs(masked_corrected_signed_area)
|
|
1183
|
+
/ masked_corrected_absolute_area
|
|
1184
|
+
)
|
|
1185
|
+
else:
|
|
1186
|
+
# M and C essentially coincide.
|
|
1187
|
+
#
|
|
1188
|
+
# There is no cancellation to diagnose. Setting
|
|
1189
|
+
# Q_mc to 1 is convenient because their difference
|
|
1190
|
+
# is perfectly consistent (zero everywhere within
|
|
1191
|
+
# numerical tolerance).
|
|
1192
|
+
masked_corrected_directionality = 1.0
|
|
1193
|
+
|
|
1194
|
+
# ==================================================
|
|
1195
|
+
# Q_mu
|
|
1196
|
+
#
|
|
1197
|
+
# |integral(M-U)| / integral(|M-U|)
|
|
1198
|
+
# ==================================================
|
|
1199
|
+
|
|
1200
|
+
if not np.isclose(
|
|
1201
|
+
masked_unmasked_absolute_area,
|
|
1202
|
+
0.0,
|
|
1203
|
+
rtol=1e-8,
|
|
1204
|
+
atol=1e-12,
|
|
1205
|
+
):
|
|
1206
|
+
masked_unmasked_directionality = (
|
|
1207
|
+
abs(masked_unmasked_signed_area)
|
|
1208
|
+
/ masked_unmasked_absolute_area
|
|
1209
|
+
)
|
|
1210
|
+
else:
|
|
1211
|
+
# Same convention as Q_mc.
|
|
1212
|
+
masked_unmasked_directionality = 1.0
|
|
1213
|
+
|
|
1214
|
+
# Numerical integration may occasionally produce
|
|
1215
|
+
# 1.0000000000000002 etc.
|
|
1216
|
+
masked_corrected_directionality = min(
|
|
1217
|
+
1.0,
|
|
1218
|
+
max(
|
|
1219
|
+
0.0,
|
|
1220
|
+
float(masked_corrected_directionality),
|
|
1221
|
+
),
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
masked_unmasked_directionality = min(
|
|
1225
|
+
1.0,
|
|
1226
|
+
max(
|
|
1227
|
+
0.0,
|
|
1228
|
+
float(masked_unmasked_directionality),
|
|
1229
|
+
),
|
|
861
1230
|
)
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1231
|
+
|
|
1232
|
+
# ----------------------------------------------------------
|
|
1233
|
+
# Convert main feature-zone results
|
|
1234
|
+
# ----------------------------------------------------------
|
|
865
1235
|
|
|
866
1236
|
feature_area = float(feature_area or 0.0)
|
|
867
1237
|
overfit_area = float(overfit_area or 0.0)
|
|
868
1238
|
masking_area = float(masking_area or 0.0)
|
|
869
|
-
masking_area_ratio = float(masking_area_ratio or 0.0)
|
|
870
1239
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1240
|
+
# ----------------------------------------------------------
|
|
1241
|
+
# Existing output fields
|
|
1242
|
+
# ----------------------------------------------------------
|
|
1243
|
+
|
|
1244
|
+
zones['feature_zone'] = keep_three_significant_digits(
|
|
1245
|
+
feature_area
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
zones['overfit_zone'] = keep_three_significant_digits(
|
|
1249
|
+
overfit_area
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
zones['masking_area'] = keep_three_significant_digits(
|
|
1253
|
+
masking_area
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
zones['feature_zone_corrected_fsc_nottouching_zero'] = (
|
|
1257
|
+
feature_zone_corrected_fsc_nottouching_zero
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
zones['feature_zone_phase_fsc_nottouching_zero'] = (
|
|
1261
|
+
feature_zone_phase_fsc_nottouching_zero
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1264
|
+
# IMPORTANT:
|
|
1265
|
+
#
|
|
1266
|
+
# Keep this key unchanged because it is already indexed by
|
|
1267
|
+
# downstream code.
|
|
1268
|
+
#
|
|
1269
|
+
# It now represents:
|
|
1270
|
+
#
|
|
1271
|
+
# integral(|M-C|) / integral(|M-U|)
|
|
1272
|
+
#
|
|
1273
|
+
if np.isfinite(masking_area_ratio):
|
|
1274
|
+
zones['masking_area_ratio'] = keep_three_significant_digits(
|
|
1275
|
+
float(masking_area_ratio)
|
|
1276
|
+
)
|
|
1277
|
+
else:
|
|
1278
|
+
zones['masking_area_ratio'] = None
|
|
1279
|
+
|
|
1280
|
+
# ----------------------------------------------------------
|
|
1281
|
+
# New masking diagnostic fields
|
|
1282
|
+
# ----------------------------------------------------------
|
|
1283
|
+
|
|
1284
|
+
# N
|
|
1285
|
+
if np.isfinite(masked_corrected_signed_area):
|
|
1286
|
+
zones['masked_corrected_signed_area'] = (
|
|
1287
|
+
keep_three_significant_digits(
|
|
1288
|
+
float(masked_corrected_signed_area)
|
|
1289
|
+
)
|
|
1290
|
+
)
|
|
1291
|
+
else:
|
|
1292
|
+
zones['masked_corrected_signed_area'] = None
|
|
1293
|
+
|
|
1294
|
+
# D
|
|
1295
|
+
if np.isfinite(masked_unmasked_signed_area):
|
|
1296
|
+
zones['masked_unmasked_signed_area'] = (
|
|
1297
|
+
keep_three_significant_digits(
|
|
1298
|
+
float(masked_unmasked_signed_area)
|
|
1299
|
+
)
|
|
1300
|
+
)
|
|
1301
|
+
else:
|
|
1302
|
+
zones['masked_unmasked_signed_area'] = None
|
|
1303
|
+
|
|
1304
|
+
# Absolute M-C area
|
|
1305
|
+
if np.isfinite(masked_corrected_absolute_area):
|
|
1306
|
+
zones['masked_corrected_absolute_area'] = (
|
|
1307
|
+
keep_three_significant_digits(
|
|
1308
|
+
float(masked_corrected_absolute_area)
|
|
1309
|
+
)
|
|
1310
|
+
)
|
|
1311
|
+
else:
|
|
1312
|
+
zones['masked_corrected_absolute_area'] = None
|
|
1313
|
+
|
|
1314
|
+
# Absolute M-U area
|
|
1315
|
+
if np.isfinite(masked_unmasked_absolute_area):
|
|
1316
|
+
zones['masked_unmasked_absolute_area'] = (
|
|
1317
|
+
keep_three_significant_digits(
|
|
1318
|
+
float(masked_unmasked_absolute_area)
|
|
1319
|
+
)
|
|
1320
|
+
)
|
|
1321
|
+
else:
|
|
1322
|
+
zones['masked_unmasked_absolute_area'] = None
|
|
1323
|
+
|
|
1324
|
+
# R_signed
|
|
1325
|
+
if np.isfinite(masking_signed_ratio):
|
|
1326
|
+
zones['masking_signed_ratio'] = (
|
|
1327
|
+
keep_three_significant_digits(
|
|
1328
|
+
float(masking_signed_ratio)
|
|
1329
|
+
)
|
|
1330
|
+
)
|
|
1331
|
+
else:
|
|
1332
|
+
zones['masking_signed_ratio'] = None
|
|
1333
|
+
|
|
1334
|
+
# Q_mc
|
|
1335
|
+
if np.isfinite(masked_corrected_directionality):
|
|
1336
|
+
zones['masked_corrected_directionality'] = (
|
|
1337
|
+
keep_three_significant_digits(
|
|
1338
|
+
float(masked_corrected_directionality)
|
|
1339
|
+
)
|
|
1340
|
+
)
|
|
1341
|
+
else:
|
|
1342
|
+
zones['masked_corrected_directionality'] = None
|
|
1343
|
+
|
|
1344
|
+
# Q_mu
|
|
1345
|
+
if np.isfinite(masked_unmasked_directionality):
|
|
1346
|
+
zones['masked_unmasked_directionality'] = (
|
|
1347
|
+
keep_three_significant_digits(
|
|
1348
|
+
float(masked_unmasked_directionality)
|
|
1349
|
+
)
|
|
1350
|
+
)
|
|
1351
|
+
else:
|
|
1352
|
+
zones['masked_unmasked_directionality'] = None
|
|
1353
|
+
|
|
1354
|
+
# ----------------------------------------------------------
|
|
1355
|
+
# Feature-zone ratio
|
|
1356
|
+
# ----------------------------------------------------------
|
|
875
1357
|
|
|
876
1358
|
total_area = feature_area + overfit_area
|
|
877
|
-
|
|
1359
|
+
|
|
1360
|
+
if np.isclose(total_area, 0.0):
|
|
878
1361
|
zones['feature_zone_ratio'] = 0
|
|
879
1362
|
else:
|
|
880
|
-
zones['feature_zone_ratio'] =
|
|
881
|
-
|
|
1363
|
+
zones['feature_zone_ratio'] = (
|
|
1364
|
+
keep_three_significant_digits(
|
|
1365
|
+
feature_area / total_area
|
|
1366
|
+
)
|
|
882
1367
|
)
|
|
883
1368
|
|
|
884
1369
|
result['feature_zones'] = zones
|
|
885
1370
|
|
|
886
1371
|
except Exception as exc:
|
|
887
1372
|
print(f'Feature zone calculation failed: {exc}')
|
|
1373
|
+
|
|
888
1374
|
result['feature_zones'] = {
|
|
889
1375
|
'feature_zone': 0,
|
|
890
1376
|
'overfit_zone': 0,
|
|
1377
|
+
|
|
1378
|
+
'feature_zone_corrected_fsc_nottouching_zero': None,
|
|
1379
|
+
'feature_zone_phase_fsc_nottouching_zero': None,
|
|
1380
|
+
|
|
1381
|
+
# Existing indexed fields
|
|
891
1382
|
'masking_area': 0,
|
|
892
|
-
'masking_area_ratio':
|
|
1383
|
+
'masking_area_ratio': None,
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
# New masking diagnostics
|
|
1387
|
+
'masked_corrected_signed_area': None,
|
|
1388
|
+
'masked_unmasked_signed_area': None,
|
|
1389
|
+
'masked_corrected_absolute_area': None,
|
|
1390
|
+
'masked_unmasked_absolute_area': None,
|
|
1391
|
+
'masking_signed_ratio': None,
|
|
1392
|
+
'masked_corrected_directionality': None,
|
|
1393
|
+
'masked_unmasked_directionality': None,
|
|
1394
|
+
|
|
893
1395
|
'feature_zone_ratio': 0,
|
|
894
1396
|
'error': str(exc),
|
|
895
1397
|
}
|
|
@@ -77,6 +77,7 @@ from va.metrics.qscore import *
|
|
|
77
77
|
from va.metrics.inclusion import *
|
|
78
78
|
from va.metrics.connected_percentage import *
|
|
79
79
|
from va.metrics.overlap_percentage import *
|
|
80
|
+
from va.metrics.map_map_cc import run_map_map_cc, write_map_map_cc_json
|
|
80
81
|
from metrics.map_data_validation import run_map_data_validation
|
|
81
82
|
import va
|
|
82
83
|
|
|
@@ -227,8 +228,8 @@ class ValidationAnalysis:
|
|
|
227
228
|
self.get_resolution()
|
|
228
229
|
self.relion_mask = None
|
|
229
230
|
self.masked_rawmap = None
|
|
230
|
-
if self.rawmap:
|
|
231
|
-
|
|
231
|
+
# if self.rawmap:
|
|
232
|
+
# self.relion_mask = self.get_relion_mask()
|
|
232
233
|
# make a symbolic link for relion_mask in the va folder
|
|
233
234
|
if self.relion_mask:
|
|
234
235
|
mask_name = os.path.basename(self.relion_mask)
|
|
@@ -4053,6 +4054,42 @@ class ValidationAnalysis:
|
|
|
4053
4054
|
print('Mising half map(s).')
|
|
4054
4055
|
|
|
4055
4056
|
|
|
4057
|
+
def map_map_cc(self):
|
|
4058
|
+
"""Calculate PHENIX map-map CC for the primary and half maps.
|
|
4059
|
+
|
|
4060
|
+
The metric is opt-in through the ``mapmapcc`` run token. It writes a
|
|
4061
|
+
JSON fragment for each successful primary/half-map or half-map pair
|
|
4062
|
+
and skips cleanly when the required maps are unavailable.
|
|
4063
|
+
"""
|
|
4064
|
+
if self.map is None or self.hmeven is None or self.hmodd is None:
|
|
4065
|
+
print('Map-map CC skipped: primary map and both half maps are required.')
|
|
4066
|
+
return None
|
|
4067
|
+
|
|
4068
|
+
primary_path = getattr(self.map, 'fullname', None)
|
|
4069
|
+
even_path = getattr(self.hmeven, 'fullname', None)
|
|
4070
|
+
odd_path = getattr(self.hmodd, 'fullname', None)
|
|
4071
|
+
if not primary_path or not even_path or not odd_path:
|
|
4072
|
+
print('Map-map CC skipped: primary or half-map filenames are unavailable.')
|
|
4073
|
+
return None
|
|
4074
|
+
if not all(os.path.isfile(path) for path in (primary_path, even_path, odd_path)):
|
|
4075
|
+
print('Map-map CC skipped: one or more required map files do not exist.')
|
|
4076
|
+
return None
|
|
4077
|
+
|
|
4078
|
+
map_pairs = (
|
|
4079
|
+
(primary_path, odd_path),
|
|
4080
|
+
(primary_path, even_path),
|
|
4081
|
+
(even_path, odd_path),
|
|
4082
|
+
)
|
|
4083
|
+
for map1_path, map2_path in map_pairs:
|
|
4084
|
+
errlist, result, _stdout = run_map_map_cc(map1_path, map2_path, self.workdir)
|
|
4085
|
+
if errlist:
|
|
4086
|
+
for error in errlist:
|
|
4087
|
+
sys.stderr.write('Map-map CC error: {}\n'.format(error))
|
|
4088
|
+
continue
|
|
4089
|
+
write_map_map_cc_json(map1_path, map2_path, result['value'], self.workdir)
|
|
4090
|
+
return None
|
|
4091
|
+
|
|
4092
|
+
|
|
4056
4093
|
def readfsc(self, asym=1.0):
|
|
4057
4094
|
"""
|
|
4058
4095
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|