
commit
aa050f339f
12 changed files with 2116 additions and 0 deletions
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
# ---> Python |
||||
# Byte-compiled / optimized / DLL files |
||||
__pycache__/ |
||||
*.py[cod] |
||||
*$py.class |
||||
|
||||
# test data |
||||
mtor-bilder/ |
||||
oringinal-daten/ |
||||
|
||||
# C extensions |
||||
*.so |
||||
|
||||
# Virtual environments |
||||
env/ |
||||
.env/ |
||||
venv/ |
||||
.venv/ |
||||
|
||||
# Distribution / packaging |
||||
.Python |
||||
build/ |
||||
develop-eggs/ |
||||
dist/ |
||||
downloads/ |
||||
eggs/ |
||||
.eggs/ |
||||
lib/ |
||||
lib64/ |
||||
parts/ |
||||
sdist/ |
||||
var/ |
||||
*.egg-info/ |
||||
.installed.cfg |
||||
*.egg |
||||
pip-wheel-metadata/ |
||||
|
||||
# PyInstaller |
||||
# Usually these files are written by a python script from a template |
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it. |
||||
*.manifest |
||||
*.spec |
||||
|
||||
# Installer logs |
||||
pip-log.txt |
||||
pip-delete-this-directory.txt |
||||
|
||||
# Unit test / coverage reports |
||||
htmlcov/ |
||||
.tox/ |
||||
.coverage |
||||
.coverage.* |
||||
.cache |
||||
nosetests.xml |
||||
coverage.xml |
||||
*,cover |
||||
|
||||
# Translations |
||||
*.mo |
||||
*.pot |
||||
|
||||
# Django stuff: |
||||
*.log |
||||
|
||||
# Sphinx documentation |
||||
docs/_build/ |
||||
|
||||
# PyBuilder |
||||
target/ |
||||
|
||||
# OS specific stuff |
||||
.DS_Store |
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
.PHONY: clean clean-test clean-pyc clean-build help |
||||
.DEFAULT_GOAL := help |
||||
|
||||
define BROWSER_PYSCRIPT |
||||
import os, webbrowser, sys |
||||
|
||||
try: |
||||
from urllib import pathname2url |
||||
except: |
||||
from urllib.request import pathname2url |
||||
|
||||
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) |
||||
endef |
||||
export BROWSER_PYSCRIPT |
||||
|
||||
define PRINT_HELP_PYSCRIPT |
||||
import re, sys |
||||
|
||||
for line in sys.stdin: |
||||
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) |
||||
if match: |
||||
target, help = match.groups() |
||||
print("%-20s %s" % (target, help)) |
||||
endef |
||||
export PRINT_HELP_PYSCRIPT |
||||
|
||||
BROWSER := python -c "$$BROWSER_PYSCRIPT" |
||||
|
||||
help: |
||||
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) |
||||
|
||||
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
||||
|
||||
clean-build: ## remove build artifacts
|
||||
rm -fr build/ |
||||
rm -fr dist/ |
||||
rm -fr .eggs/ |
||||
find . -name '*.egg-info' -exec rm -fr {} + |
||||
find . -name '*.egg' -exec rm -f {} + |
||||
|
||||
clean-pyc: ## remove Python file artifacts
|
||||
find . -name '*.pyc' -exec rm -f {} + |
||||
find . -name '*.pyo' -exec rm -f {} + |
||||
find . -name '*~' -exec rm -f {} + |
||||
find . -name '__pycache__' -exec rm -fr {} + |
||||
|
||||
clean-test: ## remove test and coverage artifacts
|
||||
rm -fr .tox/ |
||||
rm -f .coverage |
||||
rm -fr htmlcov/ |
||||
|
||||
lint: ## check style with flake8
|
||||
black mtor |
||||
flake8 mtor |
||||
|
||||
test: ## run tests quickly with the default Python
|
||||
py.test -x --disable-warnings |
||||
|
||||
coverage: ## check code coverage with the default Python
|
||||
coverage run --source lxpy -m pytest |
||||
coverage report -m |
||||
coverage html |
||||
$(BROWSER) htmlcov/index.html |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
Image Class |
||||
----------- |
||||
|
||||
convert(self, mode=None, matrix=None, dither=None, palette=0, colors=256) |
||||
Returns a converted copy of this image. For the "P" mode, this |
||||
method translates pixels through the palette. If mode is |
||||
omitted, a mode is chosen so that all information in the image |
||||
and the palette can be represented without a palette. |
||||
|
||||
The current version supports all possible conversions between |
||||
"L", "RGB" and "CMYK." The **matrix** argument only supports "L" |
||||
and "RGB". |
||||
|
||||
crop(self, box=None) |
||||
Returns a rectangular region from this image. The box is a |
||||
4-tuple defining the left, upper, right, and lower pixel |
||||
coordinate. See :ref:`coordinate-system`. |
||||
|
||||
Note: Prior to Pillow 3.4.0, this was a lazy operation. |
||||
|
||||
:param box: The crop rectangle, as a (left, upper, right, lower)-tuple. |
||||
:rtype: :py:class:`~PIL.Image.Image` |
||||
:returns: An :py:class:`~PIL.Image.Image` object. |
||||
|
||||
|
||||
getextrema(self) |
||||
Gets the the minimum and maximum pixel values for each band in |
||||
the image. |
||||
|
||||
:returns: For a single-band image, a 2-tuple containing the |
||||
minimum and maximum pixel value. For a multi-band image, |
||||
a tuple containing one 2-tuple for each band. |
||||
|
||||
histogram(self, mask=None, extrema=None) |
||||
Returns a histogram for the image. The histogram is returned as |
||||
a list of pixel counts, one for each pixel value in the source |
||||
image. If the image has more than one band, the histograms for |
||||
all bands are concatenated (for example, the histogram for an |
||||
"RGB" image contains 768 values). |
||||
|
||||
save(self, fp, format=None, **params) |
||||
Saves this image under the given filename. If no format is |
||||
specified, the format to use is determined from the filename |
||||
extension, if possible. |
||||
|
||||
Keyword options can be used to provide additional instructions |
||||
to the writer. If a writer doesn't recognise an option, it is |
||||
silently ignored. The available options are described in the |
||||
:doc:`image format documentation |
||||
<../handbook/image-file-formats>` for each writer. |
||||
|
||||
show(self, title=None, command=None) |
||||
Displays this image. This method is mainly intended for |
||||
debugging purposes. |
||||
|
||||
On Unix platforms, this method saves the image to a temporary |
||||
PPM file, and calls the **display**, **eog** or **xv** |
||||
utility, depending on which one can be found. |
||||
|
||||
On macOS, this method saves the image to a temporary PNG file, and |
||||
opens it with the native Preview application. |
||||
|
||||
|
||||
ImageOps Module |
||||
--------------- |
||||
|
||||
The ImageOps module contains a number of ‘ready-made’ image processing |
||||
operations. This module is somewhat experimental, and most operators only work |
||||
on L and RGB images. |
||||
|
||||
Only bug fixes have been added since the Pillow fork. |
||||
|
||||
New in version 1.1.3. |
||||
|
||||
PIL.ImageOps.autocontrast(image, cutoff=0, ignore=None)[source] |
||||
Maximize (normalize) image contrast. This function calculates a histogram |
||||
of the input image, removes cutoff percent of the lightest and darkest |
||||
pixels from the histogram, and remaps the image so that the darkest pixel |
||||
becomes black (0), and the lightest becomes white (255). |
||||
|
||||
|
||||
PIL.ImageOps.equalize(image, mask=None)[source] |
||||
Equalize the image histogram. This function applies a non-linear mapping to |
||||
the input image, in order to create a uniform distribution of grayscale |
||||
values in the output image. |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
import warnings |
||||
|
||||
from .prescan import prescan_workflow |
||||
from .imageproc import image_workflow |
||||
from .dataproc import data_workflow, cached_workflow |
||||
from .filesort import filesort_workflow |
||||
|
||||
warnings.filterwarnings("ignore") |
||||
|
||||
|
||||
def run(): |
||||
#settings = prescan_workflow("mtor-bilder", 50, 910, 300, 660) |
||||
settings = prescan_workflow("mtor-bilder", 50, 725+150, 300, 725) |
||||
stats_results = image_workflow(settings) |
||||
selected_files = data_workflow(stats_results, settings) |
||||
filesort_workflow(selected_files, settings) |
||||
|
||||
|
||||
def run_data(): |
||||
selected_files, settings = cached_workflow("mtor-bilder") |
||||
filesort_workflow(selected_files, settings) |
@ -0,0 +1,430 @@
@@ -0,0 +1,430 @@
|
||||
import numpy |
||||
import pandas |
||||
import pathlib |
||||
import peakutils |
||||
import pickle |
||||
import seaborn |
||||
import matplotlib.pyplot as pyplot |
||||
|
||||
from peakutils.plot import plot as peakplot |
||||
from scipy.signal import savgol_filter |
||||
|
||||
from .imageproc import STATS_FUNCS, LEFT_GUARD, ROI_NAME, RIGHT_GUARD |
||||
|
||||
|
||||
def peakplot_iloc(x, y, ind): |
||||
""" |
||||
Plots the original data with the peaks that were identified |
||||
|
||||
The original peakutils.plot function imported as "peakplot" has a little |
||||
bug, when displaying dataframes with some values removed. The |
||||
implementation below fixes this. |
||||
|
||||
Parameters |
||||
---------- |
||||
x : array-like |
||||
Data on the x-axis |
||||
y : array-like |
||||
Data on the y-axis |
||||
ind : array-like |
||||
Indexes of the identified peaks |
||||
""" |
||||
pyplot.plot(x, y, "--") |
||||
pyplot.plot( |
||||
x.iloc[ind], |
||||
y.iloc[ind], |
||||
"r+", |
||||
ms=5, |
||||
mew=2, |
||||
label="{} peaks".format(len(ind)), |
||||
) |
||||
pyplot.legend() |
||||
|
||||
|
||||
def set_plotting_styles(): |
||||
seaborn.set_style("darkgrid") |
||||
seaborn.set_style( |
||||
"ticks", |
||||
{ |
||||
"legend.frameon": True, |
||||
"xtick.direction": "in", |
||||
"ytick.direction": "in", |
||||
"axes.linewidth": 2, |
||||
}, |
||||
) |
||||
seaborn.set(rc={"figure.figsize": (12, 9)}) |
||||
seaborn.set_context("talk") |
||||
|
||||
|
||||
def init_pandas_data_frame(): |
||||
columns = ["file", "frame"] |
||||
for prefix in (LEFT_GUARD, ROI_NAME, RIGHT_GUARD): |
||||
for key in STATS_FUNCS: |
||||
columns.append(f"{prefix}.{key}") |
||||
return pandas.DataFrame(columns=columns) |
||||
|
||||
|
||||
def construct_data_frame(stats_results): |
||||
data_frame = init_pandas_data_frame() |
||||
data_frame = data_frame.append(stats_results, ignore_index=True) |
||||
return data_frame.sort_values(by=["frame"]).reset_index(drop=True) |
||||
|
||||
|
||||
def find_guard_value(data_frame, settings): |
||||
left_avg = data_frame["left.average"] |
||||
right_avg = data_frame["right.average"] |
||||
guards_avg = left_avg.append(right_avg, ignore_index=True) |
||||
guard_data = numpy.histogram( |
||||
guards_avg, bins=settings.guard_histogram_bins |
||||
) |
||||
guard_counts = guard_data[0].astype(numpy.float16) |
||||
guard_edges = guard_data[1][1:] # edges enclose the counts |
||||
|
||||
pyplot.clf() |
||||
seaborn.lineplot(x=guard_edges, y=guard_counts) |
||||
pyplot.title("Histogram of Guard Avarages (not filtered)") |
||||
pyplot.xlabel("Average Intensity [au]") |
||||
pyplot.ylabel("Number of Observations [1]") |
||||
path = settings.data_dir / "1-histogram-of-guard-avarages-not-filtered.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
guard_counts_filtered = savgol_filter( |
||||
guard_counts, |
||||
settings.guard_filter_window, |
||||
settings.guard_filter_polynom, |
||||
) |
||||
|
||||
pyplot.clf() |
||||
seaborn.lineplot(x=guard_edges, y=guard_counts_filtered) |
||||
pyplot.title("Histogram of Guard Avarages (Savitzky-Golay filter)") |
||||
pyplot.xlabel("Average Intensity [au]") |
||||
pyplot.ylabel("Number of Observations [1]") |
||||
path = settings.data_dir / "2-histogram-of-guard-avarages-filtered.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
# Finding the first minima after the first peak |
||||
# In a first step, the location of the first peak is determined. |
||||
# Since the algorithm only finds peaks, we need to inverse the data points |
||||
# and start after the first peak to find the first minima. |
||||
|
||||
indexes = peakutils.indexes(guard_counts_filtered) |
||||
first_peak_position = indexes[0] |
||||
|
||||
# looks like magic, but is standard numpy behaviour |
||||
inverted_series = max(guard_counts_filtered) - guard_counts_filtered |
||||
indexes = peakutils.indexes( |
||||
inverted_series[first_peak_position:], |
||||
min_dist=settings.guards_minima_min_dist, |
||||
) |
||||
|
||||
# since we shortened the data, we need to add the first peak position |
||||
first_minima_position = indexes[0] + first_peak_position |
||||
settings.guard_max_value = guard_edges[first_minima_position] |
||||
|
||||
pyplot.clf() |
||||
peakplot(guard_edges, guard_counts_filtered, [first_minima_position]) |
||||
pyplot.title( |
||||
( |
||||
f"Histogram of Guard Avarages (Savitzky-Golay filter)," |
||||
f" first minima at {int(settings.guard_max_value)} au" |
||||
) |
||||
) |
||||
pyplot.xlabel("Average Intensity [au]") |
||||
pyplot.ylabel("Number of Observations [1]") |
||||
path = ( |
||||
settings.data_dir |
||||
/ "3-histogram-of-guard-avarages-filtered-with-first-minima.png" |
||||
) |
||||
pyplot.savefig(str(path)) |
||||
|
||||
|
||||
def check_guards(data_frame, settings): |
||||
for what in (LEFT_GUARD, RIGHT_GUARD): |
||||
ok_col = f"{what}.ok" |
||||
data_frame[ok_col] = False |
||||
mask = data_frame[f"{what}.average"] < settings.guard_max_value |
||||
data_frame.loc[mask, ok_col] = True |
||||
data_frame["guards.ok"] = ( |
||||
data_frame[f"{LEFT_GUARD}.ok"] & data_frame[f"{RIGHT_GUARD}.ok"] |
||||
) |
||||
|
||||
mask = data_frame["guards.ok"] == True |
||||
guarded_df = data_frame[mask].copy() |
||||
|
||||
pyplot.clf() |
||||
ax = seaborn.scatterplot( |
||||
x="frame", |
||||
y=f"{ROI_NAME}.average", |
||||
data=data_frame, |
||||
hue="guards.ok", |
||||
hue_order=[True, False], |
||||
palette={True: "b", False: "r"}, |
||||
) |
||||
pyplot.title("Selection based on guard values") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
pyplot.ylabel("Frame Number [1]") |
||||
path = settings.data_dir / "4-image-selection-based-on-guard-values.png" |
||||
pyplot.savefig(str(path)) |
||||
settings.charts_y_limit = ax.get_ylim() |
||||
|
||||
pyplot.clf() |
||||
ax = seaborn.scatterplot( |
||||
x="frame", y=f"{ROI_NAME}.average", data=guarded_df |
||||
) |
||||
count_all_images = len(data_frame) |
||||
count_guarded_images = len(guarded_df) |
||||
pyplot.title( |
||||
( |
||||
f"Selection, based on guard values" |
||||
f" ({count_guarded_images} of {count_all_images})" |
||||
) |
||||
) |
||||
pyplot.xlabel("Frame Number [1]") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
ax.set_ylim(settings.charts_y_limit) |
||||
path = settings.data_dir / "5-selected-values-based-on-guard-values.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
|
||||
return data_frame |
||||
|
||||
|
||||
def find_outliers(data_frame, settings): |
||||
|
||||
mask = data_frame["guards.ok"] == True |
||||
guarded_df = data_frame[mask].copy() |
||||
|
||||
pyplot.clf() |
||||
seaborn.boxplot(data=guarded_df, x=f"{ROI_NAME}.average") |
||||
pyplot.title(f"Boxblot of guarded values") |
||||
pyplot.xlabel("Average Intensity [au]") |
||||
path = settings.data_dir / "6-boxplot-of-guarded-values.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
lower_quartil = guarded_df[f"{ROI_NAME}.average"].quantile(0.25) |
||||
upper_quartil = guarded_df[f"{ROI_NAME}.average"].quantile(0.75) |
||||
inter_quartil_range = upper_quartil - lower_quartil |
||||
settings.outlier_upper_limit = upper_quartil + 1.5 * inter_quartil_range |
||||
|
||||
data_frame["outlier.ok"] = ( |
||||
data_frame[f"{ROI_NAME}.average"] < settings.outlier_upper_limit |
||||
) |
||||
return data_frame |
||||
|
||||
|
||||
def select_value_on_guards_and_outliers(data_frame, settings): |
||||
data_frame["outlier_guards.ok"] = ( |
||||
data_frame["guards.ok"] & data_frame["outlier.ok"] |
||||
) |
||||
mask = data_frame["outlier_guards.ok"] == True |
||||
selected_df = data_frame[mask].copy() |
||||
|
||||
pyplot.clf() |
||||
ax = seaborn.scatterplot( |
||||
x="frame", y=f"{ROI_NAME}.average", data=selected_df |
||||
) |
||||
pyplot.title(f"Selected Images, outliers removed") |
||||
pyplot.xlabel("Frame Number [1]") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
ax.set_ylim(settings.charts_y_limit) |
||||
path = settings.data_dir / "7-selected-images-outliers-removed.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
return selected_df |
||||
|
||||
|
||||
def smooth_rolling_min(selected_df, settings): |
||||
rm = ( |
||||
selected_df[f"{ROI_NAME}.average"] |
||||
.rolling(settings.rolling_min_window) |
||||
.min() |
||||
) |
||||
|
||||
# after a rolling window calculation, the first values will be NaN, |
||||
# we need to fill them |
||||
selected_df[f"{ROI_NAME}.average.rolling.min"] = rm.fillna( |
||||
method="backfill" |
||||
) |
||||
|
||||
pyplot.clf() |
||||
ax = seaborn.scatterplot( |
||||
x="frame", y=f"{ROI_NAME}.average.rolling.min", data=selected_df |
||||
) |
||||
pyplot.title(f"Selected Images, outliers removed, rolling min applied") |
||||
pyplot.xlabel("Frame Number [1]") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
ax.set_ylim(settings.charts_y_limit) |
||||
path = ( |
||||
settings.data_dir |
||||
/ "8-selected-images-outliers-removed-rolling-min-applied.png" |
||||
) |
||||
pyplot.savefig(str(path)) |
||||
|
||||
return selected_df |
||||
|
||||
|
||||
def smooth_savgol_filter(selected_df, settings): |
||||
filtered = savgol_filter( |
||||
selected_df[f"{ROI_NAME}.average.rolling.min"], |
||||
settings.savgol_filter_window, |
||||
settings.savgol_filter_polynom, |
||||
) |
||||
selected_df[f"{ROI_NAME}.average.savgol"] = filtered |
||||
|
||||
pyplot.clf() |
||||
seaborn.lineplot( |
||||
x="frame", y=f"{ROI_NAME}.average.savgol", data=selected_df |
||||
) |
||||
pyplot.title( |
||||
( |
||||
f"Selected Images, outliers removed," |
||||
f" rolling min applied, Savitzky-Golay filtered" |
||||
) |
||||
) |
||||
pyplot.xlabel("Frame Number [1]") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
path = ( |
||||
settings.data_dir |
||||
/ "9-selected-images-outliers-removed-rolling-min-savgol-filtered.png" |
||||
) |
||||
pyplot.savefig(str(path)) |
||||
|
||||
return selected_df |
||||
|
||||
|
||||
def find_extremas(selected_df, settings): |
||||
|
||||
indexes = peakutils.indexes( |
||||
selected_df[f"{ROI_NAME}.average.savgol"], |
||||
thres=settings.peak_threshold, |
||||
min_dist=settings.peak_min_distance, |
||||
) |
||||
maximas = selected_df.iloc[indexes].copy() |
||||
|
||||
pyplot.clf() |
||||
peakplot_iloc( |
||||
selected_df["frame"], |
||||
selected_df[f"{ROI_NAME}.average.savgol"], |
||||
indexes, |
||||
) |
||||
pyplot.title(f"Finding Maximas") |
||||
pyplot.xlabel("Frame Number [1]") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
path = settings.data_dir / "10-finding-maximas.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
inverted_series = ( |
||||
max(selected_df[f"{ROI_NAME}.average.savgol"]) |
||||
- selected_df[f"{ROI_NAME}.average.savgol"] |
||||
) |
||||
indexes = peakutils.indexes( |
||||
inverted_series, min_dist=settings.peak_min_distance |
||||
) |
||||
minimas = selected_df.iloc[indexes].copy() |
||||
|
||||
pyplot.clf() |
||||
peakplot_iloc( |
||||
selected_df["frame"], |
||||
selected_df[f"{ROI_NAME}.average.savgol"], |
||||
indexes, |
||||
) |
||||
pyplot.title(f"Finding Minimas") |
||||
pyplot.xlabel("Frame Number [1]") |
||||
pyplot.ylabel("Average Intensity [au]") |
||||
path = settings.data_dir / "11-finding-minimas.png" |
||||
pyplot.savefig(str(path)) |
||||
|
||||
maximas["is_maxima"] = True |
||||
minimas["is_maxima"] = False |
||||
extremas_df = pandas.concat([maximas, minimas]).sort_index() |
||||
return extremas_df |
||||
|
||||
|
||||
def save_data(data_frame, selected_df, extremas_df, settings): |
||||
path = settings.data_dir / "numeric-data.xlsx" |
||||
writer = pandas.ExcelWriter(path, engine="xlsxwriter") |
||||
|
||||
extremas_df.to_excel(writer, sheet_name="extremas") |
||||
selected_df.to_excel(writer, sheet_name="selected data") |
||||
data_frame.to_excel(writer, sheet_name="raw data") |
||||
|
||||
ignore_settings = {"tif_list", "cuts_dir"} |
||||
tmp_settings = { |
||||
k: [v] for k, v in settings.items() if k not in ignore_settings |
||||
} |
||||
tmp_setings_df = pandas.DataFrame(tmp_settings).T |
||||
tmp_setings_df.to_excel(writer, sheet_name="parameters") |
||||
|
||||
writer.save() |
||||
|
||||
|
||||
def data_workflow(stats_results, settings): |
||||
print("3/4: Data analysis") |
||||
set_plotting_styles() |
||||
data_frame = construct_data_frame(stats_results) |
||||
save_temp(data_frame, settings) |
||||
find_guard_value(data_frame, settings) |
||||
data_frame = check_guards(data_frame, settings) |
||||
data_frame = find_outliers(data_frame, settings) |
||||
|
||||
selected_df = select_value_on_guards_and_outliers(data_frame, settings) |
||||
selected_df = smooth_rolling_min(selected_df, settings) |
||||
selected_df = smooth_savgol_filter(selected_df, settings) |
||||
|
||||
extremas_df = find_extremas(selected_df, settings) |
||||
|
||||
save_data(data_frame, selected_df, extremas_df, settings) |
||||
|
||||
return list(selected_df["file"]) |
||||
|
||||
|
||||
def save_temp(data_frame, settings): |
||||
csv_path = settings.tif_dir / "_data.csv" |
||||
data_frame.to_csv(csv_path, sep="\t") |
||||
xls_path = settings.tif_dir / "_data.xlsx" |
||||
data_frame.to_excel(xls_path) |
||||
settings_path = settings.tif_dir / "_settings.pickle" |
||||
with open(settings_path, "wb") as fh: |
||||
pickle.dump(settings, fh) |
||||
|
||||
|
||||
def load_temp(folder): |
||||
dir_path = pathlib.Path(folder) |
||||
csv_path = dir_path / "_data.csv" |
||||
data_frame = pandas.read_csv(csv_path, sep="\t", index_col=0) |
||||
settings_path = dir_path / "_settings.pickle" |
||||
with open(settings_path, "rb") as fh: |
||||
settings = pickle.load(fh) |
||||
|
||||
settings.cut_pad_x = 50 |
||||
settings.cut_pad_y = 25 |
||||
settings.guard_histogram_bins = 100 |
||||
settings.guard_filter_window = 7 |
||||
settings.guard_filter_polynom = 3 |
||||
settings.guards_minima_min_dist = 10 |
||||
settings.rolling_min_window = 5 |
||||
settings.savgol_filter_window = 51 |
||||
settings.savgol_filter_polynom = 1 |
||||
settings.peak_min_distance = 5 |
||||
settings.peak_threshold = 0.3 |
||||
|
||||
return data_frame, settings |
||||
|
||||
|
||||
def cached_workflow(folder): |
||||
print("3/4: Data analysis") |
||||
set_plotting_styles() |
||||
data_frame, settings = load_temp(folder) |
||||
find_guard_value(data_frame, settings) |
||||
data_frame = check_guards(data_frame, settings) |
||||
data_frame = find_outliers(data_frame, settings) |
||||
|
||||
selected_df = select_value_on_guards_and_outliers(data_frame, settings) |
||||
selected_df = smooth_rolling_min(selected_df, settings) |
||||
selected_df = smooth_savgol_filter(selected_df, settings) |
||||
|
||||
extremas_df = find_extremas(selected_df, settings) |
||||
|
||||
save_data(data_frame, selected_df, extremas_df, settings) |
||||
|
||||
return list(selected_df["file"]), settings |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
import pathlib |
||||
|
||||
from tqdm import tqdm |
||||
|
||||
from .prescan import LABEL_SELECTED, LABEL_DISCARDED |
||||
|
||||
|
||||
def stem_file_list(selected_files): |
||||
return {pathlib.Path(filename).stem for filename in selected_files} |
||||
|
||||
|
||||
def rename_color_coded_images(file_stems, settings): |
||||
rename_pairs = [] |
||||
for path in settings.colored_dir.iterdir(): |
||||
if not path.stem.startswith("."): |
||||
label = ( |
||||
LABEL_SELECTED if path.stem in file_stems else LABEL_DISCARDED |
||||
) |
||||
new_name = path.with_name(f"{path.stem}_{label}{path.suffix}") |
||||
rename_pairs.append((path, new_name)) |
||||
return rename_pairs |
||||
|
||||
|
||||
def sort_cut_images(file_stems, settings): |
||||
sort_pairs = [] |
||||
for path in settings.cuts_dir.iterdir(): |
||||
if not path.stem.startswith("."): |
||||
stem = path.stem[:-4] # removes the "_cut" suffix |
||||
label = LABEL_SELECTED if stem in file_stems else LABEL_DISCARDED |
||||
new_path = ( |
||||
settings[f"cuts_{label}_dir"] |
||||
/ f"{path.stem}_{label}{path.suffix}" |
||||
) |
||||
sort_pairs.append((path, new_path)) |
||||
return sort_pairs |
||||
|
||||
|
||||
def remove_cuts_dir(settings): |
||||
for item in settings["cuts_dir"].iterdir(): |
||||
item.unlink() |
||||
settings["cuts_dir"].rmdir() |
||||
|
||||
|
||||
def filesort_workflow(selected_files, settings): |
||||
print("4/4: Sorting images") |
||||
file_stems = stem_file_list(selected_files) |
||||
cc_rename_pairs = rename_color_coded_images(file_stems, settings) |
||||
cut_sort_pairs = sort_cut_images(file_stems, settings) |
||||
file_pairs = cc_rename_pairs + cut_sort_pairs |
||||
for old_path, new_path in tqdm(file_pairs): |
||||
old_path.rename(new_path) |
||||
remove_cuts_dir(settings) |
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
from collections import namedtuple |
||||
from functools import partial |
||||
from multiprocessing import Pool |
||||
from PIL import Image |
||||
from tqdm import tqdm |
||||
|
||||
import numpy |
||||
|
||||
from .luts import FIRE_LUT |
||||
|
||||
|
||||
STATS_FUNCS = { |
||||
"min": numpy.amin, |
||||
"max": numpy.amax, |
||||
"1quantile": partial(numpy.percentile, q=25), |
||||
"2quantile": partial(numpy.percentile, q=50), |
||||
"3quantile": partial(numpy.percentile, q=75), |
||||
"average": numpy.average, |
||||
"median": numpy.median, |
||||
"std": numpy.std, |
||||
} |
||||
|
||||
LEFT_GUARD = "left" |
||||
ROI_NAME = "roi" |
||||
RIGHT_GUARD = "right" |
||||
|
||||
ROI_BORDER_INTENSITY = 2**15 |
||||
|
||||
TifArray = namedtuple("TifArray", ["path", "frame", "data"]) |
||||
|
||||
|
||||
def open_as_array(tif_image): |
||||
with tif_image.path.open("rb") as filehandle: |
||||
image = Image.open(filehandle) |
||||
image_array = numpy.array(image, dtype=numpy.int32) |
||||
return TifArray( |
||||
path=tif_image.path, frame=tif_image.frame, data=image_array |
||||
) |
||||
|
||||
|
||||
def scale_and_colorize(tif_array, settings): |
||||
adjusted_array = (tif_array.data - settings.offset) // settings.scale |
||||
|
||||
# paint roi |
||||
|
||||
top = settings.roi_top |
||||
bottom = settings.roi_bottom |
||||
right = settings.roi_right |
||||
left = settings.roi_left |
||||
adjusted_array[top, left:right] = ROI_BORDER_INTENSITY |
||||
adjusted_array[bottom, left:right] = ROI_BORDER_INTENSITY |
||||
adjusted_array[top:bottom, left] = ROI_BORDER_INTENSITY |
||||
adjusted_array[top:bottom, right] = ROI_BORDER_INTENSITY |
||||
|
||||
adjusted = Image.fromarray(adjusted_array) |
||||
converted = adjusted.convert(mode="L").convert(mode="P") |
||||
converted.putpalette(FIRE_LUT) |
||||
return converted.convert("RGB") |
||||
|
||||
|
||||
def safe_color_coded(tif_array, settings): |
||||
png_image = scale_and_colorize(tif_array, settings) |
||||
png_path = settings.colored_dir / (tif_array.path.stem + ".jpg") |
||||
with png_path.open("wb") as outhandle: |
||||
png_image.save(outhandle) |
||||
del png_image |
||||
|
||||
|
||||
def safe_cut(tif_array, settings): |
||||
cut_path = settings.cuts_dir / (tif_array.path.stem + "_cut.tif") |
||||
|
||||
top = settings.cut_top |
||||
bottom = settings.cut_bottom |
||||
right = settings.cut_right |
||||
left = settings.cut_left |
||||
|
||||
with cut_path.open("wb") as outhandle: |
||||
cut_array = tif_array.data[top:bottom, left:right] |
||||
cut_img = Image.fromarray(cut_array) |
||||
cut_img.save(outhandle) |
||||
del cut_img |
||||
|
||||
|
||||
def get_roi(tif_array, settings): |
||||
top = settings.roi_top |
||||
bottom = settings.roi_bottom |
||||
right = settings.roi_right |
||||
left = settings.roi_left |
||||
return tif_array.data[top:bottom, left:right] |
||||
|
||||
|
||||
def analyse_roi(tif_array, settings): |
||||
data = get_roi(tif_array, settings) |
||||
results = {} |
||||
results.update(get_stats(data[:, 1], LEFT_GUARD)) |
||||
results.update(get_stats(data[:, 1:-1], ROI_NAME)) |
||||
results.update(get_stats(data[:, -1], RIGHT_GUARD)) |
||||
return results |
||||
|
||||
|
||||
def get_stats(array, prefix): |
||||
return {f"{prefix}.{k}": f(array) for k, f in STATS_FUNCS.items()} |
||||
|
||||
|
||||
def process_one_tif(tif_image, settings): |
||||
tif_array = open_as_array(tif_image) |
||||
safe_color_coded(tif_array, settings) |
||||
safe_cut(tif_array, settings) |
||||
stats_result = analyse_roi(tif_array, settings) |
||||
stats_result["file"] = tif_image.path.name |
||||
stats_result["frame"] = tif_image.frame |
||||
return stats_result |
||||
|
||||
|
||||
def image_workflow(settings): |
||||
print("2/4: Image analysis and conversion") |
||||
func = partial(process_one_tif, settings=settings) |
||||
tif_list = settings.tif_list |
||||
num_items = len(tif_list) |
||||
with Pool(4) as p: |
||||
stat_results = list(tqdm(p.imap(func, tif_list), total=num_items)) |
||||
return stat_results |
@ -0,0 +1,770 @@
@@ -0,0 +1,770 @@
|
||||
FIRE_LUT = [ |
||||
0, |
||||
0, |
||||
0, |
||||
0, |
||||
0, |
||||
7, |
||||
0, |
||||
0, |
||||
15, |
||||
0, |
||||
0, |
||||
22, |
||||
0, |
||||
0, |
||||
30, |
||||
0, |
||||
0, |
||||
38, |
||||
0, |
||||
0, |
||||
45, |
||||
0, |
||||
0, |
||||
53, |
||||
0, |
||||
0, |
||||
61, |
||||
0, |
||||
0, |
||||
65, |
||||
0, |
||||
0, |
||||
69, |
||||
0, |
||||
0, |
||||
74, |
||||
0, |
||||
0, |
||||
78, |
||||
0, |
||||
0, |
||||
82, |
||||
0, |
||||
0, |
||||
87, |
||||
0, |
||||
0, |
||||
91, |
||||
1, |
||||
0, |
||||
96, |
||||
4, |
||||
0, |
||||
100, |
||||
7, |
||||
0, |
||||
104, |
||||
10, |
||||
0, |
||||
108, |
||||
13, |
||||
0, |
||||
113, |
||||
16, |
||||
0, |
||||
117, |
||||
19, |
||||
0, |
||||
121, |
||||
22, |
||||
0, |
||||
125, |
||||
25, |
||||
0, |
||||
130, |
||||
28, |
||||
0, |
||||
134, |
||||
31, |
||||
0, |
||||
138, |
||||
34, |
||||
0, |
||||
143, |
||||
37, |
||||
0, |
||||
147, |
||||
40, |
||||
0, |
||||
151, |
||||
43, |
||||
0, |
||||
156, |
||||
46, |
||||
0, |
||||
160, |
||||
49, |
||||
0, |
||||
165, |
||||
52, |
||||
0, |
||||
168, |
||||
55, |
||||
0, |
||||
171, |
||||
58, |
||||
0, |
||||
175, |
||||
61, |
||||
0, |
||||
178, |
||||
64, |
||||
0, |
||||
181, |
||||
67, |
||||
0, |
||||
185, |
||||
70, |
||||
0, |
||||
188, |
||||
73, |
||||
0, |
||||
192, |
||||
76, |
||||
0, |
||||
195, |
||||
79, |
||||
0, |
||||
199, |
||||
82, |
||||
0, |
||||
202, |
||||
85, |
||||
0, |
||||
206, |
||||
88, |
||||
0, |
||||
209, |
||||
91, |
||||
0, |
||||
213, |
||||
94, |
||||
0, |
||||
216, |
||||
98, |
||||
0, |
||||
220, |
||||
101, |
||||
0, |
||||
220, |
||||
104, |
||||
0, |
||||
221, |
||||
107, |
||||
0, |
||||
222, |
||||
110, |
||||
0, |
||||
223, |
||||
113, |
||||
0, |
||||
224, |
||||
116, |
||||
0, |
||||
225, |
||||
119, |
||||
0, |
||||
226, |
||||
122, |
||||
0, |
||||
227, |
||||
125, |
||||
0, |
||||
224, |
||||
128, |
||||
0, |
||||
222, |
||||
131, |
||||
0, |
||||
220, |
||||
134, |
||||
0, |
||||
218, |
||||
137, |
||||
0, |
||||
216, |
||||
140, |
||||
0, |
||||
214, |
||||
143, |
||||
0, |
||||
212, |
||||
146, |
||||
0, |
||||
210, |
||||
148, |
||||
0, |
||||
206, |
||||
150, |
||||
0, |
||||
202, |
||||
152, |
||||
0, |
||||
199, |
||||
154, |
||||
0, |
||||
195, |
||||
156, |
||||
0, |
||||
191, |
||||
158, |
||||
0, |
||||
188, |
||||
160, |
||||
0, |
||||
184, |
||||
162, |
||||
0, |
||||
181, |
||||
163, |
||||
0, |
||||
177, |
||||
164, |
||||
0, |
||||
173, |
||||
166, |
||||
0, |
||||
169, |
||||
167, |
||||
0, |
||||
166, |
||||
168, |
||||
0, |
||||
162, |
||||
170, |
||||
0, |
||||
158, |
||||
171, |
||||
0, |
||||
154, |
||||
173, |
||||
0, |
||||
151, |
||||
174, |
||||
0, |
||||
147, |
||||
175, |
||||
0, |
||||
143, |
||||
177, |
||||
0, |
||||
140, |
||||
178, |
||||
0, |
||||
136, |
||||
179, |
||||
0, |
||||
132, |
||||
181, |
||||
0, |
||||
129, |
||||
182, |
||||
0, |
||||
125, |
||||
184, |
||||
0, |
||||
122, |
||||
185, |
||||
0, |
||||
118, |
||||
186, |
||||
0, |
||||
114, |
||||
188, |
||||
0, |
||||
111, |
||||
189, |
||||
0, |
||||
107, |
||||
190, |
||||
0, |
||||
103, |
||||
192, |
||||
0, |
||||
100, |
||||
193, |
||||
0, |
||||
96, |
||||
195, |
||||
0, |
||||
93, |
||||
196, |
||||
1, |
||||
89, |
||||
198, |
||||
3, |
||||
85, |
||||
199, |
||||
5, |
||||
82, |
||||
201, |
||||
7, |
||||
78, |
||||
202, |
||||
8, |
||||
74, |
||||
204, |
||||
10, |
||||
71, |
||||
205, |
||||
12, |
||||
67, |
||||
207, |
||||
14, |
||||
64, |
||||
208, |
||||
16, |
||||
60, |
||||
209, |
||||
19, |
||||
56, |
||||
210, |
||||
21, |
||||
53, |
||||
212, |
||||
24, |
||||
49, |
||||
213, |
||||
27, |
||||
45, |
||||
214, |
||||
29, |
||||
42, |
||||
215, |
||||
32, |
||||
38, |
||||
217, |
||||
35, |
||||
35, |
||||
218, |
||||
37, |
||||
31, |
||||
220, |
||||
40, |
||||
27, |
||||
221, |
||||
43, |
||||
23, |
||||
223, |
||||
46, |
||||
20, |
||||
224, |
||||
48, |
||||
16, |
||||
226, |
||||
51, |
||||
12, |
||||
227, |
||||
54, |
||||
8, |
||||
229, |
||||
57, |
||||
5, |
||||
230, |
||||
59, |
||||
4, |
||||
231, |
||||
62, |
||||
3, |
||||
233, |
||||
65, |
||||
3, |
||||
234, |
||||
68, |
||||
2, |
||||
235, |
||||
70, |
||||
1, |
||||
237, |
||||
73, |
||||
1, |
||||
238, |
||||
76, |
||||
0, |
||||
240, |
||||
79, |
||||
0, |
||||
241, |
||||
81, |
||||
0, |
||||
243, |
||||
84, |
||||
0, |
||||
244, |
||||
87, |
||||
0, |
||||
246, |
||||
90, |
||||
0, |
||||
247, |
||||
92, |
||||
0, |
||||
249, |
||||
95, |
||||
0, |
||||
250, |
||||
98, |
||||
0, |
||||
252, |
||||
101, |
||||
0, |
||||
252, |
||||
103, |
||||
0, |
||||
252, |
||||
105, |
||||
0, |
||||
253, |
||||
107, |
||||
0, |
||||
253, |
||||
109, |
||||
0, |
||||
253, |
||||
111, |
||||
0, |
||||
254, |
||||
113, |
||||
0, |
||||
254, |
||||
115, |
||||
0, |
||||
255, |
||||
117, |
||||
0, |
||||
255, |
||||
119, |
||||
0, |
||||
255, |
||||
121, |
||||
0, |
||||
255, |
||||
123, |
||||
0, |
||||
255, |
||||
125, |
||||
0, |
||||
255, |
||||
127, |
||||
0, |
||||
255, |
||||
129, |
||||
0, |
||||
255, |
||||
131, |
||||
0, |
||||
255, |
||||
133, |
||||
0, |
||||
255, |
||||
134, |
||||
0, |
||||
255, |
||||
136, |
||||
0, |
||||
255, |
||||
138, |
||||
0, |
||||
255, |
||||
140, |
||||
0, |
||||
255, |
||||
141, |
||||
0, |
||||
255, |
||||
143, |
||||
0, |
||||
255, |
||||
145, |
||||
0, |
||||
255, |
||||
147, |
||||
0, |
||||
255, |
||||
148, |
||||
0, |
||||
255, |
||||
150, |
||||
0, |
||||
255, |
||||
152, |
||||
0, |
||||
255, |
||||
154, |
||||
0, |
||||
255, |
||||
155, |
||||
0, |
||||
255, |
||||
157, |
||||
0, |
||||
255, |
||||
159, |
||||
0, |
||||
255, |
||||
161, |
||||
0, |
||||
255, |
||||
162, |
||||
0, |
||||
255, |
||||
164, |
||||
0, |
||||
255, |
||||
166, |
||||
0, |
||||
255, |
||||
168, |
||||
0, |
||||
255, |
||||
169, |
||||
0, |
||||
255, |
||||
171, |
||||
0, |
||||
255, |
||||
173, |
||||
0, |
||||
255, |
||||
175, |
||||
0, |
||||
255, |
||||
176, |
||||
0, |
||||
255, |
||||
178, |
||||
0, |
||||
255, |
||||
180, |
||||
0, |
||||
255, |
||||
182, |
||||
0, |
||||
255, |
||||
184, |
||||
0, |
||||
255, |
||||
186, |
||||
0, |
||||
255, |
||||
188, |
||||
0, |
||||
255, |
||||
190, |
||||
0, |
||||
255, |
||||
191, |
||||
0, |
||||
255, |
||||
193, |
||||
0, |
||||
255, |
||||
195, |
||||
0, |
||||
255, |
||||
197, |
||||
0, |
||||
255, |
||||
199, |
||||
0, |
||||
255, |
||||
201, |
||||
0, |
||||
255, |
||||
203, |
||||
0, |
||||
255, |
||||
205, |
||||
0, |
||||
255, |
||||
206, |
||||
0, |
||||
255, |
||||
208, |
||||
0, |
||||
255, |
||||
210, |
||||
0, |
||||
255, |
||||
212, |
||||
0, |
||||
255, |
||||
213, |
||||
0, |
||||
255, |
||||
215, |
||||
0, |
||||
255, |
||||
217, |
||||
0, |
||||
255, |
||||
219, |
||||
0, |
||||
255, |
||||
220, |
||||
0, |
||||
255, |
||||
222, |
||||
0, |
||||
255, |
||||
224, |
||||
0, |
||||
255, |
||||
226, |
||||
0, |
||||
255, |
||||
228, |
||||
0, |
||||
255, |
||||
230, |
||||
0, |
||||
255, |
||||
232, |
||||
0, |
||||
255, |
||||
234, |
||||
0, |
||||
255, |
||||
235, |
||||
4, |
||||
255, |
||||
237, |
||||
8, |
||||
255, |
||||
239, |
||||
13, |
||||
255, |
||||
241, |
||||
17, |
||||
255, |
||||
242, |
||||
21, |
||||
255, |
||||
244, |
||||
26, |
||||
255, |
||||
246, |
||||
30, |
||||
255, |
||||
248, |
||||
35, |
||||
255, |
||||
248, |
||||
42, |
||||
255, |
||||
249, |
||||
50, |
||||
255, |
||||
250, |
||||
58, |
||||
255, |
||||
251, |
||||
66, |
||||
255, |
||||
252, |
||||
74, |
||||
255, |
||||
253, |
||||
82, |
||||
255, |
||||
254, |
||||
90, |
||||
255, |
||||
255, |
||||
98, |
||||
255, |
||||
255, |
||||
105, |
||||
255, |
||||
255, |
||||
113, |
||||
255, |
||||
255, |
||||
121, |
||||
255, |
||||
255, |
||||
129, |
||||
255, |
||||
255, |
||||
136, |
||||
255, |
||||
255, |
||||
144, |
||||
255, |
||||
255, |
||||
152, |
||||
255, |
||||
255, |
||||
160, |
||||
255, |
||||
255, |
||||
167, |
||||
255, |
||||
255, |
||||
175, |
||||
255, |
||||
255, |
||||
183, |
||||
255, |
||||
255, |
||||
191, |
||||
255, |
||||
255, |
||||
199, |
||||
255, |
||||
255, |
||||
207, |
||||
255, |
||||
255, |
||||
215, |
||||
255, |
||||
255, |
||||
223, |
||||
255, |
||||
255, |
||||
227, |
||||
255, |
||||
255, |
||||
231, |
||||
255, |
||||
255, |
||||
235, |
||||
255, |
||||
255, |
||||
239, |
||||
255, |
||||
255, |
||||
243, |
||||
255, |
||||
255, |
||||
247, |
||||
255, |
||||
255, |
||||
251, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
255, |
||||
] |
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
import re |
||||
|
||||
from collections import namedtuple |
||||
from pathlib import Path |
||||
from PIL import Image |
||||
from tqdm import tqdm |
||||
|
||||
|
||||
RE_DIGITS = re.compile(r"(\d+)") |
||||
|
||||
LABEL_SELECTED = "selected" |
||||
LABEL_DISCARDED = "discarded" |
||||
|
||||
|
||||
TifImage = namedtuple("TifImage", ["path", "frame"]) |
||||
|
||||
|
||||
class Settings(dict): |
||||
""" namespacing the constant values for a analysis with nice access """ |
||||
|
||||
def __getattr__(self, key): |
||||
""" return a settings value as instance property """ |
||||
return self[key] |
||||
|
||||
def __setattr__(self, key, value): |
||||
""" set a settings value via instance property """ |
||||
self[key] = value |
||||
|
||||
def __getstate__(self): |
||||
# Copy the object's state from self.__dict__ which contains |
||||
# all our instance attributes. Always use the dict.copy() |
||||
# method to avoid modifying the original state. |
||||
return self.__dict__.copy() |
||||
|
||||
def __setstate__(self, state): |
||||
# Restore instance attributes (i.e., filename and lineno). |
||||
self.__dict__.update(state) |
||||
|
||||
|
||||
def scan_image_dir(settings): |
||||
""" scan a folder for tif images """ |
||||
tif_dir = Path(settings.folder) |
||||
if not tif_dir.is_dir(): |
||||
raise IOError(f"Not a directory: {settings.folder}") |
||||
visible = (i for i in tif_dir.iterdir() if not i.stem.startswith(".")) |
||||
tifs = (item for item in visible if item.suffix == ".tif") |
||||
results = [] |
||||
for path in tifs: |
||||
try: |
||||
match = RE_DIGITS.search(path.name).group() |
||||
except AttributeError as ex: |
||||
raise ValueError( |
||||
f"no sequence number found in {path.name}" |
||||
) from ex |
||||
results.append(TifImage(path=path, frame=int(match))) |
||||
settings.tif_dir = tif_dir |
||||
settings.tif_list = results |
||||
return settings |
||||
|
||||
|
||||
def prepare_output_dirs(settings): |
||||
dir_names = ( |
||||
"colored", |
||||
"cuts", |
||||
f"cuts_{LABEL_DISCARDED}", |
||||
f"cuts_{LABEL_SELECTED}", |
||||
"data", |
||||
) |
||||
for subdir in dir_names: |
||||
sub_path = settings.tif_dir / subdir |
||||
sub_path.mkdir(exist_ok=True) |
||||
for item in sub_path.iterdir(): |
||||
item.unlink() |
||||
settings[f"{subdir}_dir"] = sub_path |
||||
return settings |
||||
|
||||
|
||||
def pre_scan_tifs(settings): |
||||
""" scan a sequence of tif images for common values """ |
||||
lowest = 2 ** 16 |
||||
highest = 0 |
||||
width = None |
||||
height = None |
||||
|
||||
print("1/4: scanning tifs for common autocontrast values") |
||||
|
||||
for tif in tqdm(settings.tif_list): |
||||
with tif.path.open("rb") as filehandle: |
||||
image = Image.open(filehandle) |
||||
mi, ma = image.getextrema() |
||||
lowest = min(lowest, mi) |
||||
highest = max(highest, ma) |
||||
if width is None: |
||||
width, height = image.size |
||||
elif width != image.width or height != image.height: |
||||
raise ValueError("Images have different sizes") |
||||
|
||||
print(f"intensity range: {lowest} to {highest}") |
||||
print(f"image size: {width} x {height} pixels") |
||||
|
||||
settings.intensity_min = lowest |
||||
settings.intensity_max = highest |
||||
settings.image_width = width |
||||
settings.image_height = height |
||||
|
||||
return settings |
||||
|
||||
|
||||
def check_roi_size(settings, top, right, bottom, left): |
||||
if ( |
||||
top > settings.image_height |
||||
or bottom > settings.image_height |
||||
or right > settings.image_width |
||||
or left > settings.image_width |
||||
): |
||||
raise ValueError("ROI out of bounce") |
||||
if right < left: |
||||
left, right = right, left |
||||
if bottom < top: |
||||
top, bottom = bottom, top |
||||
|
||||
settings.roi_top = top |
||||
settings.roi_bottom = bottom |
||||
settings.roi_right = right |
||||
settings.roi_left = left |
||||
|
||||
settings.cut_top = min(settings.image_height, top - settings.cut_pad_y) |
||||
settings.cut_bottom = max(0, bottom + settings.cut_pad_y) |
||||
settings.cut_right = min(settings.image_width, right + settings.cut_pad_x) |
||||
settings.cut_left = max(0, left - settings.cut_pad_x) |
||||
|
||||
return settings |
||||
|
||||
|
||||
def prescan_workflow(folder, top, right, bottom, left, boost=10, **kargs): |
||||
settings = Settings() |
||||
settings.folder = folder |
||||
settings.boost = boost |
||||
|
||||
settings.cut_pad_x = 50 |
||||
settings.cut_pad_y = 25 |
||||
settings.guard_histogram_bins = 100 |
||||
settings.guard_filter_window = 7 |
||||
settings.guard_filter_polynom = 3 |
||||
settings.guards_minima_min_dist = 10 |
||||
settings.rolling_min_window = 5 |
||||
settings.savgol_filter_window = 51 |
||||
settings.savgol_filter_polynom = 1 |
||||
settings.peak_min_distance = 5 |
||||
settings.peak_threshold = 0.3 |
||||
|
||||
settings.update(kargs) |
||||
|
||||
settings = scan_image_dir(settings) |
||||
settings = prepare_output_dirs(settings) |
||||
settings = pre_scan_tifs(settings) |
||||
settings = check_roi_size(settings, top, right, bottom, left) |
||||
|
||||
settings.offset = settings.intensity_min |
||||
max_offset = settings.intensity_max - settings.intensity_min |
||||
settings.scale = max_offset / 255 / boost |
||||
|
||||
return settings |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
[tool.black] |
||||
line-length = 79 |
||||
py37 = true |
||||
include = '\.pyi?$' |
||||
exclude = ''' |
||||
/( |
||||
\.git |
||||
| \.tox |
||||
| \.venv |
||||
| build |
||||
| dist |
||||
)/ |
||||
''' |
@ -0,0 +1,316 @@
@@ -0,0 +1,316 @@
|
||||
from pathlib import Path |
||||
from PIL import Image, ImageOps |
||||
from tqdm import tqdm |
||||
import numpy |
||||
import skimage |
||||
import warnings |
||||
|
||||
FIRE_LUT = [ |
||||
0, 0, 0, |
||||
0, 0, 7, |
||||
0, 0, 15, |
||||
0, 0, 22, |
||||
0, 0, 30, |
||||
0, 0, 38, |
||||
0, 0, 45, |
||||
0, 0, 53, |
||||
0, 0, 61, |
||||
0, 0, 65, |
||||
0, 0, 69, |
||||
0, 0, 74, |
||||
0, 0, 78, |
||||
0, 0, 82, |
||||
0, 0, 87, |
||||
0, 0, 91, |
||||
1, 0, 96, |
||||
4, 0, 100, |
||||
7, 0, 104, |
||||
10, 0, 108, |
||||
13, 0, 113, |
||||
16, 0, 117, |
||||
19, 0, 121, |
||||
22, 0, 125, |
||||
25, 0, 130, |
||||
28, 0, 134, |
||||
31, 0, 138, |
||||
34, 0, 143, |
||||
37, 0, 147, |
||||
40, 0, 151, |
||||
43, 0, 156, |
||||
46, 0, 160, |
||||
49, 0, 165, |
||||
52, 0, 168, |
||||
55, 0, 171, |
||||
58, 0, 175, |
||||
61, 0, 178, |
||||
64, 0, 181, |
||||
67, 0, 185, |
||||
70, 0, 188, |
||||
73, 0, 192, |
||||
76, 0, 195, |
||||
79, 0, 199, |
||||
82, 0, 202, |
||||
85, 0, 206, |
||||
88, 0, 209, |
||||
91, 0, 213, |
||||
94, 0, 216, |
||||
98, 0, 220, |
||||
101, 0, 220, |
||||
104, 0, 221, |
||||
107, 0, 222, |
||||
110, 0, 223, |
||||
113, 0, 224, |
||||
116, 0, 225, |
||||
119, 0, 226, |
||||
122, 0, 227, |
||||
125, 0, 224, |
||||
128, 0, 222, |
||||
131, 0, 220, |
||||
134, 0, 218, |
||||
137, 0, 216, |
||||
140, 0, 214, |
||||
143, 0, 212, |
||||
146, 0, 210, |
||||
148, 0, 206, |
||||
150, 0, 202, |
||||
152, 0, 199, |
||||
154, 0, 195, |
||||
156, 0, 191, |
||||
158, 0, 188, |
||||
160, 0, 184, |
||||
162, 0, 181, |
||||
163, 0, 177, |
||||
164, 0, 173, |
||||
166, 0, 169, |
||||
167, 0, 166, |
||||
168, 0, 162, |
||||
170, 0, 158, |
||||
171, 0, 154, |
||||
173, 0, 151, |
||||
174, 0, 147, |
||||
175, 0, 143, |
||||
177, 0, 140, |
||||
178, 0, 136, |
||||
179, 0, 132, |
||||
181, 0, 129, |
||||
182, 0, 125, |
||||
184, 0, 122, |
||||
185, 0, 118, |
||||
186, 0, 114, |
||||
188, 0, 111, |
||||
189, 0, 107, |
||||
190, 0, 103, |
||||
192, 0, 100, |
||||
193, 0, 96, |
||||
195, 0, 93, |
||||
196, 1, 89, |
||||
198, 3, 85, |
||||
199, 5, 82, |
||||
201, 7, 78, |
||||
202, 8, 74, |
||||
204, 10, 71, |
||||
205, 12, 67, |
||||
207, 14, 64, |
||||
208, 16, 60, |
||||
209, 19, 56, |
||||
210, 21, 53, |
||||
212, 24, 49, |
||||
213, 27, 45, |
||||
214, 29, 42, |
||||
215, 32, 38, |
||||
217, 35, 35, |
||||
218, 37, 31, |
||||
220, 40, 27, |
||||
221, 43, 23, |
||||
223, 46, 20, |
||||
224, 48, 16, |
||||
226, 51, 12, |
||||
227, 54, 8, |
||||
229, 57, 5, |
||||
230, 59, 4, |
||||
231, 62, 3, |
||||
233, 65, 3, |
||||
234, 68, 2, |
||||
235, 70, 1, |
||||
237, 73, 1, |
||||
238, 76, 0, |
||||
240, 79, 0, |
||||
241, 81, 0, |
||||
243, 84, 0, |
||||
244, 87, 0, |
||||
246, 90, 0, |
||||
247, 92, 0, |
||||
249, 95, 0, |
||||
250, 98, 0, |
||||
252, 101, 0, |
||||
252, 103, 0, |
||||
252, 105, 0, |
||||
253, 107, 0, |
||||
253, 109, 0, |
||||
253, 111, 0, |
||||
254, 113, 0, |
||||
254, 115, 0, |
||||
255, 117, 0, |
||||
255, 119, 0, |
||||
255, 121, 0, |
||||
255, 123, 0, |
||||
255, 125, 0, |
||||
255, 127, 0, |
||||
255, 129, 0, |
||||
255, 131, 0, |
||||
255, 133, 0, |
||||
255, 134, 0, |
||||
255, 136, 0, |
||||
255, 138, 0, |
||||
255, 140, 0, |
||||
255, 141, 0, |
||||
255, 143, 0, |
||||
255, 145, 0, |
||||
255, 147, 0, |
||||
255, 148, 0, |
||||
255, 150, 0, |
||||
255, 152, 0, |
||||
255, 154, 0, |
||||
255, 155, 0, |
||||
255, 157, 0, |
||||
255, 159, 0, |
||||
255, 161, 0, |
||||
255, 162, 0, |
||||
255, 164, 0, |
||||
255, 166, 0, |
||||
255, 168, 0, |
||||
255, 169, 0, |
||||
255, 171, 0, |
||||
255, 173, 0, |
||||
255, 175, 0, |
||||
255, 176, 0, |
||||
255, 178, 0, |
||||
255, 180, 0, |
||||
255, 182, 0, |
||||
255, 184, 0, |
||||
255, 186, 0, |
||||
255, 188, 0, |
||||
255, 190, 0, |
||||
255, 191, 0, |
||||
255, 193, 0, |
||||
255, 195, 0, |
||||
255, 197, 0, |
||||
255, 199, 0, |
||||
255, 201, 0, |
||||
255, 203, 0, |
||||
255, 205, 0, |
||||
255, 206, 0, |
||||
255, 208, 0, |
||||
255, 210, 0, |
||||
255, 212, 0, |
||||
255, 213, 0, |
||||
255, 215, 0, |
||||
255, 217, 0, |
||||
255, 219, 0, |
||||
255, 220, 0, |
||||
255, 222, 0, |
||||
255, 224, 0, |
||||
255, 226, 0, |
||||
255, 228, 0, |
||||
255, 230, 0, |
||||
255, 232, 0, |
||||
255, 234, 0, |
||||
255, 235, 4, |
||||
255, 237, 8, |
||||
255, 239, 13, |
||||
255, 241, 17, |
||||
255, 242, 21, |
||||
255, 244, 26, |
||||
255, 246, 30, |
||||
255, 248, 35, |
||||
255, 248, 42, |
||||
255, 249, 50, |
||||
255, 250, 58, |
||||
255, 251, 66, |
||||
255, 252, 74, |
||||
255, 253, 82, |
||||
255, 254, 90, |
||||
255, 255, 98, |
||||
255, 255, 105, |
||||
255, 255, 113, |
||||
255, 255, 121, |
||||
255, 255, 129, |
||||
255, 255, 136, |
||||
255, 255, 144, |
||||
255, 255, 152, |
||||
255, 255, 160, |
||||
255, 255, 167, |
||||
255, 255, 175, |
||||
255, 255, 183, |
||||
255, 255, 191, |
||||
255, 255, 199, |
||||
255, 255, 207, |
||||
255, 255, 215, |
||||
255, 255, 223, |
||||
255, 255, 227, |
||||
255, 255, 231, |
||||
255, 255, 235, |
||||
255, 255, 239, |
||||
255, 255, 243, |
||||
255, 255, 247, |
||||
255, 255, 251, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
255, 255, 255, |
||||
] |
||||
|
||||
|
||||
tif_dir = Path("original_tifs") |
||||
png_dir = Path("auto_pngs") |
||||
for item in png_dir.iterdir(): |
||||
item.unlink() |
||||
|
||||
|
||||
visible_items = (i for i in tif_dir.iterdir() if not i.stem.startswith(".")) |
||||
tifs = [i for i in visible_items if i.suffix == ".tif"] |
||||
|
||||
|
||||
lowest = 2**16 |
||||
lowest = 309 |
||||
highest = 0 |
||||
highest = 23922//4 |
||||
|
||||
print("1/3: scanning tifs for common autocontrast values") |
||||
for path in tqdm(tifs): |
||||
break |
||||
with path.open("rb") as filehandle: |
||||
image = Image.open(filehandle) |
||||
mi, ma = image.getextrema() |
||||
lowest = min(lowest, mi) |
||||
highest = max(highest, ma) |
||||
print("lowest intensity found: ", lowest) |
||||
print("highest intensity found: ", highest) |
||||
|
||||
|
||||
scaling_factor = highest / 255 |
||||
|
||||
print("2/3: adjusting contrast, converting to png") |
||||
for path in tqdm(tifs): |
||||
break |
||||
with path.open("rb") as filehandle: |
||||
image = Image.open(filehandle) |
||||
image_array = numpy.array(image, dtype=numpy.int32) |
||||
converted = Image.fromarray((image_array - lowest) // scaling_factor) |
||||
adjusted = converted.convert(mode="L").convert(mode="P") |
||||
adjusted.putpalette(FIRE_LUT) |
||||
png_path = png_dir / (path.stem + ".png") |
||||
with png_path.open("wb") as outhandle: |
||||
adjusted.save(outhandle) |
||||
cut = image_array[50:300,660:910] |
||||
import mtor |
||||
print(mtor.analyse_cut(cut)) |
||||
|
||||
|
||||
|
||||
#x = 660, 50, |
||||
#w, h = 250, 250 |
Loading…
Reference in new issue