Beispiel #1
0
def show_status() -> ServerInfo:
    """
    PIMS Server status.
    """
    return ServerInfo(version=__version__,
                      api_version=__api_version__,
                      settings=get_settings())
Beispiel #2
0
async def startup():
    # Check PIMS configuration
    try:
        settings = get_settings()
        logger.info("[green bold]PIMS is starting with config:[/]")
        for k, v in settings.dict().items():
            logger.info(f"[green]* {k}:[/] [blue]{v}[/]",
                        extra={"highlight": False})
    except ValidationError as e:
        logger.error("Impossible to read or parse some PIMS settings:")
        logger.error(e)
        exit(-1)

    # Check optimisation are enabled for external libs
    from pydantic import compiled as pydantic_compiled
    if not pydantic_compiled:
        logger.warning(f"[red]Pydantic is running in non compiled mode.")

    from pyvips import API_mode as pyvips_binary
    if not pyvips_binary:
        logger.warning("[red]Pyvips is running in non binary mode.")

    from shapely.speedups import enabled as shapely_speedups
    if not shapely_speedups:
        logger.warning("[red]Shapely is running without speedups.")

    # Caching
    if not get_settings().cache_enabled:
        logger.warning(f"[orange3]Cache is disabled by configuration.")
    else:
        try:
            await _startup_cache(__version__)
            logger.info(f"[green]Cache is ready!")
        except ConnectionError:
            logger.error(f"[red]Impossible to connect to cache database. "
                         f"Disabling cache!")
Beispiel #3
0
    def import_collection(self, collection: Path, prefer_copy: bool = False):
        """Import recursively children of the collection."""
        cytomine = None
        for listener in self.listeners:
            if isinstance(listener, CytomineListener):
                cytomine = listener
                break
        if cytomine:
            task = Task.IMPORT_WITH_CYTOMINE
        else:
            task = Task.IMPORT

        imported = list()
        format_factory = FormatFactory()
        tasks = list()
        # Collection children are extracted recursively into collection
        # directories, until the directory is an image format (we can thus have
        # multi-file formats as directories in a collection).
        for child in collection.get_extracted_children(
                stop_recursion_cond=format_factory.match
        ):
            self.notify(
                ImportEventType.REGISTER_FILE, child, self.upload_path
            )
            try:
                if cytomine:
                    new_listener = cytomine.new_listener_from_registered_child(child)
                    args = [
                        new_listener.auth, str(child), child.name,
                        new_listener, prefer_copy
                    ]
                else:
                    args = [str(child), child.name, prefer_copy]
                tasks.append((task, args))
            except Exception as _:  # noqa
                # Do not propagate error to siblings
                # Each importer is independent
                pass

        def _sequential_imports():
            for name, args_ in tasks:
                func_from_str(BG_TASK_MAPPING.get(name))(*args_)

        if not get_settings().task_queue_enabled:
            _sequential_imports()
        else:
            try:
                task_group = group([
                    signature(CELERY_TASK_MAPPING.get(name), args_)
                    for name, args_ in tasks
                ])
                # WARNING !
                # These tasks are synchronous with respect to the parent task (the archive)
                # It is required to update the parent (the archive) status when everything is
                # finished. Code should be refactored to use Celery callbacks but it does not
                # seem so easy.
                # Current implementation may cause deadlock if the worker pool is exhausted,
                # while the parent task is waiting for subtasks to finish.
                # http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks
                with allow_join_result():
                    r = task_group.apply_async()
                    r.get()  # Wait for group to finish
            except Exception as e:  # noqa
                # WARNING !
                # Catch too many exceptions such as those related to import logic ?
                # TODO: identify Celery exception raised when trying to use it while rabbitmq is
                #  down
                # However, it should not happen in production.
                _sequential_imports()

        return imported
Beispiel #4
0
)
from pims.files.histogram import Histogram
from pims.files.image import Image
from pims.formats import AbstractFormat
from pims.formats.utils.factories import FormatFactory, SpatialReadableFormatFactory
from pims.importer.listeners import (
    CytomineListener, ImportEventType, ImportListener,
    StdoutListener
)
from pims.processing.histograms.utils import build_histogram_file
from pims.tasks.queue import BG_TASK_MAPPING, CELERY_TASK_MAPPING, Task, func_from_str
from pims.utils.strings import unique_name_generator

log = logging.getLogger("pims.app")

PENDING_PATH = Path(get_settings().pending_path)
FILE_ROOT_PATH = Path(get_settings().root)


class FileErrorProblem(BadRequestException):
    pass


class ImageParsingProblem(BadRequestException):
    pass


