Skip to content

Commit

Permalink
Stack image legend implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
plankter committed Jul 24, 2019
1 parent fdfbb19 commit 2e0b5d4
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 74 deletions.
2 changes: 1 addition & 1 deletion backend/app/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# PyCharm Debugging
import pydevd_pycharm
# TODO: Don't forget to modify IP address!!
pydevd_pycharm.settrace('192.168.1.129', port=5679, stdoutToServer=True, stderrToServer=True, suspend=False)
pydevd_pycharm.settrace('130.60.106.36', port=5679, stdoutToServer=True, stderrToServer=True, suspend=False)

pass
except Exception as e:
Expand Down
6 changes: 6 additions & 0 deletions backend/app/app/modules/channel/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ class FilterModel(BaseModel):
settings: Optional[dict]


class LegendModel(BaseModel):
apply: bool
settings: Optional[dict]


class ChannelStackModel(BaseModel):
filter: FilterModel
legend: LegendModel
channels: List[ChannelSettingsModel]
format: Optional[str] = 'png'
118 changes: 73 additions & 45 deletions backend/app/app/modules/channel/router.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import os
from io import BytesIO
from typing import List
from typing import List, Tuple

import cv2
import numpy as np
import redis
import ujson
from fastapi import APIRouter, Depends
from matplotlib.colors import to_rgb, to_rgba
from sqlalchemy.orm import Session
from starlette.requests import Request
from starlette.responses import StreamingResponse, UJSONResponse
Expand All @@ -15,7 +16,6 @@
from app.api.utils.security import get_current_active_superuser, get_current_active_user
from app.core.utils import colorize, scale_image, apply_filter
from app.modules.user.db import User

from . import crud
from .models import ChannelModel, ChannelStatsModel, ChannelStackModel

Expand All @@ -26,10 +26,10 @@

@router.get("/", response_model=List[ChannelModel])
def read_channels(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_current_active_superuser),
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_current_active_superuser),
):
"""
Retrieve channels
Expand All @@ -40,9 +40,9 @@ def read_channels(

@router.get("/{id}", response_model=ChannelModel)
def read_channel_by_id(
id: int,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
id: int,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Get a specific channel by id
Expand All @@ -59,14 +59,38 @@ async def stream_image(record: bytes, chunk_size: int = 65536):
data = stream.read(chunk_size)


@router.get("/{id}/stats", response_model=ChannelStatsModel)
async def read_channel_stats(
id: int,
request: Request,
bins: int = 100,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Get channel stats by id
"""
content = r.get(request.url.path)
if content:
return UJSONResponse(content=ujson.loads(content))

item = crud.get(db, id=id)

data = np.load(os.path.join(item.location, "origin.npy"))
hist, edges = np.histogram(data.ravel(), bins=bins)
content = {"hist": hist.tolist(), "edges": edges.tolist()}
r.set(request.url.path, ujson.dumps(content))
return UJSONResponse(content=content)


