Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
quality_check_gui
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Container Registry
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Wallenfang, Nils
quality_check_gui
Commits
432d6483
Commit
432d6483
authored
Aug 13, 2020
by
Wallenfang, Nils
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into 'data_processing'
# Conflicts: # quality_check_gui.py
parents
893db35f
34ec7b23
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
346 additions
and
82 deletions
+346
-82
TODO.md
TODO.md
+15
-7
analyze_section.py
analyze_section.py
+44
-0
generate_preview.py
generate_preview.py
+71
-3
gui_components.py
gui_components.py
+68
-6
qchecks/sine_check.py
qchecks/sine_check.py
+48
-13
quality_check_gui.py
quality_check_gui.py
+96
-53
resources/DejaVuSans.ttf
resources/DejaVuSans.ttf
+0
-0
resources/icon.png
resources/icon.png
+0
-0
resources/icon_source.txt
resources/icon_source.txt
+2
-0
utils/default_config.ini
utils/default_config.ini
+2
-0
No files found.
TODO.md
View file @
432d6483
...
...
@@ -5,18 +5,26 @@
Tasks related to the quality check algorithms.
*
[ ] automatically filter tiles without tissue
## Previews
-
[X] Put font file in resources directory
-
[x] Add checkbox to mark outlier pixels in preview
-
Also save clean preview
-
[X] Save marked preview in preview directory
## GUI / UX
*
[
] add default config file that is read when no
`config.ini`
exists
*
[
X
] add default config file that is read when no
`config.ini`
exists
-
push this default config to repo instead of specifig config
-
[
] implement a logging functionality
-
[
] use separate GUI and logic threads to prevent stutter
-
[
X
] implement a logging functionality
-
[
X
] use separate GUI and logic threads to prevent stutter
## Technical
-
[ ] validate config input
-
[ ] add requirements.txt or setup.py
-
[
] use default config file that is used when no config is found
-
[
X
] use default config file that is used when no config is found
# DONE
-
[X] introduce pixel sampling quality check
\ No newline at end of file
-
[X] introduce pixel sampling quality check
-
[X] automatically filter tiles without tissue
\ No newline at end of file
analyze_section.py
View file @
432d6483
...
...
@@ -51,3 +51,47 @@ def analyze_section(root_path, quality_check, on_tile_done=None):
on_tile_done
(
x
,
y
,
is_outlier
,
error_measure
)
return
error_measure_matrix
def
analyze_section_with_coords
(
root_path
,
quality_check
,
on_tile_done
=
None
):
"""
Args:
root_path (str): Path of the root directory of the raw data
quality_check (QualityCheck): object of type SineCheck that defines an error measure for each tile.
on_tile_done (function(int, int, list)): Callback function. If passed, this function
will be called after every processed tile. The function will be called with the x- and
y-indices of the tile as int and a list that contains outlier image indices (or is empty).
Returns:
[np.array()]: 2d-array with dimensions corresponding to number of tiles. Each entry
of the 2d-array is a float with the error measure of the respective tile.
[np.array()]: TODO
"""
# get last (alphabetic ordering) directory name in root_path to extract x- and y-dimension
directories
=
sorted
([
f
for
f
in
listdir
(
root_path
)
if
isdir
(
join
(
root_path
,
f
))])
last_dir
=
directories
[
-
1
]
xdim
,
ydim
=
last_dir
.
split
(
"_"
)
# tiles are zero indexed
xdim
,
ydim
=
int
(
xdim
)
+
1
,
int
(
ydim
)
+
1
error_measure_matrix
=
np
.
zeros
(
shape
=
(
xdim
,
ydim
))
outlier_matrix
=
np
.
empty
(
shape
=
(
xdim
,
ydim
),
dtype
=
object
)
filtered_matrix
=
np
.
empty
(
shape
=
(
xdim
,
ydim
),
dtype
=
object
)
# iterate over each tile
for
x
in
range
(
xdim
):
for
y
in
range
(
ydim
):
# call analysis and save outlier indices
img_path
=
f
"
{
root_path
}
/
{
str
(
x
).
zfill
(
2
)
}
_
{
str
(
y
).
zfill
(
2
)
}
/"
data
=
read_data
(
img_path
)
error_measure
,
outlier_coords
,
filtered_coords
=
quality_check
.
calc_error_measure
(
data
,
return_coords
=
True
)
error_measure_matrix
[
x
,
y
]
=
error_measure
outlier_matrix
[
x
,
y
]
=
outlier_coords
filtered_matrix
[
x
,
y
]
=
filtered_coords
is_outlier
=
quality_check
.
threshold
(
error_measure
)
if
on_tile_done
is
not
None
:
on_tile_done
(
x
,
y
,
is_outlier
,
error_measure
)
return
error_measure_matrix
,
outlier_matrix
,
filtered_matrix
generate_preview.py
View file @
432d6483
...
...
@@ -71,7 +71,7 @@ def generate_preview(tiff_path, overlap=0.28, scaling=None, image_index=0, origi
# tiles are zero indexed
nx
,
ny
=
int
(
xdim
)
+
1
,
int
(
ydim
)
+
1
if
scaling
is
None
:
if
scaling
is
None
or
scaling
==
""
:
scaling
=
determine_scaling_factor
(
nx
,
ny
,
overlap
)
# resolution of cropped tile (without overlapping region)
...
...
@@ -126,7 +126,9 @@ def generate_preview(tiff_path, overlap=0.28, scaling=None, image_index=0, origi
def
insert_labels
(
preview
,
xdim
,
ydim
):
img
=
Image
.
fromarray
(
preview
)
draw
=
ImageDraw
.
Draw
(
img
)
# font = ImageFont.truetype(config['PREVIEWS']['font_path'], 12)
preview_tile_x
=
preview
.
shape
[
0
]
/
xdim
font
=
ImageFont
.
truetype
(
'resources/DejaVuSans.ttf'
,
int
(
preview_tile_x
*
0.08
))
# TODO scale font with resolution
# tile dimensions
res_x
=
int
(
preview
.
shape
[
0
]
/
xdim
)
...
...
@@ -138,11 +140,77 @@ def insert_labels(preview, xdim, ydim):
# should be black or white
avgColor
=
np
.
mean
(
preview
[
x
*
res_x
:
x
*
res_x
+
20
,
y
*
res_y
:
y
*
res_y
+
20
])
# draw.text((y * res_y, x * res_x), f"{x:02d}_{y:02d}", fill=(255 if avgColor > 1000 else 0))
draw
.
text
((
y
*
res_y
,
x
*
res_x
),
f
"
{
xdim
-
x
-
1
:
02
d
}
_
{
ydim
-
y
-
1
:
02
d
}
"
,
(
2
**
12
if
avgColor
<
1200
else
0
))
draw
.
text
((
y
*
res_y
,
x
*
res_x
),
f
"
{
xdim
-
x
-
1
:
02
d
}
_
{
ydim
-
y
-
1
:
02
d
}
"
,
2
**
12
,
font
=
font
)
# if avgColor < 1200 else 0
return
np
.
asarray
(
img
)
def
mark_preview
(
preview
,
matrix
,
outlier_coords
,
filtered_coords
):
"""
Draw outlier markings in a given preview image
Args:
preview: Preview image as array.
matrix: Error measure matrix with dimensions corresponding to number of tiles in x/y
outlier_coords: Coordinates of outlier pixels per tile (unscaled)
filtered_coords: Coordinates of filtered pixels per tile (unscaled)
Returns: Marked preview image as array.
"""
preview
=
preview
.
copy
()
overlap
=
0.28
res_x
,
res_y
=
preview
.
shape
[
0
]
//
matrix
.
shape
[
0
],
preview
.
shape
[
1
]
//
matrix
.
shape
[
1
]
# width to be used for red rectangles and pixel markings
width
=
int
(
res_x
/
min
(
matrix
.
shape
)
*
0.05
+
0.5
)
idx
=
np
.
argwhere
((
matrix
<
0.775
)
&
(
matrix
!=
-
1
))
img
=
Image
.
fromarray
(
preview
/
2
**
4
).
convert
(
'RGB'
)
draw
=
ImageDraw
.
Draw
(
img
)
full_x
,
full_y
=
preview
.
shape
print
(
idx
)
# mark outlier tiles with red square
for
x
,
y
in
idx
:
draw
.
rectangle
([
full_y
-
(
y
+
1
)
*
res_y
,
full_x
-
(
x
+
1
)
*
res_x
,
full_y
-
y
*
res_y
-
1
,
full_x
-
x
*
res_x
-
1
],
outline
=
'red'
,
width
=
width
)
# mark coords
for
x
in
range
(
matrix
.
shape
[
0
]):
for
y
in
range
(
matrix
.
shape
[
1
]):
coords
=
filtered_coords
[
x
,
y
]
for
coord
in
coords
:
# TODO respect overlap!
y_from
=
int
(
full_y
-
(
y
+
1
)
*
res_y
+
coord
[
1
]
/
tile_y
*
res_y
+
0.5
)
x_from
=
int
(
full_x
-
(
x
+
1
)
*
res_x
+
coord
[
0
]
/
tile_x
*
res_x
+
0.5
)
y_to
=
y_from
+
width
x_to
=
x_from
+
width
# print(f'draw rectangle from ({y_from, x_from}) to ({y_to, x_to})')
draw
.
rectangle
([
y_from
,
x_from
,
y_to
,
x_to
],
fill
=
'blue'
)
coords
=
outlier_coords
[
x
,
y
]
if
matrix
[
x
,
y
]
==
-
1
:
continue
# mark outliers with red filled squares
for
coord
in
coords
:
# TODO respect overlap!
y_from
=
int
(
full_y
-
(
y
+
1
)
*
res_y
+
coord
[
0
,
1
]
/
tile_y
*
res_y
+
0.5
)
x_from
=
int
(
full_x
-
(
x
+
1
)
*
res_x
+
coord
[
0
,
0
]
/
tile_x
*
res_x
+
0.5
)
y_to
=
y_from
+
width
x_to
=
x_from
+
width
# print(f'draw rectangle from ({y_from, x_from}) to ({y_to, x_to})')
draw
.
rectangle
([
y_from
,
x_from
,
y_to
,
x_to
],
fill
=
'red'
)
# mark filtered coords with blue filled squares
return
np
.
array
(
img
)
if
__name__
==
'__main__'
:
def
main
():
preview
=
generate_preview
(
'/data/PLI-Group/Nils/stitching_minimal/'
)
...
...
gui_components.py
View file @
432d6483
...
...
@@ -5,22 +5,83 @@ from tkinter import ttk
from
utils.read_config
import
config
class
ToolTip
:
"""
see
https://stackoverflow.com/questions/20399243/display-message-when-hovering-over-something-with-mouse-cursor-in-python
"""
def
__init__
(
self
,
widget
):
self
.
widget
=
widget
self
.
tipwindow
=
None
self
.
id
=
None
self
.
text
=
None
self
.
x
=
self
.
y
=
0
def
showtip
(
self
,
text
):
# Display text in tooltip window
self
.
text
=
text
if
self
.
tipwindow
or
not
self
.
text
:
return
x
,
y
,
cx
,
cy
=
self
.
widget
.
bbox
(
"insert"
)
x
=
x
+
self
.
widget
.
winfo_rootx
()
+
57
y
=
y
+
cy
+
self
.
widget
.
winfo_rooty
()
+
27
self
.
tipwindow
=
tw
=
tkinter
.
Toplevel
(
self
.
widget
)
tw
.
wm_overrideredirect
(
1
)
tw
.
wm_geometry
(
"+%d+%d"
%
(
x
,
y
))
label
=
tkinter
.
Label
(
tw
,
text
=
self
.
text
,
justify
=
tkinter
.
LEFT
,
background
=
"#ffffe0"
,
relief
=
tkinter
.
SOLID
,
borderwidth
=
1
,
font
=
(
"tahoma"
,
"8"
,
"normal"
))
label
.
pack
(
ipadx
=
1
)
def
hidetip
(
self
):
tw
=
self
.
tipwindow
self
.
tipwindow
=
None
if
tw
:
tw
.
destroy
()
def
add_tooltip
(
widget
,
text
):
toolTip
=
ToolTip
(
widget
)
def
enter
(
event
):
toolTip
.
showtip
(
text
)
def
leave
(
event
):
toolTip
.
hidetip
()
widget
.
bind
(
'<Enter>'
,
enter
)
widget
.
bind
(
'<Leave>'
,
leave
)
class
PreviewOptions
(
tkinter
.
LabelFrame
):
def
__init__
(
self
,
master
):
super
().
__init__
(
master
=
master
,
text
=
'Preview generation'
)
# add the state variable to master frame for ease of access
master
.
preview_state
=
tkinter
.
IntVar
()
master
.
preview_state
.
set
(
1
)
master
.
mark_outliers_state
=
tkinter
.
IntVar
()
master
.
mark_outliers_state
.
set
(
0
)
self
.
chk_preview
=
tkinter
.
Checkbutton
(
self
,
text
=
' Generate preview'
,
variable
=
master
.
preview_state
)
self
.
chk_mark_outliers
=
tkinter
.
Checkbutton
(
self
,
text
=
' Mark outliers'
,
variable
=
master
.
mark_outliers_state
)
master
.
overlap
=
tkinter
.
StringVar
()
master
.
overlap
.
set
(
'0.28'
)
master
.
scaling
=
tkinter
.
StringVar
()
master
.
scaling
.
set
(
''
)
self
.
overlap_label
=
tkinter
.
Label
(
self
,
text
=
'Overlap: '
)
self
.
overlap_entry
=
tkinter
.
Entry
(
self
,
textvariable
=
master
.
overlap
)
self
.
scaling_label
=
tkinter
.
Label
(
self
,
text
=
'Scaling factor: '
)
self
.
scaling_entry
=
tkinter
.
Entry
(
self
,
textvariable
=
master
.
scaling
)
add_tooltip
(
self
.
scaling_label
,
'use automatic scaling factor if left empty'
)
add_tooltip
(
self
.
scaling_entry
,
'use automatic scaling factor if left empty'
)
self
.
chk_preview
.
grid
(
row
=
0
,
column
=
0
)
self
.
overlap_label
.
grid
(
row
=
1
,
column
=
0
)
self
.
overlap_entry
.
grid
(
row
=
1
,
column
=
1
)
self
.
chk_preview
.
grid
(
row
=
0
,
column
=
0
,
sticky
=
'W'
)
self
.
chk_mark_outliers
.
grid
(
row
=
1
,
column
=
0
,
sticky
=
'W'
)
self
.
overlap_label
.
grid
(
row
=
2
,
column
=
0
)
self
.
overlap_entry
.
grid
(
row
=
2
,
column
=
1
)
self
.
scaling_label
.
grid
(
row
=
3
,
column
=
0
)
self
.
scaling_entry
.
grid
(
row
=
3
,
column
=
1
)
class
QualityCheckOptions
(
tkinter
.
LabelFrame
):
...
...
@@ -41,8 +102,9 @@ class QualityCheckOptions(tkinter.LabelFrame):
self
.
dropdown
=
ttk
.
OptionMenu
(
self
,
master
.
dropdown_state
,
options
[
2
],
*
options
)
self
.
chk_quality
.
grid
(
row
=
0
,
column
=
0
)
self
.
method_label
.
grid
(
row
=
1
,
column
=
0
)
self
.
dropdown
.
grid
(
row
=
1
,
column
=
1
)
if
config
.
getboolean
(
'GUI'
,
'debug'
):
self
.
method_label
.
grid
(
row
=
1
,
column
=
0
)
self
.
dropdown
.
grid
(
row
=
1
,
column
=
1
)
class
DirectoryChooser
(
tkinter
.
Frame
):
...
...
@@ -127,4 +189,4 @@ class GUI(tkinter.Frame):
if
severity
!=
'debug'
:
self
.
result_text
.
insert
(
'end'
,
msg
+
'
\n
'
)
self
.
result_text
.
see
(
tkinter
.
END
)
\ No newline at end of file
self
.
result_text
.
see
(
tkinter
.
END
)
qchecks/sine_check.py
View file @
432d6483
...
...
@@ -43,7 +43,6 @@ def get_samples_grid(data, num=400):
"""
dim
=
data
.
shape
[
1
:]
stack
=
np
.
zeros
((
num
,
data
.
shape
[
0
]))
grid_length
=
int
(
np
.
sqrt
(
num
))
for
y
in
range
(
grid_length
):
for
x
in
range
(
grid_length
):
...
...
@@ -59,6 +58,33 @@ def get_samples_grid(data, num=400):
return
stack
def
get_samples_grid_coords
(
data
,
num
=
400
):
"""
Get samples that are evenly distributed over picture.
"""
dim
=
data
.
shape
[
1
:]
stack
=
np
.
zeros
((
num
,
data
.
shape
[
0
]))
coords
=
[]
grid_length
=
int
(
np
.
sqrt
(
num
))
for
y
in
range
(
grid_length
):
for
x
in
range
(
grid_length
):
stack
[
y
*
grid_length
+
x
,
:]
=
data
[:,
y
*
dim
[
0
]
//
grid_length
+
dim
[
0
]
%
grid_length
,
x
*
dim
[
1
]
//
grid_length
+
dim
[
1
]
%
grid_length
]
coords
.
append
((
y
*
dim
[
0
]
//
grid_length
+
dim
[
0
]
%
grid_length
,
x
*
dim
[
1
]
//
grid_length
+
dim
[
1
]
%
grid_length
))
# sample remainder randomly
for
i
in
range
(
grid_length
**
2
,
num
):
rand_x
,
rand_y
=
np
.
random
.
randint
(
0
,
data
.
shape
[
1
]),
np
.
random
.
randint
(
0
,
data
.
shape
[
2
])
stack
[
i
,
:]
=
data
[:,
rand_x
,
rand_y
]
coords
.
append
((
rand_y
,
rand_x
))
return
stack
,
np
.
array
(
coords
)
def
get_samples_random
(
data
,
num
=
1
):
dims
=
data
.
shape
[
1
:]
stack
=
np
.
zeros
((
num
,
data
.
shape
[
0
]))
...
...
@@ -110,23 +136,22 @@ class SineCheck(QualityCheck):
self
.
measure_func
=
measure_func
self
.
threshold_func
=
threshold_func
def
calc_error_measure
(
self
,
data
,
debug_plot
=
False
):
def
calc_error_measure
(
self
,
data
,
return_coords
=
False
,
debug_plot
=
False
):
"""
For a given tile data (image stack) calculate an error measure that represents
how well the signal of each pixel over the image stack corresponds to a sine signal.
Args:
data: tile data (shape=(n, y_dim, x_dim))
return_coords (bool): return indices of outlier pixels for this tile in addition to error measure if set
to True.
debug_plot: bool for debugging purposes, if set to True display each sine fit along with the datapoints.
Returns:
Aggregated error measure (e.g. mean of all sine fit errors). This function also returns -1 if more than 30%
of fits had to be discarded because the signal was too weak.
"""
filtered_coords
=
[]
# samples that have been ignored due to weakness of signal
errors
=
[]
samples
=
get_samples_grid
(
data
,
num
=
self
.
num
)
# number of samples that have been ignored due to weakness of signal
filtered_counter
=
0
samples
,
coords
=
get_samples_grid_coords
(
data
,
num
=
self
.
num
)
for
i
,
sample
in
enumerate
(
samples
):
try
:
...
...
@@ -138,22 +163,32 @@ class SineCheck(QualityCheck):
if
fit
.
amplitude
<
10
:
# too much noise / not enough refraction
# ignore this sample
filtered_co
unter
+=
1
filtered_co
ords
.
append
(
coords
[
i
])
continue
if
fit
.
mean
>
2000
:
# very bright, likely to be outside of tissue
# ignore this sample
filtered_co
unter
+=
1
filtered_co
ords
.
append
(
coords
[
i
])
continue
error
=
self
.
measure_func
(
sample
,
fit
)
errors
.
append
(
error
)
if
filtered_counter
/
self
.
num
>
0.775
:
# more than 75% of samples ignored, unable to judge this tile
return
-
1
errors
=
np
.
array
(
errors
)
if
return_coords
:
idx
=
np
.
argwhere
(
errors
<
0.7
)
outlier_coords
=
coords
[
idx
]
if
len
(
filtered_coords
)
/
self
.
num
>
0.775
:
# more than 75% of samples ignored, unable to judge this tile
return
-
1
,
outlier_coords
,
filtered_coords
else
:
return
self
.
aggregate_func
(
errors
),
outlier_coords
,
filtered_coords
else
:
return
self
.
aggregate_func
(
np
.
array
(
errors
))
if
len
(
filtered_coords
)
/
self
.
num
>
0.775
:
# more than 75% of samples ignored, unable to judge this tile
return
-
1
else
:
return
self
.
aggregate_func
(
errors
)
def
threshold
(
self
,
error_measure
):
if
error_measure
==
-
1
:
# unable to evaluate
...
...
quality_check_gui.py
View file @
432d6483
...
...
@@ -6,6 +6,7 @@ from datetime import datetime
from
os
import
listdir
from
os.path
import
isdir
import
logging
import
threading
import
numpy
as
np
import
matplotlib.pyplot
as
plt
...
...
@@ -13,9 +14,10 @@ import wx
from
dateutil.tz
import
tzlocal
from
wx.lib.agw.multidirdialog
import
(
DD_DIR_MUST_EXIST
,
DD_MULTIPLE
,
MultiDirDialog
)
from
PIL
import
Image
from
analyze_section
import
analyze_section
from
generate_preview
import
IndexOrigin
,
generate_preview
from
analyze_section
import
analyze_section
,
analyze_section_with_coords
from
generate_preview
import
IndexOrigin
,
generate_preview
,
mark_preview
from
utils.upload_log
import
upload_log
from
gui_components
import
GUI
from
qchecks.zero_check
import
ZeroCheck
...
...
@@ -23,8 +25,8 @@ from qchecks.sine_check import SineCheck
from
qchecks.sine_measures
import
r2_measure
from
utils.read_config
import
config
__version__
=
'0.1.
1
'
__revision_date
=
'
09.06
.2020'
__version__
=
'0.1.
3
'
__revision_date
=
'
28.07
.2020'
# TODO add file with python dependencies, especially opencv and wxpython, maybe add error handling if these libraries
...
...
@@ -92,47 +94,62 @@ def get_logger_filename(dir_name):
def
clicked_start
(
frame
):
entry
=
frame
.
dir_frame
.
tiff_path
.
get
()
for
path
in
entry
.
split
(
';'
):
norm
=
os
.
path
.
normpath
(
path
)
# identify the section that is being analysed by its directory name
dir_name
=
norm
.
split
(
os
.
sep
)[
-
2
]
path
=
check_directory
(
path
,
frame
)
if
not
path
:
# invalid directory
continue
if
frame
.
preview_state
.
get
():
# generate previews
frame
.
progress_text
.
set
(
f
'
{
dir_name
}
: Generating preview..'
)
frame
.
update
()
try
:
process_preview_gen
(
frame
,
path
)
except
ValueError
as
e
:
frame
.
print
(
'Invalid preview resolution provided, see config file "config.ini".'
)
# init logger
logger_path
=
get_logger_filename
(
dir_name
)
frame
.
logger
=
logging
.
getLogger
()
logging
.
basicConfig
(
level
=
logging
.
INFO
,
)
fh
=
logging
.
FileHandler
(
logger_path
)
fh
.
setLevel
(
logging
.
DEBUG
)
frame
.
logger
.
addHandler
(
fh
)
if
frame
.
quality_state
.
get
():
# run quality check
frame
.
progress_text
.
set
(
f
'
{
dir_name
}
: Running quality check..'
)
frame
.
update
()
process_quality_check
(
frame
,
path
,
logger_path
)
frame
.
progress_text
.
set
(
'Done!'
)
def
save_preview
(
preview
,
frame
,
tiff_path
):
def
logic_thread_task
():
preview
=
None
for
directory_entry
in
entry
.
split
(
';'
):
paths
=
check_directory
(
directory_entry
,
frame
)
if
not
paths
:
# invalid directory
continue
for
path
in
paths
:
norm
=
os
.
path
.
normpath
(
path
)
# identify the section that is being analysed by its directory name
dir_name
=
norm
.
split
(
os
.
sep
)[
-
2
]
frame
.
print
(
f
'---
{
dir_name
}
---
\n
'
)
if
frame
.
preview_state
.
get
():
# generate previews
frame
.
progress_text
.
set
(
f
'
{
dir_name
}
: Generating preview..'
)
frame
.
update
()
try
:
preview
=
process_preview_gen
(
frame
,
path
)
except
ValueError
as
e
:
frame
.
print
(
'Invalid preview resolution provided, see config file "config.ini".'
)
# init logger
log_path
=
get_logger_filename
(
dir_name
)
frame
.
logger
=
logging
.
getLogger
()
logging
.
basicConfig
(
level
=
logging
.
INFO
,
)
fh
=
logging
.
FileHandler
(
log_path
)
fh
.
setLevel
(
logging
.
DEBUG
)
frame
.
logger
.
addHandler
(
fh
)
if
frame
.
quality_state
.
get
():
# run quality check
frame
.
progress_text
.
set
(
f
'
{
dir_name
}
: Running quality check..'
)
frame
.
print
(
f
'Running quality check..'
)
frame
.
update
()
error_matrix
,
outlier_coords
,
filtered_coords
=
process_quality_check
(
frame
,
path
)
frame
.
print
(
f
'Saved logfile at "
{
log_path
}
".'
)
if
frame
.
mark_outliers_state
.
get
():
# generate preview with marked outliers
# TODO catch error when no preview was generated
marked_preview
=
mark_preview
(
preview
,
error_matrix
,
outlier_coords
,
filtered_coords
)
norm
=
os
.
path
.
normpath
(
directory_entry
)
# identify the section that is being analysed by its directory name
dir_name
=
norm
.
split
(
os
.
sep
)[
-
2
]
+
'_marked'
save_preview
(
marked_preview
,
frame
,
dir_name
)
frame
.
print
(
f
''
)
frame
.
progress_text
.
set
(
'Done!'
)
threading
.
Thread
(
target
=
logic_thread_task
).
start
()
def
save_preview
(
preview
,
frame
,
dir_name
):
preview_root
=
config
[
'PREVIEWS'
][
'preview_path'
]
# save preview to a fixed path
time_stamp
=
datetime
.
now
(
tzlocal
()).
strftime
(
'%Y%m%d_%H-%M'
)
norm
=
os
.
path
.
normpath
(
tiff_path
)
# identify the section that is being analysed by its directory name
dir_name
=
norm
.
split
(
os
.
sep
)[
-
2
]
preview_path
=
f
'
{
preview_root
}
/
{
dir_name
}
_
{
time_stamp
}
.png'
# TODO ensure no file overrides happen
...
...
@@ -155,7 +172,7 @@ def process_preview_gen(frame, tiff_path):
frame
.
progress
[
"value"
]
=
frame
.
progress
[
"value"
]
+
1
frame
.
progress
.
update
()
# read overlap from
gui
# read overlap from
GUI
overlap
=
frame
.
overlap
.
get
()
bad_overlap
=
False
try
:
...
...
@@ -167,17 +184,33 @@ def process_preview_gen(frame, tiff_path):
frame
.
print
(
f
'Invalid overlap provided, using default value
{
default_overlap
}
.'
)
overlap
=
default_overlap
# read scaling factor from GUI
scaling
=
frame
.
scaling
.
get
()
if
not
scaling
==
""
:
bad_scaling
=
False
try
:
scaling
=
float
(
scaling
)
except
ValueError
:
bad_scaling
=
True
if
bad_scaling
or
scaling
<=
0
:
frame
.
print
(
f
'Invalid scaling factor provided, using automatic scaling.'
)
scaling
=
None
# benchmark time
time_pre
=
time
.
time
()
preview
=
generate_preview
(
tiff_path
,
overlap
=
overlap
,
origin
=
IndexOrigin
.
BOTTOM_RIGHT
,
preview
=
generate_preview
(
tiff_path
,
overlap
=
overlap
,
scaling
=
scaling
,
origin
=
IndexOrigin
.
BOTTOM_RIGHT
,
on_tile_done
=
progressbar_callback
,
insert_index_labels
=
True
)
time_post
=
time
.
time
()
frame
.
print
(
f
'Generated preview in
{
time_post
-
time_pre
:.
2
f
}
s'
)
norm
=
os
.
path
.
normpath
(
tiff_path
)
# identify the section that is being analysed by its directory name
dir_name
=
norm
.
split
(
os
.
sep
)[
-
2
]
save_preview
(
preview
,
frame
,
dir_name
)
save_preview
(
preview
,
frame
,
tiff_path
)
return
preview
def
process_quality_check
(
frame
,
tiff_path
,
logger_path
):
...
...
@@ -200,13 +233,13 @@ def process_quality_check(frame, tiff_path, logger_path):
frame
.
progress
.
update
()
outlier_coords
=
None
filtered_coords
=
None
frame
.
progress
[
"value"
]
=
0
if
tiff_path
is
None
:
tiff_path
=
frame
.
tiff_path
.
get
()
frame
.
print
(
f
"Analyzing section
{
tiff_path
}
"
)
# determine method to be used based on drop down menu
method_string
=
frame
.
dropdown_state
.
get
()
...
...
@@ -233,8 +266,11 @@ def process_quality_check(frame, tiff_path, logger_path):
# call the actual analysis method
time_pre
=
time
.
time
()
error_matrix
=
analyze_section
(
tiff_path
,
quality_check
,
on_tile_done
=
progressbar_callback
)
if
frame
.
mark_outliers_state
.
get
()
==
1
:
error_matrix
,
outlier_coords
,
filtered_coords
=
analyze_section_with_coords
(
tiff_path
,
quality_check
,
on_tile_done
=
progressbar_callback
)
else
:
error_matrix
=
analyze_section
(
tiff_path
,
quality_check
,
on_tile_done
=
progressbar_callback
)
# save error matrix
# use name of section as file name for matrix
...
...
@@ -270,6 +306,9 @@ def check_directory(tiff_path, frame):
tiff_path
=
os
.
path
.
join
(
tiff_path
,
directories
[
0
])
directories
=
([
f
for
f
in
listdir
(
tiff_path
)
if
isdir
(
os
.
path
.
join
(
tiff_path
,
f
))])
# if there are multiple directories, return each one
# postpone check if every single subdirectory is valid
if
not
directories
or
len
(
directories
[
-
1
].
split
(
"_"
))
!=
2
: