def test_write_float32_from_float64(self): float64_image = mi.MibiImage(DATA.astype(np.float64), CHANNELS, **METADATA) tiff.write(self.filename, float64_image, multichannel=True) image = tiff.read(self.filename) np.testing.assert_equal(image.data, DATA) self.assertEqual(image.data.dtype, np.float32)
def test_write_float32_as_uint16_fails(self): lossy_image = mi.MibiImage(DATA + 0.001, CHANNELS, **METADATA) with self.assertRaises(ValueError): tiff.write(self.filename, lossy_image, multichannel=True, dtype=np.uint16)
def test_write_float_tiff(self): tiff.write(self.filename, self.image, multichannel=True, write_float=True) image = tiff.read(self.filename) self.assertEqual(image.data.dtype, np.float32) np.testing.assert_equal(image.data, self.image.data.astype(np.float32))
def test_default_ranges(self): tiff.write(self.filename, self.image) with TiffFile(self.filename) as tif: for i, page in enumerate(tif.pages): self.assertEqual(page.tags['smin_sample_value'].value, 0) self.assertEqual(page.tags['smax_sample_value'].value, self.image.data[:, :, i].max())
def test_write_uint16_from_uint8(self): uint8_image = mi.MibiImage( np.random.randint(0, 256, (32, 32, 5), dtype=np.uint8), CHANNELS, **METADATA) tiff.write(self.filename, uint8_image, multichannel=True) image = tiff.read(self.filename) np.testing.assert_equal(image.data, uint8_image.data.astype(np.uint16)) self.assertEqual(image.data.dtype, np.uint16)
def test_sims_extra_masses(self): tiff.write(self.filename, self.float_image) with warnings.catch_warnings(record=True) as warns: image = tiff.read(self.filename, masses=[1, 2, 6]) expected_image = tiff.read(self.filename).slice_image([1, 2]) self.assertEqual(expected_image, image) messages = [str(w.message) for w in warns] self.assertTrue('Requested masses not found in file: [6]' in messages)
def test_sims_and_sed_and_optical_and_label(self): tiff.write(self.filename, self.image, sed=SED, optical=CAROUSEL) image, sed, optical, label = tiff.read(self.filename, sed=True, optical=True, label=True) self.assertEqual(image, self.image) np.testing.assert_array_equal(sed, SED) np.testing.assert_array_equal(optical, CAROUSEL) np.testing.assert_array_equal(label, LABEL)
def test_custom_ranges(self): ranges = list(zip([1]*5, range(2, 7))) tiff.write(self.filename, self.image, ranges=ranges) with TiffFile(self.filename) as tif: for i, page in enumerate(tif.pages): self.assertEqual(page.tags['smin_sample_value'].value, ranges[i][0]) self.assertEqual(page.tags['smax_sample_value'].value, ranges[i][1])
def test_write_uint16_from_float32_dtype_np_uint16(self): tiff.write(self.filename, self.float_image, multichannel=True, dtype=np.uint16) image = tiff.read(self.filename) self.assertEqual(image.data.dtype, np.uint16) np.testing.assert_equal(image.data, self.float_image.data.astype(np.uint16))
def test_page_names_non_ascii(self): tiff.write(self.filename, self.float_image_non_ascii, multichannel=True) with TiffFile(self.filename) as tif: for page_ind, page in enumerate(tif.pages): page_name_actual = page.tags['PageName'].value page_name_expected = CHANNELS_NON_ASCII[page_ind][1] + \ ' (' + str(CHANNELS_NON_ASCII[page_ind][0]) + ')' self.assertEqual(page_name_actual, page_name_expected)
def test_write_float32_from_float32_tiff_dtype_none_non_ascii(self): tiff.write(self.filename, self.float_image_non_ascii, multichannel=True) image = tiff.read(self.filename) self.assertEqual(image.data.dtype, np.float32) np.testing.assert_equal( image.data, self.float_image_non_ascii.data.astype(np.float32)) np.testing.assert_equal(image.channels, self.float_image_non_ascii.channels)
def test_sims_and_sed(self): tiff.write(self.filename, self.image, sed=SED) image = tiff.read(self.filename) self.assertEqual(image, self.image) sims, sed, optical, label = tiff.read(self.filename, sed=True, optical=True, label=True) self.assertEqual(sims, self.image) np.testing.assert_array_equal(sed, SED) self.assertIsNone(optical) self.assertIsNone(label)
def test_read_metadata_only(self): tiff.write(self.filename, self.image) metadata = tiff.info(self.filename) expected = METADATA.copy() expected.update({ 'conjugates': list(CHANNELS), 'date': datetime.datetime.strptime(expected['date'], '%Y-%m-%dT%H:%M:%S') }) self.assertEqual(metadata, expected)
def test_sims_extra_targets(self): tiff.write(self.filename, self.float_image) target = self.float_image.targets[1] with warnings.catch_warnings(record=True) as warns: image = tiff.read(self.filename, targets=['Target0', target]) expected_image = tiff.read(self.filename).slice_image([target]) self.assertEqual(expected_image, image) messages = [str(w.message) for w in warns] self.assertTrue( 'Requested targets not found in file: [\'Target0\']' in messages)
def test_sims_only(self): tiff.write(self.filename, self.image) image = tiff.read(self.filename) self.assertEqual(image, self.image) sims, sed, optical, label = tiff.read(self.filename, sed=True, optical=True, label=True) self.assertEqual(sims, self.image) self.assertIsNone(sed) self.assertIsNone(optical) self.assertIsNone(label) self.assertEqual(image.data.dtype, np.uint16)
def test_read_optical_and_label_only(self): tiff.write(self.filename, self.image, optical=CAROUSEL) optical, label = tiff.read(self.filename, sims=False, optical=True, label=True) np.testing.assert_array_equal(optical, CAROUSEL) np.testing.assert_array_equal(label, LABEL) optical_only = tiff.read(self.filename, sims=False, optical=True) np.testing.assert_array_equal(optical_only, CAROUSEL) label_only = tiff.read(self.filename, sims=False, label=True) np.testing.assert_array_equal(label_only, LABEL)
def test_read_metadata_with_user_defined_metadata(self): tiff.write(self.filename, self.image_user_defined_metadata) metadata = tiff.info(self.filename) expected = METADATA.copy() expected.update({ 'conjugates': list(CHANNELS), 'date': datetime.datetime.strptime(expected['date'], '%Y-%m-%dT%H:%M:%S'), **USER_DEFINED_METADATA }) self.assertEqual(metadata, expected)
def test_sort_channels_before_writing(self): # Unordered indices: [2, 0, 4, 1, 3] unordered_channels = ((3, 'Target3'), (1, 'Target1'), (5, 'Target5'), (2, 'Target2'), (4, 'Target4')) unordered_data = np.stack([DATA[:, :, 2], DATA[:, :, 0], DATA[:, :, 4], DATA[:, :, 1], DATA[:, :, 3]], axis=2) unordered_image = mi.MibiImage(unordered_data, unordered_channels, **METADATA) tiff.write(self.filename, unordered_image) image = tiff.read(self.filename) self.assertEqual(image, self.image)
def test_read_old_mibiscope_metadata(self): tiff.write(self.filename, self.image_old_mibiscope_metadata) metadata = tiff.info(self.filename) expected = OLD_MIBISCOPE_METADATA.copy() expected.update({ 'conjugates': list(CHANNELS), 'aperture': 'B', 'date': datetime.datetime.strptime(expected['date'], '%Y-%m-%dT%H:%M:%S'), }) self.assertEqual(metadata, expected)
def test_tiff_dtype_correct_arguments(self): supported_dtypes = [np.float32, np.uint16] for dtype in supported_dtypes: tiff.write(self.filename, self.float_image, multichannel=True, dtype=dtype) unsupported_types = ['abcdef', np.str, np.bool, np.uint8, np.float64] for dtype in unsupported_types: with self.assertRaises(ValueError): tiff.write(self.filename, self.float_image, multichannel=True, dtype=dtype)
def test_write_single_channel_tiffs(self): basepath = os.path.split(self.filename)[0] tiff.write(basepath, self.image, multichannel=False) for i, (_, channel) in enumerate(CHANNELS): formatted = util.format_for_filename(channel) filename = os.path.join(basepath, '{}.tiff'.format(formatted)) tif = tiff.read(filename) np.testing.assert_equal(np.squeeze(tif.data), DATA[:, :, i]) self.assertTupleEqual(tif.channels, (CHANNELS[i], )) self.assertEqual(tif.data.dtype, np.uint16)
def test_read_old_metadata(self): tiff.write(self.filename, self.image_old_metadata) metadata = tiff.info(self.filename) expected = METADATA.copy() expected.update({ 'point_name': OLD_METADATA['point_name'], 'conjugates': list(CHANNELS), 'date': datetime.datetime.strptime(expected['date'], '%Y-%m-%dT%H:%M:%S'), 'description': None, 'version': None }) self.assertEqual(metadata, expected)
def _write_mibitiff(base_dir, fov_names, channel_names, shape, sub_dir, fills, dtype): """Generates and writes mibitiffs to into base_dir Args: base_dir (str): Path to base directory fov_names (list): List of fov files to write channel_names (list): Channel names shape (tuple): Single image shape (x pixels, y pixels) sub_dir (str): Ignored. fills (bool): If False, data is randomized. If True, each single image will be filled with a value one less than that of the next channel. If said image is the last channel, then the value is one less than that of the first channel in the next fov. dtype (type): Data type for generated images Returns: tuple (dict, numpy.ndarray): - File locations, indexable by fov names - Image data as an array with shape (num_fovs, shape[0], shape[1], num_channels) """ tif_data = _gen_tif_data(len(fov_names), len(channel_names), shape, fills, dtype) filelocs = {} mass_map = tuple(enumerate(channel_names, 1)) for i, fov in enumerate(fov_names): tif_obj = mi.MibiImage(tif_data[i, :, :, :], mass_map, **MIBITIFF_METADATA) tiffpath = os.path.join(base_dir, f'{fov}.tiff') tiff.write(tiffpath, tif_obj, dtype=dtype) filelocs[fov] = tiffpath return filelocs, tif_data
def test_bioformats(self): n = 1024 data = np.random.randint(0, 255, (n, n, len(CHANNELS_NON_ASCII))).astype(float) big_float_image = mi.MibiImage(data, CHANNELS_NON_ASCII, **METADATA) tiff.write(self.filename, big_float_image, multichannel=True, dtype=np.float32) bftools_url = ('https://downloads.openmicroscopy.org/bio-formats/' '6.7.0/artifacts/bftools.zip') bftools_zip = os.path.basename(bftools_url) self.assertEqual(os.system(f'wget {bftools_url}'), 0) self.assertEqual(os.system(f'unzip {bftools_zip}'), 0) self.assertEqual(os.system(f'rm {bftools_zip}'), 0) # Using a convert script here since it doesn't need GUI and # still errors out if the MIBItiff cannot be read using the # bioformats plugin. self.assertEqual( os.system(f'./bftools/bfconvert {self.filename} converted.tiff'), 0) self.assertEqual(os.system(f'rm -rf bftools'), 0) self.assertEqual(os.system(f'rm {self.filename} converted.tiff'), 0)
def merge_mibitiffs(input_folder, out=None): """Merges a folder of single-channel MIBItiff files into a single MIBItiff. Args: input_folder: Path to a folder containing MIBItiff files. While these files may be single-channel, they are assumed to have accurate and consistent MIBI metadata. out: Optionally, a path to a location for saving the combined TIFF. If not specified, defaults to 'combined.tiff' inside the input folder. """ pattern = re.compile(r'.+\.tiff?$') paths = [ os.path.join(input_folder, f) for f in os.listdir(input_folder) if re.match(pattern, f.lower()) ] merged = tiff.read(paths[0]) for path in paths[1:]: image = tiff.read(path) merged.append(image) if out is None: out = os.path.join(input_folder, 'combined.tiff') tiff.write(out, merged, multichannel=True)
def test_write_invalid_input(self): # not MibiImage with self.assertRaises(ValueError): tiff.write(self.filename, DATA) # no coordinates metadata = METADATA.copy() del metadata['coordinates'] image = mi.MibiImage(DATA, CHANNELS, **metadata) with self.assertRaises(ValueError): tiff.write(self.filename, image) # no size metadata = METADATA.copy() del metadata['size'] image = mi.MibiImage(DATA, CHANNELS, **metadata) with self.assertRaises(ValueError): tiff.write(self.filename, image) # string rather than tuple channels channels = [c[1] for c in CHANNELS] image = mi.MibiImage(DATA, channels, **METADATA) with self.assertRaises(ValueError): tiff.write(self.filename, image)
'scans': '0,5', 'folder': 'Point1/RowNumber0/Depth_Profile0', 'aperture': '300um', 'instrument': 'MIBIscope1', 'tissue': 'Random', 'panel': '20100101_1x', 'version': None, 'mass_offset': 0.1, 'mass_gain': 0.2, 'time_resolution': 0.5, 'miscalibrated': False, 'check_reg': False, 'filename': '20191113_random_old_metadata' } # create MibiImage with random data np.random.seed(2468) random_channel_data = np.random.randint(0, 65535, (128, 128, 3), dtype=np.uint16) channels = ['Channel 1', 'Channel 2', 'Channel 3'] random_mibi_image = mi.MibiImage(random_channel_data, channels, **OLD_METADATA) # INFO: use old software if tiff should be saved with old metadata format # (before version '1.0') # save a tiff out_file_name = random_mibi_image.filename + '.tiff' if SAVE_OUTPUT: print(f'Saving image to {out_file_name}') tiff.write(out_file_name, new_image)
def test_read_with_invalid_return_types(self): tiff.write(self.filename, self.image) with self.assertRaises(ValueError): tiff.read(self.filename, sims=False)
def create_mibitiffs(input_folder, run_path, point, panel_path, slide, size, run_label=None, instrument=None, tissue=None, aperture=None, out=None): """Combines single-channel TIFFs into a MIBItiff. The input TIFFs are not assumed to have any MIBI metadata. If they do, it is suggested to use the simpler :meth:`~merge_mibitiffs` instead. Args: input_folder: Path to a folder containing single-channel TIFFs. run_path: Path to a run xml. point: Point name of the image, e.g. Point1 or Point2. This should match the name of folder generated for the raw data as it is listed in the run xml file. panel_path: Path to a panel CSV. slide: The slide ID. size: The size of the FOV in microns, i.e. 500. run_label: Optional custom run label for the combined TIFF. If uploading the output to MIBItracker, the run label set here must match the label of the MIBItracker run. Defaults to the name of the run xml. instrument: Optionally, the instrument ID. tissue: Optionally, the name of tissue. aperture: Optionally, the name of the aperture or imaging preset. out: Optionally, a path to a location for saving the combined TIFF. If not specified, defaults to 'combined.tiff' inside the input folder. run_label: Optionally, a custom run label for the `run` property of the image. """ panel_df = panels.read_csv(panel_path) panel_name, _ = os.path.splitext(os.path.basename(panel_path)) tiff_files = os.listdir(input_folder) fovs, calibration = runs.parse_xml(run_path) point_number = int(point[5:]) try: fov = fovs[point_number - 1] # point number is 1-based, not 0-based except IndexError: raise IndexError('{} not found in run xml.'.format(point)) if fov['date']: run_date = datetime.datetime.strptime(fov['date'], '%Y-%m-%dT%H:%M:%S').date() else: run_date = datetime.datetime.now().date() image_data = [] for i in panel_df.index: tiff_path = os.path.join( input_folder, _match_target_filename(tiff_files, panel_df['Target'][i])) data = _load_single_channel(tiff_path) image_data.append(data) image_data = np.stack(image_data, axis=2) image = mi.MibiImage(image_data, list(zip(panel_df['Mass'], panel_df['Target']))) image.size = int(size) image.coordinates = (fov['coordinates']) image.filename = fov['run'] image.run = run_label if run_label else fov['run'] image.version = tiff.SOFTWARE_VERSION image.instrument = instrument image.slide = slide image.dwell = fov['dwell'] image.scans = fov['scans'] image.aperture = aperture image.point_name = fov['point_name'] image.folder = fov['folder'] image.tissue = tissue image.panel = panel_name image.date = run_date image.mass_offset = calibration['MassOffset'] image.mass_gain = calibration['MassGain'] image.time_resolution = calibration['TimeResolution'] if out is None: out = os.path.join(input_folder, 'combined.tiff') tiff.write(out, image, multichannel=True)
def test_read_sed_only(self): tiff.write(self.filename, self.image, sed=SED) sed = tiff.read(self.filename, sims=False, sed=True) np.testing.assert_array_equal(sed, SED)