@router.get("/{id}/image", responses={200: {"content": {"image/png": {}}}})
async def read_channel_image(
id: int,
color: str = None,
min: float = None,
max: float = None,
# current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
id: int,
color: str = None,
min: float = None,
max: float = None,
# current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Get channel image by id
Expand All @@ -78,7 +102,7 @@ async def read_channel_image(
levels = (min, max) if min is not None and max is not None else (item.min_intensity, item.max_intensity)
data = scale_image(data, levels)

color = f'#{color}' if color else None
color = f'#{color}' if color else '#ffffff'
image = colorize(data, color)

image = cv2.cvtColor(image.astype(data.dtype), cv2.COLOR_BGR2RGB)
Expand All @@ -87,50 +111,30 @@ async def read_channel_image(
return StreamingResponse(stream_image(result), media_type="image/png")


@router.get("/{id}/stats", response_model=ChannelStatsModel)
async def read_channel_stats(
id: int,
request: Request,
bins: int = 100,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Get channel stats by id
"""
content = r.get(request.url.path)
if content:
return UJSONResponse(content=ujson.loads(content))

item = crud.get(db, id=id)

data = np.load(os.path.join(item.location, "origin.npy"))
hist, edges = np.histogram(data.ravel(), bins=bins)
content = {"hist": hist.tolist(), "edges": edges.tolist()}
r.set(request.url.path, ujson.dumps(content))
return UJSONResponse(content=content)


@router.post("/stack")
async def download_channel_stack(
params: ChannelStackModel,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
params: ChannelStackModel,
current_user: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
"""
Download channel stack (additive) image
"""
additive_image: np.ndarray = None
legend_labels: List[Tuple[str, str]] = list()
for channel in params.channels:
item = crud.get(db, id=channel.id)
data = np.load(os.path.join(item.location, "origin.npy"))

levels = (channel.min, channel.max) if channel.min is not None and channel.max is not None else (item.min_intensity, item.max_intensity)
levels = (channel.min, channel.max) if channel.min is not None and channel.max is not None else (
item.min_intensity, item.max_intensity)
data = scale_image(data, levels)

color = channel.color if channel.color else None
color = channel.color if channel.color else '#ffffff'
image = colorize(data, color)

legend_labels.append((item.label, color))

if additive_image is None:
additive_image = np.zeros(shape=(data.shape[0], data.shape[1], 4), dtype=data.dtype)
additive_image += image
Expand All @@ -143,6 +147,30 @@ async def download_channel_stack(
if params.filter.apply:
additive_image = apply_filter(additive_image, params.filter)

if params.legend.apply:
for i, label in enumerate(legend_labels):
cv2.rectangle(
additive_image,
(5, 50 * (i + 1) - 30),
(15 + cv2.getTextSize(label[0], cv2.FONT_HERSHEY_DUPLEX, 1, 1)[0][0], 50 * (i + 1) - 30 + 40),
(0, 0, 0),
cv2.FILLED,
cv2.LINE_AA
)

b, g, r = tuple([255 * x for x in to_rgb(label[1])])
color = (r, g, b)
cv2.putText(
additive_image,
label[0],
(10, 50 * (i + 1)),
cv2.FONT_HERSHEY_DUPLEX,
1,
color,
1,
cv2.LINE_AA
)

format = params.format if params.format is not None else 'png'
status, result = cv2.imencode(f".{format}", additive_image.astype(int) if format == 'tiff' else additive_image)
return StreamingResponse(stream_image(result), media_type=f"image/{format}")
5 changes: 5 additions & 0 deletions frontend/src/modules/experiment/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ export class ExperimentActions extends Actions<ExperimentState, ExperimentGetter

async getChannelStackImage() {
const params = this.prepareStackParams();
if (params.channels.length === 0) {
return;
}
try {
const response = await api.downloadChannelStackImage(this.main!.getters.token, params);
const blob = await response.blob();
Expand Down Expand Up @@ -284,10 +287,12 @@ export class ExperimentActions extends Actions<ExperimentState, ExperimentGetter
});

const filter = this.settings!.getters.filter;
const legend = this.settings!.getters.legend;

return {
format: format,
filter: filter,
legend: legend,
channels: channels,
};
}
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/modules/experiment/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,25 @@ export interface IChannelStats {
edges: number[];
}

export interface IImageFilter {
apply: boolean;
type: string;
settings?: any;
}

export interface IImageLegend {
apply: boolean;
settings?: any;
}

export interface IChannelStack {
format?: string;
filter: IImageFilter;
legend: IImageLegend;
channels: Array<{
id: number;
color?: string;
min?: number;
max?: number;
}>;
}

export interface IImageFilter {
apply: boolean;
type: string;
settings?: any;
}
4 changes: 4 additions & 0 deletions frontend/src/modules/settings/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ export class SettingsGetters extends Getters<SettingsState> {
get filter() {
return this.state.filter;
}

get legend() {
return this.state.legend;
}
}
6 changes: 5 additions & 1 deletion frontend/src/modules/settings/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IImageFilter } from '@/modules/experiment/models';
import { IImageFilter, IImageLegend } from '@/modules/experiment/models';
import { Module } from 'vuex-smart-module';
import { SettingsActions } from './actions';
import { SettingsGetters } from './getters';
Expand All @@ -12,6 +12,10 @@ export class SettingsState {
apply: false,
type: 'gaussian',
settings: {},
};
legend: IImageLegend = {
apply: false,
settings: {},
}
}

Expand Down
10 changes: 9 additions & 1 deletion frontend/src/modules/settings/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IImageFilter } from '@/modules/experiment/models';
import { IImageFilter, IImageLegend } from '@/modules/experiment/models';
import { Mutations } from 'vuex-smart-module';
import { SettingsState } from '.';
import { IChannelSettings } from './models';
Expand All @@ -13,6 +13,10 @@ export class SettingsMutations extends Mutations<SettingsState> {
type: 'gaussian',
settings: {},
};
this.state.legend = {
apply: false,
settings: {},
};
}

setChannelSettings(channelSettings: IChannelSettings) {
Expand All @@ -27,4 +31,8 @@ export class SettingsMutations extends Mutations<SettingsState> {
setFilter(filter: IImageFilter) {
this.state.filter = filter;
}

setLegend(legend: IImageLegend) {
this.state.legend = legend;
}
}
Loading

0 comments on commit 2e0b5d4

Please sign in to comment.