Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Planning

![logo](docs/TURMorIC.png)

Developers: Colin, Krista, Sergi, Muna, Heather

This repository contains the test functions for the updated VAMPIRE (Visually Aided Morpho-Phenotyping Image Recognition) program. The updated program humorously titled "TURMERIC" serves to extract the functions of VAMPIRE and create callable function packages and a user interface. These will allow any user interested in performing brain slice image analysis to do so more readily.
Expand All @@ -10,16 +12,16 @@ This repository contains the test functions for the updated VAMPIRE (Visually Ai
- the docs, example_dataset, notebooks, src, and test directories (detailed below)

# docs Directory
This directory contains documents related to the initial design process for the software package including use cases, user stories, and identified components
This directory contains documents related to the initial design process for the software package including use cases, user stories, and identified components as well as the team logo and details on the GUI. Finally, there is a slide deck with a presentation given to the CHEM E 546: Software Engineering for Molecular Science and Engineering showcasing the design process of the TURMorIC project.

# example_dataset Directory
This directory includes example images and expected outputs for the preprocessing.
This directory includes example .tiff images and expected outputs, like shape mode distribution, for the preprocessing as well as some examples of the segmentation process as well. These serve as potential practice images for understanding the existing code pipeline.

# notebooks Directory
This directory contains Jupyter notebooks that hold the previous code from VAMPIRE as the developers work on creating the TURMERIC functions.
This directory contains Jupyter notebooks that hold the previous code from VAMPIRE in the subdirectory "notebooks_from_existing_codebase" as the developers work on creating the TURMorIC functions. The subdirectory "temporary_notebooks_for_concept_testing" are Jupyter notebooks that the team can use to work on code before pushing to the main branch.

# src Directory
This directory contains all of the source code for the callable functions of the TURMERIC package.
# src/Nosferatu Directory
This directory contains all of the source code for the functions of the TURMorIC package. This will be where functions will be committed to the main branch. The directory also holds the GUI components directory and the __init__.py file.

# test Directory
This directory holds all of the test functions for each of the components. This includes functions to test if the image directory exists,
This directory holds all of the test functions for each of the components. This includes functions to test if the image directory exists, cleaning the file and folder paths, adjusting contrast among other preprocessing steps.
Binary file added docs/CHEME546_final_presentation.pptx
Binary file not shown.
Binary file added docs/TURMorIC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 0 additions & 12 deletions environment.yml

This file was deleted.

218 changes: 190 additions & 28 deletions src/nosferatu/GUI_components/CentralNode.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,219 @@
from PyQt6.QtCore import Qt, pyqtSignal,pyqtSlot, QThread, QObject
from PyQt6.QtGui import QImage, QPixmap

import sys
import pickle

import ImageHandler
from ImageHandler import ImageHandler
import FunctionHandler
import ModelHandler
from PIL import Image
from PIL.TiffTags import TAGS_V2
import numpy as np


class CentralNode(QObject):
"""
The CentralNode is responsible for facilitating communication between the main window and QThread-based worker classes.

This class sends information to the worker threads, and receives updates from them to forward back to the main window.
It serves as an intermediary between the GUI and the backend processing threads.

The CentralNode is responsible for facilitating communication between the main window
and QThread-based worker classes. It acts as an intermediary between the GUI (main window)
and backend processing threads (such as image processing and model building).

Attributes:
update_image (pyqtSignal): Signal emitted to update the image in the main window.
main_window (MainWindow): Reference to the main window instance for UI updates.
current_control (int): Keeps track of the current control index (e.g., page number).
image_path (str): Path to the currently loaded image.
metadata (dict): Metadata tags extracted from the loaded image.
image_data (Image.Image or None): The image data of the loaded image.
original_image (Image.Image or None): The original image data before processing.
image_handler (ImageHandler or None): The handler responsible for image manipulation and processing.
"""
update_image = pyqtSignal(QImage)

def __init__(self, main_window):
"""
Initializes the CentralNode instance and sets up connections to worker classes and main window.

Args:
main_window (MainWindow): The main window instance to update the UI based on backend operations.
"""
super(CentralNode, self).__init__()
self.main_window = main_window
self.current_control=0 # current set of controls
self.stuff = None
self.current_control = 0
self.image_path = self.metadata = self.image_data = None
self.image_handler = self.original_image = None

# Placeholder for worker instances
# self.image_handler = ImageHandler() # Example image handler
# self.function_handler = FunctionHandler(data="some data") # Example function handler
# self.model_handler = ModelHandler(data="some data") # Example model handler

# Connections to MainWindow (Example)
# self.function_handler.update_image.connect(self.main_window.displayImage)
# self.image_handler.update_image.connect(self.main_window.displayImage)
# self.function_handler.update_data.connect(self.main_window.update_data)
# self.build_model.progress_changed.connect(self.main_window.update_progress)
# self.build_model.status_updated.connect(self.main_window.update_status)
# self.function_handler.update_model.connect(self.main_window.update_model)

def update_controls(self, current_controls):
"""
Updates the current set of controls (e.g., page number) based on the page number.

Args:
current_controls (int): The new control index (e.g., page number).
"""
self.current_control = current_controls

def load_image(self, image_path):
"""
Loads an image from the specified path, extracts metadata if available, and emits the image to the main window.

Args:
image_path (str): The path to the image file to load.
"""
# Open the image using Pillow
self.image_data = Image.open(image_path)
self.image_data = self.image_data.convert("RGBA") # Ensure the image is in RGBA format
self.original_image = self.image_data # Store original image

# Convert Pillow image to QImage for display in GUI
qimg = self.pil_to_qimage(self.image_data)
print("Emitting image to main window...")
self.update_image.emit(qimg) # Emit the image for UI update

# Instantiate workers
self.image_handler = ImageHandler(data="some data")
self.function_handler=FunctionHandler(data="some data")
self.model_handler = ModelHandler(data="some data")

# Connections to MainWindow
self.function_handler.update_image.connect()
self.image_handler.update_image.connect(self.main_window.displayImage)
self.function_handler.update_data.connect()
self.build_model.progress_changed.connect(self.main_window.update_progress)
self.build_model.status_updated.connect(self.main_window.update_status)
self.function_handler.update_model.connect()

def update_controls(self):
self.current_control+=1
# Extract metadata (tags) from TIFF images
if self.image_data.format == 'TIFF':
self.metadata = self.image_data.tag_v2
print("Metadata tags:")
for tag, value in self.metadata.items():
tag_name = TAGS_V2.get(tag, tag)
print(f"{tag_name}: {value}")

def pil_to_qimage(self, pil_image):
"""
Converts a PIL Image to a QImage for use in the Qt GUI.

Args:
pil_image (PIL.Image.Image or np.ndarray): The PIL image to convert.

Returns:
QImage: The converted QImage object.
"""
qim = None # Default value for QImage

if isinstance(pil_image, np.ndarray):
# Convert numpy ndarray to QImage (raw byte data format)
data = pil_image.tobytes("raw")
qim = QImage(data, pil_image.shape[1], pil_image.shape[0], pil_image.shape[1] * 4, QImage.Format.Format_RGBA8888)
elif isinstance(pil_image, Image.Image):
# Convert from PIL Image to raw bytes (ensure RGBA format)
pil_image = pil_image.convert("RGBA")
data = pil_image.tobytes("raw")
qim = QImage(data, pil_image.width, pil_image.height, pil_image.width * 4, QImage.Format.Format_RGBA8888)

# Return the QImage or None if conversion failed
return qim

def revert_back_to_original_image(self):
"""
Reverts any modifications and returns the original loaded image.

Returns:
Image.Image: The original image before any processing.
"""
self.single_image = self.original_image
return self.single_image

def apply_filter(self, filter_type):
"""
Applies a filter to the image based on the given filter type.

Args:
filter_type (str): The type of filter to apply (e.g., "blur", "sharpen").
"""
# Get the original image
image = self.revert_back_to_original_image()

if isinstance(image, Image.Image):
image_data = np.array(image) # Convert to numpy array for processing

# Create the image handler and connect the image update signal
self.image_handler = ImageHandler(image)
self.image_handler.update_image.connect(self.emit_image)

# Start the processing in the worker thread
self.start_processing(self.image_handler)

# Apply the filter
self.image_handler.apply_filter_to_image(image_data, filter_type)
self.image_handler.wait() # Wait for processing to complete

# Get the processed image and emit it
self.single_image = self.image_handler.image_data
qimg = self.pil_to_qimage(self.single_image)
# self.update_image.emit(qimg) # Uncomment if emitting the image after filtering

def save_single_image(self, file_path):
"""
Saves the currently loaded image to a specified file path.

Args:
file_path (str): The path where the image should be saved.
"""
if self.image_data is not None:
# Ensure the file path has a valid image extension (e.g., .png)
if not any(file_path.endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.bmp', '.tiff']):
file_path += '.png' # Default to PNG if no valid extension is found

if isinstance(self.image_data, Image.Image):
try:
self.image_data.save(file_path)
print(f"Image saved to {file_path}")
except ValueError as e:
print(f"Error saving image: {e}")
elif isinstance(self.image_data, QImage):
self.image_data.save(file_path)
print(f"Image saved to {file_path}")
else:
print("No valid image data to save.")
else:
print("No image data available to save.")

def emit_image(self, qimage):
"""
Emits the updated image to the main window for display.

Args:
qimage (QImage): The updated QImage to be displayed in the main window.
"""
self.update_image.emit(qimage)
print('Signal emitted by CentralNode')

def update_status(self, new_status):
"""
Updates the status of the operation in the main window.

Args:
new_status (str): The new status message to display in the main window.
"""
self.update_status_signal.emit(new_status)

def start_processing(self, process, **param):
"""
Start a process.
Starts a background process (e.g., an image processing task).

Args:
process (QThread): The process to be started.
**param: Additional parameters to be passed to the process.
"""
process.start(process)
process.start()

def stop_process(self, process):
"""
Stop a process.
Stops a background process if needed.

Args:
process (QThread): The process to stop.
"""
process.requestInterruption()
process.wait()
process.wait()
8 changes: 7 additions & 1 deletion src/nosferatu/GUI_components/FunctionHandler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@

from PyQt6.QtCore import pyqtSignal, QThread
import pandas as pd

class FunctionHandler(QThread):
""" Description"""
update_data = pyqtSignal(pd.DataFrame)
def __init__(self, FunctionHandler):
""" doc string"""
super().__init__()
self.data=data

def run(self):
print("executing function")
print("executing function")


Loading
Loading