diff --git a/ms2deepscore/MS2DeepScore.py b/ms2deepscore/MS2DeepScore.py index 8f7bdf7a..745c02ad 100644 --- a/ms2deepscore/MS2DeepScore.py +++ b/ms2deepscore/MS2DeepScore.py @@ -1,13 +1,24 @@ from typing import List + import numpy as np from matchms import Spectrum -from matchms.similarity.BaseSimilarity import BaseSimilarity + +from ms2deepscore.matchms_compat import ( + MATCHMS_V1_API, + as_matchms_scores, + normalize_score_fields, + assert_legacy_symmetric_inputs, +) +if MATCHMS_V1_API: + from matchms.similarity.base_similarity import BaseSimilarity +else: + from matchms.similarity.BaseSimilarity import BaseSimilarity from ms2deepscore.models.SiameseSpectralModel import SiameseSpectralModel from .vector_operations import cosine_similarity, cosine_similarity_matrix class MS2DeepScore(BaseSimilarity): - """Calculate MS2DeepScore similarity scores between a reference and a query. + """Calculate MS2DeepScore similarity scores between spectra. Using a trained model, binned spectrums will be converted into spectrum vectors using a deep neural network. The MS2DeepScore similarity is then @@ -19,13 +30,13 @@ class MS2DeepScore(BaseSimilarity): .. code-block:: python from matchms import calculate_scores() - from matchms.importing import load_from_json + from matchms.importing import load_ms2_dataset from ms2deepscore import MS2DeepScore from ms2deepscore.models import load_model # Import data - references = load_from_json("abc.json") - queries = load_from_json("xyz.json") + references = load_ms2_dataset("abc.mgf") + queries = load_ms2_dataset("xyz.mgf") # Load pretrained model model = load_model("model_file_123.pt") @@ -34,9 +45,12 @@ class MS2DeepScore(BaseSimilarity): # Calculate scores and get matchms.Scores object scores = calculate_scores(references, queries, similarity_measure) - + This implementation supports both the matchms <=0.33 matrix API (NumPy + return values) and the matchms >=1.0 API (``matchms.Scores`` return values). """ + score_fields = ("score",) + def __init__(self, model: SiameseSpectralModel, progress_bar: bool = True): """ @@ -54,17 +68,24 @@ def __init__(self, model: SiameseSpectralModel, progress_bar: bool = True): self.output_vector_dim = self.model.model_settings.embedding_dim self.progress_bar = progress_bar - def get_embedding_array(self, spectrums, datatype: str = "numpy", batch_size: int = 1024) -> np.ndarray: - """Calculate the spectrum embeddings for a list of spectrums.""" + def get_embedding_array( + self, + spectra, + datatype: str = "numpy", + batch_size: int = 1024, + progress_bar: bool | None = None, + ) -> np.ndarray: + """Calculate embeddings for a collection of spectra.""" + show_progress = self.progress_bar if progress_bar is None else progress_bar return self.model.compute_embedding_array( - spectrums, + spectra, datatype=datatype, - progress_bar=self.progress_bar, - batch_size=batch_size - ) + progress_bar=show_progress, + batch_size=batch_size, + ) def pair(self, reference: Spectrum, query: Spectrum) -> float: - """Calculate the MS2DeepScore similaritiy between a reference and a query spectrum. + """Calculate a single MS2DeepScore similarity. Parameters ---------- @@ -78,13 +99,17 @@ def pair(self, reference: Spectrum, query: Spectrum) -> float: ms2ds_similarity MS2DeepScore similarity score. """ - embedding_reference = self.get_embedding_array([reference]) - embedding_query = self.get_embedding_array([query]) - return cosine_similarity(embedding_reference[0, :], embedding_query[0, :]) - - def matrix(self, references: List[Spectrum], queries: List[Spectrum], - array_type: str = "numpy", - is_symmetric: bool = False) -> np.ndarray: + embeddings = self.get_embedding_array([reference, query]) + return cosine_similarity(embeddings[0, :], embeddings[1, :]) + + def _matrix_numpy( + self, + references: List[Spectrum], + queries: List[Spectrum], + *, + is_symmetric: bool, + progress_bar: bool, + ) -> np.ndarray: """Calculate the MS2DeepScore similarities between all references and queries. Parameters @@ -106,13 +131,56 @@ def matrix(self, references: List[Spectrum], queries: List[Spectrum], ms2ds_similarity Array of MS2DeepScore similarity scores. """ - embeddings_reference = self.get_embedding_array(references) + embeddings_reference = self.get_embedding_array( + references, progress_bar=progress_bar + ) if is_symmetric: - assert np.all(references == queries), \ - "Expected references to be equal to queries for is_symmetric=True" embeddings_query = embeddings_reference else: - embeddings_query = self.get_embedding_array(queries) - - ms2ds_similarity = cosine_similarity_matrix(embeddings_reference, embeddings_query) - return ms2ds_similarity + embeddings_query = self.get_embedding_array( + queries, progress_bar=progress_bar + ) + return cosine_similarity_matrix(embeddings_reference, embeddings_query) + + if MATCHMS_V1_API: + + def matrix( + self, + spectra_1: List[Spectrum], + spectra_2: List[Spectrum] | None = None, + score_fields=None, + progress_bar: bool = True, + ): + """Return a matchms >=1.0 ``Scores`` object.""" + normalize_score_fields(score_fields, self.score_fields) + is_symmetric = spectra_2 is None or spectra_2 is spectra_1 + queries = spectra_1 if spectra_2 is None else spectra_2 + score_matrix = self._matrix_numpy( + spectra_1, + queries, + is_symmetric=is_symmetric, + progress_bar=progress_bar, + ) + return as_matchms_scores({"score": score_matrix}) + + else: + + def matrix( + self, + references: List[Spectrum], + queries: List[Spectrum], + array_type: str = "numpy", + is_symmetric: bool = False, + progress_bar: bool = True, + ) -> np.ndarray: + """Return a NumPy matrix for the matchms <=0.33 API.""" + if array_type != "numpy": + raise NotImplementedError("MS2DeepScore currently supports only array_type='numpy'.") + if is_symmetric: + assert_legacy_symmetric_inputs(references, queries) + return self._matrix_numpy( + references, + queries, + is_symmetric=is_symmetric, + progress_bar=progress_bar, + ) diff --git a/ms2deepscore/MS2DeepScoreEvaluated.py b/ms2deepscore/MS2DeepScoreEvaluated.py index de9f5f8b..514704b0 100644 --- a/ms2deepscore/MS2DeepScoreEvaluated.py +++ b/ms2deepscore/MS2DeepScoreEvaluated.py @@ -1,17 +1,26 @@ from typing import List + import numpy as np from matchms import Spectrum -from matchms.similarity.BaseSimilarity import BaseSimilarity -from ms2deepscore.models.LinearEmbeddingEvaluation import \ - compute_error_predictions + +from ms2deepscore.matchms_compat import ( + MATCHMS_V1_API, + as_matchms_scores, + normalize_score_fields, + assert_legacy_symmetric_inputs, +) +if MATCHMS_V1_API: + from matchms.similarity.base_similarity import BaseSimilarity +else: + from matchms.similarity.BaseSimilarity import BaseSimilarity +from ms2deepscore.models.LinearEmbeddingEvaluation import compute_error_predictions from ms2deepscore.models.SiameseSpectralModel import SiameseSpectralModel -from ms2deepscore.vector_operations import (cosine_similarity, - cosine_similarity_matrix) +from ms2deepscore.vector_operations import cosine_similarity, cosine_similarity_matrix class MS2DeepScoreEvaluated(BaseSimilarity): - """Calculate MS2DeepScore similarity scores between a reference and a query. - + """MS2DeepScore plus a predicted absolute-error field. + Using a trained model, binned spectrums will be converted into spectrum vectors using a deep neural network. The MS2DeepScore similarity is then the cosine similarity score between two spectrum vectors. @@ -19,6 +28,8 @@ class MS2DeepScoreEvaluated(BaseSimilarity): Example code to calcualte MS2DeepScore similarities between query and reference spectrums: + # TODO: update code example to matchms 1.0 + .. code-block:: python from matchms import calculate_scores() @@ -40,53 +51,52 @@ class MS2DeepScoreEvaluated(BaseSimilarity): scores = calculate_scores(references, queries, similarity_measure) """ - # Set output data type, e.g. ("score", "float") or [("score", "float"), ("matches", "int")] - score_datatype = [("score", np.float32), ("predicted_absolute_error", np.float32)] - def __init__(self, model: SiameseSpectralModel, - embedding_evaluator, - score_evaluator, - progress_bar: bool = True): - """ - - Parameters - ---------- - model: - Expected input is a SiameseModel that has been trained on - the desired set of spectra. - embedding_evaluator: - Model trained on predicting the score quality (in form of MSE) based on an embedding. - progress_bar: - Set to True to monitor the embedding creating with a progress bar. - Default is False. - """ + score_datatype = np.dtype( + [("score", np.float32), ("predicted_absolute_error", np.float32)] + ) + score_fields = ("score", "predicted_absolute_error") + + def __init__( + self, + model: SiameseSpectralModel, + embedding_evaluator, + score_evaluator, + progress_bar: bool = True, + ): self.model = model self.model.eval() self.embedding_evaluator = embedding_evaluator - self.embedding_evaluator .eval() + self.embedding_evaluator.eval() self.score_evaluator = score_evaluator self.output_vector_dim = self.model.model_settings.embedding_dim self.progress_bar = progress_bar - def get_embedding_array(self, spectrums, datatype="numpy", batch_size=1024): + def get_embedding_array( + self, + spectra, + datatype="numpy", + batch_size=1024, + progress_bar: bool | None = None, + ): + show_progress = self.progress_bar if progress_bar is None else progress_bar return self.model.compute_embedding_array( - spectrums, + spectra, datatype=datatype, batch_size=batch_size, - progress_bar=self.progress_bar, + progress_bar=show_progress, ) def get_embedding_evaluations(self, embeddings): - """Compute the RMSE. - """ + """Compute predicted embedding RMSE values.""" predicted_mse = self.embedding_evaluator(embeddings) predicted_mse[predicted_mse < 0] = 0 - return predicted_mse ** 0.5 + return predicted_mse**0.5 def get_score_evaluations(self, predicted_mse1, predicted_mse2): return compute_error_predictions(predicted_mse1, predicted_mse2, self.score_evaluator) - def pair(self, reference: Spectrum, query: Spectrum) -> float: + def pair(self, reference: Spectrum, query: Spectrum): """Calculate the MS2DeepScore similaritiy between a reference and a query spectrum. Parameters @@ -101,42 +111,37 @@ def pair(self, reference: Spectrum, query: Spectrum) -> float: ms2ds_similarity MS2DeepScore similarity score. """ - embedding_reference = self.get_embedding_array([reference], datatype="pytorch") - embedding_query = self.get_embedding_array([query], datatype="pytorch") - - embedding_ref_mse = ( - self.get_embedding_evaluations( - embedding_reference.reshape(-1, 1, self.output_vector_dim) - ) - .detach() - .numpy() + embeddings = self.get_embedding_array( + [reference, query], datatype="pytorch" ) - embedding_query_mse = ( + evaluations = ( self.get_embedding_evaluations( - embedding_query.reshape(-1, 1, self.output_vector_dim) + embeddings.reshape(-1, 1, self.output_vector_dim) ) .detach() + .cpu() .numpy() ) - score = cosine_similarity( - embedding_reference[0, :].detach().numpy(), - embedding_query[0, :].detach().numpy(), + embeddings[0, :].detach().cpu().numpy(), + embeddings[1, :].detach().cpu().numpy(), ) score_predicted_ae = self.get_score_evaluations( - embedding_ref_mse, - embedding_query_mse, + evaluations[0:1], evaluations[1:2] )[0, 0] - return np.asarray( - (float(score), float(score_predicted_ae)), - dtype=self.score_datatype, + (float(score), float(score_predicted_ae)), dtype=self.score_datatype ) - - def matrix(self, references: List[Spectrum], queries: List[Spectrum], - array_type: str = "numpy", - is_symmetric: bool = False) -> np.ndarray: + def _matrix_components( + self, + references: List[Spectrum], + queries: List[Spectrum], + *, + is_symmetric: bool, + progress_bar: bool, + requested_fields: tuple[str, ...], + ) -> dict[str, np.ndarray]: """Calculate the MS2DeepScore similarities between all references and queries. Parameters @@ -145,34 +150,102 @@ def matrix(self, references: List[Spectrum], queries: List[Spectrum], Reference spectrum. queries: Query spectrum. - array_type - Specify the output array type. Can be "numpy" or "sparse". - Currently, only "numpy" is supported and will return a numpy array. - Future versions will include "sparse" as option to return a COO-sparse array. - is_symmetric: - Set to True if references == queries to speed up calculation about 2x. - Uses the fact that in this case score[i, j] = score[j, i]. Default is False. - - Returns - ------- - ms2ds_similarity - Array of MS2DeepScore similarity scores. + progress_bar: + Set to True to monitor the embedding creating with a progress bar. """ - embeddings_reference = self.get_embedding_array(references, datatype="pytorch") + embeddings_reference = self.get_embedding_array( + references, datatype="pytorch", progress_bar=progress_bar + ) if is_symmetric: - assert np.all(references == queries), \ - "Expected references to be equal to queries for is_symmetric=True" embeddings_query = embeddings_reference else: - embeddings_query = self.get_embedding_array(queries, datatype="pytorch") - - embeddings_ref_mse = self.get_embedding_evaluations(embeddings_reference.reshape(-1, 1, self.output_vector_dim)).detach().numpy() - embeddings_query_mse = self.get_embedding_evaluations(embeddings_query.reshape(-1, 1, self.output_vector_dim)).detach().numpy() - - ms2ds_similarity = cosine_similarity_matrix(embeddings_reference.detach().numpy(), embeddings_query.detach().numpy()) - ms2ds_uncertainty = self.get_score_evaluations(embeddings_ref_mse, embeddings_query_mse) - similarities=np.empty((ms2ds_similarity.shape[0], - ms2ds_similarity.shape[1]), dtype=self.score_datatype) - similarities["score"] = ms2ds_similarity - similarities["predicted_absolute_error"] = ms2ds_uncertainty - return similarities + embeddings_query = self.get_embedding_array( + queries, datatype="pytorch", progress_bar=progress_bar + ) + + result: dict[str, np.ndarray] = {} + + if "score" in requested_fields: + result["score"] = cosine_similarity_matrix( + embeddings_reference.detach().cpu().numpy(), + embeddings_query.detach().cpu().numpy(), + ).astype(np.float32, copy=False) + + if "predicted_absolute_error" in requested_fields: + embeddings_ref_mse = ( + self.get_embedding_evaluations( + embeddings_reference.reshape(-1, 1, self.output_vector_dim) + ) + .detach() + .cpu() + .numpy() + ) + if is_symmetric: + embeddings_query_mse = embeddings_ref_mse + else: + embeddings_query_mse = ( + self.get_embedding_evaluations( + embeddings_query.reshape(-1, 1, self.output_vector_dim) + ) + .detach() + .cpu() + .numpy() + ) + result["predicted_absolute_error"] = self.get_score_evaluations( + embeddings_ref_mse, embeddings_query_mse + ).astype(np.float32, copy=False) + + return result + + if MATCHMS_V1_API: + + def matrix( + self, + spectra_1: List[Spectrum], + spectra_2: List[Spectrum] | None = None, + score_fields=None, + progress_bar: bool = True, + ): + requested_fields = normalize_score_fields(score_fields, self.score_fields) + is_symmetric = spectra_2 is None or spectra_2 is spectra_1 + queries = spectra_1 if spectra_2 is None else spectra_2 + score_arrays = self._matrix_components( + spectra_1, + queries, + is_symmetric=is_symmetric, + progress_bar=progress_bar, + requested_fields=requested_fields, + ) + return as_matchms_scores(score_arrays) + + else: + + def matrix( + self, + references: List[Spectrum], + queries: List[Spectrum], + array_type: str = "numpy", + is_symmetric: bool = False, + progress_bar: bool = True, + ) -> np.ndarray: + if array_type != "numpy": + raise NotImplementedError( + "MS2DeepScoreEvaluated currently supports only array_type='numpy'." + ) + if is_symmetric: + assert_legacy_symmetric_inputs(references, queries) + components = self._matrix_components( + references, + queries, + is_symmetric=is_symmetric, + progress_bar=progress_bar, + requested_fields=self.score_fields, + ) + similarities = np.empty( + components["score"].shape, dtype=self.score_datatype + ) + similarities["score"] = components["score"] + similarities["predicted_absolute_error"] = components[ + "predicted_absolute_error" + ] + return similarities diff --git a/ms2deepscore/MS2DeepScoreONNX.py b/ms2deepscore/MS2DeepScoreONNX.py index fd4a89b8..f9e46f04 100644 --- a/ms2deepscore/MS2DeepScoreONNX.py +++ b/ms2deepscore/MS2DeepScoreONNX.py @@ -1,7 +1,18 @@ from typing import List + import numpy as np from matchms import Spectrum -from matchms.similarity.BaseSimilarity import BaseSimilarity + +from ms2deepscore.matchms_compat import ( + MATCHMS_V1_API, + as_matchms_scores, + normalize_score_fields, + assert_legacy_symmetric_inputs, +) +if MATCHMS_V1_API: + from matchms.similarity.base_similarity import BaseSimilarity +else: + from matchms.similarity.BaseSimilarity import BaseSimilarity from ms2deepscore.models import SiameseSpectralModelONNX from .vector_operations import cosine_similarity, cosine_similarity_matrix @@ -37,6 +48,8 @@ class MS2DeepScoreONNX(BaseSimilarity): """ + score_fields = ("score",) + def __init__(self, model: SiameseSpectralModelONNX, progress_bar: bool = True): """ @@ -51,8 +64,9 @@ def __init__(self, model: SiameseSpectralModelONNX, progress_bar: bool = True): self.output_vector_dim = self.model.model_settings.embedding_dim self.progress_bar = progress_bar - def get_embedding_array(self, spectra) -> np.ndarray: - return self.model.compute_embedding_array(spectra, progress_bar=self.progress_bar) + def get_embedding_array(self, spectra, progress_bar: bool | None = None) -> np.ndarray: + show_progress = self.progress_bar if progress_bar is None else progress_bar + return self.model.compute_embedding_array(spectra, progress_bar=show_progress) def pair(self, reference: Spectrum, query: Spectrum) -> float: """Calculate the MS2DeepScore similaritiy between a reference and a query spectrum. @@ -69,18 +83,16 @@ def pair(self, reference: Spectrum, query: Spectrum) -> float: ms2ds_similarity MS2DeepScore similarity score. """ - embedding_reference = self.get_embedding_array([reference]) - embedding_query = self.get_embedding_array([query]) + embeddings = self.get_embedding_array([reference, query]) + return cosine_similarity(embeddings[0, :], embeddings[1, :]) - return cosine_similarity(embedding_reference[0, :], embedding_query[0, :]) - - def matrix( + def _matrix_numpy( self, references: List[Spectrum], queries: List[Spectrum], - array_type: str = "numpy", - is_symmetric: bool = False, - progress_bar: bool = True, + *, + is_symmetric: bool, + progress_bar: bool, ) -> np.ndarray: """Calculate the MS2DeepScore similarities between all references and queries. @@ -99,19 +111,55 @@ def matrix( Uses the fact that in this case score[i, j] = score[j, i]. Default is False. progress_bar: When True a progress bar is shown. Default is True. - - Returns - ------- - ms2ds_similarity - Array of MS2DeepScore similarity scores. """ - embeddings_reference = self.get_embedding_array(references) + embeddings_reference = self.get_embedding_array( + references, progress_bar=progress_bar + ) if is_symmetric: - assert np.all(references == queries), "Expected references to be equal to queries for is_symmetric=True" embeddings_query = embeddings_reference else: - embeddings_query = self.get_embedding_array(queries) - - ms2ds_similarity = cosine_similarity_matrix(embeddings_reference, embeddings_query) - - return ms2ds_similarity + embeddings_query = self.get_embedding_array( + queries, progress_bar=progress_bar + ) + return cosine_similarity_matrix(embeddings_reference, embeddings_query) + + if MATCHMS_V1_API: + + def matrix( + self, + spectra_1: List[Spectrum], + spectra_2: List[Spectrum] | None = None, + score_fields=None, + progress_bar: bool = True, + ): + normalize_score_fields(score_fields, self.score_fields) + is_symmetric = spectra_2 is None or spectra_2 is spectra_1 + queries = spectra_1 if spectra_2 is None else spectra_2 + score_matrix = self._matrix_numpy( + spectra_1, + queries, + is_symmetric=is_symmetric, + progress_bar=progress_bar, + ) + return as_matchms_scores({"score": score_matrix}) + + else: + + def matrix( + self, + references: List[Spectrum], + queries: List[Spectrum], + array_type: str = "numpy", + is_symmetric: bool = False, + progress_bar: bool = True, + ) -> np.ndarray: + if array_type != "numpy": + raise NotImplementedError("MS2DeepScoreONNX currently supports only array_type='numpy'.") + if is_symmetric: + assert_legacy_symmetric_inputs(references, queries) + return self._matrix_numpy( + references, + queries, + is_symmetric=is_symmetric, + progress_bar=progress_bar, + ) diff --git a/ms2deepscore/MetadataFeatureGenerator.py b/ms2deepscore/MetadataFeatureGenerator.py index d7a275d7..bc7f760b 100644 --- a/ms2deepscore/MetadataFeatureGenerator.py +++ b/ms2deepscore/MetadataFeatureGenerator.py @@ -3,7 +3,7 @@ from typing import List, Optional, Tuple, Union from torch import zeros, tensor from matchms import Metadata -from matchms.Spectrum import Spectrum +from matchms import Spectrum from tqdm import tqdm diff --git a/ms2deepscore/matchms_compat.py b/ms2deepscore/matchms_compat.py new file mode 100644 index 00000000..c1698bbd --- /dev/null +++ b/ms2deepscore/matchms_compat.py @@ -0,0 +1,54 @@ +"""Compatibility helpers for matchms pre-1.0 and >=1.0 APIs. +""" + +from __future__ import annotations +from typing import Iterable, Tuple +import matchms.__version__ as matchms_version + +MATCHMS_V1_API = matchms_version.startswith("1.") +if MATCHMS_V1_API: + from matchms import Scores +else: + Scores = None + + +def normalize_score_fields( + score_fields: Iterable[str] | str | None, + available_fields: Tuple[str, ...], +) -> Tuple[str, ...]: + """Validate/normalize requested score fields for the matchms >=1.0 API.""" + if score_fields is None: + return available_fields + if isinstance(score_fields, str): + fields = (score_fields,) + else: + fields = tuple(score_fields) + + unknown = tuple(field for field in fields if field not in available_fields) + if unknown: + raise ValueError( + f"Unknown score field(s): {unknown}. Available fields are {available_fields}." + ) + if len(fields) == 0: + raise ValueError("score_fields must contain at least one field.") + return fields + + +def as_matchms_scores(score_arrays: dict[str, object]): + """Wrap score matrices in the matchms >=1.0 Scores container.""" + if not MATCHMS_V1_API or Scores is None: + raise RuntimeError("matchms.Scores wrapping is only available with the matchms >=1.0 API.") + return Scores(score_arrays) + + +def assert_legacy_symmetric_inputs(references, queries) -> None: + """Preserve the <=0.33 ``is_symmetric=True`` validation behavior.""" + if references is queries: + return + try: + equal = len(references) == len(queries) and all( + reference == query for reference, query in zip(references, queries) + ) + except Exception: + equal = False + assert equal, "Expected references to be equal to queries for is_symmetric=True" diff --git a/ms2deepscore/models/EmbeddingEvaluatorModel.py b/ms2deepscore/models/EmbeddingEvaluatorModel.py index 4121de7b..ceb99332 100644 --- a/ms2deepscore/models/EmbeddingEvaluatorModel.py +++ b/ms2deepscore/models/EmbeddingEvaluatorModel.py @@ -6,7 +6,7 @@ from torch import device as torch_device from torch import no_grad import torch.nn.functional as F -from matchms.Spectrum import Spectrum +from matchms import Spectrum from torch import nn, optim from ms2deepscore.__version__ import __version__ from ms2deepscore.models.helper_functions import initialize_device diff --git a/ms2deepscore/train_new_model/data_augmentation.py b/ms2deepscore/train_new_model/data_augmentation.py index 0f0ef5cf..7ee8f5ab 100644 --- a/ms2deepscore/train_new_model/data_augmentation.py +++ b/ms2deepscore/train_new_model/data_augmentation.py @@ -1,5 +1,6 @@ import numpy as np -from torch import where, rand +import torch +from torch import where from ms2deepscore import SettingsMS2Deepscore @@ -13,24 +14,28 @@ def data_augmentation(spectra_tensors, model_settings: SettingsMS2Deepscore, ran def data_augmentation_spectrum(spectrum_tensor, model_settings: SettingsMS2Deepscore, random_number_generator): - """Data augmentation. - + """Apply reproducible peak-removal, intensity, and noise augmentation. + Parameters ---------- - spectrum_tensor - Spectrum in Pytorch tensor form. + spectrum_tensor : torch.Tensor + A 1D tensor representing the spectrum to be augmented. + model_settings : SettingsMS2Deepscore + Settings object containing augmentation parameters. + random_number_generator : np.random.Generator + A random number generator for reproducibility. """ - # Augmentation 1: peak removal (peaks < augment_removal_max) peak_removal_for_data_augmentation( spectrum_tensor, model_settings.augment_removal_max, model_settings.augment_removal_intensity, random_number_generator, ) - - # Augmentation 2: Change peak intensities - change_peak_intensity_for_data_augmentation(spectrum_tensor, model_settings.augment_intensity) - + change_peak_intensity_for_data_augmentation( + spectrum_tensor, + model_settings.augment_intensity, + random_number_generator, + ) peak_addition_for_data_augmentation( spectrum_tensor, model_settings.augment_noise_max, @@ -43,9 +48,10 @@ def data_augmentation_spectrum(spectrum_tensor, model_settings: SettingsMS2Deeps def peak_removal_for_data_augmentation( spectrum_tensor, augment_removal_max, augment_removal_intensity, random_number_generator ): - """Removes small peaks at random for data augmentation. + """Remove up to ``augment_removal_max`` of eligible low-intensity peaks. Parameters + ---------- spectrum_tensor: Tensorized spectrum augment_removal_max @@ -56,53 +62,74 @@ def peak_removal_for_data_augmentation( augment_removal_intensity Specifying that only peaks with intensities < max_intensity will be removed. random_number_generator - Random number generator used to generate random numbers. Can be generated with np.random.default_rng(42) + Random number generator used to generate random numbers. """ - if augment_removal_max or augment_removal_intensity: - bin_indices_below_removal_intensity = where( - (spectrum_tensor > 0) & (spectrum_tensor < augment_removal_intensity) - )[0] - fraction_of_noise_to_remove = random_number_generator.random() * augment_removal_max - number_of_peaks_to_remove = int( - np.ceil((1 - fraction_of_noise_to_remove) * len(bin_indices_below_removal_intensity)) - ) - indices = random_number_generator.choice( - bin_indices_below_removal_intensity, number_of_peaks_to_remove, replace=False - ) - if len(indices) > 0: - spectrum_tensor[indices] = 0 + if augment_removal_max <= 0: + return + candidate_indices = where( + (spectrum_tensor > 0) & (spectrum_tensor < augment_removal_intensity) + )[0] + if len(candidate_indices) == 0: + return -def change_peak_intensity_for_data_augmentation(spectrum_tensor, augment_intensity): + fraction_to_remove = random_number_generator.random() * augment_removal_max + number_of_peaks_to_remove = int(fraction_to_remove * len(candidate_indices)) + if number_of_peaks_to_remove == 0: + return + + indices = random_number_generator.choice( + candidate_indices.cpu().numpy(), number_of_peaks_to_remove, replace=False + ) + spectrum_tensor[torch.as_tensor(indices, device=spectrum_tensor.device)] = 0 + + +def change_peak_intensity_for_data_augmentation( + spectrum_tensor, augment_intensity, random_number_generator=None +): + if random_number_generator is None: + random_number_generator = np.random.default_rng() if augment_intensity: - spectrum_tensor.mul_(1 - augment_intensity * 2 * (rand(spectrum_tensor.shape) - 0.5)) + factors = random_number_generator.uniform( + 1 - augment_intensity, + 1 + augment_intensity, + size=tuple(spectrum_tensor.shape), + ) + spectrum_tensor.mul_( + torch.as_tensor( + factors, + dtype=spectrum_tensor.dtype, + device=spectrum_tensor.device, + ) + ) def peak_addition_for_data_augmentation( spectrum_tensor, augment_noise_max, augment_noise_intensity, random_number_generator ): - """Adds noise to a spectrum tensor - spectrum_tensor: - Tensorized spectrum - augment_noise_max - Max number of 'new' noise peaks to add to the spectrum, between 0 to `augment_noise_max` - of peaks are added. - augment_noise_intensity - maximum intensity of the 'new' noise peaks to add to the spectrum, - random_number_generator - Random number generator used to generate random numbers. Can be generated with np.random.default_rng(42) - """ - if augment_noise_max and augment_noise_max > 0: - bin_indices_zero = where(spectrum_tensor == 0)[0] - number_of_noise_peaks_to_add = random_number_generator.integers(0, augment_noise_max) - if len(bin_indices_zero) > number_of_noise_peaks_to_add: - selected_bin_indices_to_add_noise = random_number_generator.choice( - bin_indices_zero, - number_of_noise_peaks_to_add, - replace=False, - ) - else: - selected_bin_indices_to_add_noise = bin_indices_zero - spectrum_tensor[selected_bin_indices_to_add_noise] = augment_noise_intensity * rand( - len(selected_bin_indices_to_add_noise) + """Add between 0 and ``augment_noise_max`` random noise peaks inclusive.""" + if not augment_noise_max or augment_noise_max <= 0: + return + + bin_indices_zero = where(spectrum_tensor == 0)[0] + number_of_noise_peaks_to_add = int( + random_number_generator.integers(0, int(augment_noise_max) + 1) + ) + if number_of_noise_peaks_to_add == 0 or len(bin_indices_zero) == 0: + return + + available = bin_indices_zero.cpu().numpy() + if len(available) > number_of_noise_peaks_to_add: + selected = random_number_generator.choice( + available, number_of_noise_peaks_to_add, replace=False ) + else: + selected = available + + noise = random_number_generator.random(len(selected)) * augment_noise_intensity + selected_tensor = torch.as_tensor(selected, device=spectrum_tensor.device) + spectrum_tensor[selected_tensor] = torch.as_tensor( + noise, + dtype=spectrum_tensor.dtype, + device=spectrum_tensor.device, + ) diff --git a/notebooks/model_benchmarking/EmbeddingEvaluator_benchmarking.ipynb b/notebooks/model_benchmarking/EmbeddingEvaluator_benchmarking.ipynb index 09383e40..cf1ab3ca 100644 --- a/notebooks/model_benchmarking/EmbeddingEvaluator_benchmarking.ipynb +++ b/notebooks/model_benchmarking/EmbeddingEvaluator_benchmarking.ipynb @@ -5312,7 +5312,7 @@ "metadata": {}, "outputs": [], "source": [ - "from matchms.Spectrum import Spectrum\n", + "from matchms import Spectrum\n", "import numpy as np\n", "\n", "def plot_predicted_mse_random_noise(nr_of_peaks=10, intensity_normalization= 1, nr_of_spectra = 1000, ionmode=\"positive\", precursor_mz=None, mz_spread=990, min_mz=10):\n", diff --git a/notebooks/ms2deepscore_2_preprint/EmbeddingEvaluator.ipynb b/notebooks/ms2deepscore_2_preprint/EmbeddingEvaluator.ipynb index b6551ece..922b6533 100644 --- a/notebooks/ms2deepscore_2_preprint/EmbeddingEvaluator.ipynb +++ b/notebooks/ms2deepscore_2_preprint/EmbeddingEvaluator.ipynb @@ -1843,7 +1843,7 @@ } ], "source": [ - "from matchms.Spectrum import Spectrum\n", + "from matchms import Spectrum\n", "import numpy as np\n", "\n", "predict_mse(Spectrum(mz=np.array([5.]), intensities=np.array([1.0,]), metadata={\"precursor_mz\": 300, \"ionmode\": \"positive\",}))\n", diff --git a/tests/test_data_augmentation.py b/tests/test_data_augmentation.py index 9f1cf673..26471434 100644 --- a/tests/test_data_augmentation.py +++ b/tests/test_data_augmentation.py @@ -1,34 +1,372 @@ import numpy as np -from torch import tensor, equal +import pytest +import torch +from torch import equal, tensor + from ms2deepscore.train_new_model.data_augmentation import ( + data_augmentation, peak_addition_for_data_augmentation, peak_removal_for_data_augmentation, change_peak_intensity_for_data_augmentation, ) +# ------------------------------------------------------------------------- +# Peak removal +# ------------------------------------------------------------------------- + + def test_peak_removal_for_data_augmentation(): - spectrum_tensor = tensor([0.0, 0.12, 0.05, 0.78, 0.0, 0.34, 1.0, 0.0, 0.27, 0.65]) + spectrum_tensor = tensor( + [0.0, 0.12, 0.05, 0.78, 0.0, 0.34, 1.0, 0.0, 0.27, 0.65] + ) + peak_removal_for_data_augmentation( spectrum_tensor, augment_removal_max=0.5, augment_removal_intensity=0.3, + random_number_generator=np.random.default_rng(123), + ) + + assert equal( + spectrum_tensor, + tensor([0.0, 0.12, 0.0, 0.78, 0.0, 0.34, 1.0, 0.0, 0.27, 0.65]), + ) + + +def test_peak_removal_max_zero_does_not_remove_peaks(): + """A maximum removal fraction of zero should disable peak removal.""" + original = tensor([0.0, 0.05, 0.10, 0.20, 0.50, 1.0]) + augmented = original.clone() + + peak_removal_for_data_augmentation( + augmented, + augment_removal_max=0.0, + augment_removal_intensity=0.3, random_number_generator=np.random.default_rng(42), ) - assert equal(spectrum_tensor, tensor([0.0, 0.12, 0.0, 0.78, 0.0, 0.34, 1.0, 0.0, 0.0, 0.65])) + + assert equal(augmented, original) + + +def test_peak_removal_only_removes_peaks_below_threshold(): + """Peaks at or above the intensity threshold must never be removed.""" + original = tensor([0.0, 0.05, 0.10, 0.29, 0.30, 0.31, 0.70, 1.0]) + augmented = original.clone() + + peak_removal_for_data_augmentation( + augmented, + augment_removal_max=1.0, + augment_removal_intensity=0.3, + random_number_generator=np.random.default_rng(12), + ) + + # Originally empty bins stay empty. + assert augmented[0] == 0.0 + + # Threshold is exclusive: 0.30 itself must not be removed. + assert augmented[4] == original[4] + + # Peaks above the threshold must remain unchanged. + assert equal(augmented[4:], original[4:]) + + +def test_peak_removal_respects_maximum_fraction(): + """Never remove more than augment_removal_max of eligible peaks.""" + nr_peaks = 100 + augment_removal_max = 0.2 + + spectrum_tensor = torch.full((nr_peaks,), 0.1) + + peak_removal_for_data_augmentation( + spectrum_tensor, + augment_removal_max=augment_removal_max, + augment_removal_intensity=0.3, + random_number_generator=np.random.default_rng(42), + ) + + nr_removed = int((spectrum_tensor == 0).sum()) + + assert nr_removed <= nr_peaks * augment_removal_max + + +@pytest.mark.parametrize( + "augment_removal_intensity", + [0.0, -0.1], +) +def test_peak_removal_with_no_eligible_peaks_changes_nothing( + augment_removal_intensity, +): + original = tensor([0.0, 0.1, 0.2, 0.5, 1.0]) + augmented = original.clone() + + peak_removal_for_data_augmentation( + augmented, + augment_removal_max=0.5, + augment_removal_intensity=augment_removal_intensity, + random_number_generator=np.random.default_rng(42), + ) + + assert equal(augmented, original) + + +# ------------------------------------------------------------------------- +# Peak addition +# ------------------------------------------------------------------------- def test_peak_addition_for_data_augmentation(): - spectrum_tensor = tensor([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.27, 0.0]) - peak_addition_for_data_augmentation(spectrum_tensor, 4, 0.02, random_number_generator=np.random.default_rng(0)) + spectrum_tensor = tensor( + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.27, 0.0] + ) + + peak_addition_for_data_augmentation( + spectrum_tensor, + 4, + 0.02, + random_number_generator=np.random.default_rng(0), + ) + assert spectrum_tensor[6] == 1.0 assert spectrum_tensor[8] == 0.27 assert spectrum_tensor[0] == 0.0 - assert spectrum_tensor[2] != 0.0 # we know this one is changed because of the random number generator + assert spectrum_tensor[2] != 0.0 + + +def test_peak_addition_does_not_modify_existing_peaks(): + original = tensor([0.0, 0.2, 0.0, 0.7, 0.0, 1.0, 0.0]) + augmented = original.clone() + + peak_addition_for_data_augmentation( + augmented, + augment_noise_max=5, + augment_noise_intensity=0.05, + random_number_generator=np.random.default_rng(4), + ) + + existing_peak_mask = original > 0 + + assert equal( + augmented[existing_peak_mask], + original[existing_peak_mask], + ) + + +def test_peak_addition_only_adds_peaks_to_empty_bins(): + original = tensor([0.0, 0.2, 0.0, 0.7, 0.0, 1.0, 0.0]) + augmented = original.clone() + + peak_addition_for_data_augmentation( + augmented, + augment_noise_max=5, + augment_noise_intensity=0.05, + random_number_generator=np.random.default_rng(8), + ) + + changed = augmented != original + + # Every changed bin must have been zero before augmentation. + assert torch.all(original[changed] == 0) + + +def test_peak_addition_respects_maximum_number_of_new_peaks(): + spectrum_tensor = torch.zeros(100) + + augment_noise_max = 10 + + peak_addition_for_data_augmentation( + spectrum_tensor, + augment_noise_max=augment_noise_max, + augment_noise_intensity=0.05, + random_number_generator=np.random.default_rng(42), + ) + + nr_added = int((spectrum_tensor > 0).sum()) + + assert nr_added <= augment_noise_max + + +def test_peak_addition_respects_maximum_noise_intensity(): + spectrum_tensor = torch.zeros(100) + + augment_noise_intensity = 0.05 + + peak_addition_for_data_augmentation( + spectrum_tensor, + augment_noise_max=20, + augment_noise_intensity=augment_noise_intensity, + random_number_generator=np.random.default_rng(42), + ) + + assert torch.all(spectrum_tensor >= 0) + assert torch.all(spectrum_tensor <= augment_noise_intensity) + + +def test_peak_addition_max_zero_changes_nothing(): + original = tensor([0.0, 0.2, 0.0, 0.7, 0.0]) + augmented = original.clone() + + peak_addition_for_data_augmentation( + augmented, + augment_noise_max=0, + augment_noise_intensity=0.05, + random_number_generator=np.random.default_rng(42), + ) + + assert equal(augmented, original) + + +def test_peak_addition_when_no_empty_bins_changes_nothing(): + original = tensor([0.1, 0.2, 0.5, 0.7, 1.0]) + augmented = original.clone() + + peak_addition_for_data_augmentation( + augmented, + augment_noise_max=10, + augment_noise_intensity=0.05, + random_number_generator=np.random.default_rng(42), + ) + + assert equal(augmented, original) + + +# ------------------------------------------------------------------------- +# Peak intensity changes +# ------------------------------------------------------------------------- def test_change_peak_intensity_for_data_augmentation(): - spectrum_tensor = tensor([0.0, 0.12, 0.05, 0.78, 0.0, 0.34, 1.0, 0.0, 0.27, 0.65]) - change_peak_intensity_for_data_augmentation(spectrum_tensor, 0.2) - assert spectrum_tensor[0] == 0.0 # Check that zero's are not changed. - assert spectrum_tensor[1] != 0.12 # Check that the value is changed. + spectrum_tensor = tensor( + [0.0, 0.12, 0.05, 0.78, 0.0, 0.34, 1.0, 0.0, 0.27, 0.65] + ) + + change_peak_intensity_for_data_augmentation( + spectrum_tensor, + 0.2, + ) + + assert spectrum_tensor[0] == 0.0 + assert spectrum_tensor[1] != 0.12 + + +def test_change_peak_intensity_zero_changes_nothing(): + original = tensor([0.0, 0.12, 0.4, 1.0]) + augmented = original.clone() + + change_peak_intensity_for_data_augmentation( + augmented, + augment_intensity=0.0, + ) + + assert equal(augmented, original) + + +def test_change_peak_intensity_preserves_zero_bins(): + spectrum_tensor = tensor([0.0, 0.1, 0.0, 0.4, 0.0, 1.0]) + + change_peak_intensity_for_data_augmentation( + spectrum_tensor, + augment_intensity=0.2, + ) + + assert equal( + spectrum_tensor[[0, 2, 4]], + tensor([0.0, 0.0, 0.0]), + ) + + +def test_change_peak_intensity_stays_within_requested_range(): + """A=0.2 means intensities may change by at most +/-20%.""" + original = tensor([0.1, 0.2, 0.5, 0.8, 1.0]) + augmented = original.clone() + + augment_intensity = 0.2 + + change_peak_intensity_for_data_augmentation( + augmented, + augment_intensity=augment_intensity, + ) + + lower_bound = original * (1 - augment_intensity) + upper_bound = original * (1 + augment_intensity) + + assert torch.all(augmented >= lower_bound) + assert torch.all(augmented <= upper_bound) + + +def test_change_peak_intensity_does_not_create_or_remove_peaks(): + original = tensor([0.0, 0.1, 0.0, 0.5, 1.0, 0.0]) + augmented = original.clone() + + change_peak_intensity_for_data_augmentation( + augmented, + augment_intensity=0.2, + ) + + assert equal(augmented == 0, original == 0) + + +# ------------------------------------------------------------------------- +# Complete augmentation pipeline +# ------------------------------------------------------------------------- + + +def test_data_augmentation_preserves_shape_and_dtype(): + spectra = tensor( + [ + [0.0, 0.1, 0.2, 0.0, 1.0], + [0.3, 0.0, 0.2, 0.5, 0.0], + ] + ) + + original_shape = spectra.shape + original_dtype = spectra.dtype + + settings = _DummyAugmentationSettings() + + data_augmentation( + spectra, + settings, + np.random.default_rng(42), + ) + + assert spectra.shape == original_shape + assert spectra.dtype == original_dtype + + +def test_data_augmentation_is_reproducible_when_both_rngs_are_seeded(): + """The implementation uses both NumPy and PyTorch random generators.""" + original = tensor( + [ + [0.0, 0.05, 0.2, 0.0, 1.0], + [0.1, 0.0, 0.25, 0.5, 0.0], + ] + ) + + settings = _DummyAugmentationSettings() + + first = original.clone() + torch.manual_seed(123) + data_augmentation( + first, + settings, + np.random.default_rng(456), + ) + + second = original.clone() + torch.manual_seed(123) + data_augmentation( + second, + settings, + np.random.default_rng(456), + ) + + assert equal(first, second) + + +class _DummyAugmentationSettings: + augment_removal_max = 0.2 + augment_removal_intensity = 0.3 + augment_intensity = 0.2 + augment_noise_max = 4 + augment_noise_intensity = 0.02 \ No newline at end of file diff --git a/tests/test_ms2deepscore.py b/tests/test_ms2deepscore.py index 18b83d35..b9aa0026 100644 --- a/tests/test_ms2deepscore.py +++ b/tests/test_ms2deepscore.py @@ -3,6 +3,7 @@ import pytest from ms2deepscore import MS2DeepScore +from ms2deepscore.matchms_compat import MATCHMS_V1_API from ms2deepscore.models import load_model from tests.create_test_spectra import pesticides_test_spectra @@ -47,6 +48,8 @@ def test_MS2DeepScore_score_matrix(): """Test score calculation using *.matrix* method.""" spectrums, _, similarity_measure = get_test_ms2deepscore_instance() scores = similarity_measure.matrix(spectrums[:4], spectrums[:3]) + if MATCHMS_V1_API: + scores = scores.to_array("score") expected_scores = np.array([ [1. , 0.99036639, 0.99084978], @@ -60,7 +63,10 @@ def test_MS2DeepScore_score_matrix(): def test_MS2DeepScore_score_matrix_symmetric(): """Test score calculation using *.matrix* method.""" spectrums, _, similarity_measure = get_test_ms2deepscore_instance() - scores = similarity_measure.matrix(spectrums[:4], spectrums[:4], is_symmetric=True) + if MATCHMS_V1_API: + scores = similarity_measure.matrix(spectrums[:4]).to_array("score") + else: + scores = similarity_measure.matrix(spectrums[:4], spectrums[:4], is_symmetric=True) expected_scores = np.array([ [1. , 0.99036639, 0.99084978, 0.98811793], [0.99036639, 1. , 0.99399306, 0.96436209], @@ -71,10 +77,11 @@ def test_MS2DeepScore_score_matrix_symmetric(): def test_MS2DeepScore_score_matrix_symmetric_wrong_use(): """Test if *.matrix* method gives correct exception.""" - spectrums, _, similarity_measure = get_test_ms2deepscore_instance() - expected_msg = "Expected references to be equal to queries for is_symmetric=True" - with pytest.raises(AssertionError) as msg: - _ = similarity_measure.matrix(spectrums[:4], - [spectrums[i] for i in [1,2,3,0]], - is_symmetric=True) - assert expected_msg in str(msg), "Expected different exception message" + if not MATCHMS_V1_API: # test makes no sense for matchms >= 1.0 + spectrums, _, similarity_measure = get_test_ms2deepscore_instance() + expected_msg = "Expected references to be equal to queries for is_symmetric=True" + with pytest.raises(AssertionError) as msg: + _ = similarity_measure.matrix(spectrums[:4], + [spectrums[i] for i in [1,2,3,0]], + is_symmetric=True) + assert expected_msg in str(msg), "Expected different exception message" diff --git a/tests/test_ms2deepscore_evaluated.py b/tests/test_ms2deepscore_evaluated.py index 44ea74de..c2d32cae 100644 --- a/tests/test_ms2deepscore_evaluated.py +++ b/tests/test_ms2deepscore_evaluated.py @@ -1,6 +1,7 @@ from pathlib import Path import numpy as np from ms2deepscore import MS2DeepScoreEvaluated +from ms2deepscore.matchms_compat import MATCHMS_V1_API from ms2deepscore.SettingsMS2Deepscore import SettingsEmbeddingEvaluator from ms2deepscore.models import load_model, LinearModel, EmbeddingEvaluationModel from tests.create_test_spectra import pesticides_test_spectra @@ -48,6 +49,8 @@ def test_MS2DeepScore_score_matrix(): """Test score calculation using *.matrix* method.""" spectrums, similarity_measure = get_test_ms2deepscore_evaluated_instance() scores = similarity_measure.matrix(spectrums[:3], spectrums[:4]) + if MATCHMS_V1_API: + scores = scores.to_array("score") expected_scores = np.array([ [1. , 0.9903664 , 0.9908498 , 0.98811793], diff --git a/tests/test_ms2deepscore_onnx.py b/tests/test_ms2deepscore_onnx.py index 34d43e1c..72fa298a 100644 --- a/tests/test_ms2deepscore_onnx.py +++ b/tests/test_ms2deepscore_onnx.py @@ -3,6 +3,7 @@ import pytest from ms2deepscore import MS2DeepScoreONNX +from ms2deepscore.matchms_compat import MATCHMS_V1_API from ms2deepscore.models import SiameseSpectralModelONNX from tests.create_test_spectra import pesticides_test_spectra @@ -43,6 +44,8 @@ def test_MS2DeepScoreONNX_score_matrix(): """Test score calculation using *.matrix* method.""" spectrums, model, similarity_measure = get_test_ms2deepscore_onnx_instance() scores = similarity_measure.matrix(spectrums[:4], spectrums[:3]) + if MATCHMS_V1_API: + scores = scores.to_array("score") expected_scores = np.array( [ @@ -61,7 +64,10 @@ def test_MS2DeepScoreONNX_score_matrix(): def test_MS2DeepScoreONNX_score_matrix_symmetric(): """Test score calculation using *.matrix* method with is_symmetric=True.""" spectrums, model, similarity_measure = get_test_ms2deepscore_onnx_instance() - scores = similarity_measure.matrix(spectrums[:4], spectrums[:4], is_symmetric=True) + if MATCHMS_V1_API: + scores = similarity_measure.matrix(spectrums[:4]).to_array("score") + else: + scores = similarity_measure.matrix(spectrums[:4], spectrums[:4], is_symmetric=True) expected_scores = np.array( [ @@ -77,10 +83,11 @@ def test_MS2DeepScoreONNX_score_matrix_symmetric(): def test_MS2DeepScoreONNX_score_matrix_symmetric_wrong_use(): """Test if *.matrix* method gives correct exception when references != queries.""" - spectrums, _, similarity_measure = get_test_ms2deepscore_onnx_instance() - expected_msg = "Expected references to be equal to queries for is_symmetric=True" + if not MATCHMS_V1_API: # test makes no sense for matchms >= 1.0 + spectrums, _, similarity_measure = get_test_ms2deepscore_onnx_instance() + expected_msg = "Expected references to be equal to queries for is_symmetric=True" - with pytest.raises(AssertionError) as msg: - _ = similarity_measure.matrix(spectrums[:4], [spectrums[i] for i in [1, 2, 3, 0]], is_symmetric=True) + with pytest.raises(AssertionError) as msg: + _ = similarity_measure.matrix(spectrums[:4], [spectrums[i] for i in [1, 2, 3, 0]], is_symmetric=True) - assert expected_msg in str(msg.value), "Expected different exception message" + assert expected_msg in str(msg.value), "Expected different exception message" diff --git a/tests/test_validation_loss_calculator.py b/tests/test_validation_loss_calculator.py index 0a4dbc33..bc4ff805 100644 --- a/tests/test_validation_loss_calculator.py +++ b/tests/test_validation_loss_calculator.py @@ -1,7 +1,7 @@ import numpy as np import pandas as pd import pytest -from matchms.Spectrum import Spectrum +from matchms import Spectrum from ms2deepscore import MS2DeepScore from ms2deepscore.SettingsMS2Deepscore import SettingsMS2Deepscore