Skip to content

tiff_io

Source code in navis/io/tiff_io.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class TiffReader(base.ImageReader):
    def __init__(
        self,
        output: Literal["voxels", "dotprops", "raw"] = "voxels",
        channel: int = 0,
        threshold: Optional[Union[int, float]] = None,
        thin: bool = False,
        dotprop_kwargs: Dict[str, Any] = {},
        fmt: str = DEFAULT_FMT,
        errors: str = "raise",
        attrs: Optional[Dict[str, Any]] = None,
    ):
        if not fmt.endswith(".tif") and not fmt.endswith(".tiff"):
            raise ValueError('`fmt` must end with ".tif" or ".tiff"')

        super().__init__(
            fmt=fmt,
            attrs=attrs,
            file_ext=(".tif", ".tiff"),
            name_fallback="TIFF",
            read_binary=True,
            output=output,
            threshold=threshold,
            thin=thin,
            dotprop_kwargs=dotprop_kwargs,
            errors=errors,
        )
        self.channel = channel

    def format_output(self, x):
        # This function replaces the BaseReader.format_output()
        # This is to avoid trying to convert multiple (image, header) to NeuronList
        if self.output == "raw":
            return x
        elif x:
            return core.NeuronList([n for n in x if n])
        else:
            return core.NeuronList([])

    @base.handle_errors
    def read_buffer(
        self, f, attrs: Optional[Dict[str, Any]] = None
    ) -> Union[np.ndarray, "core.Dotprops", "core.VoxelNeuron"]:
        """Read buffer into (image, header) or a neuron.

        Parameters
        ----------
        f :         IO
                    Readable buffer (must be bytes).
        attrs :     dict | None
                    Arbitrary attributes to include in the neuron.

        Returns
        -------
        core.Dotprops | core.VoxelNeuron | np.ndarray

        """
        import tifffile

        if isinstance(f, HTTPResponse):
            f = io.StringIO(f.content)

        if isinstance(f, bytes):
            f = io.BytesIO(f)

        with tifffile.TiffFile(f) as tif:
            # The header contains some but not all the info
            if hasattr(tif, "imagej_metadata") and tif.imagej_metadata is not None:
                header = tif.imagej_metadata
            else:
                header = {}

            # Read the x/y resolution from the first "page" (i.e. the first slice)
            res = tif.pages[0].resolution
            # Resolution to spacing
            header["xy_spacing"] = (1 / res[0], 1 / res[1])

            # Get the axes; this will be something like "ZCYX" where:
            # Z = slices, C = channels, Y = rows, X = columns, S = color(?), Q = empty(?)
            axes = tif.series[0].axes

            # Generate volume
            data = tif.asarray()

        if self.output == "raw":
            return data, header

        # Drop "Q" axes if they have dimenions of 1 (we're assuming these are empty)
        while "Q" in axes and data.shape[axes.index("Q")] == 1:
            data = np.squeeze(data, axis=axes.index("Q"))
            axes = axes.replace("Q", "", 1)  # Only remove the first occurrence
        if "C" in axes:
            # Extract the requested channel from the volume
            data = data.take(self.channel, axis=axes.index("C"))
            axes = axes.replace("C", "")

        # At this point we expect 3D data
        if data.ndim != 3:
            raise ValueError(f'Expected 3D greyscale data, got {data.ndim} ("{axes}").')

        # Swap axes to XYZ order
        order = []
        for a in ("X", "Y", "Z"):
            if a not in axes:
                logger.warning(
                    f'Expected axes to contain "Z", "Y", and "X", got "{axes}". '
                    "Axes will not be automatically reordered."
                )
                order = None
                break
            order.append(axes.index(a))
        if order:
            data = np.transpose(data, order)

        # Try parsing units - this is modelled after the tif files you get from ImageJ
        units = None
        space_units = None
        voxdim = np.array([1, 1, 1], dtype=np.float64)
        if "spacing" in header:
            voxdim[2] = header["spacing"]
        if "xy_spacing" in header:
            voxdim[:2] = header["xy_spacing"]
        if "unit" in header:
            space_units = header["unit"]
            units = [f"{m} {space_units}" for m in voxdim]
        else:
            units = voxdim

        return self.convert_image(data, attrs, header, voxdim, units, space_units)

Read buffer into (image, header) or a neuron.

PARAMETER DESCRIPTION
f
    Readable buffer (must be bytes).

TYPE: IO

attrs
    Arbitrary attributes to include in the neuron.

TYPE: dict | None DEFAULT: None

RETURNS DESCRIPTION
core.Dotprops | core.VoxelNeuron | np.ndarray
Source code in navis/io/tiff_io.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@base.handle_errors
def read_buffer(
    self, f, attrs: Optional[Dict[str, Any]] = None
) -> Union[np.ndarray, "core.Dotprops", "core.VoxelNeuron"]:
    """Read buffer into (image, header) or a neuron.

    Parameters
    ----------
    f :         IO
                Readable buffer (must be bytes).
    attrs :     dict | None
                Arbitrary attributes to include in the neuron.

    Returns
    -------
    core.Dotprops | core.VoxelNeuron | np.ndarray

    """
    import tifffile

    if isinstance(f, HTTPResponse):
        f = io.StringIO(f.content)

    if isinstance(f, bytes):
        f = io.BytesIO(f)

    with tifffile.TiffFile(f) as tif:
        # The header contains some but not all the info
        if hasattr(tif, "imagej_metadata") and tif.imagej_metadata is not None:
            header = tif.imagej_metadata
        else:
            header = {}

        # Read the x/y resolution from the first "page" (i.e. the first slice)
        res = tif.pages[0].resolution
        # Resolution to spacing
        header["xy_spacing"] = (1 / res[0], 1 / res[1])

        # Get the axes; this will be something like "ZCYX" where:
        # Z = slices, C = channels, Y = rows, X = columns, S = color(?), Q = empty(?)
        axes = tif.series[0].axes

        # Generate volume
        data = tif.asarray()

    if self.output == "raw":
        return data, header

    # Drop "Q" axes if they have dimenions of 1 (we're assuming these are empty)
    while "Q" in axes and data.shape[axes.index("Q")] == 1:
        data = np.squeeze(data, axis=axes.index("Q"))
        axes = axes.replace("Q", "", 1)  # Only remove the first occurrence
    if "C" in axes:
        # Extract the requested channel from the volume
        data = data.take(self.channel, axis=axes.index("C"))
        axes = axes.replace("C", "")

    # At this point we expect 3D data
    if data.ndim != 3:
        raise ValueError(f'Expected 3D greyscale data, got {data.ndim} ("{axes}").')

    # Swap axes to XYZ order
    order = []
    for a in ("X", "Y", "Z"):
        if a not in axes:
            logger.warning(
                f'Expected axes to contain "Z", "Y", and "X", got "{axes}". '
                "Axes will not be automatically reordered."
            )
            order = None
            break
        order.append(axes.index(a))
    if order:
        data = np.transpose(data, order)

    # Try parsing units - this is modelled after the tif files you get from ImageJ
    units = None
    space_units = None
    voxdim = np.array([1, 1, 1], dtype=np.float64)
    if "spacing" in header:
        voxdim[2] = header["spacing"]
    if "xy_spacing" in header:
        voxdim[:2] = header["xy_spacing"]
    if "unit" in header:
        space_units = header["unit"]
        units = [f"{m} {space_units}" for m in voxdim]
    else:
        units = voxdim

    return self.convert_image(data, attrs, header, voxdim, units, space_units)