CoolFace
Datasetpublic

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.

sourceHugging Faceapache-2.0updated 6mo agoView on Hugging Face
4likes2.4kdownloads
dataset_Organoid.csv1751 linesDownload Raw Back to data
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

Showing the first 1,200 of 1751 lines. Download the file for the rest.