msbackup / MRI_recon /new_code /Frequency-Diffusion-main /experiments /FSMNet /dataloaders /subsample.py
| """ | |
| Copyright (c) Facebook, Inc. and its affiliates. | |
| This source code is licensed under the MIT license found in the | |
| LICENSE file in the root directory of this source tree. | |
| """ | |
| import contextlib | |
| import numpy as np | |
| import torch | |
| def temp_seed(rng, seed): | |
| state = rng.get_state() | |
| rng.seed(seed) | |
| try: | |
| yield | |
| finally: | |
| rng.set_state(state) | |
| def create_mask_for_mask_type(mask_type_str, center_fractions, accelerations): | |
| if mask_type_str == "random": | |
| return RandomMaskFunc(center_fractions, accelerations) | |
| elif mask_type_str == "equispaced": | |
| return EquispacedMaskFunc(center_fractions, accelerations) | |
| else: | |
| raise Exception(f"{mask_type_str} not supported") | |
| class MaskFunc(object): | |
| """ | |
| An object for GRAPPA-style sampling masks. | |
| This crates a sampling mask that densely samples the center while | |
| subsampling outer k-space regions based on the undersampling factor. | |
| """ | |
| def __init__(self, center_fractions, accelerations): | |
| """ | |
| Args: | |
| center_fractions (List[float]): Fraction of low-frequency columns to be | |
| retained. If multiple values are provided, then one of these | |
| numbers is chosen uniformly each time. | |
| accelerations (List[int]): Amount of under-sampling. This should have | |
| the same length as center_fractions. If multiple values are | |
| provided, then one of these is chosen uniformly each time. | |
| """ | |
| if len(center_fractions) != len(accelerations): | |
| raise ValueError( | |
| "Number of center fractions should match number of accelerations" | |
| ) | |
| self.center_fractions = center_fractions | |
| self.accelerations = accelerations | |
| self.rng = np.random | |
| def choose_acceleration(self): | |
| """Choose acceleration based on class parameters.""" | |
| choice = self.rng.randint(0, len(self.accelerations)) | |
| center_fraction = self.center_fractions[choice] | |
| acceleration = self.accelerations[choice] | |
| return center_fraction, acceleration | |
| class RandomMaskFunc(MaskFunc): | |
| """ | |
| RandomMaskFunc creates a sub-sampling mask of a given shape. | |
| The mask selects a subset of columns from the input k-space data. If the | |
| k-space data has N columns, the mask picks out: | |
| 1. N_low_freqs = (N * center_fraction) columns in the center | |
| corresponding to low-frequencies. | |
| 2. The other columns are selected uniformly at random with a | |
| probability equal to: prob = (N / acceleration - N_low_freqs) / | |
| (N - N_low_freqs). This ensures that the expected number of columns | |
| selected is equal to (N / acceleration). | |
| It is possible to use multiple center_fractions and accelerations, in which | |
| case one possible (center_fraction, acceleration) is chosen uniformly at | |
| random each time the RandomMaskFunc object is called. | |
| For example, if accelerations = [4, 8] and center_fractions = [0.08, 0.04], | |
| then there is a 50% probability that 4-fold acceleration with 8% center | |
| fraction is selected and a 50% probability that 8-fold acceleration with 4% | |
| center fraction is selected. | |
| """ | |
| def __call__(self, shape, seed=None): | |
| """ | |
| Create the mask. | |
| Args: | |
| shape (iterable[int]): The shape of the mask to be created. The | |
| shape should have at least 3 dimensions. Samples are drawn | |
| along the second last dimension. | |
| seed (int, optional): Seed for the random number generator. Setting | |
| the seed ensures the same mask is generated each time for the | |
| same shape. The random state is reset afterwards. | |
| Returns: | |
| torch.Tensor: A mask of the specified shape. | |
| """ | |
| if len(shape) < 3: | |
| raise ValueError("Shape should have 3 or more dimensions") | |
| with temp_seed(self.rng, seed): | |
| num_cols = shape[-2] | |
| center_fraction, acceleration = self.choose_acceleration() | |
| # create the mask | |
| num_low_freqs = int(round(num_cols * center_fraction)) | |
| prob = (num_cols / acceleration - num_low_freqs) / ( | |
| num_cols - num_low_freqs | |
| ) | |
| mask = self.rng.uniform(size=num_cols) < prob | |
| pad = (num_cols - num_low_freqs + 1) // 2 | |
| mask[pad : pad + num_low_freqs] = True | |
| # reshape the mask | |
| mask_shape = [1 for _ in shape] | |
| mask_shape[-2] = num_cols | |
| mask = torch.from_numpy(mask.reshape(*mask_shape).astype(np.float32)) | |
| return mask | |
| class EquispacedMaskFunc(MaskFunc): | |
| """ | |
| EquispacedMaskFunc creates a sub-sampling mask of a given shape. | |
| The mask selects a subset of columns from the input k-space data. If the | |
| k-space data has N columns, the mask picks out: | |
| 1. N_low_freqs = (N * center_fraction) columns in the center | |
| corresponding tovlow-frequencies. | |
| 2. The other columns are selected with equal spacing at a proportion | |
| that reaches the desired acceleration rate taking into consideration | |
| the number of low frequencies. This ensures that the expected number | |
| of columns selected is equal to (N / acceleration) | |
| It is possible to use multiple center_fractions and accelerations, in which | |
| case one possible (center_fraction, acceleration) is chosen uniformly at | |
| random each time the EquispacedMaskFunc object is called. | |
| Note that this function may not give equispaced samples (documented in | |
| https://github.com/facebookresearch/fastMRI/issues/54), which will require | |
| modifications to standard GRAPPA approaches. Nonetheless, this aspect of | |
| the function has been preserved to match the public multicoil data. | |
| """ | |
| def __call__(self, shape, seed): | |
| """ | |
| Args: | |
| shape (iterable[int]): The shape of the mask to be created. The | |
| shape should have at least 3 dimensions. Samples are drawn | |
| along the second last dimension. | |
| seed (int, optional): Seed for the random number generator. Setting | |
| the seed ensures the same mask is generated each time for the | |
| same shape. The random state is reset afterwards. | |
| Returns: | |
| torch.Tensor: A mask of the specified shape. | |
| """ | |
| if len(shape) < 3: | |
| raise ValueError("Shape should have 3 or more dimensions") | |
| with temp_seed(self.rng, seed): | |
| center_fraction, acceleration = self.choose_acceleration() | |
| num_cols = shape[-2] | |
| num_low_freqs = int(round(num_cols * center_fraction)) | |
| # create the mask | |
| mask = np.zeros(num_cols, dtype=np.float32) | |
| pad = (num_cols - num_low_freqs + 1) // 2 | |
| mask[pad : pad + num_low_freqs] = True | |
| # determine acceleration rate by adjusting for the number of low frequencies | |
| adjusted_accel = (acceleration * (num_low_freqs - num_cols)) / ( | |
| num_low_freqs * acceleration - num_cols | |
| ) | |
| offset = self.rng.randint(0, round(adjusted_accel)) | |
| accel_samples = np.arange(offset, num_cols - 1, adjusted_accel) | |
| accel_samples = np.around(accel_samples).astype(np.uint) | |
| mask[accel_samples] = True | |
| # reshape the mask | |
| mask_shape = [1 for _ in shape] | |
| mask_shape[-2] = num_cols | |
| mask = torch.from_numpy(mask.reshape(*mask_shape).astype(np.float32)) | |
| return mask | |