SciCodePile/SciCode-Domain-Code
DATA1: Domain-Specific Code Dataset Dataset Overview DATA1 is a large-scale domain-specific code dataset focusing on code samples from interdisciplinary fields such as biology, chemistry, materials science, and related areas. The dataset is collected and organized from GitHub repositories, covering 178 different domain topics with over 1.1 billion lines of code. Dataset Statistics Total Datasets: 178 CSV files Total Data Size: ~115 GB Total Lines… See the full description on the dataset page: https://huggingface.co/datasets/SciCodePile/SciCode-Domain-Code.
42.4k
1"keyword","repo_name","file_path","file_extension","file_size","line_count","content","language"
2"Organoid","HelmholtzAI-Consultants-Munich/napari-organoid-counter","napari_organoid_counter/settings.py",".py","2277","56","from pathlib import Path3 4def init():5 6 global MODELS7 MODELS = {8 ""faster r-cnn"": {""filename"": ""faster-rcnn_r50_fpn_organoid_best_coco_bbox_mAP_epoch_68.pth"", 9 ""source"": ""https://zenodo.org/records/11388549/files/faster-rcnn_r50_fpn_organoid_best_coco_bbox_mAP_epoch_68.pth""10 },11 ""ssd"": {""filename"": ""ssd_organoid_best_coco_bbox_mAP_epoch_86.pth"", 12 ""source"": ""https://zenodo.org/records/11388549/files/ssd_organoid_best_coco_bbox_mAP_epoch_86.pth""13 },14 ""yolov3"": {""filename"": ""yolov3_416_organoid_best_coco_bbox_mAP_epoch_27.pth"",15 ""source"": ""https://zenodo.org/records/11388549/files/yolov3_416_organoid_best_coco_bbox_mAP_epoch_27.pth""16 },17 ""rtmdet"": {""filename"": ""rtmdet_l_organoid_best_coco_bbox_mAP_epoch_323.pth"",18 ""source"": ""https://zenodo.org/records/11388549/files/rtmdet_l_organoid_best_coco_bbox_mAP_epoch_323.pth""19 },20 }21 22 global MODELS_DIR23 MODELS_DIR = Path.home() / "".cache/napari-organoid-counter/models""24 25 global MODEL_TYPE26 MODEL_TYPE = '.pth'27 28 global CONFIGS29 CONFIGS = {30 ""faster r-cnn"": {""source"": ""https://zenodo.org/records/11388549/files/faster-rcnn_r50_fpn_organoid.py"",31 ""destination"": "".mim/configs/faster_rcnn/faster-rcnn_r50_fpn_organoid.py""32 },33 ""ssd"": {""source"": ""https://zenodo.org/records/11388549/files/ssd_organoid.py"",34 ""destination"": "".mim/configs/ssd/ssd_organoid.py""35 },36 ""yolov3"": {""source"": ""https://zenodo.org/records/11388549/files/yolov3_416_organoid.py"",37 ""destination"": "".mim/configs/yolo/yolov3_416_organoid.py""38 },39 ""rtmdet"": {""source"": ""https://zenodo.org/records/11388549/files/rtmdet_l_organoid.py"",40 ""destination"": "".mim/configs/rtmdet/rtmdet_l_organoid.py""41 }42 43}44 45 # Add color definitions46 global COLOR_CLASS_147 COLOR_CLASS_1 = [85 / 255, 1.0, 0, 1.0] # Green48 49 global COLOR_CLASS_250 COLOR_CLASS_2 = [0, 29 / 255, 1.0, 1.0] # Blue51 52 global COLOR_DEFAULT53 COLOR_DEFAULT = [1., 0, 1., 1.] # Magenta54 55 56 57","Python"
58"Organoid","HelmholtzAI-Consultants-Munich/napari-organoid-counter","napari_organoid_counter/_utils.py",".py","8253","197","from contextlib import contextmanager59import os60from pathlib import Path61import pkgutil62 63import numpy as np64import math65import json66import csv67from skimage.transform import rescale68from skimage.color import gray2rgb69 70import torch71from torchvision.ops import nms72 73from napari_organoid_counter import settings74 75 76def add_local_models():77 """""" Checks the models directory for any local models previously added by the user.78 If some are found then these are added to the model dictionary (see settings). """"""79 if not os.path.exists(settings.MODELS_DIR): return80 model_names_in_dir = [file for file in os.listdir(settings.MODELS_DIR)]81 model_names_in_dict = [settings.MODELS[key][""filename""] for key in settings.MODELS.keys()]82 for model_name in model_names_in_dir:83 if model_name not in model_names_in_dict and model_name.endswith(settings.MODEL_TYPE):84 _ = add_to_dict(model_name)85 86def add_to_dict(filepath):87 """""" Given the full path and name of a model in filepath the model is added to the models dict (see settings)""""""88 filepath = Path(filepath)89 name = filepath.name90 stem_name = filepath.stem91 settings.MODELS[stem_name] = {""filename"": name, ""source"": ""local""}92 return stem_name93 94def return_is_file(path, filename):95 """""" Return True if the file exists in path and False otherwise """"""96 full_path = join_paths(path, filename)97 return os.path.isfile(full_path)98 99def join_paths(path1, path2):100 """""" Returns output of os.path.join """"""101 return os.path.join(path1, path2)102 103@contextmanager104def set_dict_key(dictionary, key, value):105 """""" Used to set a new value in the napari layer metadata """"""106 dictionary[key] = value107 yield108 del dictionary[key]109 110def get_diams(bbox):111 """""" Get the lengths of the bounding boxes """"""112 x1_real, y1_real, x2_real, y2_real = bbox113 dx = abs(x1_real - x2_real)114 dy = abs(y1_real - y2_real)115 return dx, dy116 117def write_to_json(name, data):118 """""" Write data to a json file. Here data is a dict """"""119 with open(name, 'w') as outfile:120 json.dump(data, outfile) 121 122def get_bboxes_as_dict(bboxes, bbox_ids, scores, scales, labels):123 """""" Write all data, boxes, ids and scores, scale and class label, to a dict so we can later save as a json """"""124 data_json = {} 125 for idx, bbox in enumerate(bboxes):126 x1, y1 = bbox[0]127 x2, y2 = bbox[2]128 129 data_json.update({str(bbox_ids[idx]): {'box_id': str(bbox_ids[idx]),130 'x1': str(x1),131 'x2': str(x2),132 'y1': str(y1),133 'y2': str(y2),134 'confidence': str(scores[idx]),135 'scale_x': str(scales[0]),136 'scale_y': str(scales[1]),137 'class': labels[idx]138 }139 })140 return data_json141 142def write_to_csv(name, data):143 """""" Write data to a csv file. Here data is a list of lists, where each item represents a row in the csv file. """"""144 with open(name, 'w') as f:145 write = csv.writer(f, delimiter=';')146 write.writerow(['OrganoidID', 'D1[um]','D2[um]', 'Area [um^2]'])147 write.writerows(data)148 149def get_bbox_diameters(bboxes, bbox_ids, scales):150 """""" Write all data, box diameters and area, ids and scale, to a list so we can later save as a csv """"""151 data_csv = []152 # save diameters and area of organoids (approximated as ellipses)153 for idx, bbox in enumerate(bboxes):154 d1 = abs(bbox[0][0] - bbox[2][0]) * scales[0]155 d2 = abs(bbox[0][1] - bbox[2][1]) * scales[1]156 area = math.pi * d1 * d2157 data_csv.append([bbox_ids[idx], round(d1,3), round(d2,3), round(area,3)])158 return data_csv159 160def squeeze_img(img):161 """""" Squeeze image - all dims that have size one will be removed """"""162 return np.squeeze(img)163 164def prepare_img(test_img, step, window_size, rescale_factor):165 """""" The original image is prepared for running model inference """"""166 # squeeze and resize image167 test_img = squeeze_img(test_img)168 test_img = rescale(test_img, rescale_factor, preserve_range=True)169 img_height, img_width = test_img.shape170 # pad image171 pad_x = (img_height//step)*step + window_size - img_height172 pad_y = (img_width//step)*step + window_size - img_width173 test_img = np.pad(test_img, ((0, int(pad_x)), (0, int(pad_y))), mode='edge')174 # normalise and convert to RGB - model input has size 3175 test_img = (test_img-np.min(test_img))/(np.max(test_img)-np.min(test_img)) 176 test_img = (255*test_img).astype(np.uint8)177 test_img = gray2rgb(test_img) #[H,W,C]178 179 # convert from RGB to GBR - expected from DetInferencer 180 test_img = test_img[..., ::-1] 181 182 return test_img, img_height, img_width183 184def apply_nms(bbox_preds, scores_preds, iou_thresh=0.5):185 """""" Function applies non max suppression to iteratively remove lower scoring boxes which have an IoU greater than iou_threshold 186 with another (higher scoring) box. The boxes and corresponding scores whihc remain are returned. """"""187 # torchvision returns the indices of the bboxes to keep188 keep = nms(bbox_preds, scores_preds, iou_thresh)189 # filter existing boxes and scores and return190 bbox_preds_kept = bbox_preds[keep]191 scores_preds = scores_preds[keep]192 return bbox_preds_kept, scores_preds193 194def convert_boxes_to_napari_view(pred_bboxes):195 """""" The bboxes are converted from tensors in model output form to a form which can be visualised in the napari viewer """"""196 if pred_bboxes is None: return []197 new_boxes = []198 for idx in range(pred_bboxes.size(0)):199 # convert to numpy and take coordinates 200 x1_real, y1_real, x2_real, y2_real = pred_bboxes[idx].numpy()201 # append to a list in form napari exects202 new_boxes.append(np.array([[x1_real, y1_real],203 [x1_real, y2_real],204 [x2_real, y2_real],205 [x2_real, y1_real]]))206 return new_boxes207 208def convert_boxes_from_napari_view(pred_bboxes):209 """""" The bboxes are converted from the form they were in the napari viewer to tensors that correspond to the model output form """"""210 new_boxes = []211 for idx in range(len(pred_bboxes)):212 # read coordinates213 x1 = pred_bboxes[idx][0][0]214 x2 = pred_bboxes[idx][2][0]215 y1 = pred_bboxes[idx][0][1]216 y2 = pred_bboxes[idx][2][1]217 # convert to tensor and append to list218 new_boxes.append(torch.Tensor([x1, y1, x2, y2]))219 if len(new_boxes) > 0: new_boxes = torch.stack(new_boxes)220 return new_boxes221 222def apply_normalization(img):223 """""" Normalize image""""""224 # squeeze and change dtype225 img = squeeze_img(img)226 img = img.astype(np.float64)227 # adapt img to range 0-255228 img_min = np.min(img) # 31.3125 png 0229 img_max = np.max(img) # 2899.25 png 178230 img_norm = (255 * (img - img_min) / (img_max - img_min)).astype(np.uint8)231 return img_norm232 233def get_package_init_file(package_name):234 loader = pkgutil.get_loader(package_name)235 if loader is None or not hasattr(loader, 'get_filename'):236 raise ImportError(f""Cannot find package {package_name}"")237 package_path = loader.get_filename(package_name)238 # Determine the path to the __init__.py file239 if os.path.isdir(package_path):240 init_file_path = os.path.join(package_path, '__init__.py')241 else:242 init_file_path = package_path243 if not os.path.isfile(init_file_path):244 raise FileNotFoundError(f""__init__.py file not found for package {package_name}"")245 return init_file_path246 247def update_version_in_mmdet_init_file(package_name, old_version, new_version):248 init_file_path = get_package_init_file(package_name)249 with open(init_file_path, 'r') as file:250 lines = file.readlines()251 with open(init_file_path, 'w') as file:252 for line in lines:253 if f""mmcv_maximum_version = '{old_version}'"" in line:254 file.write(line.replace(old_version, new_version))","Python"
255"Organoid","HelmholtzAI-Consultants-Munich/napari-organoid-counter","napari_organoid_counter/_reader.py",".py","2407","60","import json256import numpy as np257from napari import layers258from pathlib import Path259 260readable_extensions = '.json'261 262def get_reader(path):263 """""" A basic implementation of the napari_get_reader hook specification """"""264 # if we know we cannot read the file, we immediately return None.265 if not path.endswith(readable_extensions):266 return None267 # otherwise we return the *function* that can read ``path``.268 return reader_function269 270def reader_function(path: str) -> layers.Shapes:271 """""" Reads the labels in the json file and adds a shapes layer to the napari viewer """"""272 # laod json273 f = open(path)274 annot = json.load(f)275 # initialise empty lists for boxes, ids and scores276 bboxes = []277 ids = []278 scores = []279 # for each box280 for key in annot.keys():281 # read coordinates282 x1 = round(int(float(annot[key]['x1'])))283 y1 = round(int(float(annot[key]['y1'])))284 x2 = round(int(float(annot[key]['x2'])))285 y2 = round(int(float(annot[key]['y2'])))286 # append in style readable by napari viewer 287 bboxes.append(np.array([[x1, y1],288 [x1, y2],289 [x2, y2],290 [x2, y1]]))291 # and append scores and ids whihc will be used to display as text292 ids.append(int(annot[key]['box_id']))293 scores.append(float(annot[key]['confidence']))294 295 # scale will adjust boxes according to physical resolution of image296 scale = (float(annot[key]['scale_x']), float(annot[key]['scale_y'])) # do only once297 # name of layer which will be created298 labels_name = 'Labels-'+Path(path).stem299 # properties used for dusplaying text300 properties = {'box_id': ids,'scores': scores}301 text_params = {'string': 'ID: {box_id}\nConf.: {scores:.2f}',302 'size': 12,303 'anchor': 'upper_left',}304 layer_attributes = {'name': labels_name,305 'scale': scale,306 'properties': properties,307 'text': text_params,308 'face_color': 'transparent', 309 'edge_color': 'magenta',310 'shape_type': 'rectangle',311 'edge_width': 12312 }313 # return data, attributes for displaying and type of layer to add to viewer314 return [(bboxes, layer_attributes, 'shapes')]","Python"
315"Organoid","HelmholtzAI-Consultants-Munich/napari-organoid-counter","napari_organoid_counter/_orgacount.py",".py","14778","286","from urllib.request import urlretrieve316from napari.utils import progress317 318from napari_organoid_counter._utils import *319from napari_organoid_counter import settings320 321#update_version_in_mmdet_init_file('mmdet', '2.2.0', '2.3.0')322import torch323import mmdet324from mmdet.apis import DetInferencer325 326class OrganoiDL():327 '''328 The back-end of the organoid counter widget329 Attributes330 ----------331 device: torch.device332 The current device, either 'cpu' or 'gpu:0'333 cur_confidence: float334 The confidence threshold of the model335 cur_min_diam: float336 The minimum diameter of the organoids337 model: frcnn338 The Faster R-CNN model339 img_scale: list of floats340 A list holding the image resolution in x and y341 pred_bboxes: dict342 Each key will be a set of predictions of the model, either past or current, and values will be the numpy arrays 343 holding the predicted bounding boxes344 pred_scores: dict345 Each key will be a set of predictions of the model and the values will hold the confidence of the model for each346 predicted bounding box347 pred_ids: dict348 Each key will be a set of predictions of the model and the values will hold the box id for each349 predicted bounding box350 next_id: dict351 Each key will be a set of predictions of the model and the values will hold the next id to be attributed to a 352 newly added box353 '''354 def __init__(self, handle_progress):355 super().__init__()356 357 self.handle_progress = handle_progress358 self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')359 self.cur_confidence = 0.05360 self.cur_min_diam = 30361 362 self.model = None363 self.img_scale = [0., 0.]364 self.pred_bboxes = {}365 self.pred_scores = {}366 self.pred_ids = {}367 self.next_id = {}368 369 def set_scale(self, img_scale):370 ''' Set the image scale: used to calculate real box sizes. '''371 self.img_scale = img_scale372 373 def set_model(self, model_name):374 ''' Initialise model instance and load model checkpoint and send to device. '''375 376 model_checkpoint = join_paths(str(settings.MODELS_DIR), settings.MODELS[model_name][""filename""])377 mmdet_path = os.path.dirname(mmdet.__file__)378 config_dst = join_paths(mmdet_path, str(settings.CONFIGS[model_name][""destination""]))379 # download the corresponding config if it doesn't exist already380 if not os.path.exists(config_dst):381 urlretrieve(settings.CONFIGS[model_name][""source""], config_dst, self.handle_progress)382 self.model = DetInferencer(config_dst, model_checkpoint, self.device, show_progress=False)383 384 def download_model(self, model_name='yolov3'):385 ''' Downloads the model from zenodo and stores it in settings.MODELS_DIR '''386 # specify the url of the model which is to be downloaded387 down_url = settings.MODELS[model_name][""source""]388 # specify save location where the file is to be saved389 save_loc = join_paths(str(settings.MODELS_DIR), settings.MODELS[model_name][""filename""])390 # downloading using urllib391 urlretrieve(down_url, save_loc, self.handle_progress)392 393 def sliding_window(self,394 test_img,395 step,396 window_size,397 rescale_factor,398 prepadded_height,399 prepadded_width,400 pred_bboxes=[],401 scores_list=[]):402 ''' Runs sliding window inference and returns predicting bounding boxes and confidence scores for each box.403 Inputs404 ----------405 test_img: Tensor of size [B, C, H, W]406 The image ready to be given to model as input407 step: int408 The step of the sliding window, same in x and y409 window_size: int410 The sliding window size, same in x and y411 rescale_factor: float412 The rescaling factor by which the image has already been resized. Is 1/downsampling413 prepadded_height: int414 The image height before padding was applied415 prepadded_width: int416 The image width before padding was applied417 pred_bboxes: list of418 The419 scores_list: list of420 The421 Outputs422 ----------423 pred_bboxes: list of Tensors, default is an empty list424 The resulting predicted boxes are appended here - if model is run at different window425 sizes and downsampling this list will store results of all runs of the sliding window426 so will not be empty the second, third etc. time.427 scores_list: list of Tensor, default is an empty list428 The resulting confidence scores of the model for the predicted boxes are appended here 429 Same as pred_bboxes, can be empty on first run but stores results of all runs.430 '''431 for i in progress(range(0, prepadded_height, step)):432 for j in progress(range(0, prepadded_width, step)):433 # crop434 img_crop = test_img[i:(i+window_size), j:(j+window_size)]435 # get predictions436 output = self.model(img_crop)437 preds = output['predictions'][0]['bboxes']438 if len(preds)==0: continue439 else:440 for bbox_id in range(len(preds)):441 y1, x1, y2, x2 = preds[bbox_id] # predictions from model will be in form x1,y1,x2,y2442 x1_real = torch.div(x1+i, rescale_factor, rounding_mode='floor')443 x2_real = torch.div(x2+i, rescale_factor, rounding_mode='floor')444 y1_real = torch.div(y1+j, rescale_factor, rounding_mode='floor')445 y2_real = torch.div(y2+j, rescale_factor, rounding_mode='floor')446 pred_bboxes.append(torch.Tensor([x1_real, y1_real, x2_real, y2_real]))447 scores_list.append(output['predictions'][0]['scores'][bbox_id])448 return pred_bboxes, scores_list449 450 def run(self, 451 img, 452 shapes_name,453 window_sizes,454 downsampling_sizes, 455 window_overlap):456 ''' Runs inference for an image at multiple window sizes and downsampling rates using sliding window ineference.457 The results are filtered using the NMS algorithm and are then stored to dicts.458 Inputs459 ----------460 img: Numpy array of size [H, W]461 The image ready to be given to model as input462 shapes_name: str463 The name of the new predictions464 window_size: list of ints465 The sliding window size, same in x and y, if multiple sliding window will run mulitple times466 downsampling_sizes: list of ints467 The downsampling factor of the image, list size must match window_size468 window_overlap: float469 The window overlap for the sliding window inference.470 ''' 471 bboxes = []472 scores = []473 # run for all window sizes474 for window_size, downsampling in zip(window_sizes, downsampling_sizes):475 # compute the step for the sliding window, based on window overlap476 rescale_factor = 1 / downsampling477 # window size after rescaling478 window_size = round(window_size * rescale_factor)479 step = round(window_size * window_overlap)480 # prepare image for model - norm, tensor, etc.481 ready_img, prepadded_height, prepadded_width = prepare_img(img,482 step,483 window_size,484 rescale_factor)485 # and run sliding window over whole image486 bboxes, scores = self.sliding_window(ready_img,487 step,488 window_size,489 rescale_factor,490 prepadded_height,491 prepadded_width,492 bboxes,493 scores)494 # stack results495 bboxes = torch.stack(bboxes)496 scores = torch.Tensor(scores)497 # apply NMS to remove overlaping boxes498 bboxes, pred_scores = apply_nms(bboxes, scores)499 self.pred_bboxes[shapes_name] = bboxes500 self.pred_scores[shapes_name] = pred_scores501 num_predictions = bboxes.size(0)502 self.pred_ids[shapes_name] = [(i+1) for i in range(num_predictions)]503 self.next_id[shapes_name] = num_predictions+1504 505 def apply_params(self, shapes_name, confidence, min_diameter_um):506 """""" After results have been stored in dict this function will filter the dicts based on the confidence507 and min_diameter_um thresholds for the given results defined by shape_name and return the filtered dicts. """"""508 self.cur_confidence = confidence509 self.cur_min_diam = min_diameter_um510 pred_bboxes, pred_scores, pred_ids = self._apply_confidence_thresh(shapes_name)511 if pred_bboxes.size(0)!=0:512 pred_bboxes, pred_scores, pred_ids = self._filter_small_organoids(pred_bboxes, pred_scores, pred_ids)513 pred_bboxes = convert_boxes_to_napari_view(pred_bboxes)514 return pred_bboxes, pred_scores, pred_ids515 516 def _apply_confidence_thresh(self, shapes_name):517 """""" Filters out results of shapes_name based on the current confidence threshold. """"""518 if shapes_name not in self.pred_bboxes.keys(): return torch.empty((0))519 keep = (self.pred_scores[shapes_name]>self.cur_confidence).nonzero(as_tuple=True)[0]520 result_bboxes = self.pred_bboxes[shapes_name][keep]521 result_scores = self.pred_scores[shapes_name][keep]522 result_ids = [self.pred_ids[shapes_name][int(i)] for i in keep.tolist()]523 return result_bboxes, result_scores, result_ids524 525 def _filter_small_organoids(self, pred_bboxes, pred_scores, pred_ids):526 """""" Filters out small result boxes of shapes_name based on the current min diameter size. """"""527 if pred_bboxes is None: return None528 if len(pred_bboxes)==0: return None529 min_diameter_x = self.cur_min_diam / self.img_scale[0]530 min_diameter_y = self.cur_min_diam / self.img_scale[1]531 keep = []532 for idx in range(len(pred_bboxes)):533 dx, dy = get_diams(pred_bboxes[idx])534 if (dx >= min_diameter_x and dy >= min_diameter_y) or pred_scores[idx] == 1: keep.append(idx) 535 pred_bboxes = pred_bboxes[keep]536 pred_scores = pred_scores[keep]537 pred_ids = [pred_ids[i] for i in keep]538 return pred_bboxes, pred_scores, pred_ids539 540 def update_bboxes_scores(self, shapes_name, new_bboxes, new_scores, new_ids):541 ''' Updated the results dicts, self.pred_bboxes, self.pred_scores and self.pred_ids with new results.542 If the shapes name doesn't exist as a key in the dicts the results are added with the new key. If the543 key exists then new_bboxes, new_scores and new_ids are compared to the class result dicts and the dicts 544 are updated, either by adding some box (user added box) or removing some box (user deleted a prediction).'''545 546 new_bboxes = convert_boxes_from_napari_view(new_bboxes)547 new_scores = torch.Tensor(list(new_scores))548 new_ids = list(new_ids)549 # if run hasn't been run550 if shapes_name not in self.pred_bboxes.keys():551 self.pred_bboxes[shapes_name] = new_bboxes552 self.pred_scores[shapes_name] = new_scores553 self.pred_ids[shapes_name] = new_ids554 self.next_id[shapes_name] = len(new_ids)+1555 556 elif len(new_ids)==0: return557 558 else:559 min_diameter_x = self.cur_min_diam / self.img_scale[0]560 min_diameter_y = self.cur_min_diam / self.img_scale[1]561 # find ids that do are not in self.pred_ids but are in new_ids562 added_box_ids = list(set(new_ids).difference(self.pred_ids[shapes_name]))563 if len(added_box_ids) > 0:564 added_ids = [new_ids.index(box_id) for box_id in added_box_ids]565 # and add them566 self.pred_bboxes[shapes_name] = torch.cat((self.pred_bboxes[shapes_name], new_bboxes[added_ids]))567 self.pred_scores[shapes_name] = torch.cat((self.pred_scores[shapes_name], new_scores[added_ids]))568 new_ids_to_add = [new_ids[i] for i in added_ids]569 self.pred_ids[shapes_name].extend(new_ids_to_add)570 571 # and find ids that are in self.pred_ids and not in new_ids572 potential_removed_box_ids = list(set(self.pred_ids[shapes_name]).difference(new_ids))573 if len(potential_removed_box_ids) > 0:574 potential_removed_ids = [self.pred_ids[shapes_name].index(box_id) for box_id in potential_removed_box_ids]575 remove_ids = []576 for idx in potential_removed_ids:577 dx, dy = get_diams(self.pred_bboxes[shapes_name][idx])578 if self.pred_scores[shapes_name][idx] > self.cur_confidence and dx > min_diameter_x and dy > min_diameter_y:579 remove_ids.append(idx)580 # and remove them581 for idx in reversed(remove_ids):582 self.pred_bboxes[shapes_name] = torch.cat((self.pred_bboxes[shapes_name][:idx, :], self.pred_bboxes[shapes_name][idx+1:, :]))583 self.pred_scores[shapes_name] = torch.cat((self.pred_scores[shapes_name][:idx], self.pred_scores[shapes_name][idx+1:]))584 new_pred_ids = self.pred_ids[shapes_name][:idx]585 new_pred_ids.extend(self.pred_ids[shapes_name][idx+1:])586 self.pred_ids[shapes_name] = new_pred_ids587 588 def update_next_id(self, shapes_name, c=0):589 """""" Updates the next id to append to result dicts. If input c is given then that will be the next id. """"""590 if c!=0:591 self.next_id[shapes_name] = c592 else: self.next_id[shapes_name] += 1593 594 def remove_shape_from_dict(self, shapes_name):595 """""" Removes results of shapes_name from all result dicts. """"""596 del self.pred_bboxes[shapes_name]597 del self.pred_scores[shapes_name]598 del self.pred_ids[shapes_name]599 del self.next_id[shapes_name]600","Python"
601"Organoid","HelmholtzAI-Consultants-Munich/napari-organoid-counter","napari_organoid_counter/__init__.py",".py","178","8","try:602 from ._version import version as __version__603except ImportError:604 __version__ = ""unknown""605 606from ._widget import OrganoidCounterWidget607from ._reader import get_reader608","Python"
609"Organoid","HelmholtzAI-Consultants-Munich/napari-organoid-counter","napari_organoid_counter/_widget.py",".py","49792","995","from typing import List610 611from skimage.io import imsave612from datetime import datetime613 614import napari615 616from napari import layers617from napari.utils.notifications import show_info, show_error, show_warning618 619import numpy as np620 621from qtpy.QtCore import Qt622from qtpy.QtWidgets import QWidget, QVBoxLayout, QApplication, QDialog, QFileDialog, QGroupBox, QHBoxLayout, QLabel, QComboBox, QPushButton, QLineEdit, QProgressBar, QSlider623 624from napari_organoid_counter._orgacount import OrganoiDL625from napari_organoid_counter import _utils as utils626from napari_organoid_counter import settings627 628import warnings629warnings.filterwarnings(""ignore"")630 631 632class OrganoidCounterWidget(QWidget):633 '''634 The main widget of the organoid counter635 Parameters636 ----------637 napari_viewer: string638 The current napari viewer639 window_sizes: list of ints, default [1024]640 A list with the sizes of the windows on which the model will be run. If more than one window_size is given then the model will run on several window sizes and then 641 combine the results642 downsampling:list of ints, default [2]643 A list with the sizes of the downsampling ratios for each window size. List size must be the same as the window_sizes list644 min_diameter: int, default 30645 The minimum organoid diameter given in um646 confidence: float, default 0.8647 The model confidence threhsold - equivalent to box_score_thresh of faster_rcnn648 Attributes649 ----------650 model_name: str651 The name of the model user has selected652 image_layer_names: list of strings653 Will hold the names of all the currently open images in the viewer654 image_layer_name: string655 The image we are currently working on656 shape_layer_names: list of strings657 Will hold the names of all the currently open images in the viewer658 save_layer_name: string659 The name of the shapes layer that has been selected for saving660 cur_shapes_name: string661 The name of the shapes layer that has been selected for visualisation662 cur_shapes_layer: napari.layers.Shapes663 The current shapes layer we are working on - it's name should correspond to cur_shapes_name664 organoiDL: OrganoiDL665 The class in which all the computations are performed for computing and storing the organoids bounding boxes and confidence scores666 num_organoids: int667 The current number of organoids668 original_images: dict669 original_contrast: dict670 '''671 def __init__(self, 672 napari_viewer,673 window_sizes: List = [1024],674 downsampling: List = [2],675 window_overlap: float = 0.5,676 min_diameter: int = 30,677 confidence: float = 0.8):678 super().__init__()679 680 # assign class variables681 self.viewer = napari_viewer 682 683 # create cache dir for models if it doesn't exist and add any previously added local684 # models to the model dict685 settings.init()686 settings.MODELS_DIR.mkdir(parents=True, exist_ok=True)687 utils.add_local_models()688 self.model_id = 2 # yolov3689 self.model_name = list(settings.MODELS.keys())[self.model_id]690 691 # init params 692 self.window_sizes = window_sizes693 self.downsampling = downsampling694 self.window_overlap = window_overlap695 self.min_diameter = min_diameter696 self.confidence = confidence697 698 self.image_layer_names = []699 self.image_layer_name = None 700 self.shape_layer_names = []701 self.save_layer_name = ''702 self.cur_shapes_name = ''703 self.cur_shapes_layer = None704 self.num_organoids = 0705 self.original_images = {}706 self.original_contrast = {}707 self.stored_confidences = {}708 self.stored_diameters = {}709 710 # Initialize multi_annotation_mode to False by default711 self.multi_annotation_mode = False712 # self.single_annotation_mode = True # Initially, it's single annotation mode713 714 # setup gui 715 self.setLayout(QVBoxLayout())716 self.layout().addWidget(self._setup_input_widget())717 self.layout().addWidget(self._setup_output_widget())718 719 # initialise organoidl instance720 self.organoiDL = OrganoiDL(self.handle_progress)721 722 # get already opened layers723 self.image_layer_names = self._get_layer_names()724 if len(self.image_layer_names)>0: self._update_added_image(self.image_layer_names)725 self.shape_layer_names = self._get_layer_names(layer_type=layers.Shapes)726 if len(self.shape_layer_names)>0: self._update_added_shapes(self.shape_layer_names)727 # and watch for newly added images or shapes728 self.viewer.layers.events.inserted.connect(self._added_layer)729 self.viewer.layers.events.removed.connect(self._removed_layer)730 self.viewer.layers.selection.events.changed.connect(self._sel_layer_changed)731 732 # setup flags used for changing slider and text of min diameter and confidence threshold733 self.diameter_slider_changed = False 734 self.confidence_slider_changed = False735 736 # Key binding to change the edge_color of the bounding boxes to green737 @self.viewer.bind_key('g')738 def change_edge_color_to_green(viewer: napari.Viewer):739 if not self.multi_annotation_mode: # Check if single-annotation mode is active740 show_error(""Cannot change edge color. Change to multi-annotation mode to enable this feature."")741 return742 if self.cur_shapes_layer is not None: # Ensure shapes layer exists743 selected_shapes = self.cur_shapes_layer.selected_data # Retrieves indices of shapes currently selected, returns a set 744 if len(selected_shapes) > 0:745 # Modify the edge color only for the selected shapes746 current_edge_colors = self.cur_shapes_layer.edge_color 747 for idx in selected_shapes:748 # Save original color749 # if idx not in self.original_colors: 750 # self.original_colors[idx] = current_edge_colors[idx].copy()751 # Update to the new color752 current_edge_colors[idx] = settings.COLOR_CLASS_1753 self.cur_shapes_layer.edge_color = current_edge_colors # Apply the changes754 show_info(f""Changed edge color of shapes {list(selected_shapes)} to green."")755 else:756 show_warning(""No shapes selected to change edge color."")757 758 # Key binding to change the edge_color of the bounding boxes to blue759 @self.viewer.bind_key('h')760 def change_edge_color_to_blue(viewer: napari.Viewer):761 if not self.multi_annotation_mode: # Check if single-annotation mode is active762 show_error(""Cannot change edge color. Change to multi-annotation mode to enable this feature."")763 return 764 if self.cur_shapes_layer is not None: # Ensure shapes layer exists765 selected_shapes = self.cur_shapes_layer.selected_data766 if len(selected_shapes) > 0:767 # Modify the edge color only for the selected shapes768 current_edge_colors = self.cur_shapes_layer.edge_color769 for idx in selected_shapes:770 # Save original color771 # if idx not in self.original_colors: 772 # self.original_colors[idx] = current_edge_colors[idx].copy()773 # Update to the new color774 current_edge_colors[idx] = settings.COLOR_CLASS_2775 self.cur_shapes_layer.edge_color = current_edge_colors # Apply the changes776 show_info(f""Changed edge color of {list(selected_shapes)} to blue."")777 else:778 show_warning(""No shapes selected to change edge color."")779 780 # Key binding to reset the edge_color of selected bounding boxes to the original magenta color781 @self.viewer.bind_key('m')782 def change_to_original_color(viewer: napari.Viewer):783 if not self.multi_annotation_mode: # Check if single-annotation mode is active784 show_info(""Cannot change edge color. Change to multi-annotation mode to enable this feature."")785 return786 if self.cur_shapes_layer is not None: # Ensure shapes layer exists787 selected_shapes = self.cur_shapes_layer.selected_data788 if len(selected_shapes) > 0:789 current_edge_colors = self.cur_shapes_layer.edge_color790 # Modify the edge color only for the selected shapes791 current_edge_colors = self.cur_shapes_layer.edge_color792 for idx in selected_shapes:793 # if idx in self.original_colors:794 # Revert to the original color795 current_edge_colors[idx] = settings.COLOR_DEFAULT796 self.cur_shapes_layer.edge_color = current_edge_colors # Apply the changes797 show_info(f""Reset edge color of {list(selected_shapes)} to magenta."")798 else:799 show_warning(""No shapes selected to reset edge color."")800 801 802 def handle_progress(self, blocknum, blocksize, totalsize):803 """""" When the model is being downloaded, this method is called and th progress of the download804 is calculated and displayed on the progress bar. This function was re-implemented from:805 https://www.geeksforgeeks.org/pyqt5-how-to-automate-progress-bar-while-downloading-using-urllib/ """"""806 read_data = blocknum * blocksize # calculate the progress807 if totalsize > 0:808 download_percentage = read_data * 100 / totalsize809 self.progress_bar.setValue(int(download_percentage))810 QApplication.processEvents()811 812 def _sel_layer_changed(self, event):813 """""" Is called whenever the user selects a different layer to work on. """"""814 cur_layer_list = list(self.viewer.layers.selection)815 if len(cur_layer_list)==0: return816 cur_seg_selected = cur_layer_list[-1]817 # switch to values of other shapes layer if clicked818 if type(cur_seg_selected)==layers.Shapes:819 if self.cur_shapes_layer is not None:820 self.stored_confidences[self.cur_shapes_name] = self.confidence_slider.value()/100821 self.stored_diameters[self.cur_shapes_name] = self.min_diameter_slider.value()822 self.cur_shapes_layer = cur_seg_selected823 self.cur_shapes_name = cur_seg_selected.name824 # update min diameter text and slider with previous value of that layer825 self.min_diameter = self.stored_diameters[self.cur_shapes_name]826 self.min_diameter_textbox.setText(str(self.min_diameter))827 # update confidence text and slider with previous value of that layer828 self.confidence = self.stored_confidences[self.cur_shapes_name]829 self.confidence_textbox.setText(str(self.confidence))830 831 def _added_layer(self, event):832 # get names of added layers, image and shapes833 new_image_layer_names = self._get_layer_names()834 new_shape_layer_names = self._get_layer_names(layer_type=layers.Shapes)835 new_image_layer_names = [name for name in new_image_layer_names if name not in self.image_layer_names]836 new_shape_layer_names = [name for name in new_shape_layer_names if name not in self.shape_layer_names]837 if len(new_image_layer_names)>0 : 838 self._update_added_image(new_image_layer_names)839 self.image_layer_names.extend(new_image_layer_names)840 if len(new_shape_layer_names)>0:841 self._update_added_shapes(new_shape_layer_names)842 self.shape_layer_names.extend(new_shape_layer_names)843 844 def _removed_layer(self, event):845 """""" Is called whenever a layer has been deleted (by the user) and removes the layer from GUI and backend. """"""846 new_image_layer_names = self._get_layer_names()847 new_shape_layer_names = self._get_layer_names(layer_type=layers.Shapes)848 removed_image_layer_names = [name for name in self.image_layer_names if name not in new_image_layer_names]849 removed_shape_layer_names = [name for name in self.shape_layer_names if name not in new_shape_layer_names]850 if len(removed_image_layer_names)>0:851 self._update_removed_image(removed_image_layer_names)852 self.image_layer_names = new_image_layer_names853 if len(removed_shape_layer_names)>0:854 self._update_remove_shapes(removed_shape_layer_names)855 self.shape_layer_names = new_shape_layer_names856 857 def _preprocess(self):858 """""" Preprocess the current image in the viewer to improve visualisation for the user """"""859 img = self.original_images[self.image_layer_name]860 img = utils.apply_normalization(img)861 self.viewer.layers[self.image_layer_name].data = img862 self.viewer.layers[self.image_layer_name].contrast_limits = (0,255)863 864 def _update_num_organoids(self, len_bboxes):865 """""" Updates the number of organoids displayed in the viewer """"""866 self.num_organoids = len_bboxes867 new_text = 'Number of organoids: '+str(self.num_organoids)868 self.organoid_number_label.setText(new_text)869 870 def _update_vis_bboxes(self, bboxes, scores, box_ids, labels_layer_name):871 """""" Adds the shapes layer to the viewer or updates it if already there """"""872 self._update_num_organoids(len(bboxes))873 # if layer already exists874 if labels_layer_name in self.shape_layer_names: 875 self.viewer.layers[labels_layer_name].data = bboxes # hack to get edge_width stay the same!876 self.viewer.layers[labels_layer_name].properties = {'box_id': box_ids,'scores': scores}877 self.viewer.layers[labels_layer_name].edge_width = 12878 self.viewer.layers[labels_layer_name].refresh()879 self.viewer.layers[labels_layer_name].refresh_text()880 # or if this is the first run881 else:882 # if no organoids were found just make an empty shapes layer883 if self.num_organoids==0: 884 self.cur_shapes_layer = self.viewer.add_shapes(name=labels_layer_name,885 properties={'box_id': [],'scores': []})886 # otherwise make the layer and add the boxes887 else:888 properties = {'box_id': box_ids,'scores': scores}889 text_params = {'string': 'ID: {box_id}\nConf.: {scores:.2f}',890 'size': 12,891 'anchor': 'upper_left',}892 self.cur_shapes_layer = self.viewer.add_shapes(bboxes, 893 name=labels_layer_name,894 scale=self.viewer.layers[self.image_layer_name].scale,895 face_color='transparent', 896 properties = properties,897 text = text_params,898 edge_color=settings.COLOR_DEFAULT,899 shape_type='rectangle',900 edge_width=12) # warning generated here901 902 # set current_edge_width so edge width is the same when users annotate - doesnt' fix new preds being added!903 self.viewer.layers[labels_layer_name].current_edge_width = 12904 905 906 def _on_preprocess_click(self):907 """""" Is called whenever preprocess button is clicked """"""908 if not self.image_layer_name: show_info('Please load an image first and try again!')909 else: self._preprocess()910 911 def _on_run_click(self):912 """""" Is called whenever Run Organoid Counter button is clicked """"""913 # check if an image has been loaded914 if not self.image_layer_name: 915 show_info('Please load an image first and try again!')916 return917 # check if model exists locally and if not ask user if it's ok to download918 if not utils.return_is_file(settings.MODELS_DIR, settings.MODELS[self.model_name][""filename""]): 919 confirm_window = ConfirmUpload(self)920 confirm_window.exec_()921 # if user clicks cancel return doing nothing 922 if confirm_window.result() != QDialog.Accepted: return923 # otherwise donwload model and display progress in progress bar924 else: 925 self.progress_box.show()926 self.organoiDL.download_model(self.model_name)927 self.progress_box.hide()928 929 # load model checkpoint930 self.organoiDL.set_model(self.model_name)931 if self.organoiDL.img_scale[0]==0: self.organoiDL.set_scale(self.viewer.layers[self.image_layer_name].scale)932 933 # make sure the number of windows and downsamplings are the same934 if len(self.window_sizes) != len(self.downsampling): 935 show_info('Keep number of window sizes and downsampling the same and try again!')936 return937 938 # get the current image 939 img_data = self.viewer.layers[self.image_layer_name].data940 941 # check that image is grayscale942 if len(utils.squeeze_img(img_data).shape) > 2:943 show_info('Only grayscale images currently supported. Try a different image or process it first and try again!')944 return 945 946 # update the viewer with the new bboxes947 labels_layer_name = 'Labels-'+self.image_layer_name948 if labels_layer_name in self.shape_layer_names:949 show_info('Found existing labels layer. Please remove or rename it and try again!')950 return 951 952 # show activity docker for progrgess bar while running 953 self.viewer.window._status_bar._toggle_activity_dock(True)954 955 # run inference956 self.organoiDL.run(img_data, 957 labels_layer_name,958 self.window_sizes,959 self.downsampling,960 self.window_overlap)961 962 # set the confidence threshold, remove small organoids and get bboxes in format o visualise963 bboxes, scores, box_ids = self.organoiDL.apply_params(labels_layer_name, self.confidence, self.min_diameter)964 # hide activcity dock on completion965 self.viewer.window._status_bar._toggle_activity_dock(False)966 # update widget with results967 self._update_vis_bboxes(bboxes, scores, box_ids, labels_layer_name)968 # and update cur_shapes_name to newly created shapes layer969 self.cur_shapes_name = labels_layer_name970 # preprocess the image if not done so already to improve visualisation971 self._preprocess() 972 973 def _on_model_selection_changed(self):974 """""" Is called when user selects a new model from the dropdown menu. """"""975 self.model_name = self.model_selection.currentText()976 977 def _on_choose_model_clicked(self):978 """""" Is called whenever browse button is clicked for model selection """"""979 # called when the user hits the 'browse' button to select a model980 fd = QFileDialog()981 fd.setFileMode(QFileDialog.AnyFile)982 if fd.exec_():983 model_path = fd.selectedFiles()[0]984 import shutil985 shutil.copy2(model_path, settings.MODELS_DIR)986 model_name = utils.add_to_dict(model_path)987 self.model_selection.addItem(model_name)988 989 def _on_window_sizes_changed(self):990 """""" Is called whenever user changes the window sizes text box """"""991 new_window_sizes = self.window_sizes_textbox.text()992 new_window_sizes = new_window_sizes.split(',')993 self.window_sizes = [int(win_size) for win_size in new_window_sizes]994 995 def _on_downsampling_changed(self):996 """""" Is called whenever user changes the downsampling text box """"""997 new_downsampling = self.downsampling_textbox.text()998 new_downsampling = new_downsampling.split(',')999 self.downsampling = [int(ds) for ds in new_downsampling]1000 1001 def _rerun(self):1002 """""" Is called whenever user changes one of the two parameter sliders """"""1003 # check if OrganoiDL instance exists - create it if not and set there current boxes, scores and ids 1004 if self.organoiDL.img_scale[0]==0: self.organoiDL.set_scale(self.cur_shapes_layer.scale)1005 self.organoiDL.update_next_id(self.cur_shapes_name, len(self.cur_shapes_layer.scale)+1)1006 1007 # make sure to add info to cur_shapes_layer.metadata to differentiate this action from when user adds/removes boxes1008 with utils.set_dict_key( self.cur_shapes_layer.metadata, 'napari-organoid-counter:_rerun', True):1009 # first update bboxes in organoiDLin case user has added/removed1010 self.organoiDL.update_bboxes_scores(self.cur_shapes_name,1011 self.cur_shapes_layer.data, 1012 self.cur_shapes_layer.properties['scores'],1013 self.cur_shapes_layer.properties['box_id'])1014 # and get new boxes, scores and box ids based on new confidence and min_diameter values 1015 bboxes, scores, box_ids = self.organoiDL.apply_params(self.cur_shapes_name, self.confidence, self.min_diameter)1016 self._update_vis_bboxes(bboxes, scores, box_ids, self.cur_shapes_name)1017 1018 def _on_diameter_slider_changed(self):1019 """""" Is called whenever user changes the Minimum Diameter slider """"""1020 # get current value1021 self.min_diameter = self.min_diameter_slider.value()1022 self.diameter_slider_changed = True1023 if int(self.min_diameter_textbox.text())!= self.min_diameter:1024 self.min_diameter_textbox.setText(str(self.min_diameter))1025 self.diameter_slider_changed = False1026 # check if no labels loaded yet1027 if len(self.shape_layer_names)==0: return1028 self._rerun() 1029 1030 def _on_diameter_textbox_changed(self):1031 """""" Is called whenever user changes the minimum diameter from the textbox """"""1032 # check if no labels loaded yet1033 if self.diameter_slider_changed: return1034 self.min_diameter = int(self.min_diameter_textbox.text())1035 if self.min_diameter_slider.value() != self.min_diameter:1036 self.min_diameter_slider.setValue(self.min_diameter)1037 if len(self.shape_layer_names)==0: return1038 self._rerun()1039 1040 def _on_confidence_slider_changed(self):1041 """""" Is called whenever user changes the confidence slider """"""1042 self.confidence = self.confidence_slider.value()/1001043 self.confidence_slider_changed = True1044 if float(self.confidence_textbox.text()) != self.confidence:1045 self.confidence_textbox.setText(str(self.confidence))1046 self.confidence_slider_changed = False1047 # check if no labels loaded yet1048 if len(self.shape_layer_names)==0: return1049 self._rerun()1050 1051 def _on_confidence_textbox_changed(self):1052 """""" Is called whenever user changes the confidence value from the textbox """"""1053 if self.confidence_slider_changed: return1054 self.confidence = float(self.confidence_textbox.text())1055 slider_conf_value = int(self.confidence*100)1056 if self.confidence_slider.value() != slider_conf_value:1057 self.confidence_slider.setValue(slider_conf_value)1058 if len(self.shape_layer_names)==0: return1059 self._rerun()1060 1061 def _on_image_selection_changed(self):1062 """""" Is called whenever a new image has been selected from the drop down box """"""1063 self.image_layer_name = self.image_layer_selection.currentText()1064 1065 def _on_shapes_selection_changed(self):1066 """""" Is called whenever a new shapes layer has been selected from the drop down box """"""1067 self.save_layer_name = self.output_layer_selection.currentText()1068 1069 def _on_reset_click(self):1070 """""" Is called whenever Reset Configs button is clicked """"""1071 # reset params1072 self.min_diameter = 301073 self.confidence = 0.81074 vis_confidence = int(self.confidence*100)1075 self.min_diameter_slider.setValue(self.min_diameter)1076 self.confidence_slider.setValue(vis_confidence)1077 if self.image_layer_name:1078 # reset to original image1079 self.viewer.layers[self.image_layer_name].data = self.original_images[self.image_layer_name]1080 self.viewer.layers[self.image_layer_name].contrast_limits = self.original_contrast[self.image_layer_name]1081 1082 def _on_screenshot_click(self):1083 """""" Is called whenever Take Screenshot button is clicked """"""1084 screenshot=self.viewer.screenshot()1085 if not self.image_layer_name: potential_name = datetime.now().strftime(""%d%m%Y%H%M%S"")+'screenshot.png'1086 else: potential_name = self.image_layer_name+datetime.now().strftime(""%d%m%Y%H%M%S"")+'_screenshot.png'1087 fd = QFileDialog()1088 name,_ = fd.getSaveFileName(self, 'Save File', potential_name, 'Image files (*.png);;(*.tiff)') #, 'CSV Files (*.csv)')1089 if name: imsave(name, screenshot)1090 1091 def on_annotation_mode_changed(self, index):1092 """"""Callback for dropdown selection.""""""1093 if index == 0: # Single Annotation1094 self.multi_annotation_mode = False1095 # self.single_annotation_mode = True1096 show_info(""Switched to Single Annotation mode."")1097 elif index == 1: # Multi Annotation1098 self.multi_annotation_mode = True1099 # self.single_annotation_mode = False1100 show_info(""Switched to Multi Annotation mode."")1101 1102 def _on_save_csv_click(self): 1103 """""" Is called whenever Save features button is clicked """"""1104 bboxes = self.viewer.layers[self.save_layer_name].data1105 if not bboxes: show_info('No organoids detected! Please run auto organoid counter or run algorithm first and try again!')1106 else:1107 # write diameters and area to csv1108 data_csv = utils.get_bbox_diameters(bboxes, 1109 self.viewer.layers[self.save_layer_name].properties['box_id'],1110 self.viewer.layers[self.save_layer_name].scale)1111 fd = QFileDialog()1112 name, _ = fd.getSaveFileName(self, 'Save File', self.save_layer_name, 'CSV files (*.csv)')#, 'CSV Files (*.csv)')1113 if name: utils.write_to_csv(name, data_csv)1114 1115 def _on_save_json_click(self):1116 """""" Is called whenever Save boxes button is clicked """"""1117 bboxes = self.viewer.layers[self.save_layer_name].data1118 #scores = #add1119 if not bboxes: 1120 show_info('No organoids detected! Please run auto organoid counter or run algorithm first and try again!')1121 return1122 1123 # Check for multi-annotation mode1124 if self.multi_annotation_mode:1125 1126 # Get the edge colors for all bounding boxes1127 edge_colors = self.cur_shapes_layer.edge_color1128 labels = []1129 1130 # Check if all bounding boxes have their edge color set (not green or blue)1131 green = np.array(settings.COLOR_CLASS_1)1132 blue = np.array(settings.COLOR_CLASS_2)1133 1134 all_colored = True1135 for edge_color in edge_colors:1136 # Compare the colors with a tolerance using np.allclose to account for floating-point errors1137 if not (np.allclose(edge_color[:3], green[:3]) or np.allclose(edge_color[:3], blue[:3])):1138 all_colored = False1139 break1140 1141 if not all_colored:1142 show_error('Please change the color of all bounding boxes before saving.')1143 return1144 1145 # Assign organoid label based on edge_color1146 for edge_color in edge_colors:1147 if np.allclose(edge_color[:3], green[:3]):1148 labels.append(0) # Label for green1149 elif np.allclose(edge_color[:3], blue[:3]):1150 labels.append(1) # Label for blue1151 else:1152 raise ValueError(f""Unexpected edge color {edge_color[:3]} encountered."")1153 1154 #elif self.single_annotation_mode:1155 else:1156 # Single annotation mode: all bounding boxes get a default label1157 labels = [0] * len(bboxes) # Default label for single annotation mode1158 1159 data_json = utils.get_bboxes_as_dict(bboxes, 1160 self.viewer.layers[self.save_layer_name].properties['box_id'],1161 self.viewer.layers[self.save_layer_name].properties['scores'],1162 self.viewer.layers[self.save_layer_name].scale,1163 labels=labels)1164 1165 1166 # write bbox coordinates to json1167 fd = QFileDialog()1168 name,_ = fd.getSaveFileName(self, 'Save File', self.save_layer_name, 'JSON files (*.json)')#, 'CSV Files (*.csv)')1169 if name: utils.write_to_json(name, data_json)1170 1171 def _update_added_image(self, added_items):1172 """"""1173 Update the selection box with new images if images have been added and update the self.original_images and self.original_contrast dicts.1174 Set the latest added image to the current working image (self.image_layer_name)1175 """"""1176 for layer_name in added_items:1177 self.image_layer_selection.addItem(layer_name)1178 self.original_images[layer_name] = self.viewer.layers[layer_name].data1179 self.original_contrast[layer_name] = self.viewer.layers[self.image_layer_name].contrast_limits1180 self.image_layer_name = added_items[0]1181 1182 def _update_removed_image(self, removed_layers):1183 """"""1184 Update the selection box by removing image names if image has been deleted and remove items from self.original_images and self.original_contrast dicts.1185 """"""1186 # update drop-down selection box and remove image from dict1187 for removed_layer in removed_layers:1188 item_id = self.image_layer_selection.findText(removed_layer)1189 self.image_layer_selection.removeItem(item_id)1190 del self.original_images[removed_layer]1191 del self.original_contrast[removed_layer]1192 1193 def _update_added_shapes(self, added_items):1194 """"""1195 Update the selection box by shape layer names if it they have been added, update current working shape layer and instantiate OrganoiDL if not already there1196 """"""1197 # update the drop down box displaying shape layer names for saving1198 for layer_name in added_items:1199 self.output_layer_selection.addItem(layer_name)1200 # set the latest added shapes layer to the shapes layer that has been selected for saving and visualisation