Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix bugged TV denoising tests #100

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions meteor/rsmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,10 @@ def from_gemmi(
uncertainty_column=uncertainty_column,
)

def to_3d_numpy_map(self, *, map_sampling: int) -> np.ndarray:
realspace_map = self.to_ccp4_map(map_sampling=map_sampling)
return np.array(realspace_map.grid)

@classmethod
@cellify("cell")
def from_3d_numpy_map(
Expand Down
14 changes: 5 additions & 9 deletions meteor/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ def assert_phases_allclose(array1: np.ndarray, array2: np.ndarray, atol: float =
raise AssertionError(msg)


def diffmap_realspace_rms(map1: Map, map2: Map) -> float:
map1_array = np.array(map1.to_ccp4_map(map_sampling=MAP_SAMPLING).grid)
map2_array = np.array(map2.to_ccp4_map(map_sampling=MAP_SAMPLING).grid)

# standardize
map1_array /= map1_array.std()
map2_array /= map2_array.std()

return float(np.linalg.norm(map2_array - map1_array))
def map_corrcoeff(map1: Map, map2: Map) -> float:
map1_np = map1.to_3d_numpy_map(map_sampling=MAP_SAMPLING).flatten()
map2_np = map2.to_3d_numpy_map(map_sampling=MAP_SAMPLING).flatten()
rho = np.corrcoef(map1_np, map2_np)
return rho[0, 1]


def check_test_file_exists(path: Path) -> None:
Expand Down
3 changes: 1 addition & 2 deletions meteor/tv.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ def tv_denoise_difference_map(
>>> denoised_map, result = tv_denoise_difference_map(coefficients, full_output=True)
>>> print(f"Optimal: {result.optimal_tv_weight}, Negentropy: {result.optimal_negentropy}")
"""
realspace_map = difference_map.to_ccp4_map(map_sampling=MAP_SAMPLING)
realspace_map_array = np.array(realspace_map.grid)
realspace_map_array = difference_map.to_3d_numpy_map(map_sampling=MAP_SAMPLING)

def negentropy_objective(tv_weight: float) -> float:
denoised_map = _tv_denoise_array(map_as_array=realspace_map_array, weight=tv_weight)
Expand Down
3 changes: 1 addition & 2 deletions meteor/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ def map_negentropy(map_to_assess: Map, *, tolerance: float = 0.1) -> float:
ValueError: If the computed negentropy is less than the negative tolerance,
indicating potential issues with the computation.
"""
realspace_map = map_to_assess.to_ccp4_map(map_sampling=MAP_SAMPLING)
realspace_map_array = np.array(realspace_map.grid)
realspace_map_array = map_to_assess.to_3d_numpy_map(map_sampling=MAP_SAMPLING)
return negentropy(realspace_map_array, tolerance=tolerance)


Expand Down
8 changes: 4 additions & 4 deletions test/unit/test_iterative.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
_assert_are_dataseries,
)
from meteor.rsmap import Map
from meteor.testing import diffmap_realspace_rms
from meteor.testing import map_corrcoeff
from meteor.tv import TvDenoiseResult


Expand Down Expand Up @@ -109,11 +109,11 @@ def test_iterative_tv_denoiser(
assert expected_col in metadata.columns

# test correctness by comparing denoised dataset to noise-free
noisy_error = diffmap_realspace_rms(very_noisy_map, noise_free_map)
denoised_error = diffmap_realspace_rms(denoised_map, noise_free_map)
noisy_cc = map_corrcoeff(very_noisy_map, noise_free_map)
denoised_cc = map_corrcoeff(denoised_map, noise_free_map)

# insist on improvement
assert denoised_error < noisy_error
assert denoised_cc > noisy_cc

# insist that the negentropy and phase change decrease (or stay approx same) at every iteration
negentropy_change = metadata["negentropy_after_tv"].diff().to_numpy()
Expand Down
14 changes: 12 additions & 2 deletions test/unit/test_rsmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,19 @@ def test_from_ccp4_map(ccp4_map: gemmi.Ccp4Map) -> None:
assert len(rsmap) > 0


def test_from_numpy(noise_free_map: Map) -> None:
def test_to_3d_numpy(noise_free_map: Map) -> None:
array = noise_free_map.to_3d_numpy_map(map_sampling=3)
assert array.shape == (30, 30, 30)

# consistency with gemmi
ccp4_map = noise_free_map.to_ccp4_map(map_sampling=3)
ccp4_array = np.array(ccp4_map.grid)
np.testing.assert_allclose(array, ccp4_array)


def test_from_3d_numpy(noise_free_map: Map) -> None:
_, resolution = noise_free_map.resolution_limits
array = np.array(noise_free_map.to_ccp4_map(map_sampling=3).grid)
array = noise_free_map.to_3d_numpy_map(map_sampling=3)
new_map = Map.from_3d_numpy_map(
array,
spacegroup=noise_free_map.spacegroup,
Expand Down
18 changes: 9 additions & 9 deletions test/unit/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ def test_phases_allclose() -> None:
mt.assert_phases_allclose(close1, far)


def test_diffmap_realspace_rms(noise_free_map: Map) -> None:
assert mt.diffmap_realspace_rms(noise_free_map, noise_free_map) == 0.0
def test_map_corrcoeff(noise_free_map: Map, np_rng: np.random.Generator) -> None:
assert mt.map_corrcoeff(noise_free_map, noise_free_map) == 1.0

map2 = noise_free_map.copy()
map2 += 1
map3 = noise_free_map.copy()
map3 += 2
noisy_map = noise_free_map.copy()
noisy_map.amplitudes += np_rng.normal(size=len(noise_free_map))
noisier_map = noise_free_map.copy()
noisier_map.amplitudes += 10.0 * np_rng.normal(size=len(noise_free_map))

dist12 = mt.diffmap_realspace_rms(noise_free_map, map2)
dist13 = mt.diffmap_realspace_rms(noise_free_map, map3)
assert dist13 > dist12
noisy_cc = mt.map_corrcoeff(noise_free_map, noisy_map)
noisier_cc = mt.map_corrcoeff(noise_free_map, noisier_map)
assert 1.0 > noisy_cc > noisier_cc > 0.0


def test_single_carbon_structure_smoke() -> None:
Expand Down
16 changes: 8 additions & 8 deletions test/unit/test_tv.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from meteor import tv
from meteor.rsmap import Map
from meteor.testing import diffmap_realspace_rms
from meteor.testing import map_corrcoeff
from meteor.validate import map_negentropy

DEFAULT_WEIGHTS_TO_SCAN = np.logspace(-2, 0, 25)
Expand Down Expand Up @@ -87,13 +87,13 @@ def test_tv_denoise_map(
noise_free_map: Map,
noisy_map: Map,
) -> None:
def rms_to_noise_free(test_map: Map) -> float:
return diffmap_realspace_rms(test_map, noise_free_map)
def cc_to_noise_free(test_map: Map) -> float:
return map_corrcoeff(test_map, noise_free_map)

# Normally, the `tv_denoise_difference_map` function only returns the best result -- since we
# know the ground truth, work around this to test all possible results.

lowest_rms: float = np.inf
best_cc: float = 0.0
best_weight: float = 0.0

for trial_weight in DEFAULT_WEIGHTS_TO_SCAN:
Expand All @@ -104,9 +104,9 @@ def rms_to_noise_free(test_map: Map) -> float:
],
full_output=True,
)
rms = rms_to_noise_free(denoised_map)
if rms < lowest_rms:
lowest_rms = rms
cc = cc_to_noise_free(denoised_map)
if cc > best_cc:
best_cc = cc
best_weight = trial_weight

# now run the denoising algorithm and make sure we get a result that's close
Expand All @@ -117,7 +117,7 @@ def rms_to_noise_free(test_map: Map) -> float:
full_output=True,
)

assert rms_to_noise_free(denoised_map) < rms_to_noise_free(noisy_map), "error didnt drop"
assert cc_to_noise_free(denoised_map) > cc_to_noise_free(noisy_map)
np.testing.assert_allclose(
result.optimal_tv_weight, best_weight, rtol=0.5, err_msg="opt weight"
)
Expand Down