You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							187 lines
						
					
					
						
							7.4 KiB
						
					
					
				
			
		
		
	
	
							187 lines
						
					
					
						
							7.4 KiB
						
					
					
				| import pathlib | |
| 
 | |
| from PIL import Image | |
| 
 | |
| from .commons import RE_DIGITS, OUTPUT_FOLDER_NAMES, TifImage | |
| 
 | |
| 
 | |
| class Parameters(dict): | |
|     """ namespacing the parameters for a analysis with nice access | |
|  | |
|     parmeters used for analysing the images | |
|  | |
|     boost:                  linear factor for brightness on color coded images | |
|     charts_y_limit:         common scale for the y axis in graphs | |
|     colored_dir:            path to the directory to hold color coded images | |
|     cut_bottom:             bottom edge for croping an image | |
|     cut_left:               left edge for croping an image | |
|     cut_pad_x:              pixels added to left or right edge to crop an image | |
|     cut_pad_y:              pixels added to top or bottom edge to crop an image | |
|     cut_right:              right edge for croping an image | |
|     cut_top:                left edge for croping an image | |
|     cuts_dir:               path to the directory of croped images | |
|     data_dir:               path to the directory for graphs and numeric data | |
|     folder:                 name of the directory that holds the tif images | |
|     guard_filter_polynom:   polynomal scalar for smoothing the guards histogram | |
|     guard_filter_window:    window scalar for smoothing the guards histogram | |
|     guard_histogram_bins:   number of bins when constructing guards histogram | |
|     guard_max_value:        calculated threshold for the gurards | |
|     guard_stats:            which values to use for caclulation of guards | |
|     guards_minima_min_dist: min distance between minimas in guards histogram | |
|     image_height:           height of an image | |
|     image_width:            width of an image | |
|     intensity_max:          highest intensity found in all images | |
|     intensity_min:          lowest intensity found in all images | |
|     left_guard_column:      name of the most used left guard value column | |
|     left_guard_name:        left guard value group name | |
|     offset:                 calculated the lowest intensity to scale by | |
|     outlier_upper_limit:    calculated threshold value for outliers | |
|     peak_min_distance:      min distance for peaks in the final results | |
|     peak_threshold:         threshold peak height in the final results | |
|     right_guard_column:     name of the most used left guard value column | |
|     right_guard_name:       right guard value group name | |
|     roi_bottom:             bottom edge of the ROI | |
|     roi_column:             name for the ROI | |
|     roi_left:               bottom left of the ROI | |
|     roi_name:               roi value group name | |
|     roi_right:              bottom right of the ROI | |
|     roi_stats:              which values to use for caclulation of roi | |
|     roi_top:                top edge of the ROI | |
|     rolling_min_window:     size of the window for the rolling min calculation | |
|     savgol_filter_polynom:  polynomal scalar for smoothing the final results | |
|     savgol_filter_window:   window scalar for smoothing the final results | |
|     scale:                  calculated factor to boost image intensities | |
|     tif_dir:                path to the directory with the original tif images | |
|  | |
|     """ | |
| 
 | |
|     def __init__(self, folder, top, right, bottom, left, **kargs): | |
|         super().__init__(self) | |
| 
 | |
|         self.folder = folder | |
| 
 | |
|         # defaults | |
|         self.boost = 5 | |
|         self.cut_pad_x = 50 | |
|         self.cut_pad_y = 25 | |
|         self.guard_histogram_bins = 100 | |
|         self.guard_filter_window = 7 | |
|         self.guard_filter_polynom = 3 | |
|         self.guards_minima_min_dist = 10 | |
|         self.rolling_min_window = 5 | |
|         self.savgol_filter_window = 51 | |
|         self.savgol_filter_polynom = 1 | |
|         self.peak_min_distance = 5 | |
|         self.peak_threshold = 0.2 | |
| 
 | |
|         # labels for data items | |
|         self.roi_name = "roi" | |
|         self.roi_stats = "average" | |
|         self.roi_column = f"{self.roi_name}.{self.roi_stats}" | |
|         self.left_guard_name = "left" | |
|         self.right_guard_name = "right" | |
|         self.guard_stats = "average" | |
|         self.left_guard_column = f"{self.left_guard_name}.{self.guard_stats}" | |
|         self.right_guard_column = f"{self.right_guard_name}.{self.guard_stats}" | |
| 
 | |
|         # arbitrary keyword arguments, updating defaults if necessary | |
|         self.update(kargs) | |
| 
 | |
|         # create list of images | |
|         self.tif_dir = pathlib.Path(folder) | |
|         if not self.tif_dir.is_dir(): | |
|             raise IOError(f"Not a directory: {self.folder}") | |
|         self.tif_list = self.scan_image_dir() | |
| 
 | |
|         # set the names of output directories | |
|         for name in OUTPUT_FOLDER_NAMES: | |
|             sub_path = self.tif_dir / name | |
|             self[f"{name}_dir"] = sub_path | |
| 
 | |
|         # get image dimensions | |
|         width, height = self.get_size_of_one_image() | |
|         self.image_width = width | |
|         self.image_height = height | |
| 
 | |
|         self.set_roi_and_cut_size(top, right, bottom, left) | |
| 
 | |
|     def __getattr__(self, key): | |
|         """ return a parameters value as instance property """ | |
|         return self[key] | |
| 
 | |
|     def __setattr__(self, key, value): | |
|         """ set a parameters 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(self): | |
|         """ scan a folder for tif images """ | |
|         visible = ( | |
|             item | |
|             for item in self.tif_dir.iterdir() | |
|             if not item.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))) | |
|         return results | |
| 
 | |
|     def get_size_of_one_image(self): | |
|         """ get the dimension of one Image | |
|  | |
|         the dimensions should be the same on all images. this is ensured in | |
|         an other workflow. | |
|         """ | |
|         image = self.tif_list[0] | |
|         with open(image.path, "rb") as filehandle: | |
|             image = Image.open(filehandle) | |
|             size = image.size | |
|         return size | |
| 
 | |
|     def set_roi_and_cut_size(self, top, right, bottom, left): | |
|         if right < left: | |
|             left, right = right, left | |
|         if top > bottom: | |
|             top, bottom = bottom, top | |
|         if ( | |
|             top < 0 | |
|             or left < 0 | |
|             or bottom > self.image_height | |
|             or right > self.image_width | |
|         ): | |
|             raise ValueError("ROI out of bounce") | |
| 
 | |
|         self.roi_top = top | |
|         self.roi_bottom = bottom | |
|         self.roi_right = right | |
|         self.roi_left = left | |
| 
 | |
|         self.cut_top = min(self.image_height, top - self.cut_pad_y) | |
|         self.cut_bottom = max(0, bottom + self.cut_pad_y) | |
|         self.cut_right = min(self.image_width, right + self.cut_pad_x) | |
|         self.cut_left = max(0, left - self.cut_pad_x) | |
| 
 | |
|     def get_descriptions(self): | |
|         docs = type(self).__doc__ | |
|         lines = (line.strip() for line in docs.split("\n")) | |
|         has_colon = (line for line in lines if ":" in line) | |
|         pairs = (line.split(":") for line in has_colon) | |
|         pairs_cleanup = ((k.strip(), v.strip()) for k, v in pairs) | |
|         non_empty = ((k, v) for k, v in pairs_cleanup if k and v) | |
|         return dict(non_empty)
 | |
| 
 |