Skip to content

Commit 82f3620

Browse files
authored
Merge branch 'TissUUmaps:master' into master
2 parents 7eedc94 + 8c7ce61 commit 82f3620

31 files changed

+825
-201
lines changed

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## 3.2.1
4+
5+
- Add scalebar to capture
6+
- Add project tab with project settings
7+
8+
## 3.2.0.5
9+
10+
- Fix MacOs X libvips issue with 90% jpeg compression
11+
- Make top menu collapsible in config
12+
- Add project list and description in upper corner (optional)
13+
14+
## 3.2.0.4
15+
16+
- Run Docker container as non-root user
17+
- Allow marker size slider to be used with absolute scaling
18+
- Fix incorrect transforms of region points when image layer is flipped
19+
- Fix MacOS X libvps issue with percent value
20+
- Add new blend modes for marker rendering
21+
322
## 3.2
423

524
- Add annotation tools (in collaboration with Sanofi Digital R&D)

container/Dockerfile

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
FROM python:3.10-alpine
22

3+
# Set usertmap as a variable
4+
ENV USER=usertmap
5+
6+
# Create a non-root user
7+
RUN adduser -D $USER
8+
39
COPY ./container/requirements.txt /requirements.txt
410

511
# Install libvips
@@ -41,11 +47,14 @@ RUN pip3 install gunicorn gevent
4147

4248
COPY ./container/tissuumaps.cfg /tissuumaps.cfg
4349
COPY ./tissuumaps/ /app/tissuumaps
50+
51+
# Set up permissions and environment variables
4452
WORKDIR /app/
4553
ENV PYTHONPATH /app
46-
4754
ENV GUNICORN_CMD_ARGS "--bind=0.0.0.0:80 --workers=8 --thread=8 --worker-class=gevent --forwarded-allow-ips='*' -"
48-
4955
ENV TISSUUMAPS_CONF /tissuumaps.cfg
5056

57+
# Change to the non-root user
58+
USER $USER
59+
5160
CMD ["gunicorn", "tissuumaps:app"]

container/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ pyvips>=2.1.14
77
matplotlib>=3.2.2
88
pyyaml>=6.0
99
h5py>=3.6.0
10-
tissuumaps-schema~=1.2.0
10+
tissuumaps-schema~=1.3.0

plugins_repo/3.1/DEPICTER.gif

4.66 MB
Loading

plugins_repo/latest/DEPICTER.gif

4.66 MB
Loading

plugins_repo/latest/DEPICTER.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ name: DEPICTER
33
version: 0.1.0
44
author: Eduard Chelebian - Christophe Avenel
55
date: 2023-08-08
6-
image:
6+
image: DEPICTER.gif
77
description:

plugins_repo/latest/InteractionQC.js

+35-17
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ InteractionQC = {
2222
type: "section",
2323
collapsed: true,
2424
},
25+
_uns_postfix: {
26+
label: "For HDF5 files, postfix of the uns key containing the matrix:",
27+
type: "text",
28+
default: "/uns/{obs}_nhood_enrichment/zscore",
29+
},
2530
},
2631
_matrix: null,
2732
_matrix_header: null,
@@ -53,7 +58,7 @@ InteractionQC.init = function (container) {
5358
kind: "label",
5459
extraAttributes: { for: "matrix" },
5560
});
56-
label412.innerText = "Select file";
61+
label412.innerText = "For CSV file, select it on disk:";
5762

