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

Refactor to take out problematic dependencies #8

Merged
merged 7 commits into from
Dec 20, 2022
Merged
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
6 changes: 2 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ build-backend = "setuptools.build_meta"
name = "quoll"
version = "0.0.2"
dependencies = [
"jpype1==0.7.5",
"pims==0.4.1",
"matplotlib",
"miplib",
"miplib @ git+https://github.com/elainehoml/miplib.git@frc_deps_only",
"mrcfile",
"notebook",
"numpy",
"pandas",
"scikit-image",
"scipy",
"tifffile>2021",
"tifffile",
]
authors = [
{name="Elaine Ho", email="elaine.ho@rfi.ac.uk"}
Expand Down
54 changes: 37 additions & 17 deletions src/quoll/frc/oneimg.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
from miplib.data.containers.fourier_correlation_data import \
FourierCorrelationDataCollection
from miplib.data.containers.image import Image as miplibImage
from miplib.data.io import read as miplibread
from miplib.processing import windowing

from quoll.io import reader, tiles
from quoll.frc import frc_calibration_functions as cf

Expand All @@ -45,13 +45,13 @@ def miplib_oneimg_FRC_calibrated(
""" Calculate single image FRC, adapted from miplib

Args:
image (miplib image object): x-values in the FRC curve
image (miplib.data.containers.image.Image): Image to be evaluated
args (argparse Namespace): parameters for FRC calculation.
see *miplib.ui.frc_options* for details.
default = None
calibration_func (callable): function that applies a correction factor to the
frequencies of the FRC curve to match the 1 img
FRC to the 2 img FRC
calibration_func (callable): function that applies a correction factor
to the frequencies of the FRC curve to
match the 1 img FRC to the 2 img FRC

Returns:
FourierCorrelationData object: special dict which contains the results.
Expand Down Expand Up @@ -90,9 +90,16 @@ def miplib_oneimg_FRC_calibrated(
# Apply correction
if calibration_func is not None:
frc_data[0].correlation["frequency"] = calibration_func(freqs)

else:
frc_data[0].correlation["frequency"] = freqs

# Analyze results
analyzer = frc_analysis.FourierCorrelationAnalysis(frc_data, image1.spacing[0], args)
analyzer = frc_analysis.FourierCorrelationAnalysis(
frc_data,
image1.spacing[0],
args
)

result = analyzer.execute(z_correction=z_correction)[0]

Expand All @@ -119,7 +126,10 @@ def calc_frc_res(
FourierCorrelationData object: special dict which contains the results.
"""
if Image.img_dims[0] == Image.img_dims[1]:
miplibImg = miplibread.get_image(Image.filename)
miplibImg = miplibImage(
Image.img_data,
(Image.pixel_size, Image.pixel_size)
)
args = opts.get_frc_script_options([None])
result = miplib_oneimg_FRC_calibrated(
miplibImg,
Expand All @@ -134,38 +144,48 @@ def calc_frc_res(
def calc_local_frc(
Image: reader.Image,
tile_size: int,
tiles_dir: str,
tiles_dir: Optional[str] = None,
calibration_func: Optional[Callable] = None
):
""" Calculates local FRC on a quoll Image

Image is split into square tiles and FRC resolution is calculated for all tiles
Image is split into square tiles and FRC resolution is calculated for all
tiles

Args:
Image (reader.Image): Quoll.io.reader.Image instance
tile_size (int): length of one side of the square tile in pixels
tiles_dir (str): path to directory holding tiles
tiles_dir (str): path to directory holding tiles, none by default.
Tiles only saved if tiles_dir is not none.
calibration_func (callable): function that applies a correction factor to the
frequencies of the FRC curve to match the 1 img
FRC to the 2 img FRC. If None, no calibration is
applied.

Returns:
pandas DataFrame: df containing the resolutions in physical units
of all tiles
"""
# Create patches
tiles.create_patches(Image, tile_size, tiles_dir)
tiles.create_patches(
Image=Image,
tile_size=tile_size,
tiles_output=tiles_dir
)

# Calculate local FRC on the patches
resolutions = {"Resolution": {}}
for tile in os.listdir(tiles_dir):
for i in list(Image.tiles.keys()):
try:
Img = reader.Image(os.path.join(tiles_dir, tile))
result = calc_frc_res(Img, calibration_func)
resolutions["Resolution"][tile] = result.resolution["resolution"]
Img = reader.Image(
img_data=Image.tiles[i],
pixel_size=Image.pixel_size,
unit=Image.unit,
)
result = calc_frc_res(Img)
resolutions["Resolution"][i] = result.resolution["resolution"]

except:
resolutions["Resolution"][tile] = np.nan
resolutions["Resolution"][i] = np.nan

return pd.DataFrame.from_dict(resolutions)

Expand Down
61 changes: 42 additions & 19 deletions src/quoll/io/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import mrcfile
import os
import skimage
import tifffile
from typing import Optional

import matplotlib.pyplot as plt
import mrcfile
import numpy as np
import tifffile

from quoll.io.mdoc_parser import Mdoc

from typing import Optional

class Image:
"""
Expand All @@ -29,23 +30,33 @@ class Image:

def __init__(
self,
filename: str,
filename: Optional[str] = None,
img_data: Optional[np.ndarray] = None,
pixel_size: Optional[float] = None,
unit: Optional[str] = None,
):
"""
Initialises an image object
"""Class holding Image data + metadata

Args:
filename (str): Path to the image
pixel_size (Optional[float], optional): Size of pixel in physical units. Defaults to None.
unit (Optional[str], optional): Unit of pixel size. Defaults to None.
filename (Optional[str], optional): Filename for the image,
in tiff or mrc. Defaults to None.
img_data (Optional[np.ndarray], optional): Array holding image
data. Defaults to None.
pixel_size (Optional[float], optional): Pixel size.
Defaults to None.
unit (Optional[str], optional): Physical units. Defaults to None.
"""

self.filename = filename
self.pixel_size = pixel_size
self.unit = unit
self.get_image()

if img_data is not None:
self.img_data = img_data
self.img_dims = self.img_data.shape
self.img_bitdepth = self.img_data.dtype
else:
self.get_image()

# extra attributes for holding tiles
self.tiles = {}
Expand All @@ -55,15 +66,23 @@ def get_image(self):
"""
Import an image
"""
if os.path.splitext(self.filename)[1] != ".mrc":
self.img_data = tifffile.imread(self.filename)

elif os.path.splitext(self.filename)[1] == ".mrc":
with mrcfile.open(self.filename) as mrc:
self.img_data = mrc.data

if self.filename is not None:
if os.path.splitext(self.filename)[1] != ".mrc":
self.img_data = tifffile.imread(self.filename)

elif os.path.splitext(self.filename)[1] == ".mrc":
with mrcfile.open(self.filename) as mrc:
self.img_data = mrc.data

else:
raise IOError(
"Image cannot be imported. Quoll uses skimage and mrcfile"
)
else:
raise IOError("Image cannot be imported. Quoll uses skimage and mrcfile")
raise IOError(
"No filename or image data has been specified."
)

self.img_dims = self.img_data.shape
self.img_bitdepth = self.img_data.dtype
Expand Down Expand Up @@ -99,7 +118,11 @@ def __init__(
mdoc_file: Optional[str] = None,
tilt_angle_file: Optional[str] = None,
):
super().__init__(filename, pixel_size, unit)
super().__init__(
filename=filename,
pixel_size=pixel_size,
unit=unit
)
self.img_type = "TiltSeries"
self.mdoc_file = mdoc_file
self.tilt_angle_file = tilt_angle_file
Expand Down
46 changes: 26 additions & 20 deletions src/quoll/io/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def extract_tiles(Image, tile_size_x, tile_size_y, pad=True):
return tiles, (nTilesX, nTilesY)


def create_patches(Image, tile_size, tiles_output, pad=True):
def create_patches(Image, tile_size, tiles_output=None, pad=True):
"""Split an image into square patches. All patches will be square if pad=True.
Otherwise, patches at the edges do not fit neatly into the square patches will
be returned (these are not square). Patches are saved as .tif's in tiles_output.
Expand All @@ -93,25 +93,31 @@ def create_patches(Image, tile_size, tiles_output, pad=True):
tiles, (nTilesX, nTilesY) = extract_tiles(Image, tile_size, tile_size, pad)
resolution = (1/Image.pixel_size, 1/Image.pixel_size)

if os.path.isdir(tiles_output) is False:
os.mkdir(tiles_output)
if len(os.listdir(tiles_output)) != 0:
for f in list(os.listdir(tiles_output)):
try:
os.remove(os.path.join(tiles_output, f))
except:
print(f"Could not remove {f}")
for i, tile in enumerate(tiles):
tile_fn = os.path.join(tiles_output, f"{i:03}.tif")
tifffile.imwrite(
tile_fn,
tile.astype('uint8'),
imagej=True,
resolution=resolution,
metadata={'unit': Image.unit}
)

Image.tiles[tile_fn] = tile.astype('uint8')
if tiles_output is not None:
if os.path.isdir(tiles_output) is False:
os.mkdir(tiles_output)
if len(os.listdir(tiles_output)) != 0:
for f in list(os.listdir(tiles_output)):
try:
os.remove(os.path.join(tiles_output, f))
except:
print(f"Could not remove {f}")
for i, tile in enumerate(tiles):
tile_fn = os.path.join(tiles_output, f"{i:03}.tif")
tifffile.imwrite(
tile_fn,
tile.astype('uint8'),
imagej=True,
resolution=resolution,
metadata={'unit': Image.unit}
)

Image.tiles[i] = tile.astype('uint8')

else:
for i, tile in enumerate(tiles):
Image.tiles[i] = tile.astype('uint8')

Image.tile_arrangement = (nTilesX, nTilesY)


Expand Down
43 changes: 6 additions & 37 deletions tests/test_frc_oneimg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import numpy as np

import miplib.ui.cli.miplib_entry_point_options as opts
from miplib.data.io import read as miplibread
# from miplib.data.io import read as miplibread
from miplib.data.containers.image import Image as miplibImage
from quoll.frc import oneimg
from quoll.frc import frc_calibration_functions as cf
from quoll.io import reader
Expand All @@ -31,26 +32,11 @@ class OneImgFRCTest(unittest.TestCase):
Test image = ChargeSuppression/SerialFIB-79, Tile 42
"""

def test_miplib_basic(self):
"""
Tests that the adapted miplib one image FRC works with basic case
"""
miplibImg = miplibread.get_image("./data/042.tif")
args = opts.get_frc_script_options([None])

result = oneimg.miplib_oneimg_FRC_calibrated(
miplibImg,
args,
calibration_func=None
)

self.assertIsNotNone(result)

def test_calc_frc_res(self):
"""
Tests that the one-image FRC can be calculated from quoll.io.reader.Image
"""
Img = reader.Image("./data/042.tif")
Img = reader.Image("./data/042.tif", pixel_size=3.3724)
result = oneimg.calc_frc_res(
Image=Img,
calibration_func=cf.calibration_func_RFI
Expand Down Expand Up @@ -107,27 +93,10 @@ def test_set_calibration_func(self):
In this test, calibration function is set to return the exact same
value, so the result should be equal to uncalibrated.
"""
Img = reader.Image("./data/042.tif")
result_calibrated = oneimg.calc_frc_res(
Image=Img,
calibration_func=lambda x: x # just return original value
)
result_uncalibrated = oneimg.calc_frc_res(
Image=Img,
calibration_func=None
Img = reader.Image(
filename="./data/042.tif",
pixel_size=3.3724
)
self.assertAlmostEqual(
result_calibrated.resolution["resolution"],
result_uncalibrated.resolution["resolution"])
self.assertIsNotNone(result_calibrated.resolution["resolution"])
self.assertIsNotNone(result_uncalibrated.resolution["resolution"])
def test_set_calibration_func(self):
"""
Tests that calibration function can be set to a custom function
In this test, calibration function is set to return the exact same
value, so the result should be equal to uncalibrated.
"""
Img = reader.Image("./data/042.tif")
result_calibrated = oneimg.calc_frc_res(
Image=Img,
calibration_func=lambda x: x # just return original value
Expand Down
Loading