Project: s41467-024-46399-1 | Function: temporal_median_filter (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Applies temporal median filtering to reduce noise in time-series bacterial imaging data

**KEY TERMS:** {
  "images": "time-series bacterial images",
  "window_size": "temporal filter window",
  "half_window": "window radius",
  "median_image": "filtered frame",
  "median_filtered_images": "noise-reduced time series"
}

**CONTEXT CODE:**     # Return binary mask with filtered components
    return labeled_mask > 0

# Pythonic implementation of local contrast normalization
def normalize_local_contrast(img, blockRadius):

    block_size = blockRadius * 2 + 1
    block_average = uniform_filter(img, size=(0, block_size, block_size))
    img_normalized = img - block_average + 37488.0

    return img_normalized

# Function for cropping away boundary artifacts after registration
def crop(arr, val=0):
    mask = np.amin(arr, axis=0) != val
    mask_i = mask.any(axis=1)
    mask_j = mask.any(axis=0)
    slices_i = np.where(mask_i)[0][[0, -1]]
    slices_j = np.where(mask_j)[0][[0, -1]]
    return arr[:, slices_i[0]:slices_i[1]+1, slices_j[0]:slices_j[1]+1]

# Function to register images in a timeseries
def register(timeseries, img_shift_thresh):
    registered = np.zeros_like(timeseries, dtype=timeseries.dtype)
    registered[0] = timeseries[0]

    shifts = np.empty((timeseries.shape[0], 2), dtype=np.float64)
    cum_shift = np.array([0, 0], dtype=np.float64)
    shifts[0] = cum_shift

    # Register each image in the t-direction
    for i in range(1, timeseries.shape[0]):
        # Compute the phase cross-correlation between imepoints to get shift
        img_shift, error, _ = phase_cross_correlation(timeseries[i - 1], timeseries[i],
                                                  upsample_factor=10, normalization=None)
        cum_shift = np.add(cum_shift, img_shift, out=cum_shift)

        if np.linalg.norm(img_shift, 2) < img_shift_thresh:
            # Apply the computed shift
            registered[i] = shift(timeseries[i], shift=cum_shift)
            shifts[i] = cum_shift
        else:
            print("Error in registration")
            registered[i:, :, :] = timeseries[i:, :, :]
            shifts[i:, :] = np.zeros((shifts.shape[0] - i, 2), dtype=np.float64)
            break

    reg_crop = crop(registered)
    return reg_crop, shifts

def temporal_median_filter(images, window_size=temporal_median_window):
    # MASKED: Applies temporal median filtering to reduce noise in time-series bacterial imaging data implementation
    raise NotImplementedError("This function is masked for testing purposes.")

data = []
num_plots = 0
for j, (_, values) in enumerate(conditions.items()):
    print('Analyzing '+ _ +'...')

    replicates = len(conditions[_])
    extract_lists = replicates

    file_dict = {'Bright': []}
    for well in conditions[_]:

        matching_files = natsorted([f for f in os.listdir() if well in f])
        for file_type in file_dict.keys():
            file_dict[file_type] = [f for f in matching_files if file_type in f]

        BF_images = np.asarray([cv2.imread(f,-1) for f in file_dict['Bright']])
        BF_normalized = normalize_local_contrast(BF_images.astype(np.float32), blockRadius)
        BF_registered, shifts = register(BF_normalized.astype(np.uint16), img_shift_thresh)
        BF_output_images = []
        BF_masks = []
        BF_welldata = [well+'BF']
        img_nobacks = []
        imgs_inverted = []

        for i in range(BF_images.shape[0]):
            # First output images
            img_blurred = cv2.GaussianBlur((BF_registered[i]/256).astype(np.uint8), (0, 0), bf_sig_output)
            BF_output_images.append(img_blurred)

            # Generate noback image
            img = BF_images[i]
            img_inverted = 65535 - img
            imgs_inverted.append(img_inverted)

            img_flattened = img_inverted.flatten()
            img_reshaped = img_flattened.reshape(-1, 1)

            # Fit Bayesian Gaussian mixture model
            gmm = BayesianGaussianMixture(n_components=1, covariance_type='diag')
            gmm.fit(img_reshaped)

            min_mean_idx = np.argmin(gmm.means_)
            means = gmm.means_[min_mean_idx]
            img_noback = img_inverted.astype(np.float32) - means
            img_nobacks.append(img_noback)

        BF_output_images = np.asarray(BF_output_images)
        img_nobacks = np.asarray(img_nobacks)
        img_nobacks = np.asarray([shift(img, shift = cum_shift, mode='constant', cval=-1e6)
                                  for img, cum_shift in zip(img_nobacks, shifts)])
        img_nobacks = crop(img_nobacks, val=-1e6)
        imgs_inverted = np.asarray(imgs_inverted)
        imgs_inverted = np.asarray([shift(img, shift = cum_shift)
                                  for img, cum_shift in zip(imgs_inverted, shifts)])
        imgs_inverted = crop(imgs_inverted)
        if 'Vc' in _ or 'Va' in _:
            img_mask = ((255-BF_output_images) > bf_thresh2).astype(np.uint8)
            img_mask = temporal_median_filter(img_mask)
            img_masks = np.asarray([cv2.dilate(cv2.morphologyEx(cv2.medianBlur(img_mask_slice, 3),cv2.MORPH_CLOSE, dilat_ker), dilat_ker2)
                                    for img_mask_slice in img_mask])

        # Species-specific processing of background subtracted images to generate masks
        unnormalized_masks = []
        normalized_masks = []
        for i in range(img_nobacks.shape[0]):
            if 'Vc' in _ or 'Va' in _:
                # Vibrio cholerae and Vibrio anguillarum
                img_mask = img_masks[i]
                BF_masks.append(img_mask)
            elif 'Vp' in _:
                # Vibrio parahaemolyticus
                normalized_mask = ((255-BF_output_images[i]) > bf_thresh2).astype(np.uint8)
                normalized_mask = cv2.morphologyEx(normalized_mask, cv2.MORPH_OPEN, dilat_ker)
                normalized_mask = cv2.medianBlur(normalized_mask, 3)
                unnormalized_mask = (img_nobacks[i] > bf_thresh).astype(np.uint8)
                unnormalized_mask = cv2.morphologyEx(unnormalized_mask, cv2.MORPH_OPEN, dilat_ker)
                unnormalized_mask = cv2.medianBlur(unnormalized_mask, 5)
                unnormalized_mask = cv2.morphologyEx(unnormalized_mask, cv2.MORPH_CLOSE, dilat_ker)
                normalized_masks.append(normalized_mask)
                unnormalized_masks.append(unnormalized_mask)
            else:
                # Vibrio vulnficius
                img_mask = (img_nobacks[i] > bf_thresh).astype(np.uint8)
                img_mask = cv2.morphologyEx(img_mask, cv2.MORPH_OPEN, dilat_ker)
                img_mask = cv2.medianBlur(img_mask, 5)
                img_mask = cv2.morphologyEx(img_mask, cv2.MORPH_CLOSE, dilat_ker)
                BF_masks.append(img_mask)

        # Handle V.p. case
        if 'Vp' in _:
            unnormalized_masks = np.asarray(unnormalized_masks)
            normalized_masks = np.asarray(normalized_masks)
            for n in range(1, normalized_masks.shape[0]):
                overlap = np.logical_and(normalized_masks[n-1], unnormalized_masks[n])
                normalized_masks[n][overlap] = 1
                # Dilate and fill holes


Complete the function implementation replacing "raise NotImplementedError".