5863
input411.addEventListener("change", (event) => {
5964
var reader = new FileReader();
@@ -136,12 +141,16 @@ InteractionQC.loadFromH5AD = async function () {
136141
data_obj = dataUtils.data[_dataset];
137142
if (!data_obj._filetype == "h5") return;
138143
try {
139-
let obs = data_obj._gb_col.replace(/\/obs/g, "");
144+
let obs = data_obj._gb_col.replace(/\/obs/g, "").replace(/\//g, "");
145+
InteractionQC._className = obs;
140146
let matrix = await dataUtils._hdf5Api.get(data_obj._csv_path, {
141-
path: "/uns/" + obs + "_nhood_enrichment/zscore",
147+
path: InteractionQC.get("_uns_postfix").replace("{obs}", obs),
142148
});
143149
console.log(matrix);
144-
let _matrix_header = Object.keys(data_obj._groupgarden);
150+
let _matrix_header = await dataUtils._hdf5Api.get(data_obj._csv_path, {
151+
path: "/obs/" + obs + "/categories",
152+
});
153+
_matrix_header = _matrix_header.value;
145154
// convert matrix from 1D typed array of shape NxN to array of arrays
146155

147156
InteractionQC._matrix = [];
@@ -173,6 +182,8 @@ InteractionQC.run = async function () {
173182
let _matrix_header = InteractionQC._matrix_header;
174183
if (!InteractionQC._matrix_header) {
175184
_matrix_header = await InteractionQC.loadFromH5AD();
185+
} else {
186+
InteractionQC._className = "Cell classes";
176187
}
177188
var op = tmapp["object_prefix"];
178189

@@ -197,18 +208,19 @@ InteractionQC.run = async function () {
197208
tickvals: _matrix_header,
198209
ticktext: _matrix_header.map(function (text) {
199210
let color = document.getElementById(
200-
_dataset + "_" + text + "_color",
211+
_dataset + "_" + text.replace(/ /g, "_") + "_color",
201212
)?.value;
202-
return "<span style='font-weight:bold;color:" + color + "'>███</span>";
213+
return "<span style='font-weight:bold;color:" + color + "'>█</span>";
203214
}),
204215
ticks: "",
205-
tickangle: 90,
216+
tickangle: 0,
206217
title: {
207-
text: "Cell class 2",
218+
text: InteractionQC._className,
208219
font: {
209-
size: 25,
220+
size: 20,
210221
color: "black",
211222
},
223+
standoff: 20,
212224
},
213225
},
214226
xaxis: {
@@ -219,20 +231,19 @@ InteractionQC.run = async function () {
219231
.reverse()
220232
.map(function (text) {
221233
let color = document.getElementById(
222-
_dataset + "_" + text + "_color",
234+
_dataset + "_" + text.replace(/ /g, "_") + "_color",
223235
)?.value;
224-
return (
225-
"<span style='font-weight:bold;color:" + color + "'>███</span>"
226-
);
236+
return "<span style='font-weight:bold;color:" + color + "'>█</span>";
227237
}),
228238
ticks: "",
229-
tickangle: 0,
239+
tickangle: 90,
230240
title: {
231-
text: "Cell class 1",
241+
text: InteractionQC._className,
232242
font: {
233-
size: 25,
243+
size: 20,
234244
color: "black",
235245
},
246+
standoff: 5,
236247
},
237248
ticklabelposition: "top",
238249
side: "top",
@@ -254,7 +265,7 @@ InteractionQC.run = async function () {
254265
let legend = "";
255266
for (type of _matrix_header) {
256267
let typecolor = document.getElementById(
257-
_dataset + "_" + type + "_color",
268+
_dataset + "_" + type.replace(/ /g, "_") + "_color",
258269
)?.value;
259270
legend +=
260271
"<div style='display:inline-block;margin-right:10px;'><span style='width:15px;color:" +
@@ -285,6 +296,13 @@ InteractionQC.run = async function () {
285296
data.points[i].y.toPrecision(4) + '\n\n';
286297
}
287298
alert('Closest point clicked:\n\n'+pts);*/
299+
})
300+
.on("plotly_doubleclick", function () {
301+
setTimeout(function () {
302+
var uid = _dataset;
303+
document.getElementById(uid + "_all_check").checked = false;
304+
document.getElementById(uid + "_all_check").click();
305+
}, 200);
288306
});
289307
};
290308

plugins_repo/latest/InteractionQC.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: InteractionV&QC plugin
3-
version: 1.1
3+
version: 1.1.2
44
author: Andrea Behanova, Christophe Avenel
55
date: 2022-10-31
66
image: InteractionQC.gif

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ install_requires =
3131
h5py>=3.6.0
3232
scipy>=1.10.1
3333
packaging>=21.0
34-
tissuumaps-schema~=1.2.0
34+
tissuumaps-schema~=1.3.0
3535

3636
[options.entry_points]
3737
console_scripts =

standalone/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ matplotlib>=3.2.2
99
pyyaml>=5.0
1010
h5py>=3.6.0
1111
scipy>=1.10.1
12-
tissuumaps-schema~=1.2.0
12+
tissuumaps-schema~=1.3.0

tissuumaps/VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.2.0.1
1+
3.2.1.4

tissuumaps/__init__.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,24 @@
3030
SLIDE_DIR = "/mnt/data/shared/"
3131
DEFAULT_PROJECT = False
3232
SLIDE_CACHE_SIZE = 60
33+
3334
DEEPZOOM_FORMAT = "jpeg"
3435
DEEPZOOM_TILE_SIZE = 254
3536
DEEPZOOM_OVERLAP = 1
3637
DEEPZOOM_LIMIT_BOUNDS = True
3738
DEEPZOOM_TILE_QUALITY = 90
3839

39-
FOLDER_DEPTH = 4
40-
PLUGINS = []
40+
VIPS_MIN_OUTLIER_PERC = 0.5
41+
VIPS_MAX_OUTLIER_PERC = 99.5
42+
VIPS_FORCE_RESCALE = False
43+
VIPS_JPEG_COMPRESSION = 85
44+
VIPS_EXCLUDE_MIN_INTENSITY = False
4145

46+
PLUGINS = []
47+
DEBUG_CLI = False
4248
READ_ONLY = False
49+
COLLAPSE_TOP_MENU = False
50+
PROJECT_LIST = False
4351

4452
# determine if application is a script file or frozen exe
4553
if getattr(sys, "frozen", False):
@@ -69,6 +77,10 @@
6977
os.path.expanduser("~"), ".tissuumaps", "plugins"
7078
)
7179
app.config.from_envvar("TISSUUMAPS_CONF", silent=True)
80+
userConfigFile = os.path.join(os.path.expanduser("~"), ".tissuumaps", "tissuumaps.cfg")
81+
82+
if os.path.isfile(userConfigFile):
83+
app.config.from_pyfile(userConfigFile)
7284

7385

7486
def getPluginInFolder(folder):

tissuumaps/__main__.py

-8
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,6 @@ def main():
7575
type="int",
7676
help="tile size [254]",
7777
)
78-
parser.add_option(
79-
"-D",
80-
"--depth",
81-
metavar="LEVELS",
82-
dest="FOLDER_DEPTH",
83-
type="int",
84-
help="folder depth search for opening files [4]",
85-
)
8678
parser.add_option(
8779
"-r",
8880
"--readonly",

tissuumaps/gui.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ def main():
967967

968968
if opts.DEBUG:
969969
views.app.config["DEBUG_CLI"] = True
970+
if views.app.config["DEBUG_CLI"]:
970971
log = logging.getLogger("werkzeug")
971972
log.setLevel(logging.DEBUG)
972973
log = logging.getLogger("pyvips")
@@ -982,7 +983,6 @@ def main():
982983

983984
# os.environ['WERKZEUG_RUN_MAIN'] = 'true'
984985
else:
985-
views.app.config["DEBUG_CLI"] = False
986986
log = logging.getLogger("werkzeug")
987987
log.setLevel(logging.ERROR)
988988
log = logging.getLogger("pyvips")

tissuumaps/read_h5ad.py

+38-20
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,13 @@ def h5ad_to_tmap(basedir, path, library_id=None):
150150
"tSNE",
151151
]:
152152
if coordinates in list(adata.get("obsm", [])):
153-
globalX, globalY = f"/obsm/{coordinates};0", f"/obsm/{coordinates};1"
153+
if (
154+
isinstance(adata.get(f"/obsm/{coordinates}"), h5py.Group)
155+
and "x" in adata.get(f"/obsm/{coordinates}").keys()
156+
):
157+
globalX, globalY = f"/obsm/{coordinates}/x", f"/obsm/{coordinates}/y"
158+
else:
159+
globalX, globalY = f"/obsm/{coordinates};0", f"/obsm/{coordinates};1"
154160
break
155161

156162
if list(adata.get("/obs/__categories/", [])) != []:
@@ -170,13 +176,6 @@ def h5ad_to_tmap(basedir, path, library_id=None):
170176

171177
layers = []
172178
library_ids = list(adata.get("uns/spatial", []))
173-
try:
174-
library_ids = adata["/obs/library_id/categories"].asstr()[...]
175-
except Exception:
176-
try:
177-
library_ids = adata["/obs/__categories/library_id"].asstr()[...]
178-
except Exception:
179-
pass
180179

181180
coord_factor = 1
182181
for library_id in library_ids:
@@ -216,16 +215,28 @@ def h5ad_to_tmap(basedir, path, library_id=None):
216215
img = None
217216
import traceback
218217

218+
logging.error(traceback.format_exc())
219+
try:
220+
library_ids = adata["/obs/library_id/categories"][...]
221+
except Exception:
222+
import traceback
223+
224+
logging.error(traceback.format_exc())
225+
try:
226+
library_ids = adata["/obs/__categories/library_id"][...]
227+
except Exception:
228+
import traceback
229+
219230
logging.error(traceback.format_exc())
220231

221232
use_libraries = len(library_ids) > 1
222233

223234
library_col = ""
224235
if use_libraries:
225-
if "/obsm/spatial;" in globalX:
236+
if "/obsm/spatial;" in globalX or "/obsm/X_spatial;" in globalX:
226237
try:
227238
library_codes_array = adata["/obs/library_id/codes"][...]
228-
library_categ_array = adata["/obs/library_id/categories"].asstr()[...]
239+
library_categ_array = adata["/obs/library_id/categories"][...]
229240
library_col = "/obs/library_id/codes"
230241
except Exception:
231242
library_codes_array = adata["/obs/library_id"][...]
@@ -236,16 +247,21 @@ def h5ad_to_tmap(basedir, path, library_id=None):
236247
if not write_adata:
237248
write_adata = True
238249
adata, path = get_write_adata(adata, path, basedir)
239-
spatial_array = adata["/obsm/spatial"][()]
250+
spatial_array = adata[globalX.split(";")[0]][()]
240251

241252
spatial_scaled_array = np.ones(spatial_array.shape)
242253
for library_index, library_id in enumerate(library_categ_array):
243-
scale_factor = float(
244-
adata.get(
245-
f"/uns/spatial/{library_id}/scalefactors/tissue_{img_key}_scalef",
246-
1,
247-
)[()]
248-
)
254+
try:
255+
# Try to get the value from the specified path
256+
scale_factor = float(
257+
adata[
258+
f"/uns/spatial/{library_id}/scalefactors/tissue_{img_key}_scalef"
259+
][()]
260+
)
261+
except (KeyError, TypeError, ValueError):
262+
# Set default value to 1 if
263+
# path does not exist or if there's an error
264+
scale_factor = 1.0
249265
spatial_scaled_array[library_codes_array == library_index] = (
250266
spatial_array[library_codes_array == library_index]
251267
* scale_factor
@@ -292,14 +308,16 @@ def h5ad_to_tmap(basedir, path, library_id=None):
292308
obsListCategorical = []
293309
obsListNumerical = []
294310
palette = {}
295-
296-
obsIndex = str(adata.get("/obs").attrs["_index"])
311+
try:
312+
obsIndex = str(adata.get("/obs").attrs["_index"])
313+
except Exception:
314+
obsIndex = ""
297315
for obs in obsList:
298316
if adata.get(f"/obs/{obs}/categories") is not None:
299317
obsListCategorical.append(obs)
300318
p = getPalette(adata, obs)
301319
palette[obs] = p
302-
elif adata.get(f"/obs/{obs}").dtype.kind in "iuf":
320+
elif adata.get(f"/obs/{obs}") and adata.get(f"/obs/{obs}").dtype.kind in "iuf":
303321
obsListNumerical.append(obs)
304322
elif obs == obsIndex:
305323
continue

0 commit comments

Comments
 (0)