diff --git a/.gitignore b/.gitignore
index 08aaa8ee46cef6362c9395208a3ab9ba1f2b3f48..a49dc7eddd6144f2d4a44bbd6c0aa6323186679d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@
 .idea/*
 
 # build and conda-build artifacts
-build/*
-conda-bld/*
+*build/
+*conda-bld/
 dist/*
-*.egg-info/*
\ No newline at end of file
+*.egg-info
\ No newline at end of file
diff --git a/README.md b/README.md
index 86b9852fabe6af13b049125948a373c499a82c60..ee124fd541e5e6c00e82cea924754a35af33006a 100644
--- a/README.md
+++ b/README.md
@@ -12,14 +12,15 @@ and exposes an extensible API to facilitate low-latency analysis.
 1. Install Miniforge for environment management:<br>https://github.com/conda-forge/miniforge/releases
 2. Under the Start menu, open `Miniforge3 > Miniforge Prompt`
 
-## Option 2: install SVLT from source:
+## Install SVLT from source:
 1. Install Git:<br>https://git-scm.com/download/win
-2. In the new terminal, clone the model_server repository:<br>`cd %userprofile%`<br>`git clone https://git.embl.de/grp-almf/svlt.git`
-3. Create the target environment: `mamba env create --file requirements.yml --name svlt_env`
-4. Activate the target environment: `mamba activate svlt_env`
-5. Add the project source as a Python package: `pip install --no-deps -e .`
+2. In the new terminal, clone the model_server repository:<br>`cd %userprofile%`<br>`git clone git@git.embl.de:grp-almf/svlt.git`
+3. Requirements depend on which SVLT packages to include.  To create the target environment for svlt-pheno:<br>
+`mamba env create --file svlt-pheno/requirements.yml --name svlt-pheno-env`
+4. Activate the target environment: `mamba activate svlt-pheno-env`
+5. Add the same packages from source in editable mode: `pip install --no-deps -e ./svlt-core ./svlt-pheno`
 
 ## To start the server:
-1. From the Miniforge prompt, run `mamba activate svlt_env`
+1. From the Miniforge prompt, run `mamba activate svlt-pheno-env`
 2. Then run `python -m scripts.run_server --port 6221`
 3. A browser window should appear, with basic status information.
diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml
deleted file mode 100644
index 7a71c05a02dabd7701bd4480e5ace864e87dc657..0000000000000000000000000000000000000000
--- a/conda-recipe/meta.yaml
+++ /dev/null
@@ -1,49 +0,0 @@
-{% set name = "model_server" %}
-{% set version = "2024.7.26" %}
-{% set pyproject = load_file_data('../pyproject.toml', from_recipe_dir=True) %}
-{% set pp = pyproject.get('project') %}
-
-debug:
-  {{ pyproject|pprint }}
-
-package:
-  name: {{ pp.get('name') }}
-  version: {{ pp.get('version') }}
-
-source:
-  - path: ..
-  - path: ../tests
-    folder: tests
-
-build:
-  noarch: python
-  script: {{ PYTHON }} -m pip install -vv --no-deps .
-  number: 0
-
-requirements:
-  host:
-    - python >=3.9
-    - setuptools >=61.0
-    - pip
-  run:
-    - python >=3.9
-    {% for dep in pp.get('dependencies') %}
-    - {{ dep.lower() }}
-    {% endfor %}
-    - pip
-
-
-test:
-  imports:
-    - model_server
-  commands:
-    - python -m unittest discover
-  requires:
-    - pip
-  source_files:
-    - tests
-
-about:
-  summary: Service for analyzing microscope images
-  license: 'MIT'
-  license_file: ../LICENSE
\ No newline at end of file
diff --git a/conda-recipe/publish.py b/conda-recipe/publish.py
deleted file mode 100644
index 071d9a5f52e6950848653e4d78e010eea65993b4..0000000000000000000000000000000000000000
--- a/conda-recipe/publish.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""
-Automate registration of conda build artifacts to EMBL GitLab;
-assumes API access token is recorded in ~/.pypirc shell configuration file
-"""
-from configparser import ConfigParser
-import json
-from pathlib import Path
-import requests
-
-id = '5668'
-proj = 'model_server'
-root = Path('../conda-bld/')
-
-# get authentication info from local config file
-cfg = ConfigParser()
-cfg.read(Path.home() / '.pypirc')
-user = cfg['gitlab-model-server']['username']
-pwd = cfg['gitlab-model-server']['password']
-
-with open(root / 'channeldata.json', 'r') as fh:
-    chdata = json.load(fh)
-
-# upload to GitLab API
-res = {}
-for sd in ['noarch', 'win-64']:
-    with open(root / sd / 'repodata.json', 'r') as fh:
-        dd = json.load(fh)
-    pkgname = f'conda_{sd}'
-
-    if len(dd['packages']) == 0:
-        continue
-
-    # put each .tar.bz2
-    for fn in dd['packages'].keys():
-        ver = dd['packages'][fn]['version']
-        stem = fn.split('.tar.bz2')[0]
-        res[(sd, fn)] = requests.put(
-            f'https://git.embl.de/api/v4/projects/{id}/packages/generic/{pkgname}/{ver}/{fn}?status=default',
-            files={'file': open(root / sd / fn, 'rb')},
-            headers={'PRIVATE-TOKEN': pwd},
-        )
-
-    # put requirements.yml
-    fn = 'requirements.yml'
-    res[(sd, fn)] = requests.put(
-        f'https://git.embl.de/api/v4/projects/{id}/packages/generic/{pkgname}/{ver}/{fn}?status=default',
-        files={'file': open(root.parent / fn, 'r')},
-        headers={'PRIVATE-TOKEN': pwd, 'Content-Type': 'text/html'},
-    )
-print('Finished')
-print(res)
\ No newline at end of file
diff --git a/scripts/run_server.py b/scripts/run_server.py
deleted file mode 100644
index 3710c86eaec383a4bd8816a4a8f81992db01914f..0000000000000000000000000000000000000000
--- a/scripts/run_server.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import argparse
-
-from model_server.conf.defaults import root, server_conf
-from model_server.conf.startup import main
-
-def parse_args():
-    parser = argparse.ArgumentParser(
-        description='Start model server with optional arguments',
-    )
-    parser.add_argument(
-        '--confpath',
-        default='model_server.conf.fastapi',
-        help='path to server startup configuration',
-    )
-    parser.add_argument(
-        '--host',
-        default=server_conf['host'],
-        help='bind socket to this host'
-    )
-    parser.add_argument(
-        '--port',
-        default=str(server_conf['port']),
-        help='bind socket to this port',
-    )
-    parser.add_argument(
-        '--root',
-        default=root.__str__(),
-        help='root directory of session data'
-    )
-    parser.add_argument(
-        '--debug',
-        action='store_true',
-        help='display extra information that is helpful for debugging'
-    )
-    parser.add_argument(
-        '--reload',
-        action='store_true',
-        help='automatically restart server when changes are noticed, for development purposes'
-    )
-
-    return parser.parse_args()
-
-
-if __name__ == '__main__':
-    args = parse_args()
-    print('CLI args:\n' + str(args))
-    main(**args.__dict__)
-    print('Finished')
-
diff --git a/svlt-core/pyproject.toml b/svlt-core/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d9a17fab6e62b1d623f76a15933ecb4b3293609c
--- /dev/null
+++ b/svlt-core/pyproject.toml
@@ -0,0 +1,34 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+
+[project]
+name = "svlt-core"
+license = {file = "LICENSE"}
+version = "2025.02.01"
+authors = [
+  { name="Christopher Rhodes", email="christopher.rhodes@embl.de" },
+]
+description = "Service for analyzing microscope images"
+readme = "README.md"
+requires-python = ">=3.9"
+dependencies = [
+  "czifile",
+  "fastapi >=0.101",
+  "imagecodecs",
+  "matplotlib",
+  "numpy",
+  "pandas",
+  "pillow",
+  "pydantic ~=1.10.1",
+  "scikit-image >=0.21.0",
+  "scikit-learn >=1.5.0",
+  "tifffile",
+  "uvicorn >=0.23.0",
+  "zstd",
+]
+
+[project.urls]
+Homepage = "https://git.embl.de/rhodes/model_server"
+Issues = "https://git.embl.de/rhodes/model_server/-/issues"
\ No newline at end of file
diff --git a/svlt-core/requirements.yml b/svlt-core/requirements.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6d77a684b2e22a2e6557cdf5942822221453002a
--- /dev/null
+++ b/svlt-core/requirements.yml
@@ -0,0 +1,20 @@
+name: svlt-core
+channels:
+  - conda-forge
+dependencies:
+  - czifile
+  - fastapi>=0.101
+  - imagecodecs
+  - matplotlib
+  - nd2
+  - numpy
+  - pandas
+  - pillow
+  - pip
+  - pydantic=1.10.*
+  - python=3.9.*
+  - pytorch=1.*
+  - scikit-image>=0.21.0
+  - tifffile
+  - uvicorn>=0.23.0
+  - zstd
diff --git a/svlt-core/scripts/run_server.py b/svlt-core/scripts/run_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..57e7785d0d67b81512a0dea0296376bd93197eca
--- /dev/null
+++ b/svlt-core/scripts/run_server.py
@@ -0,0 +1,8 @@
+from svlt.conf.startup import main, parse_args
+
+if __name__ == '__main__':
+    args = parse_args()
+    print('CLI args:\n' + str(args))
+    main(confpath='svlt.api', **args.__dict__)
+    print('Finished')
+
diff --git a/svlt/core/accessors.py b/svlt-core/src/svlt/accessors.py
similarity index 99%
rename from svlt/core/accessors.py
rename to svlt-core/src/svlt/accessors.py
index 2129820310ef7f19c02326ec45b593f00ff8a863..8f7f31152101422e87dfe4fd2b0f4494f049a329 100644
--- a/svlt/core/accessors.py
+++ b/svlt-core/src/svlt/accessors.py
@@ -19,7 +19,7 @@ class GenericImageDataAccessor(ABC):
     @abstractmethod
     def __init__(self):
         """
-        Abstract base class that exposes an interfaces for image data, irrespective of whether it is instantiated
+        Abstract svlt-core class that exposes an interfaces for image data, irrespective of whether it is instantiated
         from file I/O or other means.  Enforces X, Y, C, Z dimensions in that order.
         """
         self.lazy = False
diff --git a/svlt/core/annotators.py b/svlt-core/src/svlt/annotators.py
similarity index 100%
rename from svlt/core/annotators.py
rename to svlt-core/src/svlt/annotators.py
diff --git a/svlt/core/api.py b/svlt-core/src/svlt/api.py
similarity index 90%
rename from svlt/core/api.py
rename to svlt-core/src/svlt/api.py
index 9acbf5be5427a8555aee0a92bc792b50904f9d35..f257069f5ffb8ede27d8262a6d4ef98180f5a6b0 100644
--- a/svlt/core/api.py
+++ b/svlt-core/src/svlt/api.py
@@ -7,13 +7,12 @@ from pydantic import BaseModel, Field
 
 from .accessors import generate_file_accessor, generate_multiposition_file_accessors
 from .models import BinaryThresholdSegmentationModel
-from ..rois.models import IntensityThresholdInstanceMaskSegmentationModel
-from .pipelines.shared import PipelineRecord
+from .pipelines.common import PipelineRecord
 from .session import session, AccessorIdError, InvalidPathError, WriteAccessorError
 
 app = FastAPI(debug=True)
 
-from .pipelines.router import router
+from svlt.pipelines.router import router
 app.include_router(router)
 
 
@@ -72,7 +71,7 @@ def _change_path(key, path, touch=False) -> Union[str, None]:
         session.set_data_directory(key, path)
         if touch:
             uid = str(uuid.uuid4())
-            with open(Path(path) / 'svlt.touch', 'w') as fh:
+            with open(Path(path) / '.touch', 'w') as fh:
                 fh.write(uid)
             return uid
     except InvalidPathError as e:
@@ -131,13 +130,6 @@ def load_binary_threshold_model(p: BinaryThresholdSegmentationParams, model_id=N
     return {'model_id': result}
 
 
-@app.put('/models/classify/threshold/load')
-def load_intensity_threshold_instance_segmentation_model(p: BinaryThresholdSegmentationParams, model_id=None) -> dict:
-    result = session.load_model(IntensityThresholdInstanceMaskSegmentationModel, key=model_id, params=p)
-    session.log_info(f'Loaded permissive instance segmentation model {result}')
-    return {'model_id': result}
-
-
 @app.get('/accessors')
 def list_accessors() -> Dict:
     return session.list_accessors()
@@ -257,11 +249,4 @@ def get_output_accessor_id_for_task(task_id: str) -> str:
 
 @app.get('/tasks')
 def list_tasks() -> Dict[str, TaskInfo]:
-    return session.tasks.list_tasks()
-
-
-@app.get('/phenobase/bounding_box')
-def get_phenobase_bounding_boxes() -> list:
-    if session.phenobase is None:
-        return []
-    return session.phenobase.list_bounding_boxes()
\ No newline at end of file
+    return session.tasks.list_tasks()
\ No newline at end of file
diff --git a/svlt/__init__.py b/svlt-core/src/svlt/clients/__init__.py
similarity index 100%
rename from svlt/__init__.py
rename to svlt-core/src/svlt/clients/__init__.py
diff --git a/svlt/clients/batch_runner.py b/svlt-core/src/svlt/clients/batch_runner.py
similarity index 99%
rename from svlt/clients/batch_runner.py
rename to svlt-core/src/svlt/clients/batch_runner.py
index 439cbeb65f888ce7b1cce7c33e286f4d610f3bef..30d6358e5b69d1b4071c07209939a944b1051965 100644
--- a/svlt/clients/batch_runner.py
+++ b/svlt-core/src/svlt/clients/batch_runner.py
@@ -89,7 +89,7 @@ class FileBatchRunnerClient(HttpClient):
             catch=catch
         )
         if verify:
-            pa_touch = local_path / 'svlt.touch'
+            pa_touch = local_path / 'svlt-pheno.touch'
             try:
                 with open(pa_touch, 'r') as fh:
                     cont = fh.read()
diff --git a/svlt/clients/py3.py b/svlt-core/src/svlt/clients/py3.py
similarity index 97%
rename from svlt/clients/py3.py
rename to svlt-core/src/svlt/clients/py3.py
index f96823bdbd997e26c4b28fa54622984d8eb7d8f9..0c399861235e4c290cd09e2d3f76ca8cc56615c4 100644
--- a/svlt/clients/py3.py
+++ b/svlt-core/src/svlt/clients/py3.py
@@ -7,7 +7,7 @@ class HttpClient(object):
     Local client for batch-running and testing purposes
     """
 
-    app_name = 'svlt.core.api:app'
+    app_name = 'svlt-pheno.src.api:app'
 
     def __init__(self, host='127.0.0.1', port=6221, raise_nonsuccess=False, **kwargs) -> None:
         self.uri = f'http://{host}:{port}/'
diff --git a/svlt/clients/__init__.py b/svlt-core/src/svlt/conf/__init__.py
similarity index 100%
rename from svlt/clients/__init__.py
rename to svlt-core/src/svlt/conf/__init__.py
diff --git a/svlt/conf/defaults.py b/svlt-core/src/svlt/conf/defaults.py
similarity index 76%
rename from svlt/conf/defaults.py
rename to svlt-core/src/svlt/conf/defaults.py
index 35352e91dce877be1832877c76ea80b3bb59ac76..4ee5e99c3282ffef1cb5ba80d07ea8a91b7ae81e 100644
--- a/svlt/conf/defaults.py
+++ b/svlt-core/src/svlt/conf/defaults.py
@@ -1,13 +1,13 @@
 from pathlib import Path
 
-root = Path.home() / 'svlt' / 'sessions'
+root = Path.home() / 'svlt-pheno' / 'sessions'
 
 subdirectories = {
     'conf': 'conf',
     'logs': 'logs',
     'inbound_images': 'images/inbound',
     'outbound_images': 'images/outbound',
-    'rois': 'rois',
+    'svlt-pheno': 'svlt-pheno',
     'tables': 'tables',
 }
 server_conf = {
diff --git a/svlt/conf/startup.py b/svlt-core/src/svlt/conf/startup.py
similarity index 59%
rename from svlt/conf/startup.py
rename to svlt-core/src/svlt/conf/startup.py
index b340363769b2ce49f0c994e3812c4031a1ec4fda..71c85ea03c5149ac1172db94578defa213eafd2f 100644
--- a/svlt/conf/startup.py
+++ b/svlt-core/src/svlt/conf/startup.py
@@ -1,3 +1,4 @@
+import argparse
 from multiprocessing import Process
 import os
 import requests
@@ -6,8 +7,10 @@ from urllib3 import Retry
 import uvicorn
 import webbrowser
 
+from svlt.conf.defaults import server_conf, root
 
-def main(host, port, confpath, reload, debug, root) -> None:
+
+def main(confpath, host, port, reload, debug, root) -> None:
 
     if root:
         os.environ['SVLT_SESSION_ROOT'] = root
@@ -56,3 +59,36 @@ def main(host, port, confpath, reload, debug, root) -> None:
 
     server_process.terminate()
     return session_path
+
+
+def parse_args():
+    parser = argparse.ArgumentParser(
+        description='Start model server with optional arguments',
+    )
+    parser.add_argument(
+        '--host',
+        default=server_conf['host'],
+        help='bind socket to this host'
+    )
+    parser.add_argument(
+        '--port',
+        default=str(server_conf['port']),
+        help='bind socket to this port',
+    )
+    parser.add_argument(
+        '--root',
+        default=root.__str__(),
+        help='root directory of session data'
+    )
+    parser.add_argument(
+        '--debug',
+        action='store_true',
+        help='display extra information that is helpful for debugging'
+    )
+    parser.add_argument(
+        '--reload',
+        action='store_true',
+        help='automatically restart server when changes are noticed, for development purposes'
+    )
+
+    return parser.parse_args()
diff --git a/svlt/conf/testing.py b/svlt-core/src/svlt/conf/testing.py
similarity index 94%
rename from svlt/conf/testing.py
rename to svlt-core/src/svlt/conf/testing.py
index 5dc2c72a5b4357a5449d26454b208aab09159205..da11763dcf2042970e0bf6b1e62af50cfd27d1dd 100644
--- a/svlt/conf/testing.py
+++ b/svlt-core/src/svlt/conf/testing.py
@@ -10,15 +10,13 @@ from fastapi import APIRouter
 import numpy as np
 from pydantic import BaseModel
 
-from .fastapi import app
-from ..core.accessors import GenericImageDataAccessor, InMemoryDataAccessor
-from ..core.models import SemanticSegmentationModel, InstanceMaskSegmentationModel
-from ..core.pipelines.shared import call_pipeline, PipelineParams, PipelineQueueRecord, PipelineTrace
-from ..core.session import session
+from svlt.ilastik.fastapi import app
+from svlt.accessors import generate_file_accessor, GenericImageDataAccessor, InMemoryDataAccessor
+from svlt.models import SemanticSegmentationModel, InstanceMaskSegmentationModel
+from ..pipelines.common import call_pipeline, PipelineParams, PipelineQueueRecord, PipelineTrace
+from svlt.session import session
 from ..clients.py3 import HttpClient
 
-from ..core.accessors import generate_file_accessor
-
 """
 Configure additional endpoints for testing
 """
@@ -179,7 +177,7 @@ def setup_test_data():
     data_paths = [
         os.environ.get('UNITTEST_DATA_ROOT'),
         _winpath(os.environ.get('UNITTEST_DATA_ROOT')),
-        Path.home() / 'svlt' / 'testing',
+        Path.home() / 'svlt-pheno' / 'testing',
         os.getcwd(),
     ]
     root = None
diff --git a/svlt/core/czi_util.py b/svlt-core/src/svlt/czi_util.py
similarity index 100%
rename from svlt/core/czi_util.py
rename to svlt-core/src/svlt/czi_util.py
diff --git a/svlt/core/models.py b/svlt-core/src/svlt/models.py
similarity index 98%
rename from svlt/core/models.py
rename to svlt-core/src/svlt/models.py
index f4064feb451804bee297839fa02c8213ee637243..657091791c101af34cf234d391bac15c5814a927 100644
--- a/svlt/core/models.py
+++ b/svlt-core/src/svlt/models.py
@@ -8,7 +8,7 @@ class Model(ABC):
 
     def __init__(self, autoload: bool = True, info: dict = None):
         """
-        Abstract base class for an inference model that uses image data as an input.
+        Abstract svlt-core class for an inference model that uses image data as an input.
         :param autoload: automatically load model and dependencies into memory if True
         :param info: optional dictionary of JSON-serializable information to report to API
         """
diff --git a/svlt/conf/__init__.py b/svlt-core/src/svlt/pipelines/__init__.py
similarity index 100%
rename from svlt/conf/__init__.py
rename to svlt-core/src/svlt/pipelines/__init__.py
diff --git a/svlt/core/pipelines/shared.py b/svlt-core/src/svlt/pipelines/common.py
similarity index 88%
rename from svlt/core/pipelines/shared.py
rename to svlt-core/src/svlt/pipelines/common.py
index e9a00b562fd33dbb9ea4907244703fbcf443fa43..63ffb9255a87f9a2bb5accc4620a936f9c24a247 100644
--- a/svlt/core/pipelines/shared.py
+++ b/svlt-core/src/svlt/pipelines/common.py
@@ -7,7 +7,6 @@ from fastapi import HTTPException
 from pydantic import BaseModel, Field, root_validator
 
 from ..accessors import GenericImageDataAccessor, InMemoryDataAccessor
-from svlt.rois.roiset import PatchParams, RoiSetExportParams
 from ..session import session, AccessorIdError
 
 
@@ -258,34 +257,6 @@ class PipelineTrace(OrderedDict):
         return paths
 
 
-class RoiSetPipelineParams(PipelineParams):
-    exports: RoiSetExportParams = RoiSetExportParams()
-    roiset_index: Dict[str, int]
-    patches: Dict[str, PatchParams] = {}
-
-
-def call_roiset_pipeline(func, p: RoiSetPipelineParams) -> Union[PipelineRecord, PipelineQueueRecord]:
-    """
-    Wraps pipelines.shared.call_pipeline for pipeline functions that return an RoiSet in addition to PipelineTrace.
-    Upon execution, add the returned RoiSet to session PhenoBase and otherwise delegate to call_pipeline
-    :param func: pipeline function with the signature:
-        func(accessors: Dict[str, GenericImageDataAccessor], models: Dict[str, Model], **k) -> (PipelineTrace, RoiSet)
-    :param p: pipeline parameters object, which must contain a unique index for the RoiSet
-    :return:
-        record objects describing either the results of pipeline execution or the queued task
-    """
-    def outer(*args, **kwargs):
-        trace, rois = func(*args, **kwargs)
-        session.add_roiset(
-            roiset=rois,
-            index_dict=p.roiset_index,
-            export_params=p.exports,
-        )
-        session.write_phenobase_table()
-        return trace
-    return call_pipeline(outer, p)
-
-
 class Error(Exception):
     pass
 
diff --git a/svlt/core/pipelines/router.py b/svlt-core/src/svlt/pipelines/router.py
similarity index 100%
rename from svlt/core/pipelines/router.py
rename to svlt-core/src/svlt/pipelines/router.py
diff --git a/svlt/core/pipelines/segment.py b/svlt-core/src/svlt/pipelines/segment.py
similarity index 96%
rename from svlt/core/pipelines/segment.py
rename to svlt-core/src/svlt/pipelines/segment.py
index 8604ddd276f9e277c0a654b5bead491ca15eb801..d26f55e0242e9c4244afcd41fe081d9a60f55899 100644
--- a/svlt/core/pipelines/segment.py
+++ b/svlt-core/src/svlt/pipelines/segment.py
@@ -1,7 +1,7 @@
 from fastapi import APIRouter
 from typing import Dict, Union
 
-from .shared import call_pipeline, IncompatibleModelsError, PipelineTrace, PipelineParams, PipelineQueueRecord, PipelineRecord
+from .common import call_pipeline, IncompatibleModelsError, PipelineTrace, PipelineParams, PipelineQueueRecord, PipelineRecord
 from ..accessors import GenericImageDataAccessor
 from ..models import Model, SemanticSegmentationModel
 from ..process import smooth
diff --git a/svlt/core/pipelines/segment_zproj.py b/svlt-core/src/svlt/pipelines/segment_zproj.py
similarity index 94%
rename from svlt/core/pipelines/segment_zproj.py
rename to svlt-core/src/svlt/pipelines/segment_zproj.py
index 6067553aea2aa54ee7fbf6af558e7a7a1b5bcdef..67dc8403f9f8296a38a8dba74029fa780c9a3111 100644
--- a/svlt/core/pipelines/segment_zproj.py
+++ b/svlt-core/src/svlt/pipelines/segment_zproj.py
@@ -2,7 +2,7 @@ from fastapi import APIRouter
 from typing import Dict, Union
 
 from .segment import SegmentParams, SegmentRecord, segment_pipeline
-from .shared import call_pipeline, PipelineQueueRecord, PipelineTrace
+from .common import call_pipeline, PipelineQueueRecord, PipelineTrace
 from ..accessors import GenericImageDataAccessor
 from ..models import Model
 
diff --git a/svlt/core/process.py b/svlt-core/src/svlt/process.py
similarity index 100%
rename from svlt/core/process.py
rename to svlt-core/src/svlt/process.py
diff --git a/svlt/core/session.py b/svlt-core/src/svlt/session.py
similarity index 93%
rename from svlt/core/session.py
rename to svlt-core/src/svlt/session.py
index 7c898bb3d25adb762f83f00f1b9da4e3fa87b6e1..221987d84f31795e0a32be22bedd2ef83c1fbbfb 100644
--- a/svlt/core/session.py
+++ b/svlt-core/src/svlt/session.py
@@ -2,7 +2,6 @@ from collections import OrderedDict
 import itertools
 import logging
 import os
-from typing import Dict
 
 import psutil
 import uuid
@@ -14,11 +13,9 @@ from typing import Union
 
 import pandas as pd
 
-from ..conf import defaults
+from .conf import defaults
 from .accessors import GenericImageDataAccessor
 from .models import Model
-from svlt.rois.phenobase import PhenoBase, RoiSetIndex
-from svlt.rois.roiset import RoiSetExportParams, RoiSet
 
 logger = logging.getLogger(__name__)
 
@@ -474,38 +471,6 @@ class _Session(object):
                     return mid
         return None
 
-    def add_roiset(
-            self,
-            roiset: RoiSet,
-            index_dict: RoiSetIndex,
-            export_params: RoiSetExportParams = None,
-    ):
-        """
-        Add an RoiSet into PhenoBase; initialize PhenoBase if empty
-        :param roiset: RoiSet object to be added to PhenoBase
-        :param index_dict: dict that uniquely identifies the RoiSet; keys must match existing ones
-        :param export_params: (optional) parameters for exporting each RoiSet patch series
-        return: dict of added RoiSet in PhenoBase if successful
-        """
-        if self.phenobase is None:
-            self.phenobase = PhenoBase.from_roiset(
-                root=self.paths['outbound_images'],
-                roiset=roiset,
-                index_dict=index_dict,
-                export_params=export_params,
-            )
-        else:
-            self.phenobase.push(
-                roiset,
-                index_dict=index_dict,
-                export_params=export_params,
-            )
-        return index_dict
-
-    def write_phenobase_table(self):
-        if self.phenobase is not None:
-            return self.phenobase.write_df()
-
     def restart(self, **kwargs):
         self.__init__()
 
diff --git a/svlt/core/util.py b/svlt-core/src/svlt/util.py
similarity index 96%
rename from svlt/core/util.py
rename to svlt-core/src/svlt/util.py
index 173f2954d26505f6d7938102979053b4404f8e4a..bd88a1974e2bf132d7d49cb16b719a39c92bb811 100644
--- a/svlt/core/util.py
+++ b/svlt-core/src/svlt/util.py
@@ -1,15 +1,11 @@
-from collections import OrderedDict
-from math import ceil
 from pathlib import Path
 import re
 from time import localtime, strftime
-from typing import Dict, List
-from time import perf_counter
 
 import pandas as pd
 
-from svlt.core.accessors import GenericImageDataAccessor, InMemoryDataAccessor, write_accessor_data_to_file
-from svlt.core.models import Model
+from .accessors import InMemoryDataAccessor, write_accessor_data_to_file
+
 
 def autonumber_new_directory(where: str, prefix: str) -> str:
     """
diff --git a/pyproject.toml b/svlt-ilastik/pyproject.toml
similarity index 95%
rename from pyproject.toml
rename to svlt-ilastik/pyproject.toml
index d2741541a66bcf061898d7163ef5f52cec311bf9..cb5750e093fa647dff5ea5b51690f164046c1333 100644
--- a/pyproject.toml
+++ b/svlt-ilastik/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 
 [project]
-name = "model_server"
+name = "svlt-ilastik"
 license = {file = "LICENSE"}
 version = "2025.02.01"
 authors = [
@@ -19,7 +19,6 @@ dependencies = [
   "glasbey",
   "ilastik ==1.4.1b6",
   "imagecodecs",
-  "jupyterlab",
   "matplotlib",
   "numpy",
   "pandas",
diff --git a/requirements.yml b/svlt-ilastik/requirements.yml
similarity index 96%
rename from requirements.yml
rename to svlt-ilastik/requirements.yml
index 6360af6fdc28ec878db6fa0f88028ab99b0b90a3..17b48c72bea7451e62e98aad382a3899c3cc2335 100644
--- a/requirements.yml
+++ b/svlt-ilastik/requirements.yml
@@ -8,7 +8,6 @@ dependencies:
   - fastapi>=0.101
   - ilastik=1.4.1b6
   - imagecodecs
-  - jupyterlab
   - matplotlib
   - nd2
   - numpy
diff --git a/svlt-ilastik/scripts/run_server.py b/svlt-ilastik/scripts/run_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae3a6967b7bb443ed231478e5a7d703d5af1f98d
--- /dev/null
+++ b/svlt-ilastik/scripts/run_server.py
@@ -0,0 +1,8 @@
+from svlt.conf.startup import main, parse_args
+
+if __name__ == '__main__':
+    args = parse_args()
+    print('CLI args:\n' + str(args))
+    main(confpath='svlt.ilastik.fastapi', **args.__dict__)
+    print('Finished')
+
diff --git a/svlt/core/__init__.py b/svlt-ilastik/src/svlt/ilastik/__init__.py
similarity index 100%
rename from svlt/core/__init__.py
rename to svlt-ilastik/src/svlt/ilastik/__init__.py
diff --git a/svlt/core/pipelines/__init__.py b/svlt-ilastik/src/svlt/ilastik/examples/__init__.py
similarity index 100%
rename from svlt/core/pipelines/__init__.py
rename to svlt-ilastik/src/svlt/ilastik/examples/__init__.py
diff --git a/svlt/ilastik/examples/ilastik3d.py b/svlt-ilastik/src/svlt/ilastik/examples/ilastik3d.py
similarity index 100%
rename from svlt/ilastik/examples/ilastik3d.py
rename to svlt-ilastik/src/svlt/ilastik/examples/ilastik3d.py
diff --git a/svlt-ilastik/src/svlt/ilastik/fastapi.py b/svlt-ilastik/src/svlt/ilastik/fastapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3f894413911c6db84a3f7cb9c4feaf9fb15fc7d
--- /dev/null
+++ b/svlt-ilastik/src/svlt/ilastik/fastapi.py
@@ -0,0 +1,7 @@
+import importlib
+
+from svlt.api import app
+
+for ex in ['ilastik', 'rois']:
+    m = importlib.import_module(f'svlt.{ex}.router', package=__package__)
+    app.include_router(m.router)
diff --git a/svlt/ilastik/models.py b/svlt-ilastik/src/svlt/ilastik/models.py
similarity index 97%
rename from svlt/ilastik/models.py
rename to svlt-ilastik/src/svlt/ilastik/models.py
index fdcc4faa4d9120a2e55f3e70c2cfd0e0fe057be9..54b959240292f0a7d66fb49947fd1a86ebf279f7 100644
--- a/svlt/ilastik/models.py
+++ b/svlt-ilastik/src/svlt/ilastik/models.py
@@ -7,9 +7,9 @@ import warnings
 import numpy as np
 import vigra
 
-from svlt.core.accessors import PatchStack
-from svlt.core.accessors import GenericImageDataAccessor, InMemoryDataAccessor
-from svlt.core.models import Model, ImageToImageModel, InstanceMaskSegmentationModel, InvalidInputImageError, ParameterExpectedError, SemanticSegmentationModel
+from svlt.accessors import PatchStack
+from svlt.accessors import GenericImageDataAccessor, InMemoryDataAccessor
+from svlt.models import Model, ImageToImageModel, InstanceMaskSegmentationModel, InvalidInputImageError, ParameterExpectedError, SemanticSegmentationModel
 
 
 class IlastikModel(Model):
diff --git a/svlt/ilastik/__init__.py b/svlt-ilastik/src/svlt/ilastik/pipelines/__init__.py
similarity index 100%
rename from svlt/ilastik/__init__.py
rename to svlt-ilastik/src/svlt/ilastik/pipelines/__init__.py
diff --git a/svlt/ilastik/pipelines/px_then_ob.py b/svlt-ilastik/src/svlt/ilastik/pipelines/px_then_ob.py
similarity index 92%
rename from svlt/ilastik/pipelines/px_then_ob.py
rename to svlt-ilastik/src/svlt/ilastik/pipelines/px_then_ob.py
index 312178df8bf8fb2cb55e2b4ef3c724cddb7be46b..26e072c40c00360bedaebfd91b53fa7060025e47 100644
--- a/svlt/ilastik/pipelines/px_then_ob.py
+++ b/svlt-ilastik/src/svlt/ilastik/pipelines/px_then_ob.py
@@ -3,9 +3,9 @@ from typing import Dict
 from fastapi import APIRouter, HTTPException
 from pydantic import Field
 
-from svlt.core.accessors import GenericImageDataAccessor
-from svlt.core.models import Model
-from svlt.core.pipelines.shared import call_pipeline, PipelineTrace, PipelineParams, PipelineRecord
+from svlt.accessors import GenericImageDataAccessor
+from svlt.models import Model
+from svlt.pipelines.common import call_pipeline, PipelineTrace, PipelineParams, PipelineRecord
 
 from ..models import IlastikPixelClassifierModel, IlastikObjectClassifierFromPixelPredictionsModel
 
diff --git a/svlt/ilastik/router.py b/svlt-ilastik/src/svlt/ilastik/router.py
similarity index 98%
rename from svlt/ilastik/router.py
rename to svlt-ilastik/src/svlt/ilastik/router.py
index a0180de79d633cc3e96316ade0faf17660c7f5fd..81b10757c22a9bd07cedc5965091e2b06bbc84ca 100644
--- a/svlt/ilastik/router.py
+++ b/svlt-ilastik/src/svlt/ilastik/router.py
@@ -3,7 +3,7 @@ from pathlib import Path
 from fastapi import APIRouter
 from pydantic import BaseModel, Field
 
-from svlt.core.session import session
+from svlt.session import session
 
 from svlt.ilastik import models as ilm
 
diff --git a/svlt-pheno/pyproject.toml b/svlt-pheno/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..3ce5f70b6ccbf8235c94fd0c2c79f84dc34e66cd
--- /dev/null
+++ b/svlt-pheno/pyproject.toml
@@ -0,0 +1,35 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+
+[project]
+name = "svlt-pheno"
+license = {file = "LICENSE"}
+version = "2025.02.01"
+authors = [
+  { name="Christopher Rhodes", email="christopher.rhodes@embl.de" },
+]
+description = "Service for analyzing microscope images"
+readme = "README.md"
+requires-python = ">=3.9"
+dependencies = [
+  "czifile",
+  "fastapi >=0.101",
+  "glasbey",
+  "imagecodecs",
+  "matplotlib",
+  "numpy",
+  "pandas",
+  "pillow",
+  "pydantic ~=1.10.1",
+  "scikit-image >=0.21.0",
+  "scikit-learn >=1.5.0",
+  "tifffile",
+  "uvicorn >=0.23.0",
+  "zstd",
+]
+
+[project.urls]
+Homepage = "https://git.embl.de/rhodes/model_server"
+Issues = "https://git.embl.de/rhodes/model_server/-/issues"
\ No newline at end of file
diff --git a/svlt-pheno/requirements.yml b/svlt-pheno/requirements.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5100aeeed4cf7f7bc6d6e9c6e4e970d279827a97
--- /dev/null
+++ b/svlt-pheno/requirements.yml
@@ -0,0 +1,22 @@
+name: svlt-pheno
+channels:
+  - conda-forge
+dependencies:
+  - czifile
+  - fastapi>=0.101
+  - imagecodecs
+  - matplotlib
+  - nd2
+  - numpy
+  - pandas
+  - pillow
+  - pip
+  - pydantic=1.10.*
+  - python=3.9.*
+  - scikit-image>=0.21.0
+  - scikit-learn>=1.5.0
+  - tifffile
+  - uvicorn>=0.23.0
+  - zstd
+  - pip:
+      - glasbey
diff --git a/svlt-pheno/scripts/run_server.py b/svlt-pheno/scripts/run_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6d72bdfa640cea414df474e289a5b1c70369981
--- /dev/null
+++ b/svlt-pheno/scripts/run_server.py
@@ -0,0 +1,8 @@
+from svlt.conf.startup import main, parse_args
+
+if __name__ == '__main__':
+    args = parse_args()
+    print('CLI args:\n' + str(args))
+    main(confpath='svlt.rois.fastapi', **args.__dict__)
+    print('Finished')
+
diff --git a/svlt/ilastik/examples/__init__.py b/svlt-pheno/src/svlt/rois/__init__.py
similarity index 100%
rename from svlt/ilastik/examples/__init__.py
rename to svlt-pheno/src/svlt/rois/__init__.py
diff --git a/svlt/ilastik/pipelines/__init__.py b/svlt-pheno/src/svlt/rois/clients/__init__.py
similarity index 100%
rename from svlt/ilastik/pipelines/__init__.py
rename to svlt-pheno/src/svlt/rois/clients/__init__.py
diff --git a/svlt/clients/phenobase.py b/svlt-pheno/src/svlt/rois/clients/phenobase.py
similarity index 100%
rename from svlt/clients/phenobase.py
rename to svlt-pheno/src/svlt/rois/clients/phenobase.py
diff --git a/svlt/rois/derived.py b/svlt-pheno/src/svlt/rois/derived.py
similarity index 96%
rename from svlt/rois/derived.py
rename to svlt-pheno/src/svlt/rois/derived.py
index dc24b7a967f191d1b5425d8d2489540c3ece0430..bf14df1de6ff93499e3c840ccc4a125cf3bbae2e 100644
--- a/svlt/rois/derived.py
+++ b/svlt-pheno/src/svlt/rois/derived.py
@@ -3,8 +3,8 @@ from pathlib import Path
 import numpy as np
 import pandas as pd
 
-from svlt.core.models import InstanceMaskSegmentationModel
-from svlt.core.process import mask_largest_object
+from svlt.models import InstanceMaskSegmentationModel
+from svlt.process import mask_largest_object
 from svlt.rois.roiset import RoiSetExportParams, RoiSet
 
 
diff --git a/svlt/rois/df.py b/svlt-pheno/src/svlt/rois/df.py
similarity index 100%
rename from svlt/rois/df.py
rename to svlt-pheno/src/svlt/rois/df.py
diff --git a/svlt-pheno/src/svlt/rois/fastapi.py b/svlt-pheno/src/svlt/rois/fastapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..70851e15f4e415c0a7fdb8871faf486bba3ebc3f
--- /dev/null
+++ b/svlt-pheno/src/svlt/rois/fastapi.py
@@ -0,0 +1,7 @@
+import importlib
+
+from svlt.api import app
+
+for ex in ['rois']:
+    m = importlib.import_module(f'svlt.{ex}.router', package=__package__)
+    app.include_router(m.router)
diff --git a/svlt/rois/features.py b/svlt-pheno/src/svlt/rois/features.py
similarity index 100%
rename from svlt/rois/features.py
rename to svlt-pheno/src/svlt/rois/features.py
diff --git a/svlt/rois/labels.py b/svlt-pheno/src/svlt/rois/labels.py
similarity index 99%
rename from svlt/rois/labels.py
rename to svlt-pheno/src/svlt/rois/labels.py
index c8837d610a7f766e717df414ecb9698cae0a48a9..30cd9821ba5633280143c1f6c65dfe8086029dd0 100644
--- a/svlt/rois/labels.py
+++ b/svlt-pheno/src/svlt/rois/labels.py
@@ -6,7 +6,7 @@ from scipy.stats import moment
 from skimage.filters import sobel
 from skimage.measure import label, shannon_entropy, regionprops_table
 
-from svlt.core.accessors import GenericImageDataAccessor, InMemoryDataAccessor
+from svlt.accessors import GenericImageDataAccessor, InMemoryDataAccessor
 from svlt.rois.df import filter_df, insert_level, is_df_3d, df_insert_slices
 
 
diff --git a/svlt/rois/models.py b/svlt-pheno/src/svlt/rois/models.py
similarity index 94%
rename from svlt/rois/models.py
rename to svlt-pheno/src/svlt/rois/models.py
index 367a1dfd2c744fa69a994354cf86a409a0bd83e5..50ac29adbd8317eb1f20674ced865798f63a3ab5 100644
--- a/svlt/rois/models.py
+++ b/svlt-pheno/src/svlt/rois/models.py
@@ -2,8 +2,8 @@ import numpy as np
 import pandas as pd
 from skimage.measure import regionprops_table
 
-from svlt.core.accessors import GenericImageDataAccessor, PatchStack, InMemoryDataAccessor
-from svlt.core.models import InstanceMaskSegmentationModel
+from svlt.accessors import GenericImageDataAccessor, PatchStack, InMemoryDataAccessor
+from svlt.models import InstanceMaskSegmentationModel
 from svlt.rois.labels import get_label_ids
 
 
diff --git a/svlt/rois/phenobase.py b/svlt-pheno/src/svlt/rois/phenobase.py
similarity index 88%
rename from svlt/rois/phenobase.py
rename to svlt-pheno/src/svlt/rois/phenobase.py
index 8d36e4b6381f3f8624145d8662059779b67a342d..9bd759ab216509ab7957bfadc9570f3c408ce033 100644
--- a/svlt/rois/phenobase.py
+++ b/svlt-pheno/src/svlt/rois/phenobase.py
@@ -5,8 +5,9 @@ from typing import Dict, Union
 import pandas as pd
 from sklearn.model_selection import train_test_split
 
-from svlt.core.accessors import PatchStack, GenericImageFileAccessor
-from svlt.rois.roiset import PatchParams, RoiSet, RoiSetExportParams
+from svlt.accessors import PatchStack, GenericImageFileAccessor
+from svlt.session import session
+from svlt.rois.roiset import RoiSet, RoiSetExportParams
 
 class RoiSetIndex(dict):
     """
@@ -325,6 +326,60 @@ class PhenoBase(object):
     def list_bounding_boxes(self):
         return self.df.bounding_box.reset_index().to_dict(orient='records')
 
+class _SessionPhenoBase(object):
+    def __init__(self):
+        """
+        Singleton class for a phenobase attached to a server session that persists RoiSet data
+        """
+        self._phenobase = None
+
+    def add_roiset(
+            self,
+            roiset: RoiSet,
+            index_dict: RoiSetIndex,
+            export_params: RoiSetExportParams = None,
+    ):
+        """
+        Add an RoiSet into PhenoBase; initialize PhenoBase if empty
+        :param roiset: RoiSet object to be added to PhenoBase
+        :param index_dict: dict that uniquely identifies the RoiSet; keys must match existing ones
+        :param export_params: (optional) parameters for exporting each RoiSet patch series
+        return: dict of added RoiSet in PhenoBase if successful
+        """
+        if self._phenobase is None:
+            self._phenobase = PhenoBase.from_roiset(
+                root=session.paths['outbound_images'],
+                roiset=roiset,
+                index_dict=index_dict,
+                export_params=export_params,
+            )
+        else:
+            self._phenobase.push(
+                roiset,
+                index_dict=index_dict,
+                export_params=export_params,
+            )
+        return index_dict
+
+    def write_phenobase_table(self):
+        if self._phenobase is not None:
+            return self._phenobase.write_df()
+
+    def list_bounding_boxes(self):
+        if self._phenobase is not None:
+            return self._phenobase.list_bounding_boxes()
+        return []
+
+    @property
+    def count(self):
+        if self._phenobase is not None:
+            return self._phenobase.count
+        return 0
+
+
+
+session_phenobase = _SessionPhenoBase()
+
 
 class Error(Exception):
     pass
diff --git a/svlt/rois/__init__.py b/svlt-pheno/src/svlt/rois/pipelines/__init__.py
similarity index 100%
rename from svlt/rois/__init__.py
rename to svlt-pheno/src/svlt/rois/pipelines/__init__.py
diff --git a/svlt/rois/pipelines/add_roiset.py b/svlt-pheno/src/svlt/rois/pipelines/add_roiset.py
similarity index 86%
rename from svlt/rois/pipelines/add_roiset.py
rename to svlt-pheno/src/svlt/rois/pipelines/add_roiset.py
index 35248d19077f742f8108c0c65f81e766a2334219..ae95cd4b81f7a8529701e00ba0989a7d8d0ca20e 100644
--- a/svlt/rois/pipelines/add_roiset.py
+++ b/svlt-pheno/src/svlt/rois/pipelines/add_roiset.py
@@ -3,9 +3,10 @@ from typing import Dict, Union
 from fastapi import APIRouter
 from pydantic import Field
 
-from svlt.core.accessors import GenericImageDataAccessor
-from svlt.core.models import Model
-from svlt.core.pipelines.shared import call_roiset_pipeline, RoiSetPipelineParams, PipelineQueueRecord, PipelineRecord, PipelineTrace
+from svlt.accessors import GenericImageDataAccessor
+from svlt.models import Model
+from svlt.pipelines.common import PipelineQueueRecord, PipelineRecord, PipelineTrace
+from svlt.rois.pipelines.common import RoiSetPipelineParams, call_roiset_pipeline
 from svlt.rois.roiset import RoiSet, RoiSetMetaParams
 
 
diff --git a/svlt-pheno/src/svlt/rois/pipelines/common.py b/svlt-pheno/src/svlt/rois/pipelines/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e2f783fa7d3f1b69da708d3bdde98e059be91a5
--- /dev/null
+++ b/svlt-pheno/src/svlt/rois/pipelines/common.py
@@ -0,0 +1,33 @@
+from typing import Dict, Union
+
+from ..phenobase import session_phenobase
+from svlt.pipelines.common import PipelineParams, PipelineRecord, PipelineQueueRecord, call_pipeline
+from svlt.rois.roiset import RoiSetExportParams, PatchParams
+
+
+class RoiSetPipelineParams(PipelineParams):
+    exports: RoiSetExportParams = RoiSetExportParams()
+    roiset_index: Dict[str, int]
+    patches: Dict[str, PatchParams] = {}
+
+
+def call_roiset_pipeline(func, p: RoiSetPipelineParams) -> Union[PipelineRecord, PipelineQueueRecord]:
+    """
+    Wraps pipelines.shared.call_pipeline for pipeline functions that return an RoiSet in addition to PipelineTrace.
+    Upon execution, add the returned RoiSet to session PhenoBase and otherwise delegate to call_pipeline
+    :param func: pipeline function with the signature:
+        func(accessors: Dict[str, GenericImageDataAccessor], models: Dict[str, Model], **k) -> (PipelineTrace, RoiSet)
+    :param p: pipeline parameters object, which must contain a unique index for the RoiSet
+    :return:
+        record objects describing either the results of pipeline execution or the queued task
+    """
+    def outer(*args, **kwargs):
+        trace, rois = func(*args, **kwargs)
+        session_phenobase.add_roiset(
+            roiset=rois,
+            index_dict=p.roiset_index,
+            export_params=p.exports,
+        )
+        session_phenobase.write_phenobase_table()
+        return trace
+    return call_pipeline(outer, p)
diff --git a/svlt/rois/pipelines/roiset_obmap.py b/svlt-pheno/src/svlt/rois/pipelines/roiset_obmap.py
similarity index 90%
rename from svlt/rois/pipelines/roiset_obmap.py
rename to svlt-pheno/src/svlt/rois/pipelines/roiset_obmap.py
index a16865b23502c93a9182b9bd9ee2309ea9e83f8d..69c65338589049a3eacabb8ea5ed9baf7b77e71c 100644
--- a/svlt/rois/pipelines/roiset_obmap.py
+++ b/svlt-pheno/src/svlt/rois/pipelines/roiset_obmap.py
@@ -3,15 +3,15 @@ from typing import Dict, Union
 from fastapi import APIRouter
 from pydantic import BaseModel, Field
 
-from svlt.core.accessors import GenericImageDataAccessor
-from svlt.core.pipelines.segment_zproj import segment_zproj_pipeline
-from svlt.core.pipelines.shared import call_pipeline
+from svlt.accessors import GenericImageDataAccessor
+from svlt.pipelines.segment_zproj import segment_zproj_pipeline
+from svlt.pipelines.common import call_pipeline
 from svlt.rois.roiset import RoiSet, RoiSetMetaParams, RoiSetExportParams
 from svlt.rois.labels import get_label_ids
 
-from svlt.core.pipelines.shared import PipelineQueueRecord, PipelineTrace, PipelineParams, PipelineRecord
+from svlt.pipelines.common import PipelineQueueRecord, PipelineTrace, PipelineParams, PipelineRecord
 
-from svlt.core.models import Model, InstanceMaskSegmentationModel
+from svlt.models import Model, InstanceMaskSegmentationModel
 
 class RoiSetObjectMapParams(PipelineParams):
     class _SegmentationParams(BaseModel):
diff --git a/svlt/rois/router.py b/svlt-pheno/src/svlt/rois/pipelines/router.py
similarity index 75%
rename from svlt/rois/router.py
rename to svlt-pheno/src/svlt/rois/pipelines/router.py
index a28e2ffb11bd2724c2806d24f09eda358e7bf91c..96695cb003d00513d10687c1cea36719973af8a1 100644
--- a/svlt/rois/router.py
+++ b/svlt-pheno/src/svlt/rois/pipelines/router.py
@@ -3,13 +3,13 @@ import importlib
 from fastapi import APIRouter
 
 router = APIRouter(
-    prefix='/rois/pipelines',
+    prefix='/pipelines',
     tags=['pipelines'],
 )
 
 for m in ['roiset_obmap', 'add_roiset']:
     router.include_router(
         importlib.import_module(
-            f'{__package__}.pipelines.{m}'
+            f'{__package__}.{m}'
         ).router
     )
\ No newline at end of file
diff --git a/svlt/rois/roiset.py b/svlt-pheno/src/svlt/rois/roiset.py
similarity index 98%
rename from svlt/rois/roiset.py
rename to svlt-pheno/src/svlt/rois/roiset.py
index 4acf991f72c6b9b617017ee489df2512d1ded318..2ea0c8b5f6a7881dbb8551e84ba16aab8192c7a2 100644
--- a/svlt/rois/roiset.py
+++ b/svlt-pheno/src/svlt/rois/roiset.py
@@ -14,12 +14,12 @@ from skimage import draw
 from skimage.measure import approximate_polygon, find_contours, label, points_in_poly, regionprops
 from skimage.morphology import binary_dilation, disk
 
-from svlt.core.accessors import GenericImageDataAccessor, InMemoryDataAccessor, write_accessor_data_to_file
-from svlt.core.models import InstanceMaskSegmentationModel
-from svlt.core.process import get_safe_contours, pad, rescale, make_rgb, safe_add
-from svlt.core.annotators import draw_box_on_patch, draw_contours_on_patch, draw_boxes_on_3d_image
-from svlt.core.accessors import generate_file_accessor, PatchStack
-from svlt.core.process import mask_largest_object
+from svlt.accessors import GenericImageDataAccessor, InMemoryDataAccessor, write_accessor_data_to_file
+from svlt.models import InstanceMaskSegmentationModel
+from svlt.process import get_safe_contours, pad, rescale, make_rgb, safe_add
+from svlt.annotators import draw_box_on_patch, draw_contours_on_patch, draw_boxes_on_3d_image
+from svlt.accessors import generate_file_accessor, PatchStack
+from svlt.process import mask_largest_object
 from svlt.rois.df import filter_df_overlap_seg, is_df_3d, insert_level, read_roiset_df, df_insert_slices
 from svlt.rois.labels import get_label_ids, focus_metrics, make_df_from_object_ids, make_object_ids_from_df, \
     NoDeprojectChannelSpecifiedError
diff --git a/svlt-pheno/src/svlt/rois/router.py b/svlt-pheno/src/svlt/rois/router.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6bb489b48e1ed5235c9bcb9dd6b0682a4457d43
--- /dev/null
+++ b/svlt-pheno/src/svlt/rois/router.py
@@ -0,0 +1,35 @@
+import importlib
+from typing import Union
+
+from fastapi import APIRouter
+from pydantic import BaseModel, Field
+
+from svlt.session import session
+from .phenobase import session_phenobase
+from .models import IntensityThresholdInstanceMaskSegmentationModel
+
+router = APIRouter(
+    prefix='/pheno',
+    tags=['pheno'],
+)
+
+router.include_router(
+    importlib.import_module(
+        f'{__package__}.pipelines.router'
+    ).router
+)
+
+@router.get('/phenobase/bounding_box')
+def get_phenobase_bounding_boxes() -> list:
+    if session_phenobase is None:
+        return []
+    return session_phenobase.list_bounding_boxes()
+
+class BinaryThresholdSegmentationParams(BaseModel):
+    tr: Union[int, float] = Field(0.5, description='Threshold for binary segmentation')
+
+@router.put('/models/classify/threshold/load')
+def load_intensity_threshold_instance_segmentation_model(p: BinaryThresholdSegmentationParams, model_id=None) -> dict:
+    result = session.load_model(IntensityThresholdInstanceMaskSegmentationModel, key=model_id, params=p)
+    session.log_info(f'Loaded permissive instance segmentation model {result}')
+    return {'model_id': result}
\ No newline at end of file
diff --git a/svlt/conf/fastapi.py b/svlt/conf/fastapi.py
deleted file mode 100644
index f72963229f49f231f6ecc51283ac593590607133..0000000000000000000000000000000000000000
--- a/svlt/conf/fastapi.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import importlib
-
-from ..core.api import app
-
-for ex in ['ilastik', 'rois']:
-    m = importlib.import_module(f'..{ex}.router', package=__package__)
-    app.include_router(m.router)
diff --git a/svlt/core/share.py b/svlt/core/share.py
deleted file mode 100644
index fad2b2f03d1a6c19909215a3f34c28867c37ac71..0000000000000000000000000000000000000000
--- a/svlt/core/share.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
-Classes that manage data sharing between server and outside processes.  Currently just a directory on a shared
-filesystem; but may expand to include data transfer over API, shared memory objects, etc.
-"""
-
-import os
-import pathlib
-
-def validate_directory_exists(path):
-    return os.path.exists(path)
-
-class SharedImageDirectory(object):
-
-    def __init__(self, path: pathlib.Path):
-        self.path = path
-        self.is_memory_mapped = False
-
-        if not validate_directory_exists(path):
-            raise InvalidDirectoryError(f'Invalid directory:\n{path}')
-
-class InvalidDirectoryError(Exception):
-    pass
-
diff --git a/svlt/rois/pipelines/__init__.py b/tests/svlt-core/__init__.py
similarity index 100%
rename from svlt/rois/pipelines/__init__.py
rename to tests/svlt-core/__init__.py
diff --git a/tests/base/test_accessors.py b/tests/svlt-core/test_accessors.py
similarity index 97%
rename from tests/base/test_accessors.py
rename to tests/svlt-core/test_accessors.py
index 09f5a04f05459d1595d4a445b7f6187d3808db83..9aea9097f4fee586db44696b9443d6456c37b46f 100644
--- a/tests/base/test_accessors.py
+++ b/tests/svlt-core/test_accessors.py
@@ -2,9 +2,9 @@ import unittest
 
 import numpy as np
 
-from svlt.core.accessors import PatchStack, make_patch_stack_from_file, FileNotFoundError, Nd2ImageFileAccessor
+from svlt.accessors import PatchStack, make_patch_stack_from_file, FileNotFoundError, Nd2ImageFileAccessor
 
-from svlt.core.accessors import CziImageFileAccessor, DataShapeError, generate_file_accessor, InMemoryDataAccessor, PngFileAccessor, write_accessor_data_to_file, TifSingleSeriesFileAccessor
+from svlt.accessors import CziImageFileAccessor, DataShapeError, generate_file_accessor, InMemoryDataAccessor, PngFileAccessor, write_accessor_data_to_file, TifSingleSeriesFileAccessor
 import svlt.conf.testing as conf
 
 data = conf.meta['image_files']
@@ -179,7 +179,7 @@ class TestCziImageFileAccess(unittest.TestCase):
         self.assertEqual(cf.data.shape[3], mono.data.shape[2])
 
     def test_write_two_channel_png(self):
-        from svlt.core.process import resample_to_8bit
+        from svlt.process import resample_to_8bit
         cf = CziImageFileAccessor(data['czifile']['path'])
         acc = cf.get_channels([0, 1])
         opa = output_path / f'{cf.fpath.stem}_2ch.png'
@@ -528,7 +528,7 @@ class TestNd2ImageFileAccess(unittest.TestCase):
 
 class TestMultipositionCziFileAccess(unittest.TestCase):
     def test_multiposition_czi(self):
-        from svlt.core.accessors import generate_multiposition_file_accessors
+        from svlt.accessors import generate_multiposition_file_accessors
         f = data['multipos_czi']
         c = f['c']
         nz = f['z']
diff --git a/tests/base/test_api.py b/tests/svlt-core/test_api.py
similarity index 97%
rename from tests/base/test_api.py
rename to tests/svlt-core/test_api.py
index 854b2690de8f94c991f2c84848140be625456245..cc834d232b70ec048f1753dce28a6e0b01e5a078 100644
--- a/tests/base/test_api.py
+++ b/tests/svlt-core/test_api.py
@@ -1,12 +1,11 @@
 from pathlib import Path
 
 import svlt.conf.testing as conf
-from svlt.conf.testing import TestServerBaseClass
 
 czifile = conf.meta['image_files']['czifile']
 
 
-class TestApiFromAutomatedClient(TestServerBaseClass):
+class TestApiFromAutomatedClient(conf.TestServerBaseClass):
     
     input_data = czifile
     
@@ -216,7 +215,7 @@ class TestApiFromAutomatedClient(TestServerBaseClass):
         self.assertEqual(self.assertGetSuccess(f'/tasks/get/{task_id}')['status'], 'READY')
         self.assertEqual(
             self.assertGetSuccess(f'/tasks/get/{task_id}')['target'],
-            'svlt.core.pipelines.segment.segment_pipeline'
+            'svlt.pipelines.segment.segment_pipeline'
         )
 
         # run task
@@ -226,12 +225,6 @@ class TestApiFromAutomatedClient(TestServerBaseClass):
         self.assertTrue(all(acc.unique()[0] == [0, 1]))
         self.assertTrue(all(acc.unique()[1] > 0))
 
-    def test_permissive_instance_segmentation_model(self):
-        self.assertPutSuccess(
-            '/models/classify/threshold/load',
-            body={}
-        )
-
     def test_run_dummy_task(self):
         acc_id = self.assertPutSuccess('/testing/accessors/dummy_accessor/load')
         acc_in = self.get_accessor(acc_id)
diff --git a/tests/base/test_model.py b/tests/svlt-core/test_models.py
similarity index 93%
rename from tests/base/test_model.py
rename to tests/svlt-core/test_models.py
index f8b137d986dd94dee5ef55bea977853a097764ba..eef0cbbc023cab059cdbaa76a24cefb4f915ff59 100644
--- a/tests/base/test_model.py
+++ b/tests/svlt-core/test_models.py
@@ -2,8 +2,8 @@ import unittest
 
 import svlt.conf.testing as conf
 from svlt.conf.testing import DummySemanticSegmentationModel, DummyInstanceMaskSegmentationModel
-from svlt.core.accessors import CziImageFileAccessor
-from svlt.core.models import CouldNotLoadModelError, BinaryThresholdSegmentationModel
+from svlt.accessors import CziImageFileAccessor
+from svlt.models import CouldNotLoadModelError, BinaryThresholdSegmentationModel
 
 czifile = conf.meta['image_files']['czifile']
 
diff --git a/tests/base/test_pipelines.py b/tests/svlt-core/test_pipelines.py
similarity index 95%
rename from tests/base/test_pipelines.py
rename to tests/svlt-core/test_pipelines.py
index d128d86d50d76d66f5f6ce9e84f5fade170c1467..e46cd70b8b76c76898dff491e10dfdbaa1c63db0 100644
--- a/tests/base/test_pipelines.py
+++ b/tests/svlt-core/test_pipelines.py
@@ -2,10 +2,10 @@ import unittest
 
 import numpy as np
 
-from svlt.core.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file
-from svlt.core.pipelines import segment, segment_zproj
-from svlt.core.pipelines.shared import PipelineParams, PipelineRecord, PipelineTrace
-from svlt.core.session import RunTaskError, session
+from svlt.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file
+from svlt.pipelines import segment, segment_zproj
+from svlt.pipelines.common import PipelineParams, PipelineRecord, PipelineTrace
+from svlt.session import RunTaskError, session
 
 import svlt.conf.testing as conf
 from svlt.conf.testing import DummySemanticSegmentationModel
diff --git a/tests/base/test_process.py b/tests/svlt-core/test_process.py
similarity index 95%
rename from tests/base/test_process.py
rename to tests/svlt-core/test_process.py
index 515c0c7cf87211a47d8df530ad7a6afe3b24341c..53fb83624061e3f81498c9a0114bd208f26acc5f 100644
--- a/tests/base/test_process.py
+++ b/tests/svlt-core/test_process.py
@@ -2,8 +2,8 @@ import unittest
 
 import numpy as np
 
-from svlt.core.annotators import draw_contours_on_patch
-from svlt.core.process import get_safe_contours, mask_largest_object, pad, smooth
+from svlt.annotators import draw_contours_on_patch
+from svlt.process import get_safe_contours, mask_largest_object, pad, smooth
 
 class TestProcessingUtilityMethods(unittest.TestCase):
     def setUp(self) -> None:
diff --git a/tests/base/test_session.py b/tests/svlt-core/test_session.py
similarity index 97%
rename from tests/base/test_session.py
rename to tests/svlt-core/test_session.py
index 89612d71cb919e49aa3261a62c80660ebb9f59da..62e13f82e7eedf8cab281f6d96b4e4cfdc1454a5 100644
--- a/tests/base/test_session.py
+++ b/tests/svlt-core/test_session.py
@@ -3,8 +3,8 @@ import pathlib
 import unittest
 
 import numpy as np
-from svlt.core.accessors import generate_file_accessor, InMemoryDataAccessor
-from svlt.core.session import session
+from svlt.accessors import generate_file_accessor, InMemoryDataAccessor
+from svlt.session import session
 import svlt.conf.testing as conf
 
 class TestGetSessionObject(unittest.TestCase):
@@ -46,7 +46,7 @@ class TestGetSessionObject(unittest.TestCase):
         logfile1 = session1.logfile
         self.assertTrue(logfile1.exists())
 
-        from svlt.core.session import session as session2
+        from svlt.session import session as session2
         self.assertEqual(session1, session2)
         logfile2 = session2.logfile
         self.assertTrue(logfile2.exists())
diff --git a/tests/base/__init__.py b/tests/svlt-ilastik/__init__.py
similarity index 100%
rename from tests/base/__init__.py
rename to tests/svlt-ilastik/__init__.py
diff --git a/tests/test_ilastik/test_ilastik.py b/tests/svlt-ilastik/test_ilastik.py
similarity index 98%
rename from tests/test_ilastik/test_ilastik.py
rename to tests/svlt-ilastik/test_ilastik.py
index cea849ef6906668ea80fa06b373f4b854fff8827..5e0cc8ae46946ef6ce1a3e21304929cc0f15985f 100644
--- a/tests/test_ilastik/test_ilastik.py
+++ b/tests/svlt-ilastik/test_ilastik.py
@@ -4,13 +4,13 @@ import unittest
 
 import numpy as np
 
-from svlt.core.accessors import CziImageFileAccessor, generate_file_accessor, InMemoryDataAccessor, PatchStack, write_accessor_data_to_file
-from svlt.core.api import app
+from svlt.accessors import CziImageFileAccessor, generate_file_accessor, InMemoryDataAccessor, PatchStack, write_accessor_data_to_file
+from svlt.conf.testing import app
 from svlt.ilastik import models as ilm
 from svlt.ilastik.pipelines import px_then_ob
 from svlt.ilastik.router import router
 from svlt.rois.roiset import RoiSet, RoiSetMetaParams
-from svlt.core.pipelines import segment
+from svlt.pipelines import segment
 import svlt.conf.testing as conf
 
 data = conf.meta['image_files']
@@ -206,7 +206,7 @@ class TestIlastikPixelClassification(unittest.TestCase):
 
 
 class TestServerTestCase(conf.TestServerBaseClass):
-    app_name = 'tests.test_ilastik.test_ilastik:app'
+    app_name = 'tests.svlt-ilastik.test_ilastik:app'
     input_data = czifile
 
 
diff --git a/tests/rois/__init__.py b/tests/svlt-pheno/__init__.py
similarity index 100%
rename from tests/rois/__init__.py
rename to tests/svlt-pheno/__init__.py
diff --git a/tests/rois/test_features.py b/tests/svlt-pheno/test_features.py
similarity index 96%
rename from tests/rois/test_features.py
rename to tests/svlt-pheno/test_features.py
index f8ada24d78383fac6ba619da4cacff04a6ad59bc..0d4baeddcf062167a89882888f9d5233a7c81ffa 100644
--- a/tests/rois/test_features.py
+++ b/tests/svlt-pheno/test_features.py
@@ -1,6 +1,6 @@
 import unittest
 
-from svlt.core.accessors import generate_file_accessor
+from svlt.accessors import generate_file_accessor
 import svlt.conf.testing as conf
 from svlt.rois.features import regionprops
 from svlt.rois.roiset import RoiSet, RoiSetMetaParams
@@ -73,7 +73,7 @@ class TestRoiSetMonoProducts(unittest.TestCase):
 
     def test_empty_roiset_extract_features(self):
         import numpy as np
-        from svlt.core.accessors import InMemoryDataAccessor
+        from svlt.accessors import InMemoryDataAccessor
 
         arr_mask = InMemoryDataAccessor(np.zeros([*stack.hw, 1, stack.nz], dtype='uint8'))
 
diff --git a/tests/svlt-pheno/test_models.py b/tests/svlt-pheno/test_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bf4124f926b3779986a955fa6e1536e683c6df9
--- /dev/null
+++ b/tests/svlt-pheno/test_models.py
@@ -0,0 +1,12 @@
+import svlt.conf.testing as conf
+
+czifile = conf.meta['image_files']['czifile']
+
+
+class TestApiFromAutomatedClient(conf.TestServerBaseClass):
+
+    def test_permissive_instance_segmentation_model(self):
+        self.assertPutSuccess(
+            '/pheno/models/classify/threshold/load',
+            body={}
+        )
\ No newline at end of file
diff --git a/tests/rois/test_phenobase.py b/tests/svlt-pheno/test_phenobase.py
similarity index 95%
rename from tests/rois/test_phenobase.py
rename to tests/svlt-pheno/test_phenobase.py
index bb86016096b6172c23af7e58b5408c969dc52b86..5b81537bd75d3a419d08a0b289d2dfc83def39d7 100644
--- a/tests/rois/test_phenobase.py
+++ b/tests/svlt-pheno/test_phenobase.py
@@ -4,12 +4,11 @@ import unittest
 
 import numpy as np
 
-from svlt.core.accessors import generate_file_accessor, InMemoryDataAccessor
-from svlt.core.session import session
+from svlt.accessors import generate_file_accessor, InMemoryDataAccessor
 from svlt.conf import testing as conf
 
 from svlt.rois.models import IntensityThresholdInstanceMaskSegmentationModel
-from svlt.rois.phenobase import MissingPatchSeriesError, PhenoBase, RoiSetIndexError
+from svlt.rois.phenobase import MissingPatchSeriesError, PhenoBase, RoiSetIndexError, session_phenobase
 from svlt.rois.roiset import RoiSet, PatchParams, RoiSetExportParams, RoiSetMetaParams
 
 data = conf.meta['image_files']
@@ -234,7 +233,7 @@ class TestPhenoBase(unittest.TestCase):
         self.assertEqual(phenobase.count - count, 0)
 
 
-class TestPhenoBaseApi(unittest.TestCase):
+class TestSessionPhenoBase(unittest.TestCase):
 
     def test_initiate_phenobase_with_roiset(self):
         data = conf.meta['image_files']
@@ -254,13 +253,13 @@ class TestPhenoBaseApi(unittest.TestCase):
         self.assertGreater(roiset1.count, 2)
 
         # initiate phenobase in session scope
-        self.assertIsNone(session.phenobase)
-        session.add_roiset(
+        self.assertIsNone(session_phenobase._phenobase)
+        session_phenobase.add_roiset(
             roiset1,
             index_dict={'X': 0, 'T': 0},
             export_params=RoiSetExportParams(patches={'ch0': PatchParams(white_channel=0)}),
         )
-        self.assertEqual(session.phenobase.count, roiset1.count)
+        self.assertEqual(session_phenobase.count, roiset1.count)
 
         # add to phenobase in session scope
         roiset2 = RoiSet.from_binary_mask(
@@ -274,9 +273,9 @@ class TestPhenoBaseApi(unittest.TestCase):
         )
         self.assertGreater(roiset2.count, 2)
 
-        session.add_roiset(
+        session_phenobase.add_roiset(
             roiset2,
             index_dict={'X': 1, 'T': 1},
             export_params=RoiSetExportParams(patches={'ch0': PatchParams(white_channel=0)}),
         )
-        self.assertEqual(session.phenobase.count, roiset1.count + roiset2.count)
\ No newline at end of file
+        self.assertEqual(session_phenobase.count, roiset1.count + roiset2.count)
\ No newline at end of file
diff --git a/tests/rois/test_roiset.py b/tests/svlt-pheno/test_roiset.py
similarity index 98%
rename from tests/rois/test_roiset.py
rename to tests/svlt-pheno/test_roiset.py
index 842273640ceb4477720c008b8da5112698450b4f..f0c688f0815c7d5252061224fb160444f9b737e2 100644
--- a/tests/rois/test_roiset.py
+++ b/tests/svlt-pheno/test_roiset.py
@@ -6,11 +6,10 @@ from pathlib import Path
 
 import pandas as pd
 
-from svlt.core.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file
-from svlt.core.process import smooth
+from svlt.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file
+from svlt.process import smooth
 
 import svlt.conf.testing as conf
-from svlt.conf.testing import DummyInstanceMaskSegmentationModel
 
 from svlt.rois.labels import get_label_ids
 from svlt.rois.roiset import RoiSet, RoiSetExportParams, RoiSetMetaParams
@@ -91,7 +90,7 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase):
         roiset = RoiSet.from_object_ids(self.stack_ch_pa, zero_obmap)
         self.assertEqual(roiset.count, 0)
         cl = 'dummy_class'
-        roiset.classify_by(cl, [0], DummyInstanceMaskSegmentationModel())
+        roiset.classify_by(cl, [0], conf.DummyInstanceMaskSegmentationModel())
         self.assertTrue(cl in roiset.df().classifications.columns)
 
     def test_create_roiset_with_no_3d_objects(self):
@@ -100,7 +99,7 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase):
         roiset = RoiSet.from_object_ids(self.stack_ch_pa, zero_obmap)
         self.assertEqual(roiset.count, 0)
         cl = 'dummy_class'
-        roiset.classify_by(cl, [0], DummyInstanceMaskSegmentationModel())
+        roiset.classify_by(cl, [0], conf.DummyInstanceMaskSegmentationModel())
         self.assertTrue(cl in roiset.df().classifications.columns)
 
     def test_slices_are_valid(self):
@@ -208,7 +207,7 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase):
     def test_classify_by(self):
         roiset = self._make_roi_set()
         cl = 'dummy_class'
-        roiset.classify_by(cl, [0], DummyInstanceMaskSegmentationModel())
+        roiset.classify_by(cl, [0], conf.DummyInstanceMaskSegmentationModel())
         self.assertTrue(all(roiset.df()['classifications', cl].unique() == [1]))
         self.assertTrue(all(np.unique(roiset.get_object_class_map(cl).data) == [0, 1]))
         return roiset
@@ -216,7 +215,7 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase):
     def test_classify_by_multiple_channels(self):
         roiset = RoiSet.from_binary_mask(self.stack, self.seg_mask, params=RoiSetMetaParams(deproject_channel=0))
         cl = 'dummy_class'
-        roiset.classify_by(cl, [0, 1], DummyInstanceMaskSegmentationModel())
+        roiset.classify_by(cl, [0, 1], conf.DummyInstanceMaskSegmentationModel())
         self.assertTrue(all(roiset.df().classifications[cl].unique() == [1]))
         self.assertTrue(all(np.unique(roiset.get_object_class_map('dummy_class').data) == [0, 1]))
         return roiset
@@ -234,7 +233,7 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase):
 
         # classify first RoiSet
         cl = 'dummy_class'
-        roiset1.classify_by(cl, [0, 1], DummyInstanceMaskSegmentationModel())
+        roiset1.classify_by(cl, [0, 1], conf.DummyInstanceMaskSegmentationModel())
 
         self.assertTrue(cl in roiset1.classification_columns)
         self.assertFalse(cl in roiset2.classification_columns)
@@ -707,7 +706,7 @@ class TestRoiSetMultichannelProducts(BaseTestRoiSetMonoProducts, unittest.TestCa
             'annotated_zstacks': {},
             'object_classes': True,
         })
-        self.roiset.classify_by('dummy_class', [3], DummyInstanceMaskSegmentationModel())
+        self.roiset.classify_by('dummy_class', [3], conf.DummyInstanceMaskSegmentationModel())
         interm = self.roiset.get_export_product_accessors(
             params=p
         )
diff --git a/tests/rois/test_roiset_derived.py b/tests/svlt-pheno/test_roiset_derived.py
similarity index 97%
rename from tests/rois/test_roiset_derived.py
rename to tests/svlt-pheno/test_roiset_derived.py
index 395f19e88dc5d44d340e24c9494b619363bf5e16..deabcb2457a271494fe88b9063ae7bd8532fd437 100644
--- a/tests/rois/test_roiset_derived.py
+++ b/tests/svlt-pheno/test_roiset_derived.py
@@ -5,7 +5,7 @@ import numpy as np
 
 from svlt.rois.roiset import RoiSetMetaParams
 from svlt.rois.derived import RoiSetWithDerivedChannelsExportParams, RoiSetWithDerivedChannels
-from svlt.core.accessors import generate_file_accessor, PatchStack
+from svlt.accessors import generate_file_accessor, PatchStack
 import svlt.conf.testing as conf
 from svlt.conf.testing import DummyInstanceMaskSegmentationModel
 
diff --git a/tests/rois/test_roiset_pipeline.py b/tests/svlt-pheno/test_roiset_pipeline.py
similarity index 93%
rename from tests/rois/test_roiset_pipeline.py
rename to tests/svlt-pheno/test_roiset_pipeline.py
index 0cab688745769963f90435d39112e0eec648534d..6a6b137035b22ac49ec4cf356b58d0597f5a3874 100644
--- a/tests/rois/test_roiset_pipeline.py
+++ b/tests/svlt-pheno/test_roiset_pipeline.py
@@ -4,7 +4,7 @@ import unittest
 
 import numpy as np
 
-from svlt.core.accessors import generate_file_accessor
+from svlt.accessors import generate_file_accessor
 import svlt.conf.testing as conf
 from svlt.rois.pipelines.roiset_obmap import RoiSetObjectMapParams, roiset_object_map_pipeline
 
@@ -60,7 +60,7 @@ class BaseTestRoiSetMonoProducts(object):
         }
 
     def _get_models(self):
-        from svlt.core.models import BinaryThresholdSegmentationModel
+        from svlt.models import BinaryThresholdSegmentationModel
         from svlt.rois.models import IntensityThresholdInstanceMaskSegmentationModel
         return {
             'pixel_classifier_segmentation': {
@@ -83,17 +83,17 @@ class TestRoiSetWorkflow(BaseTestRoiSetMonoProducts, unittest.TestCase):
             'pixel_classifier_segmentation_model_id': 'px_id',
             'object_classifier_model_id': 'ob_id',
             'segmentation': {
-                'channel': test_params['segmentation_channel'],
+                'channel': 0,
                 'smooth': 1.5,
                 'binary_closing': True,
             },
-            'patches_channel': test_params['patches_channel'],
+            'patches_channel': 1,
             'roi_params': self._get_roi_params(),
             'export_params': self._get_export_params(),
         }
 
     def test_object_map_workflow(self):
-        n_rois = 18
+        n_rois = 4
         acc_in = generate_file_accessor(self.fpi)
         params = RoiSetObjectMapParams(
             **self._pipeline_params(),
@@ -134,7 +134,7 @@ class TestRoiSetWorkflowOverApi(conf.TestServerBaseClass, BaseTestRoiSetMonoProd
 
     def test_load_object_classifier(self):
         mid = self.assertPutSuccess(
-            'models/classify/threshold/load/',
+            'pheno/models/classify/threshold/load/',
             body={'tr': 0}
         )['model_id']
         self.assertTrue(mid.startswith('IntensityThresholdInstanceMaskSegmentation'))
@@ -142,7 +142,7 @@ class TestRoiSetWorkflowOverApi(conf.TestServerBaseClass, BaseTestRoiSetMonoProd
 
     def _object_map_workflow(self, ob_classifer_id):
         return self.assertPutSuccess(
-            'rois/pipelines/roiset_to_obmap',
+            'pheno/pipelines/roiset_to_obmap',
             body={
                 'accessor_id': self.test_load_input_accessor(),
                 'pixel_classifier_segmentation_model_id': self.test_load_pixel_classifier(),
@@ -171,7 +171,7 @@ class TestTaskQueuedRoiSetWorkflowOverApi(TestRoiSetWorkflowOverApi):
     def _object_map_workflow(self, ob_classifer_id):
 
         res_queue = self.assertPutSuccess(
-            'rois/pipelines/roiset_to_obmap',
+            'pheno/pipelines/roiset_to_obmap',
             body={
                 'schedule': True,
                 'accessor_id': self.test_load_input_accessor(),
@@ -210,7 +210,7 @@ class TestAddRoiSetOverApi(conf.TestServerBaseClass):
         acc_id_mask = self.assertPutSuccess(f'accessors/read_from_file/{pa_mask.name}')
 
         res = self.assertPutSuccess(
-            'rois/pipelines/add_roiset',
+            'pheno/pipelines/add_roiset',
             body={
                 'accessor_id': acc_id_stack,
                 'labels_accessor_id': acc_id_mask,  # binary mask, not labels, so there's only on ROI
@@ -226,5 +226,5 @@ class TestAddRoiSetOverApi(conf.TestServerBaseClass):
         self.assertEqual(sd['P'], 1)
 
         # RoiSet has one entry
-        phenobase = self.assertGetSuccess('phenobase/bounding_box')
+        phenobase = self.assertGetSuccess('pheno/phenobase/bounding_box')
         self.assertEqual(len(phenobase), 1)
\ No newline at end of file
diff --git a/tests/test_ilastik/__init__.py b/tests/test_ilastik/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000