class FormatConversionProblem(BadRequestException):
    pass

Beispiel #5
0
from pims.api.utils.mimetype import OutputExtension, VISUALISATION_MIMETYPES, get_output_format
from pims.api.utils.models import (AssociatedName, CollectionSize, FormatId,
                                   ImageOutDisplayQueryParams, ZoomOrLevel)
from pims.api.utils.output_parameter import (get_thumb_output_dimensions,
                                             safeguard_output_dimensions)
from pims.api.utils.parameter import filepath_parameter, imagepath_parameter, path2filepath
from pims.api.utils.response import FastJsonResponse, convert_quantity, response_list
from pims.cache import cache_image_response
from pims.config import Settings, get_settings
from pims.files.file import FileRole, FileType, Path
from pims.formats.utils.structures.metadata import MetadataType
from pims.processing.image_response import AssociatedResponse

router = APIRouter()
api_tags = ['Metadata']
cache_associated_ttl = get_settings().cache_ttl_thumb


class SingleFileInfo(BaseModel):
    """
    Information about a file
    """

    file_type: FileType
    filepath: str = Field(
        ...,
        description=
        'The file path (filename with path, relative to the server root)',
        example='/a/b/c/thefile.png',
    )
    stem: str = Field(
Beispiel #6
0
#  * See the License for the specific language governing permissions and
#  * limitations under the License.

import logging
from enum import Enum
from importlib import import_module
from typing import Callable

from celery import Celery
from starlette.background import BackgroundTasks

from pims.config import get_settings

logger = logging.getLogger("pims.app")

settings = get_settings()

broker_url = f"{settings.task_queue_user}:{settings.task_queue_password}@{settings.task_queue_url}"
celery_app = Celery("worker",
                    broker=f"amqp://{broker_url}//",
                    backend=f"rpc://{broker_url}//")

celery_app.conf.update(task_serializer="pickle",
                       result_serializer="pickle",
                       accept_content=["pickle"],
                       broker_transport_options={
                           "max_retries": 3,
                           "interval_start": 0,
                           "interval_step": 0.2,
                           "interval_max": 0.5
                       })
Beispiel #7
0
from pims.api.utils.parameter import imagepath_parameter
from pims.api.utils.processing_parameter import (
    parse_bitdepth, parse_colormap_ids, parse_filter_ids,
    parse_intensity_bounds
)
from pims.cache import cache_image_response
from pims.config import Settings, get_settings
from pims.files.file import Path
from pims.filters import FILTERS
from pims.processing.colormaps import ALL_COLORMAPS
from pims.processing.image_response import ResizedResponse
from pims.utils.iterables import check_array_size_parameters, ensure_list

router = APIRouter()
api_tags = ['Resized']
cache_ttl = get_settings().cache_ttl_resized


@router.get('/image/{filepath:path}/resized{extension:path}', tags=api_tags)
async def show_resized(
    request: Request, response: Response,
    path: Path = Depends(imagepath_parameter),
    extension: OutputExtension = Depends(extension_path_parameter),
    output: ImageOutDisplayQueryParams = Depends(),
    output2: ImageOutProcessingQueryParams = Depends(),
    planes: PlaneSelectionQueryParams = Depends(),
    operations: ImageOpsProcessingQueryParams = Depends(),
    headers: ImageRequestHeaders = Depends(),
    config: Settings = Depends(get_settings)
):
    """
Beispiel #8
0
                                             check_zoom_validity,
                                             get_window_output_dimensions,
                                             safeguard_output_dimensions)
from pims.api.utils.parameter import imagepath_parameter
from pims.api.window import _show_window
from pims.cache import cache_image_response
from pims.config import Settings, get_settings
from pims.files.file import Path
from pims.processing.annotations import annotation_crop_affine_matrix, get_annotation_region
from pims.processing.image_response import MaskResponse
from pims.utils.color import WHITE
from pims.utils.iterables import ensure_list

router = APIRouter()
api_tags = ['Annotations']
cache_ttl = get_settings().cache_ttl_window


@router.post('/image/{filepath:path}/annotation/mask{extension:path}',
             tags=api_tags)
async def show_mask(
    request: Request,
    response: Response,
    body: AnnotationMaskRequest,
    path: Path = Depends(imagepath_parameter),
    extension: OutputExtension = Depends(extension_path_parameter),
    headers: ImageAnnotationRequestHeaders = Depends(),
    config: Settings = Depends(get_settings)):
    """
    **`GET with body` - when a GET with URL encoded query parameters is not possible due to URL
    size limits, a POST with body content must be used.**
Beispiel #9
0
#  *
#  *      http://www.apache.org/licenses/LICENSE-2.0
#  *
#  * Unless required by applicable law or agreed to in writing, software
#  * distributed under the License is distributed on an "AS IS" BASIS,
#  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  * See the License for the specific language governing permissions and
#  * limitations under the License.
from enum import Enum
from typing import Optional

from fastapi import Depends, Header

from pims.config import get_settings

DEFAULT_SAFE_MODE = get_settings().default_image_size_safety_mode
DEFAULT_ANNOTATION_ORIGIN = get_settings().default_annotation_origin


def serialize_header(value, style='simple', explode=False):  # noqa
    """
    Serialize a header according to https://swagger.io/docs/specification/serialization/.

    Parameters
    ----------
    value :
        Value to serialize
    style : str ('simple')
        Serialization style.
    explode : bool
        Explode the object serialization.
Beispiel #10
0
from skimage.exposure import histogram

from pims.api.utils.models import Colorspace, HistogramType
from pims.api.utils.output_parameter import get_thumb_output_dimensions
from pims.config import get_settings
from pims.files.file import Path
from pims.files.histogram import Histogram
from pims.processing.histograms import ZarrHistogramFormat
from pims.processing.histograms.format import (ZHF_ATTR_FORMAT, ZHF_ATTR_TYPE,
                                               ZHF_BOUNDS, ZHF_HIST,
                                               ZHF_PER_CHANNEL, ZHF_PER_IMAGE,
                                               ZHF_PER_PLANE)
from pims.processing.pixels import ImagePixels
from pims.utils.math import max_intensity

MAX_PIXELS_COMPLETE_HISTOGRAM = get_settings().max_pixels_complete_histogram
MAX_LENGTH_COMPLETE_HISTOGRAM = get_settings().max_length_complete_histogram


def argmin_nonzero(arr, axis=-1):
    """
    Get the smallest indices of non-zero values along the specified axis.
    """
    return np.argmax(arr != 0, axis=axis)


def argmax_nonzero(arr, axis=-1):
    """
    Get the biggest indices of non-zero values along the specified axis,
    in the reverted array. It gives the index of the last non-zero value in
    the array.
Beispiel #11
0
from pims.api.utils.parameter import imagepath_parameter
from pims.api.utils.processing_parameter import (
    parse_colormap_ids, parse_filter_ids,
    parse_intensity_bounds
)
from pims.cache import cache_image_response
from pims.config import Settings, get_settings
from pims.files.file import Path
from pims.filters import FILTERS
from pims.processing.colormaps import ALL_COLORMAPS
from pims.processing.image_response import ThumbnailResponse
from pims.utils.iterables import check_array_size_parameters, ensure_list

router = APIRouter()
api_tags = ['Thumbnails']
cache_ttl = get_settings().cache_ttl_thumb


@router.get('/image/{filepath:path}/thumb{extension:path}', tags=api_tags)
async def show_thumb(
    request: Request, response: Response,
    path: Path = Depends(imagepath_parameter),
    extension: OutputExtension = Depends(extension_path_parameter),
    output: ImageOutDisplayQueryParams = Depends(),
    planes: PlaneSelectionQueryParams = Depends(),
    operations: ImageOpsDisplayQueryParams = Depends(),
    use_precomputed: bool = Query(True),
    headers: ImageRequestHeaders = Depends(),
    config: Settings = Depends(get_settings)
):
    """
Beispiel #12
0
from pims.api.utils.parameter import imagepath_parameter
from pims.api.utils.processing_parameter import (parse_colormap_ids,
                                                 parse_filter_ids,
                                                 parse_intensity_bounds)
from pims.cache import cache_image_response
from pims.config import Settings, get_settings
from pims.files.file import Path
from pims.filters import FILTERS
from pims.processing.colormaps import ALL_COLORMAPS
from pims.processing.image_response import TileResponse, WindowResponse
from pims.utils.iterables import check_array_size_parameters, ensure_list

router = APIRouter()
tile_tags = ['Tiles']
norm_tile_tags = ['Normalized tiles']
cache_ttl = get_settings().cache_ttl_tile


@router.post('/image/{filepath:path}/tile{extension:path}', tags=tile_tags)
async def show_tile_with_body(
    request: Request,
    response: Response,
    body: TileRequest,
    path: Path = Depends(imagepath_parameter),
    extension: OutputExtension = Depends(extension_path_parameter),
    headers: ImageRequestHeaders = Depends(),
    config: Settings = Depends(get_settings)):
    """
    **`GET with body` - when a GET with URL encoded query parameters is not possible due to URL
    size limits, a POST with body content must be used.**