diff --git a/catpy/__init__.py b/catpy/__init__.py
index feba40e..895073e 100644
--- a/catpy/__init__.py
+++ b/catpy/__init__.py
@@ -10,3 +10,5 @@ __all__ = ['client']
 
 
 from catpy.client import CatmaidClient, CoordinateTransformer, CatmaidUrl  # noqa
+from catpy import image  # noqa
+from catpy import export  # noqa
diff --git a/catpy/image.py b/catpy/image.py
index a601889..cd25378 100644
--- a/catpy/image.py
+++ b/catpy/image.py
@@ -5,6 +5,8 @@ from __future__ import division, unicode_literals
 import logging
 from io import BytesIO
 from collections import OrderedDict
+
+from requests import HTTPError
 from timeit import timeit
 import itertools
 from warnings import warn
@@ -392,6 +394,12 @@ class Stack(object):
 
 
 class ProjectStack(Stack):
+    orientation_choices = {
+        0: "xy",
+        1: "xz",
+        2: "zy",
+    }
+
     def __init__(self, dimension, translation, resolution, orientation, broken_slices=None, canary_location=None):
         """
         Representation of an image stack as it pertains to a CATMAID project
@@ -430,7 +438,8 @@ class ProjectStack(Stack):
         """
         stack = cls(
             stack_info['dimension'], stack_info['translation'], stack_info['resolution'],
-            stack_info['orientation'], stack_info['broken_slices'], stack_info['canary_location']
+            cls.orientation_choices[stack_info['orientation']], stack_info['broken_slices'],
+            stack_info['canary_location']
         )
         mirrors = [StackMirror.from_dict(d) for d in stack_info['mirrors']]
 
@@ -670,6 +679,12 @@ class ImageFetcher(object):
             raise ValueError('Unknown dimension of volume: should be 2D or 3D')
         return np.moveaxis(arr, (0, 1, 2), self._dimension_mappings)
 
+    def _make_empty_tile(self, width, height=None):
+        height = height or width
+        tile = np.empty((height, width), dtype=np.uint8)
+        tile.fill(self.cval)
+        return tile
+
     def _get_tile(self, tile_index):
         """
         Get the tile from the cache, handle broken slices, or fetch.
@@ -689,9 +704,7 @@ class ImageFetcher(object):
 
         if tile_index.depth in self.stack.broken_slices:
             if self.broken_slice_handling == BrokenSliceHandling.FILL and self.cval is not None:
-                tile = np.empty((tile_index.width, tile_index.height))
-                tile.fill(self.cval)
-                return tile
+                return self._make_empty_tile(tile_index.width, tile_index.height)
             else:
                 raise NotImplementedError(
                     "'fill' with a non-None cval is the only implemented broken slice handling mode"
@@ -813,7 +826,14 @@ class ImageFetcher(object):
         Future of np.ndarray in source orientation
         """
         url = self.mirror.generate_url(tile_index)
-        return response_to_array(self._session.get(url, timeout=self.timeout))
+        try:
+            return response_to_array(self._session.get(url, timeout=self.timeout))
+        except HTTPError as e:
+            if e.response.status_code == 404:
+                logger.warning("Tile not found at %s (error 404), returning blank tile", url)
+                return self._make_empty_tile(tile_index.width, tile_index.height)
+            else:
+                raise
 
     def _reorient_roi_tgt_to_src(self, roi_tgt):
         return roi_tgt[:, self._dimension_mappings]

Instantiating ImageFetcher from CatMaid fails due to integer orientation
diff --git a/tests/test_image.py b/tests/test_image.py
index 7c02cf8..b73fa41 100644
--- a/tests/test_image.py
+++ b/tests/test_image.py
@@ -6,6 +6,7 @@ import requests
 from PIL import Image
 from io import BytesIO
 from concurrent.futures import Future
+from requests import HTTPError
 
 try:
     import mock
@@ -508,7 +509,7 @@ def test_imagefetcher_set_mirror_title_warns_no_match(min_fetcher):
 
 def test_imagefetcher_set_mirror_title_warns_too_many(min_fetcher):
     min_fetcher.stack.mirrors.append(StackMirror(IMAGE_BASE, 1, 1, TILE_SOURCE_TYPE, 'png', 'title0', 10))
-    with pytest.warns(UserWarning, match='does not exist'):
+    with pytest.warns(UserWarning, match='ore than one'):
         min_fetcher.mirror = 'title0'
     assert min_fetcher._mirror == min_fetcher.stack.mirrors[0]
 
@@ -746,3 +747,17 @@ def test_imagefetcher_get_wrappers(min_fetcher, space):
     min_fetcher.get = mock.Mock()
     getattr(min_fetcher, 'get_{}_space'.format(space.value))('roi', 'zoom_level')
     min_fetcher.get.assert_called_with('roi', space, 'zoom_level', None)
