Skip to content
Snippets Groups Projects
Commit 7228f703 authored by Christopher Randolph Rhodes's avatar Christopher Randolph Rhodes
Browse files

Merge branch 'staging' into dev_roiset_coco

# Conflicts:
#	.gitignore
#	tests/base/test_roiset.py
parents d3c9639d 15365635
No related branches found
No related tags found
No related merge requests found
Showing
with 207 additions and 428 deletions
.idea/*
*/.idea/*
*__pycache__*
# IDE profile files
.idea/*
# build and conda-build artifacts
build/*
dist/*
*.egg-info/*
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/model_server" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.9 (model_server_env)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
......
......@@ -8,8 +8,8 @@ from skimage.io import imread, imsave
import czifile
import tifffile
from model_server.base.process import make_rgb
from model_server.base.process import is_mask
from .process import make_rgb
from .process import is_mask
class GenericImageDataAccessor(ABC):
......
import numpy as np
from matplotlib import font_manager
from PIL import Image, ImageDraw, ImageFont
from model_server.base.process import rescale
from .process import rescale
def _get_font(font_size=18):
return ImageFont.truetype(
font_manager.findfont(
font_manager.FontProperties(
family='sans-serif',
weight='bold'
)
),
size=font_size,
)
def draw_boxes_on_3d_image(roiset, draw_full_depth=False, **kwargs):
h, w, chroma, nz = roiset.acc_raw.shape
font_size = kwargs.get('font_size', 18)
linewidth = kwargs.get('linewidth', 4)
if ck := kwargs.get('channel'):
......@@ -25,7 +37,7 @@ def draw_boxes_on_3d_image(roiset, draw_full_depth=False, **kwargs):
for ci in range(0, len(channels)):
pilimg = Image.fromarray(roiset.acc_raw.data[:, :, channels[ci], zi])
draw = ImageDraw.Draw(pilimg)
draw.font = ImageFont.truetype(font="arial.ttf", size=font_size)
draw.font = _get_font()
for roi in subset.itertuples('Roi'):
xm = round((roi.x0 + roi.x1) / 2)
......
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from model_server.base.models import DummyInstanceSegmentationModel, DummySemanticSegmentationModel
from model_server.base.session import Session, InvalidPathError
from model_server.base.validators import validate_workflow_inputs
from model_server.base.workflows import classify_pixels
from model_server.extensions.ilastik.workflows import infer_px_then_ob_model
from .models import DummyInstanceSegmentationModel, DummySemanticSegmentationModel
from .session import session, InvalidPathError
from .validators import validate_workflow_inputs
from .workflows import classify_pixels
app = FastAPI(debug=True)
session = Session()
import model_server.extensions.ilastik.router
app.include_router(model_server.extensions.ilastik.router.router)
from ..extensions.ilastik.router import router as ilastik_router
app.include_router(ilastik_router)
@app.on_event("startup")
def startup():
......
......@@ -5,7 +5,7 @@ import czifile
import numpy as np
import pandas as pd
from model_server.base.accessors import InMemoryDataAccessor
from .accessors import InMemoryDataAccessor
def dump_czi_subblock_table(czif: czifile.CziFile, where: Path):
......
from abc import ABC, abstractmethod
from math import floor
from typing import Union
import numpy as np
from pydantic import BaseModel
from model_server.base.accessors import GenericImageDataAccessor, InMemoryDataAccessor, PatchStack
from .accessors import GenericImageDataAccessor, InMemoryDataAccessor, PatchStack
class Model(ABC):
......
......@@ -14,12 +14,12 @@ from skimage.measure import label, regionprops_table, shannon_entropy, find_cont
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from model_server.base.accessors import GenericImageDataAccessor, InMemoryDataAccessor, write_accessor_data_to_file
from model_server.base.models import InstanceSegmentationModel
from model_server.base.process import get_safe_contours, pad, rescale, resample_to_8bit, make_rgb
from model_server.base.annotators import draw_box_on_patch, draw_contours_on_patch, draw_boxes_on_3d_image
from model_server.base.accessors import generate_file_accessor, PatchStack
from model_server.base.process import mask_largest_object
from .accessors import GenericImageDataAccessor, InMemoryDataAccessor, write_accessor_data_to_file
from .models import InstanceSegmentationModel
from .process import get_safe_contours, pad, rescale, resample_to_8bit, make_rgb
from .annotators import draw_box_on_patch, draw_contours_on_patch, draw_boxes_on_3d_image
from .accessors import generate_file_accessor, PatchStack
from .process import mask_largest_object
class PatchParams(BaseModel):
......
import logging
import os
from pathlib import Path
from pathlib import Path, PureWindowsPath
from pydantic import BaseModel
from time import strftime, localtime
from typing import Union
import pandas as pd
import model_server.conf.defaults
from model_server.base.models import Model
from ..conf import defaults
from .models import Model
logger = logging.getLogger(__name__)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class CsvTable(object):
def __init__(self, fpath: Path):
self.path = fpath
......@@ -38,7 +30,7 @@ class CsvTable(object):
self.empty = False
return True
class Session(object, metaclass=Singleton):
class _Session(object):
"""
Singleton class for a server session that persists data between API calls
"""
......@@ -46,7 +38,6 @@ class Session(object, metaclass=Singleton):
log_format = '%(asctime)s - %(levelname)s - %(message)s'
def __init__(self, root: str = None):
print('Initializing session')
self.models = {} # model_id : model object
self.paths = self.make_paths(root)
......@@ -97,13 +88,13 @@ class Session(object, metaclass=Singleton):
:return: dictionary of session paths
"""
if root is None:
root_path = Path(model_server.conf.defaults.root)
root_path = Path(defaults.root)
else:
root_path = Path(root)
sid = Session.create_session_id(root_path)
sid = _Session.create_session_id(root_path)
paths = {'root': root_path}
for pk in ['inbound_images', 'outbound_images', 'logs', 'tables']:
pa = root_path / sid / model_server.conf.defaults.subdirectories[pk]
pa = root_path / sid / defaults.subdirectories[pk]
paths[pk] = pa
try:
pa.mkdir(parents=True, exist_ok=True)
......@@ -183,7 +174,7 @@ class Session(object, metaclass=Singleton):
models = self.describe_loaded_models()
for mid, det in models.items():
if is_path:
if Path(det.get('params').get(key)) == Path(value):
if PureWindowsPath(det.get('params').get(key)).as_posix() == Path(value).as_posix():
return mid
else:
if det.get('params').get(key) == value:
......@@ -193,6 +184,11 @@ class Session(object, metaclass=Singleton):
def restart(self, **kwargs):
self.__init__(**kwargs)
# create singleton instance
session = _Session()
class Error(Exception):
pass
......
......@@ -6,8 +6,8 @@ from typing import List
import pandas as pd
from model_server.base.accessors import InMemoryDataAccessor, write_accessor_data_to_file
from model_server.base.models import Model
from .accessors import InMemoryDataAccessor, write_accessor_data_to_file
from .models import Model
def autonumber_new_directory(where: str, prefix: str) -> str:
"""
......@@ -163,4 +163,4 @@ def loop_workflow(
)
if len(failures) > 0:
pd.DataFrame(failures).to_csv(Path(output_folder_path) / 'failures.csv')
\ No newline at end of file
pd.DataFrame(failures).to_csv(Path(output_folder_path) / 'failures.csv')
from fastapi import HTTPException
from model_server.base.session import Session
session = Session()
from .session import session
def validate_workflow_inputs(model_ids, inpaths):
for mid in model_ids:
......
......@@ -6,8 +6,8 @@ from pathlib import Path
from time import perf_counter
from typing import Dict
from model_server.base.accessors import generate_file_accessor, write_accessor_data_to_file
from model_server.base.models import SemanticSegmentationModel
from .accessors import generate_file_accessor, write_accessor_data_to_file
from .models import SemanticSegmentationModel
from pydantic import BaseModel
......
import json
import os
import unittest
from multiprocessing import Process
from pathlib import Path
root = Path.home() / 'model_server' / 'testing'
filename = 'D3-selection-01.czi'
czifile = {
'filename': filename,
'path': root / filename,
'w': 1274,
'h': 1274,
'c': 5,
'z': 1,
'um_per_pixel': 1/3.9881,
}
filename = 'rgb.png'
rgbpngfile = {
'filename': filename,
'path': root / filename,
'w': 64,
'h': 128,
'c': 3,
'z': 1
}
filename = 'mono.png'
monopngfile = {
'filename': filename,
'path': root / filename,
'w': 64,
'h': 128,
'c': 1,
'z': 1
}
filename = 'zmask-test-stack.tif'
tifffile = {
'filename': filename,
'path': root / filename,
'w': 512,
'h': 512,
'c': 2,
'z': 7,
}
filename = 'mono_zstack_mask.tif'
monozstackmask = {
'filename': filename,
'path': root / filename,
'w': 256,
'h': 256,
'c': 1,
'z': 85
}
ilastik_classifiers = {
'px': root / 'ilastik' / 'demo_px.ilp',
'pxmap_to_obj': root / 'ilastik' / 'demo_obj.ilp',
'seg_to_obj': root / 'ilastik' / 'demo_obj_seg.ilp',
'px_color_zstack': root / 'ilastik' / 'px-3d-color.ilp',
'ob_pxmap_color_zstack': root / 'ilastik' / 'ob-pxmap-color-zstack.ilp',
'ob_seg_color_zstack': root / 'ilastik' / 'ob-seg-color-zstack.ilp',
}
roiset_test_data = {
'multichannel_zstack': {
'path': root / 'zmask-test-stack-chlorophyl.tif',
'w': 512,
'h': 512,
'c': 5,
'z': 7,
'mask_path': root / 'zmask-test-stack-mask.tif',
'mask_path_3d': root / 'zmask-test-stack-mask-3d.tif',
},
'pipeline_params': {
'segmentation_channel': 0,
'patches_channel': 4,
'pxmap_channel': 0,
'pxmap_threshold': 0.6,
},
'pixel_classifier': root / 'zmask' / 'AF405-bodies_boundaries.ilp',
}
output_path = root / 'testing_output'
output_path.mkdir(parents=True, exist_ok=True)
\ No newline at end of file
import requests
from urllib3 import Retry
class TestServerBaseClass(unittest.TestCase):
"""
Base class for unittests of API functionality. Implements both server and clients for testing.
"""
app_name = 'model_server.base.api:app'
def setUp(self) -> None:
import uvicorn
host = '127.0.0.1'
port = 5001
self.server_process = Process(
target=uvicorn.run,
args=(self.app_name, ),
kwargs={'host': host, 'port': port, 'log_level': 'critical'},
daemon=True
)
self.uri = f'http://{host}:{port}/'
self.server_process.start()
def _get_sesh(self):
sesh = requests.Session()
retries = Retry(
total=5,
backoff_factor=0.1,
)
sesh.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
return sesh
def _get(self, endpoint):
return self._get_sesh().get(self.uri + endpoint)
def _put(self, endpoint, query=None, body=None):
return self._get_sesh().put(
self.uri + endpoint,
params=query,
data=json.dumps(body)
)
def tearDown(self) -> None:
self.server_process.terminate()
self.server_process.join()
def setup_test_data():
"""
Look for test data, create test output directory, parse and return meta information
:return:
meta (dict) of test data and paths
"""
# places to look for test data
data_paths = [
os.environ.get('UNITTEST_DATA_ROOT'),
Path.home() / 'model_server' / 'testing',
os.getcwd(),
]
root = None
# look for first instance of summary.json
for dp in data_paths:
if dp is None:
continue
sf = (Path(dp) / 'summary.json')
if sf.exists():
with open(sf, 'r') as fh:
meta = json.load(fh)
root = dp
break
if root is None:
raise Exception('Could not find test data, try setting environmental variable UNITTEST_DATA_ROOT.')
meta['root'] = Path(root)
op_ev = os.environ.get('UNITTEST_OUTPUT', (meta['root'] / 'test_output'))
meta['output_path'] = Path(op_ev)
meta['output_path'].mkdir(parents=True, exist_ok=True)
# resolve relative paths
def _resolve_paths(d):
keys = list(d.keys())
for k in keys:
if k == 'name':
d['path'] = meta['root'] / d['name']
elif isinstance(d[k], dict):
_resolve_paths(d[k])
_resolve_paths(meta)
return meta
# object containing test data paths and metadata, for import into unittest modules
meta = setup_test_data()
\ No newline at end of file
import json
from logging import getLogger
import os
from pathlib import Path
import warnings
import numpy as np
from pydantic import BaseModel
from skimage.filters import gaussian
import vigra
import model_server.extensions.ilastik.conf
from model_server.base.accessors import PatchStack
from model_server.base.accessors import GenericImageDataAccessor, InMemoryDataAccessor
from model_server.base.process import smooth
from model_server.base.models import Model, ImageToImageModel, InstanceSegmentationModel, InvalidInputImageError, ParameterExpectedError, SemanticSegmentationModel
from ...base.accessors import PatchStack
from ...base.accessors import GenericImageDataAccessor, InMemoryDataAccessor
from ...base.process import smooth
from ...base.models import Model, ImageToImageModel, InstanceSegmentationModel, InvalidInputImageError, ParameterExpectedError, SemanticSegmentationModel
class IlastikParams(BaseModel):
project_file: str
......@@ -44,8 +45,13 @@ class IlastikModel(Model):
super().__init__(autoload, params)
def load(self):
from ilastik import app
from ilastik.applets.dataSelection.opDataSelection import PreloadedArrayDatasetInfo
# suppress warnings when loading ilastik app
getLogger('ilastik.app').setLevel('ERROR')
with warnings.catch_warnings():
warnings.filterwarnings('ignore', category=DeprecationWarning)
from ilastik import app
from ilastik.applets.dataSelection.opDataSelection import PreloadedArrayDatasetInfo
self.PreloadedArrayDatasetInfo = PreloadedArrayDatasetInfo
......
from fastapi import APIRouter, HTTPException
from model_server.base.session import Session
from model_server.base.validators import validate_workflow_inputs
from ...base.session import session
from ...base.validators import validate_workflow_inputs
from model_server.extensions.ilastik import models as ilm
from model_server.base.models import ParameterExpectedError
from model_server.extensions.ilastik.workflows import infer_px_then_ob_model
from . import models as ilm
from .workflows import infer_px_then_ob_model
router = APIRouter(
prefix='/ilastik',
tags=['ilastik'],
)
session = Session()
def load_ilastik_model(model_class: ilm.IlastikModel, params: ilm.IlastikParams) -> dict:
"""
......
......@@ -6,10 +6,9 @@ from typing import Dict
from pydantic import BaseModel
from model_server.extensions.ilastik.models import IlastikPixelClassifierModel, IlastikObjectClassifierFromPixelPredictionsModel
from model_server.base.accessors import generate_file_accessor, write_accessor_data_to_file
from model_server.base.workflows import Timer
from .models import IlastikPixelClassifierModel, IlastikObjectClassifierFromPixelPredictionsModel
from ...base.accessors import generate_file_accessor, write_accessor_data_to_file
from ...base.workflows import Timer
class WorkflowRunRecord(BaseModel):
pixel_model_id: str
......
......@@ -6,7 +6,7 @@ from urllib3 import Retry
import uvicorn
import webbrowser
from model_server.conf.defaults import server_conf
from conf.defaults import server_conf
def parse_args():
parser = argparse.ArgumentParser(
......
......@@ -4,8 +4,8 @@ import h5py
import numpy as np
import pandas as pd
from model_server.base.accessors import generate_file_accessor, write_accessor_data_to_file, InMemoryDataAccessor
from model_server.extensions.ilastik.models import IlastikPixelClassifierModel, IlastikObjectClassifierFromPixelPredictionsModel
from base.accessors import generate_file_accessor, write_accessor_data_to_file, InMemoryDataAccessor
from extensions.ilastik.models import IlastikPixelClassifierModel, IlastikObjectClassifierFromPixelPredictionsModel
def get_input_files(where_ilp: Path) -> list:
files = []
......
......@@ -2,15 +2,35 @@
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "model_server_package_rhodes"
version = "0.0.1"
name = "model_server"
license = {file = "LICENSE"}
version = "2024.07.08"
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",
"ilastik ==1.4.1b15",
"imagecodecs",
"jupyterlab",
"matplotlib",
"numpy",
"pandas",
"pillow",
"pydantic ~=1.10.1",
"pytorch ==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"
......
name: model_server_env
channels:
- pytorch
- ilastik-forge
- conda-forge
- defaults
dependencies:
- affogato=0.3.3
- annotated-types=0.5.0
- anyio=3.7.1
- aom=3.5.0
- appdirs=1.4.4
- attrs=23.1.0
- bioimageio.core=0.5.8
- bioimageio.spec=0.4.9
- blas=2.118
- blas-devel=3.9.0
- blosc=1.21.5
- boost=1.74.0
- boost-cpp=1.74.0
- brotli=1.0.9
- brotli-bin=1.0.9
- brotli-python=1.0.9
- bzip2=1.0.8
- c-blosc2=2.10.2
- ca-certificates=2023.7.22
- cached-property=1.5.2
- cached_property=1.5.2
- cachetools=5.3.1
- cairo=1.16.0
- certifi=2023.7.22
- cfitsio=4.2.0
- charls=2.3.4
- charset-normalizer=3.2.0
- click=8.1.7
- colorama=0.4.6
- contourpy=1.1.0
- cycler=0.11.0
- czifile=2019.7.2
- dav1d=1.2.1
- dill=0.3.7
- dpct=1.2.post39
- et_xmlfile=1.1.0
- exceptiongroup=1.1.3
- expat=2.5.0
- fastapi=0.101.1
- fastfilters=0.2.4.post83
- fftw=3.3.10
- font-ttf-dejavu-sans-mono=2.37
- font-ttf-inconsolata=3.000
- font-ttf-source-code-pro=2.038
- font-ttf-ubuntu=0.83
- fontconfig=2.14.2
- fonts-conda-ecosystem=1
- fonts-conda-forge=1
- fonttools=4.42.1
- freetype=2.12.1
- fribidi=1.0.10
- fs=2.4.16
- future=0.18.3
- getopt-win32=0.1
- gettext=0.21.1
- giflib=5.2.1
- glib=2.78.0
- glib-tools=2.78.0
- glpk=5.0
- graphite2=1.3.13
- graphviz=8.1.0
- greenlet=2.0.2
- grpcio=1.41.1
- gst-plugins-base=1.22.5
- gstreamer=1.22.5
- gts=0.7.6
- h11=0.14.0
- h5py=3.8.0
- harfbuzz=6.0.0
- hdf5=1.12.2
- hytra=1.1.5
- icu=70.1
- idna=3.4
- ilastik=1.4.1b6
- ilastik-core=1.4.1b6
- ilastik-feature-selection=0.2.0
- ilastikrag=0.1.4
- ilastiktools=0.2.post37
- imagecodecs=2022.9.26
- imagecodecs-lite=2019.12.3
- imageio=2.31.1
- imath=3.1.6
- importlib-resources=6.0.1
- importlib_resources=6.0.1
- inferno=v0.4.2
- intel-openmp=2023.2.0
- joblib=1.3.2
- jpeg=9e
- jsonschema=4.19.0
- jsonschema-specifications=2023.7.1
- jxrlib=1.1
- kiwisolver=1.4.5
- krb5=1.20.1
- lazy_loader=0.3
- lcms2=2.14
- lemon=1.3.1
- lerc=4.0.0
- libabseil=20230802.0
- libaec=1.0.6
- libavif=0.11.1
- libblas=3.9.0
- libbrotlicommon=1.0.9
- libbrotlidec=1.0.9
- libbrotlienc=1.0.9
- libcblas=3.9.0
- libclang=15.0.7
- libclang13=15.0.7
- libcurl=8.1.2
- libdeflate=1.14
- libexpat=2.5.0
- libffi=3.4.2
- libgd=2.3.3
- libglib=2.78.0
- libhwloc=2.9.2
- libiconv=1.17
- liblapack=3.9.0
- liblapacke=3.9.0
- libogg=1.3.4
- libpng=1.6.39
- libprotobuf=4.23.4
- libsqlite=3.43.0
- libssh2=1.11.0
- libtiff=4.4.0
- libuv=1.44.2
- libvorbis=1.3.7
- libwebp=1.3.1
- libwebp-base=1.3.1
- libxcb=1.13
- libxml2=2.11.5
- libzlib=1.2.13
- libzopfli=1.0.3
- llvmlite=0.40.1
- lz4-c=1.9.4
- m2w64-gcc-libgfortran=5.3.0
- m2w64-gcc-libs=5.3.0
- m2w64-gcc-libs-core=5.3.0
- m2w64-gmp=6.1.0
- m2w64-libwinpthread-git=5.0.0.4634.697f757
- mamutexport=0.2.1.post6
- marching_cubes=0.3.post9
- markdown-it-py=3.0.0
- marshmallow=3.20.1
- marshmallow-jsonschema=0.13.0
- marshmallow-union=0.1.15.post1
- matplotlib-base=3.7.2
- mdurl=0.1.0
- mkl=2022.1.0
- mkl-devel=2022.1.0
- mkl-include=2022.1.0
- mrcfile=1.4.3
- msys2-conda-epoch=20160418
- munkres=1.1.4
- ndstructs=0.0.5dev0
- networkx=3.1
- nifty=1.2.1
- numba=0.57.1
- numpy=1.22.4
- openexr=3.1.5
- openjpeg=2.5.0
- openpyxl=3.1.2
- openssl=3.1.2
- packaging=23.1
- pandas=1.5.3
- pango=1.50.14
- pcre2=10.40
- pillow=9.2.0
- pip=23.2.1
- pixman=0.40.0
- pkgutil-resolve-name=1.3.10
- platformdirs=3.10.0
- ply=3.11
- pooch=1.7.0
- protobuf=4.23.4
- psutil=5.9.5
- pthread-stubs=0.4
- pthreads-win32=2.9.1
- pydantic=1.10.2
- pydantic-core=2.6.3
- pygments=2.16.1
- pyopengl=3.1.6
- pyparsing=3.0.9
- pyqt=5.15.9
- pyqt5-sip=12.12.2
- pyqtgraph=0.13.3
- pysocks=1.7.1
- python=3.9.18
- python-dateutil=2.8.2
- python-elf=0.4.7
- python-stdnum=1.19
- python_abi=3.9
- pytorch=1.13.1
- pytorch-mutex=1.0
- pytz=2023.3.post1
- pywavelets=1.4.1
- pyyaml=6.0.1
- qimage2ndarray=1.8.3
- qt-main=5.15.8
- referencing=0.30.2
- requests=2.31.0
- rich=13.5.1
- rpds-py=0.10.2
- ruamel.yaml=0.17.32
- ruamel.yaml.clib=0.2.7
- scikit-image=0.21.0
- scikit-learn=1.3.0
- scipy=1.11.2
- setuptools=68.1.2
- shellingham=1.5.3
- sip=6.7.11
- six=1.16.0
- skan=0.11.0
- snappy=1.1.10
- sniffio=1.3.0
- starlette=0.27.0
- tbb=2021.10.0
- tensorboardx=2.6.2.2
- threadpoolctl=3.2.0
- tifffile=2022.10.10
- tiktorch=23.6.0
- tk=8.6.12
- toml=0.10.2
- tomli=2.0.1
- toolz=0.12.0
- torchvision=0.14.1
- tqdm=4.66.1
- typer=0.9.0
- typing-extensions=4.7.1
- typing_extensions=4.7.1
- tzdata=2023c
- ucrt=10.0.22621.0
- unicodedata2=15.0.0
- urllib3=2.0.4
- uvicorn=0.23.2
- vc=14.3
- vc14_runtime=14.36.32532
- vigra=1.11.1
- volumina=1.3.10
- vs2015_runtime=14.36.32532
- wheel=0.41.2
- win_inet_pton=1.1.0
- xarray=2023.8.0
- xorg-kbproto=1.0.7
- xorg-libice=1.0.10
- xorg-libsm=1.2.3
- xorg-libx11=1.8.4
- xorg-libxau=1.0.11
- xorg-libxdmcp=1.1.3
- xorg-libxext=1.3.4
- xorg-libxpm=3.5.16
- xorg-libxt=1.3.0
- xorg-xextproto=7.3.0
- xorg-xproto=7.0.31
- xz=5.2.6
- yaml=0.2.5
- yapsy=1.12.2
- z5py=2.0.16
- zfp=1.0.0
- zipp=3.16.2
- zlib=1.2.13
- zlib-ng=2.0.7
- zstd=1.5.5
- pip:
- build==1.0.3
- importlib-metadata==7.0.0
- pyproject-hooks==1.0.0
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment