Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change main structure of the repo #8

Merged
merged 17 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from 8 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
49 changes: 49 additions & 0 deletions load_suite2p/analysis/spatial_freq_temporal_freq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from ..objects.configurations import Config
from ..objects.photon_data import PhotonData


class SpatialFrequencyTemporalFrequency:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a shorter name for this class could be SpatialFreqTemporalFreq?

def __init__(self, data: PhotonData, config: Config):
self.data = data
self.config = config

def get_fit_parameters(self):
# calls _fit_two_dimensional_elliptical_gaussian
raise NotImplementedError("This method is not implemented yet")

def responsiveness(self):
raise NotImplementedError("This method is not implemented yet")

def responsiveness_anova_window(self):
raise NotImplementedError("This method is not implemented yet")

def get_preferred_direction_all_rois(self):
raise NotImplementedError("This method is not implemented yet")

def _fit_two_dimensional_elliptical_gaussian(self):
# as described by Priebe et al. 2006
# add the variations added by Andermann et al. 2011 / 2013
# calls _2d_gaussian
# calls _get_response_map
raise NotImplementedError("This method is not implemented yet")


class Gaussian2D:
# a 2D gaussian function
# also used by plotting functions
def __init__(self):
# different kinds of 2D gaussians:
# - 2D gaussian
# - 2D gaussian Andermann
# - 2D gaussian Priebe
raise NotImplementedError("This method is not implemented yet")


class ResponseMap:
# also used by plotting functions
def _get_response_map(self):
# calls _get_preferred_direction
raise NotImplementedError("This method is not implemented yet")

def _get_preferred_direction(self):
raise NotImplementedError("This method is not implemented yet")
12 changes: 0 additions & 12 deletions load_suite2p/load/formatted_data.py

This file was deleted.

71 changes: 71 additions & 0 deletions load_suite2p/load/load_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import logging
from pathlib import Path
from typing import Tuple

from read_config import read

from ..objects.configurations import Config

config_path = Path(__file__).parent / "config/config.yml"


def load_data(folder_name: str) -> Tuple[Config, list]:
"""Creates the configuration object and loads the data.

Parameters
----------
folder_name : string
name of the folder containing the experimental data

Returns
-------
Tuple(Config, list)
config: configuration object
data_raw: list containing all raw data
"""

config = configure(folder_name)
data_raw = load(config)

return config, data_raw


def configure(folder_name: str) -> Config:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming this function to sth more specific, e.g. add_folder_to_config

"""Create configuration object. It reads the configuration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edit docsting

file and adds the folder name and experimental details
derived from it.

Parameters
----------
folder_name : string
name of the folder containing the experimental data

Returns
-------
Config
Configuration object
"""
""""""

config = Config(read_configurations(), folder_name)
return config


def load(config: Config) -> list:
raise NotImplementedError("TODO")


def read_configurations() -> dict:
"""Read configurations regarding experiment and analysis.

Returns
-------
dict
dictionary with configurations
"""

logging.debug("Reading configurations")
config = read(config_path)
logging.debug(f"Configurations read: {config}")

return config
File renamed without changes.
118 changes: 31 additions & 87 deletions load_suite2p/main.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,42 @@
import logging
from pathlib import Path

import rich
from fancylog import fancylog
from load.folder_naming_specs import FolderNamingSpecs
from rich.prompt import Prompt
from vpn_server_connections.connections import (
can_ping_swc_server,
is_winstor_mounted,
from analysis.spatial_freq_temporal_freq import (
SpatialFrequencyTemporalFrequency,
)
from load.load_data import load_data
from objects.photon_data import PhotonData
from plots.plotter import Plotter
from rich.prompt import Prompt

from .read_config import read
from .utils import exception_handler, get_module_for_logging

config_path = Path(__file__).parent / "config/config.yml"


def start_logging():
"""Start logging to file and console. The log level to file is set to
DEBUG, to console to INFO. The log file is saved in the current working
directory. Uses fancylog to format the log messages.
"""
module = get_module_for_logging()

fancylog.start_logging(
output_dir="./", package=module, filename="load_suite2p", verbose=False
)


def read_configurations() -> dict:
"""Read configurations regarding experiment and analysis.

Returns
-------
dict
dictionary with configurations
"""

logging.debug("Reading configurations")
config = read(config_path)
logging.debug(f"Configurations read: {config}")

return config
from .utils import exception_handler, start_logging


def check_connection(config: dict):
"""Check if the connection to the server is established and if Winstor
is mounted.
@exception_handler
def main():
"""Entry point of the program. CLI or GUI functionality is added here."""
# pipeline draft
start_logging()

Parameters
----------
config : dict
Configuration file to load the data
# TODO: add TUI or GUI fuctionality to get input from user
folder_name = Prompt.ask("Please provide the folder name")

Raises
------
RuntimeError
If the connection to the VPN is not established
RuntimeError
If Winstor is not mounted
RuntimeError
If the configuration file is not correct
"""
if not (
("server" in config)
and ("paths" in config)
and ("winstor" in config["paths"])
):
raise RuntimeError(
"The configuration file is not complete."
+ "Please check the documentation."
)
# load data
config, data = load_data(folder_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_data returns first config, then data. On the other hand, the order for defining PhotonData and Analysis objects is the exact opposite: first data, then config. I would make the order the same in all instances, to avoid user confusion


if not can_ping_swc_server(config["server"]):
logging.debug("Please connect to the VPN.")
raise RuntimeError("Please connect to the VPN.")
if not is_winstor_mounted(config["paths"]["winstor"]):
logging.debug("Please mount Winstor.")
raise RuntimeError("Please mount Winstor.")
# preprocess and make PhotonData object
photon_data = PhotonData(data, config)

# make analysis object
analysis = SpatialFrequencyTemporalFrequency(photon_data, config)

@exception_handler
def main():
"""Main function of the package. It starts logging, reads the
configurations, asks the user to input the folder name and then
instantiates a :class:`FolderNamingSpecs` object.
"""
start_logging()
# calculate responsiveness and display it in a nice way
responsiveness = analysis.responsiveness(photon_data)
print(responsiveness) # TODO: nice appearance

config = read(config_path)
check_connection(config)
folder_name = Prompt.ask("Please provide the folder name")
folder_naming_specs = FolderNamingSpecs(folder_name, config)
# Plots
plotter = Plotter(analysis)

path = folder_naming_specs.get_path()
rich.print("Success! 🎉")
rich.print(path)
plotter.murakami_plot()
plotter.anova_window_plot()
plotter.polar_grid()
plotter.response_map()
plotter.sftf_fit()
plotter.traceplot()
14 changes: 14 additions & 0 deletions load_suite2p/objects/configurations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .folder_naming_specs import FolderNamingSpecs
from .options import Options


class Config:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's confusing for me that that this class has the same name as the config.yaml, though afaik it represents something completely different.

"""The class :class:`Config` represents the configuration of the
experiment and the analysis."""

def __init__(self, config: dict, folder_name: str):
self.base_paths: dict = config["paths"]
self.folder_name = folder_name
self.folder_naming_specs = FolderNamingSpecs(folder_name, config)
self.all_filenames = self.folder_naming_specs.extract_all_file_names()
self.options = Options(config)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from pathlib import Path

from .parsers.parser01 import Parser01
from .parsers.parser2pRSP import Parser2pRSP


class FolderNamingSpecs:
Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(
folder_name: str,
config: dict,
):
self.config = config
self.original_config = config

self.folder_name = folder_name

Expand Down Expand Up @@ -86,16 +86,16 @@ def parse_name(self) -> None:
not implemented
"""

if self.config["parser"] == "Parser01":
logging.debug("Parsing folder name using Parser01")
self._parser = Parser01(self.folder_name, self.config)
if self.original_config["parser"] == "Parser2pRSP":
logging.debug("Parsing folder name using Parser2pRSP")
self._parser = Parser2pRSP(self.folder_name, self.original_config)
else:
logging.debug(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be logging.error?

f"Scientist's parser {self.config['scientist']} \
f"Parser {self.original_config['parser']} \
not supported"
)
raise ValueError(
f"Scientist's parser {self.config['scientist']} \
f"Parser {self.original_config['parser']} \
not supported"
)

Expand All @@ -121,3 +121,10 @@ def check_if_file_exists(self) -> bool:
True if folder exists, False otherwise
"""
return os.path.exists(self.get_path())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use pathlib for consistency, e.g. return self.get_path().exists().
If you specifically want to check whether the path is a directory, use: return self.get_path().is_dir()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if this function check for a folder (not a file), consider renaming the function

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!!


def extract_all_file_names(self) -> list:
# get filenames by day
# search for files called 'suite2p', 'plane0', 'Fall.mat'
# get session names to get name of stim files
# corrects for exceptions
raise NotImplementedError("This method is not implemented yet")
46 changes: 46 additions & 0 deletions load_suite2p/objects/options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class Options:
"""Class with the options to analize data from suite2p and registers2p.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: analyze

Based on original Options class in the MATLAB project."""

def __init__(self, config: dict):
self.response = self.ResponseOptions()
self.fitting = self.FittingOptions()
# Attributes to evaluate:
# self.trials
# self.days_to_analyze
# self.directions
# self.colour
pass

class ResponseOptions:
def __init__(self):
# Attributes to evaluate:
# self.baseline_frames
# self.frames
# self.peak_window
# self.peak_enable
# self.measure
# self.trials
# self.average_using
# self.subtract_baseline
# self.response_frames
# self.p_threashold
# self.use_magnitude
# self.magnitude_threshold
pass

class FittingOptions:
def __init__(self):
# Attributes to evaluate:
# self.lower_bound
# self.start_cond
# self.upper_bound
# self.jitter
# self.n_starting_points
pass

class SftfMapOptions:
def __init__(self):
# Attributes to evaluate:
# self.use_pref_dir
pass
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .parser import Parser
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a stdlib module named parser. Consider modifying tha name of your parser module, to avoid overriding the stdlib one.



class Parser01(Parser):
class Parser2pRSP(Parser):
"""Parses the folder name and evaluates the parameters `mouse_line`,
`mouse_id`, `hemisphere`, `brain_region`, `monitor_position, `fov`
and `cre`.
Expand Down
Loading