+
+
+def test_404_handled_correctly(min_fetcher):
+    idx = TileIndex(0, 0, 0, 0, 100, 100)
+    min_fetcher._session.get = mock.Mock(side_effect=HTTPError(response=mock.Mock(status_code=404)))
+    with mock.patch('catpy.image.response_to_array', mock.Mock()):
+        tile = min_fetcher._fetch(idx)
+    assert tile.shape == (100, 100)
+    assert (tile == 0).sum() == tile.size
+
+
+@pytest.mark.xfail(reason="404 handling not implemented for threaded fetcher")
+def test_404_handled_correctly_threaded(min_fetcher):
+    assert False

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
3.6
requirements/prod.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
-e git+https://github.com/catmaid/catpy.git@ab4f858dda1144bec732738f406054248af7103d#egg=catpy
certifi==2021.5.30
coverage==6.2
decorator==5.1.1
execnet==1.9.0
importlib-metadata==4.8.3
iniconfig==1.1.1
networkx==1.11
numpy==1.12.1
packaging==21.3
Pillow==5.0.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
requests==2.14.2
requests-futures==0.9.7
six==1.10.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: catpy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - coverage==6.2
      - decorator==5.1.1
      - execnet==1.9.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - networkx==1.11
      - numpy==1.12.1
      - packaging==21.3
      - pillow==5.0.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - requests==2.14.2
      - requests-futures==0.9.7
      - six==1.10.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/catpy

