diff --git a/mtor/__init__.py b/mtor/__init__.py index 5c53421..404a84c 100644 --- a/mtor/__init__.py +++ b/mtor/__init__.py @@ -13,6 +13,7 @@ warnings.filterwarnings("ignore") __version__ = "0.1.0" + def run(): # pw = prescan_workflow("mtor-bilder", 50, 910, 300, 660) pw = prescan_workflow("mtor-bilder", 50, 725 + 150, 300, 725) diff --git a/mtor/cli.py b/mtor/cli.py index 72535c3..eb91253 100644 --- a/mtor/cli.py +++ b/mtor/cli.py @@ -1,16 +1,54 @@ -# -*- coding: utf-8 -*- - """Console script for mtor.""" -import sys + import click +import sys +import warnings + +from .workflows import ( + prescan_workflow, + image_workflow, + data_workflow, + postprocessing_workflow, +) + +warnings.filterwarnings("ignore") @click.command() -def main(args=None): - """Console script for mtor.""" - click.echo("Replace this message by putting your code into " - "mtor.cli.main") - click.echo("See click documentation at http://click.pocoo.org/") +@click.argument( + "folder", + type=click.Path( + exists=True, + file_okay=False, + dir_okay=True, + writable=True, + readable=True, + resolve_path=True, + allow_dash=False, + ), +) +@click.argument("top", type=click.INT) +@click.argument("right", type=click.INT) +@click.argument("bottom", type=click.INT) +@click.argument("left", type=click.INT) +def main(folder, top, right, bottom, left): + """Console script for analysing mtor images. + + You need to provide the following information: + + \b + FOLDER path to the directory containing tif images to analyse + TOP top edge position of the ROI in pixels + RIGHT right edge position of the ROI in pixels + BOTTOM bottom edge position of the ROI in pixels + LEFT left edge position of the ROI in pixels + """ + + pw = prescan_workflow(folder, top, right, bottom, left) + iw = image_workflow(pw.parameters) + dw = data_workflow(iw.data, iw.parameters) + fw = postprocessing_workflow(dw.data, dw.parameters) # noqa: F841 + return 0 diff --git a/mtor/dataproc.py b/mtor/dataproc.py index aad23cf..8dd35cb 100644 --- a/mtor/dataproc.py +++ b/mtor/dataproc.py @@ -23,6 +23,20 @@ from scipy.signal import savgol_filter from .commons import ROI_STATISTIC_FUNCTIONS +IMAGE_NAMES = { + 1: "1-histogram-of-guard-avarages-not-filtered.png", + 2: "2-histogram-of-guard-avarages-filtered.png", + 3: "3-histogram-of-guard-avarages-filtered-with-first-minima.png", + 4: "4-image-selection-based-on-guard-values.png", + 5: "5-selected-values-based-on-guard-values.png", + 6: "6-boxplot-of-guarded-values.png", + 7: "7-selected-images-outliers-removed.png", + 8: "8-selected-images-outliers-removed-rolling-min-applied.png", + 9: "9-selected-images-outliers-removed-rolling-min-savgol-filtered.png", + 10: "11-finding-minima-and-maxima.png", +} + + def peakplot_iloc(x, y, ix1, ix1_label="peaks", ix2=None, ix2_label="peaks"): """ Plots the original data with the peaks that were identified @@ -112,9 +126,7 @@ def find_guard_threshold(data_frame, parameters): pyplot.title("Histogram of Guard Avarages (not filtered)") pyplot.xlabel("Average Intensity [au]") pyplot.ylabel("Number of Observations [1]") - path = ( - parameters.data_dir / "1-histogram-of-guard-avarages-not-filtered.png" - ) + path = parameters.data_dir / IMAGE_NAMES[1] pyplot.savefig(str(path)) guard_counts_filtered = savgol_filter( @@ -128,7 +140,7 @@ def find_guard_threshold(data_frame, parameters): pyplot.title("Histogram of Guard Avarages (Savitzky-Golay filter)") pyplot.xlabel("Average Intensity [au]") pyplot.ylabel("Number of Observations [1]") - path = parameters.data_dir / "2-histogram-of-guard-avarages-filtered.png" + path = parameters.data_dir / IMAGE_NAMES[2] pyplot.savefig(str(path)) # Finding the first minima after the first peak @@ -165,10 +177,7 @@ def find_guard_threshold(data_frame, parameters): ) pyplot.xlabel("Average Intensity [au]") pyplot.ylabel("Number of Observations [1]") - path = ( - parameters.data_dir - / "3-histogram-of-guard-avarages-filtered-with-first-minima.png" - ) + path = parameters.data_dir / IMAGE_NAMES[3] pyplot.savefig(str(path)) @@ -201,7 +210,7 @@ def check_guards(data_frame, parameters): pyplot.title("Selection based on guard values") pyplot.ylabel("Average Intensity [au]") pyplot.ylabel("Frame Number [1]") - path = parameters.data_dir / "4-image-selection-based-on-guard-values.png" + path = parameters.data_dir / IMAGE_NAMES[4] pyplot.savefig(str(path)) parameters.charts_y_limit = ax.get_ylim() @@ -220,7 +229,7 @@ def check_guards(data_frame, parameters): pyplot.xlabel("Frame Number [1]") pyplot.ylabel("Average Intensity [au]") ax.set_ylim(parameters.charts_y_limit) - path = parameters.data_dir / "5-selected-values-based-on-guard-values.png" + path = parameters.data_dir / IMAGE_NAMES[5] pyplot.savefig(str(path)) return data_frame @@ -235,7 +244,7 @@ def find_outliers(data_frame, parameters): seaborn.boxplot(data=guarded_df, x=parameters.roi_column) pyplot.title(f"Boxblot of guarded values") pyplot.xlabel("Average Intensity [au]") - path = parameters.data_dir / "6-boxplot-of-guarded-values.png" + path = parameters.data_dir / IMAGE_NAMES[6] pyplot.savefig(str(path)) lower_quartil = guarded_df[parameters.roi_column].quantile(0.25) @@ -264,7 +273,7 @@ def select_on_guards_and_outliers(data_frame, parameters): pyplot.xlabel("Frame Number [1]") pyplot.ylabel("Average Intensity [au]") ax.set_ylim(parameters.charts_y_limit) - path = parameters.data_dir / "7-selected-images-outliers-removed.png" + path = parameters.data_dir / IMAGE_NAMES[7] pyplot.savefig(str(path)) return selected_df @@ -291,10 +300,7 @@ def smooth_rolling_min(selected_df, parameters): pyplot.xlabel("Frame Number [1]") pyplot.ylabel("Average Intensity [au]") ax.set_ylim(parameters.charts_y_limit) - path = ( - parameters.data_dir - / "8-selected-images-outliers-removed-rolling-min-applied.png" - ) + path = parameters.data_dir / IMAGE_NAMES[8] pyplot.savefig(str(path)) return selected_df @@ -320,10 +326,7 @@ def smooth_savgol_filter(selected_df, parameters): ) pyplot.xlabel("Frame Number [1]") pyplot.ylabel("Average Intensity [au]") - path = ( - parameters.data_dir - / "9-selected-images-outliers-removed-rolling-min-savgol-filtered.png" - ) + path = parameters.data_dir / IMAGE_NAMES[9] pyplot.savefig(str(path)) return selected_df @@ -337,20 +340,6 @@ def find_extremas(selected_df, parameters): min_dist=parameters.peak_min_distance, ) maximas = selected_df.iloc[max_indexes].copy() - - pyplot.clf() - peakplot_iloc( - selected_df["frame"], - selected_df[f"{parameters.roi_name}.savgol"], - max_indexes, - "maxima", - ) - pyplot.title(f"Finding Maximas") - pyplot.xlabel("Frame Number [1]") - pyplot.ylabel("Average Intensity [au]") - path = parameters.data_dir / "10-finding-maximas.png" - pyplot.savefig(str(path)) - inverted_series = ( max(selected_df[f"{parameters.roi_name}.savgol"]) - selected_df[f"{parameters.roi_name}.savgol"] @@ -372,7 +361,7 @@ def find_extremas(selected_df, parameters): pyplot.title(f"Finding Minimas") pyplot.xlabel("Frame Number [1]") pyplot.ylabel("Average Intensity [au]") - path = parameters.data_dir / "11-finding-minimas.png" + path = parameters.data_dir / IMAGE_NAMES[10] pyplot.savefig(str(path)) maximas["is_maxima"] = True @@ -423,12 +412,14 @@ def create_report(data_frame, selected_df, extremas_df, parameters): num_selected = len(selected_df) num_discarded = num_images - num_selected - def text_and_graph(text, name): + def text_and_graph(image_nr, text): flowable = KeepTogether( [ Paragraph(text, style_text), Image( - str(data_dir / name), width=img_width, height=img_height + str(data_dir / IMAGE_NAMES[image_nr]), + width=img_width, + height=img_height, ), Spacer(1, 7 * mm), ] @@ -440,80 +431,72 @@ def create_report(data_frame, selected_df, extremas_df, parameters): Spacer(1, 10 * mm), Paragraph("Estimating Guard Threshold", style_section), text_and_graph( + 1, ( "In a first step, the histogram of the combined left and " "right guard values is calculated." ), - "1-histogram-of-guard-avarages-not-filtered.png", ), text_and_graph( + 2, ( "A Savitzky-Golay filter is applied to the histogram to " "smooth the curve." ), - "2-histogram-of-guard-avarages-filtered.png", ), text_and_graph( + 3, ( "The first minima after the first peak is used as the guard " f"threshold value: {int(parameters.guard_max_value)} au" ), - "3-histogram-of-guard-avarages-filtered-with-first-minima.png", ), text_and_graph( + 4, ( "The images with one of the guard values above the threshold " "are discarded." ), - "4-image-selection-based-on-guard-values.png", ), Image( - str(data_dir / "5-selected-values-based-on-guard-values.png"), - width=img_width, - height=img_height, + str(data_dir / IMAGE_NAMES[5]), width=img_width, height=img_height ), PageBreak(), Paragraph("Removing Outliers", style_section), + text_and_graph(6, "From the remaining values, outliers are removed."), text_and_graph( - "From the remaining values, outliers are removed.", - "6-boxplot-of-guarded-values.png", - ), - text_and_graph( + 7, ( f"From {num_images} images {num_discarded} images were " f"discarded, leaving {num_selected} selected. The finally " "selected values are listed in the excel sheet 'selection' " "in the data file." ), - "7-selected-images-outliers-removed.png", ), PageBreak(), Paragraph( "Experimental: Applying a rolling min calculation", style_section ), text_and_graph( + 8, ( "Due to the nature of the experiment, unusable images tend " "to have a higher value as the desiered ones. Therfore a " "rolling min filter is applied" ), - "8-selected-images-outliers-removed-rolling-min-applied.png", ), Paragraph("Experimental: Finding Maxima and Minima", style_section), text_and_graph( + 9, "To smooth the resulting curve, a Savitzky-Golay filter is used.", - ( - "9-selected-images-outliers-" - "removed-rolling-min-savgol-filtered.png" - ), ), text_and_graph( + 10, ( "The most interesting data points should be the maxima and " "minima of this curve. These are listed in the sheet " "'extremas' in the data file" ), - "11-finding-minimas.png", ), ] doc.build(story) diff --git a/mtor/gui.py b/mtor/gui.py new file mode 100644 index 0000000..293c44f --- /dev/null +++ b/mtor/gui.py @@ -0,0 +1,186 @@ +from pathlib import Path + +home = Path.home() + + +import sys +from PyQt5.QtWidgets import ( + QApplication, + QMainWindow, + QPushButton, + QDesktopWidget, + QHBoxLayout, + QVBoxLayout, + QWidget, + QLabel, + QLineEdit, + QGridLayout, + QFileDialog, +) +from PyQt5.QtGui import QIcon, QIntValidator + +from .workflows import ( + prescan_workflow, + image_workflow, + data_workflow, + cached_data_workflow, + postprocessing_workflow, +) + + +class MtorImageAnalysis(QWidget): + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + + # analysis parameters + self.analysis_parameters = None + + # directory browser + self.dir_label = QLabel("Image Folder") + self.dir_btn = QPushButton("Browse") + self.dir_btn.clicked.connect(self.get_tif_dir) + self.dir_selected = QLabel() + + # roi input fields + self.roi_top_label = QLabel("Top edge of ROI") + self.roi_top_input = QLineEdit() + self.roi_top_input.setValidator(QIntValidator()) + self.roi_top_input.textChanged.connect(self.check_button_state) + + self.roi_left_label = QLabel("Left edge of ROI") + self.roi_left_input = QLineEdit() + self.roi_left_input.setValidator(QIntValidator()) + self.roi_left_input.textChanged.connect(self.check_button_state) + + self.roi_right_label = QLabel("Right edge of ROI") + self.roi_right_input = QLineEdit() + self.roi_right_input.setValidator(QIntValidator()) + self.roi_right_input.textChanged.connect(self.check_button_state) + + self.roi_bottom_label = QLabel("Bottom edge of ROI") + self.roi_bottom_input = QLineEdit() + self.roi_bottom_input.setValidator(QIntValidator()) + self.roi_bottom_input.textChanged.connect(self.check_button_state) + + # action buttons + self.btn_run = QPushButton("Run", self) + self.btn_run.setEnabled(False) + self.btn_run.clicked.connect(self.run_analysis) + + self.btn_exit = QPushButton("Exit", self) + self.btn_exit.clicked.connect(QApplication.instance().quit) + + # Layout + grid = QGridLayout() + grid.setSpacing(10) + + grid.addWidget(self.dir_label, 1, 0) + grid.addWidget(self.dir_btn, 1, 1) + + grid.addWidget(self.dir_selected, 2, 0, 1, 2) + + grid.addWidget(QLabel(), 3, 0, 1, 2) + + grid.addWidget(self.roi_top_label, 4, 0) + grid.addWidget(self.roi_top_input, 4, 1) + + grid.addWidget(self.roi_right_label, 5, 0) + grid.addWidget(self.roi_right_input, 5, 1) + + grid.addWidget(self.roi_bottom_label, 6, 0) + grid.addWidget(self.roi_bottom_input, 6, 1) + + grid.addWidget(self.roi_left_label, 7, 0) + grid.addWidget(self.roi_left_input, 7, 1) + + grid.addWidget(QLabel(), 8, 0, 1, 2) + + grid.addWidget(self.btn_exit, 9, 0) + grid.addWidget(self.btn_run, 9, 1) + + self.setLayout(grid) + #self.resize(350, 300) + self.center_window_on_desktop() + self.setWindowTitle("mTor Image Analysis") + self.setWindowIcon(QIcon("web.png")) + + # helpers + self.dir_selected.setText("/Users/holgerfrey/Developer/mtor/mtor-bilder") + self.roi_top_input.setText("50") + self.roi_right_input.setText("875") + self.roi_bottom_input.setText("300") + self.roi_left_input.setText("725") + + self.show() + + def center_window_on_desktop(self): + cp = QDesktopWidget().availableGeometry().center() + fg = self.frameGeometry() + fg.moveCenter(cp) + self.move(fg.topLeft()) + + def get_tif_dir(self): + dlg = QFileDialog(directory=str(home)) + dlg.setFileMode(QFileDialog.Directory) + + if dlg.exec_(): + filenames = dlg.selectedFiles() + folder = Path(filenames[0]) + items = (i for i in folder.iterdir() if not i.stem.startswith(".")) + tifs = [i for i in items if i.suffix == ".tif"] + if len(tifs) == 0: + self.dir_selected.setText("") + self.check_button_state() + else: + self.dir_selected.setText(filenames[0]) + self.check_button_state() + + + def check_button_state(self): + values = self.get_values() + self.btn_run.setEnabled(all(values)) + + def get_values(self): + fields = [ + (self.dir_selected, str), + (self.roi_top_input, int), + (self.roi_right_input, int), + (self.roi_bottom_input, int), + (self.roi_left_input, int), + ] + result = [] + for field, func in fields: + raw_data = field.text().strip() + if raw_data: + if func is int: + # in qt5, a point as 1000 separator is allowed + raw_data = raw_data.replace(".", "") + result.append(func(raw_data)) + else: + result.append(None) + return tuple(result) + + def run_analysis(self): + parameters = self.get_values() + if all(parameters): + self.analysis_parameters = parameters + self.hide() + QApplication.instance().quit() + + +def run_gui(): + app = QApplication(sys.argv) + mia = MtorImageAnalysis() + app.exec_() + + if mia.analysis_parameters is not None: + analysis_parameters = tuple( (p for p in mia.analysis_parameters) ) + pw = prescan_workflow(*analysis_parameters) + iw = image_workflow(pw.parameters) + dw = data_workflow(iw.data, iw.parameters) + fw = postprocessing_workflow(dw.data, dw.parameters) # noqa: F841 + + sys.exit() diff --git a/setup.py b/setup.py index 2a196e5..bd6d01d 100644 --- a/setup.py +++ b/setup.py @@ -8,9 +8,6 @@ from setuptools import setup, find_packages with open("README.md") as readme_file: readme = readme_file.read() -with open("HISTORY.rst") as history_file: - history = history_file.read() - requirements = [ "Click>=6.0", "pandas>=0.24", @@ -25,6 +22,7 @@ requirements = [ "xlwt>=1.3", "xlsxwriter>=1.1", "reportlab>=3.5", + "PyQt5>=5.12" ] setup_requirements = [ ] diff --git a/test.py b/test.py index 5ca37fb..83149ab 100644 --- a/test.py +++ b/test.py @@ -1,316 +1,4 @@ -from pathlib import Path -from PIL import Image, ImageOps -from tqdm import tqdm -import numpy -import skimage -import warnings +from mtor.gui import run_gui -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, - ] +run_gui() - -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