tests/test_image.py::test_404_handled_correctly
tests/test_image.py::test_vol_maker[0-shape0-1] tests/test_image.py::test_vol_maker[1-shape1-2] tests/test_image.py::test_vol_maker[0-shape2-10] tests/test_image.py::test_vol_maker[0-shape3-46] tests/test_image.py::test_vol_maker[1-shape4-47] tests/test_image.py::test_response_to_array_png[L] tests/test_image.py::test_response_to_array_png[RGB] tests/test_image.py::test_response_to_array_png[RGBA] tests/test_image.py::test_response_to_array_jpeg[L] tests/test_image.py::test_response_to_array_jpeg[RGB] tests/test_image.py::test_predefined_format_urls_are_valid[TileSourceType.FILE_BASED-{image_base}{{depth}}/{{row}}_{{col}}_{{zoom_level}}.{file_extension}] tests/test_image.py::test_predefined_format_urls_are_valid[TileSourceType.FILE_BASED_WITH_ZOOM_DIRS-{image_base}{{depth}}/{{zoom_level}}/{{row}}_{{col}}.{file_extension}] tests/test_image.py::test_predefined_format_urls_are_valid[TileSourceType.DIR_BASED-{image_base}{{zoom_level}}/{{depth}}/{{row}}/{{col}}.{file_extension}] tests/test_image.py::test_predefined_format_urls_are_valid[TileSourceType.RENDER_SERVICE-{image_base}largeDataTileSource/{tile_width}/{tile_height}/{{zoom_level}}/{{depth}}/{{row}}/{{col}}.{file_extension}] tests/test_image.py::test_predefined_format_urls_are_valid[TileSourceType.FLIXSERVER-{image_base}{{depth}}/{{row}}_{{col}}_{{zoom_level}}.{file_extension}] tests/test_image.py::test_as_future_for_not_future tests/test_image.py::test_as_future_for_future tests/test_image.py::test_fill_tiled_cuboid tests/test_image.py::test_fill_tiled_cuboid_raises tests/test_image.py::test_dict_subtract_mismatched_keys tests/test_image.py::test_dict_subtract tests/test_image.py::test_tile_index_coords tests/test_image.py::test_tile_index_comparable[zoom_level] tests/test_image.py::test_tile_index_comparable[height] tests/test_image.py::test_tile_index_comparable[width] tests/test_image.py::test_tile_index_url_kwargs tests/test_image.py::test_stackmirror_corrects_image_base tests/test_image.py::test_stackmirror_corrects_file_extension tests/test_image.py::test_stackmirror_formats_url[TileSourceType.FILE_BASED] tests/test_image.py::test_stackmirror_formats_url[TileSourceType.FILE_BASED_WITH_ZOOM_DIRS] tests/test_image.py::test_stackmirror_formats_url[TileSourceType.DIR_BASED] tests/test_image.py::test_stackmirror_formats_url[TileSourceType.RENDER_SERVICE] tests/test_image.py::test_stackmirror_formats_url[TileSourceType.FLIXSERVER] tests/test_image.py::test_stackmirror_raises_on_incompatible_tile_index tests/test_image.py::test_stackmirror_get_tile_index tests/test_image.py::test_stack_sets_broken_slices_canary tests/test_image.py::test_stack_fastest_mirror_calls_get tests/test_image.py::test_stack_fastest_mirror_raises tests/test_image.py::test_tilecache_can_set tests/test_image.py::test_tilecache_set_refreshes_old tests/test_image.py::test_tilecache_can_get tests/test_image.py::test_tilecache_lru tests/test_image.py::test_tilecache_can_clear tests/test_image.py::test_tilecache_can_constrain_len tests/test_image.py::test_tilecache_can_constrain_bytes tests/test_image.py::test_imagefetcher_can_instantiate tests/test_image.py::test_imagefetcher_mirror_fallback_warning tests/test_image.py::test_imagefetcher_set_mirror_none tests/test_image.py::test_imagefetcher_set_mirror_mirror tests/test_image.py::test_imagefetcher_set_mirror_mirror_raises tests/test_image.py::test_imagefetcher_set_mirror_int tests/test_image.py::test_imagefetcher_set_mirror_int_as_str tests/test_image.py::test_imagefetcher_set_mirror_position_warns_no_match tests/test_image.py::test_imagefetcher_set_mirror_position_warns_too_many tests/test_image.py::test_imagefetcher_set_mirror_title tests/test_image.py::test_imagefetcher_set_mirror_title_warns_no_match tests/test_image.py::test_imagefetcher_set_mirror_title_warns_too_many tests/test_image.py::test_imagefetcher_get_auth_default tests/test_image.py::test_imagefetcher_get_auth_from_mirror tests/test_image.py::test_imagefetcher_get_auth_fallback tests/test_image.py::test_imagefetcher_clear_cache tests/test_image.py::test_imagefetcher_map_dimensions tests/test_image.py::test_imagefetcher_reorient tests/test_image.py::test_imagefetcher_reorient_expands tests/test_image.py::test_imagefetcher_reorient_throws tests/test_image.py::test_imagefetcher_roi_to_tiles[roi0-expected_drc0-expected_yx_minmax0] tests/test_image.py::test_imagefetcher_roi_to_tiles[roi1-expected_drc1-expected_yx_minmax1] tests/test_image.py::test_imagefetcher_roi_to_tiles[roi2-expected_drc2-expected_yx_minmax2] tests/test_image.py::test_imagefetcher_roi_to_tiles[roi3-expected_drc3-expected_yx_minmax3] tests/test_image.py::test_imagefetcher_roi_to_tiles[roi4-expected_drc4-expected_yx_minmax4] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-scaled-0-expected0] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-stack-0-expected1] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-stack--2-expected2] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-stack-1-expected3] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-project-0-expected4] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-project--2-expected5] tests/test_image.py::test_imagefetcher_roi_to_scaled[ImageFetcher-project-1-expected6] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-scaled-0-expected0] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-stack-0-expected1] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-stack--2-expected2] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-stack-1-expected3] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-project-0-expected4] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-project--2-expected5] tests/test_image.py::test_imagefetcher_roi_to_scaled[ThreadedImageFetcher-project-1-expected6] tests/test_image.py::test_imagefetcher_roi_to_scaled_raises[ImageFetcher] tests/test_image.py::test_imagefetcher_roi_to_scaled_raises[ThreadedImageFetcher] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi0-1] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi1-2] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi2-1] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi3-2] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi4-2] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi5-4] tests/test_image.py::test_imagefetcher_get[ImageFetcher-roi6-12] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi0-1] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi1-2] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi2-1] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi3-2] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi4-2] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi5-4] tests/test_image.py::test_imagefetcher_get[ThreadedImageFetcher-roi6-12] tests/test_image.py::test_imagefetcher_get_into_array[ImageFetcher] tests/test_image.py::test_imagefetcher_get_into_array[ThreadedImageFetcher] tests/test_image.py::test_imagefetcher_get_tile_from_cache[ImageFetcher] tests/test_image.py::test_imagefetcher_get_tile_from_cache[ThreadedImageFetcher] tests/test_image.py::test_imagefetcher_get_tile_from_broken_slice[ImageFetcher] tests/test_image.py::test_imagefetcher_get_tile_from_broken_slice[ThreadedImageFetcher] tests/test_image.py::test_imagefetcher_get_tile_from_fetch tests/test_image.py::test_imagefetcher_fetch tests/test_image.py::test_imagefetcher_get_wrappers[stack] tests/test_image.py::test_imagefetcher_get_wrappers[scaled] tests/test_image.py::test_imagefetcher_get_wrappers[project]
catpy/image.py catpy/__init__.py
catpy/image.py catpy/__init__.py