From 1db03fdef0110f3b933bd424eb2626653809e302 Mon Sep 17 00:00:00 2001 From: christofhenkel Date: Mon, 24 Feb 2025 15:45:05 +0000 Subject: [PATCH 01/13] initial add of new competition tutorial --- .../Cryo-ET/1st_place_solution/README.md | 88 ++ .../configs/cfg_effnetb3.py | 23 + .../configs/cfg_resnet34.py | 22 + .../configs/cfg_resnet34_ds.py | 19 + .../configs/common_config.py | 198 +++ .../Cryo-ET/1st_place_solution/data/ds_1.py | 92 ++ .../1st_place_solution/metrics/metric_1.py | 237 +++ .../1st_place_solution/models/mdl_1.py | 318 +++++ .../1st_place_solution/postprocess/pp_1.py | 62 + .../1st_place_solution/requirements.txt | 10 + .../Cryo-ET/1st_place_solution/train.py | 469 ++++++ .../1st_place_solution/train_folded_v1.csv | 1270 +++++++++++++++++ .../Cryo-ET/1st_place_solution/utils.py | 581 ++++++++ 13 files changed, 3389 insertions(+) create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/README.md create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/train.py create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/train_folded_v1.csv create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/utils.py diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md new file mode 100644 index 0000000000..c32c47dd28 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -0,0 +1,88 @@ +## Introduction + +This tutorial illustrates how to use MONAI for cryo electron tomography. The pipeline and models were partly used to win the [Cryo-ET competition on kaggle](https://www.kaggle.com/competitions/czii-cryo-et-object-identification/overview). The tutorial was tested with nvidia/pytorch:24.08-py3 docker container and a single A100 GPU. + +## What is Cryo-ET? + +If you ask ChatGPT: + +Cryo-ET (Cryo-Electron Tomography) is an advanced imaging technique that allows scientists to visualize biological structures in near-native states at high resolution. It combines cryogenic sample preservation with electron tomography to generate three-dimensional (3D) reconstructions of cellular structures, protein complexes, and organelles. + +### How It Works +1. Cryo-Fixation: The sample (e.g., a cell or a purified macromolecular complex) is rapidly frozen using liquid ethane or similar methods to prevent ice crystal formation, preserving its natural state. +2. Electron Microscopy: The frozen sample is placed under a transmission electron microscope (TEM), where images are taken from multiple angles by tilting the sample. +3. Tomographic Reconstruction: Computational algorithms combine these 2D images to create a detailed 3D model of the structure. + +### Applications +Studying cellular architecture at nanometer resolution. +Visualizing macromolecular complexes in their native environments. +Understanding interactions between viruses and host cells. +Investigating neurodegenerative diseases, cancer, and infectious diseases. +Cryo-ET is particularly powerful because it enables direct imaging of biological systems without the need for staining or chemical fixation, preserving their native conformation. + + +## Environment: + +To have a common environment its suggested to use the basic pytorch docker container and add a few pip packages on top + +1. This tutorial was tested with tag 24.08-py3, i.e. run the following command to pull/ start the container. + +```docker run nvcr.io/nvidia/pytorch:24.08-py3``` + +2. Within the container clone this repository + +``` +git clone https://github.com/ProjectMONAI/tutorials +cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ +``` + + +3. And install necessary additional pip packages via + +```pip install -r requirements.txt``` + +## Required Data + +This tutorial is build upon the official Cryo ET competition data. It can be downloaded directly from kaggle: https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data + +Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```) + +```kaggle competitions download -c czii-cryo-et-object-identification``` + +and adjust path to it in ```configs/common_config.py``` with ```cfg.data_folder``` + + + +## Training models + +For the competition we created a cross-validation scheme by simply simply splitting the 7 training tomographs into 7 folds. I.e. we train on 6 tomographs and use the 7th as validation. +For convenience we provide a file ```train_folded_v1.csv``` which contains the original training annotations and was also extended by a column containing fold_ids. + +We solve the competition with a 3D-segmentation approach leveraging [MONAI's FlexibleUNet](https://docs.monai.io/en/stable/networks.html#flexibleunet) architecture. Compared to the original implementation we adjusted the network to output more featuremap and enable deep-supervision. The following illustrates the resulting architecture at a high level: + +![alt text](partly_Unet.png "Partly UNet") + +We provide three different configurations which differ only in the used backbone and output feature maps. The configuration files are .py files and located under ```configs``` and share all other hyper-parameters. Each hyperparameter can be overwriten by adding a flag to the training command. To train a resnet34 version of our segmentation model simply run + +```python train.py -C cfg_resnet34 --output_dir WHATEVERISYOUROUTPUTDIR``` + +This will save checkpoints under the specified WHATEVERISYOUROUTPUTDIR. +By default models are trained using bfloat16 which requires a GPU capable of that. Alternatively you can set ```cfg.bf16=False``` or overwrite as flag ```--bf16 False``` when running ```train.py ```. + +### Replicating 1st place solution (segmentation part) + +To train checkpoints necessary for replicating the segmentation part of the 1st place solution run training of 2x fullfits for each model. Thereby ```cfg.fold = -1``` results in training on all data, and using ```fold 0``` as validation. +``` +python train.py -C cfg_resnet34 --fold -1 +python train.py -C cfg_resnet34 --fold -1 +python train.py -C cfg_resnet34_ds --fold -1 +python train.py -C cfg_resnet34_ds --fold -1 +python train.py -C cfg_effnetb3 --fold -1 +python train.py -C cfg_effnetb3 --fold -1 +``` + +## Inference + +Inference after models are converted with torch jit is shown in our 1st place submission kaggle kernel. + +https://www.kaggle.com/code/christofhenkel/cryo-et-1st-place-solution?scriptVersionId=223259615 \ No newline at end of file diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py new file mode 100644 index 0000000000..104b28d47a --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py @@ -0,0 +1,23 @@ +from common_config import basic_cfg +import os +import pandas as pd +import numpy as np +import monai.transforms as mt + +cfg = basic_cfg + +cfg.name = os.path.basename(__file__).split(".")[0] +cfg.output_dir = f"/mount/cryo/models/{os.path.basename(__file__).split('.')[0]}" + +#model +cfg.backbone = 'efficientnet-b3' +cfg.backbone_args = dict(spatial_dims=3, + in_channels=cfg.in_channels, + out_channels=cfg.n_classes, + backbone=cfg.backbone, + pretrained=cfg.pretrained) +cfg.class_weights = np.array([64,64,64,64,64,64,1]) +cfg.lvl_weights = np.array([0,0,0,1]) + + + diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py new file mode 100644 index 0000000000..4b3591f705 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py @@ -0,0 +1,22 @@ +from common_config import basic_cfg +import os +import pandas as pd +import numpy as np + +cfg = basic_cfg + +# paths +cfg.name = os.path.basename(__file__).split(".")[0] +cfg.output_dir = f"/mount/cryo/models/{os.path.basename(__file__).split('.')[0]}" + + +#model + +cfg.backbone = 'resnet34' +cfg.backbone_args = dict(spatial_dims=3, + in_channels=cfg.in_channels, + out_channels=cfg.n_classes, + backbone=cfg.backbone, + pretrained=cfg.pretrained) +cfg.class_weights = np.array([256,256,256,256,256,256,1]) +cfg.lvl_weights = np.array([0,0,0,1]) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py new file mode 100644 index 0000000000..d9dfa3c1a6 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py @@ -0,0 +1,19 @@ +from common_config import basic_cfg +import os +import pandas as pd +import numpy as np + +cfg = basic_cfg + +# paths +cfg.name = os.path.basename(__file__).split(".")[0] +cfg.output_dir = f"/mount/cryo/models/{os.path.basename(__file__).split('.')[0]}" + +cfg.backbone = 'resnet34' +cfg.backbone_args = dict(spatial_dims=3, + in_channels=cfg.in_channels, + out_channels=cfg.n_classes, + backbone=cfg.backbone, + pretrained=cfg.pretrained) +cfg.class_weights = np.array([64,64,64,64,64,64,1]) +cfg.lvl_weights = np.array([0,0,1,1]) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py new file mode 100644 index 0000000000..5bc4f0a215 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py @@ -0,0 +1,198 @@ +from types import SimpleNamespace +from monai import transforms as mt + +cfg = SimpleNamespace(**{}) + +# stages +cfg.train = True +cfg.val = True +cfg.test = True +cfg.train_val = True + +# dataset +cfg.batch_size_val = None +cfg.use_custom_batch_sampler = False +cfg.val_df = None +cfg.test_df = None +cfg.val_data_folder = None +cfg.train_aug = None +cfg.val_aug = None +cfg.data_sample = -1 + +# model + +cfg.pretrained = False +cfg.pretrained_weights = None +cfg.pretrained_weights_strict = True +cfg.pop_weights = None +cfg.compile_model = False + +# training routine +cfg.fold = 0 +cfg.optimizer = "Adam" +cfg.sgd_momentum = 0 +cfg.sgd_nesterov = False +cfg.lr = 1e-4 +cfg.schedule = "cosine" +cfg.num_cycles = 0.5 +cfg.weight_decay = 0 +cfg.epochs = 10 +cfg.seed = -1 +cfg.resume_training = False +cfg.distributed = False +cfg.clip_grad = 0 +cfg.save_val_data = True +cfg.gradient_checkpointing = False +cfg.apex_ddp = False +cfg.synchronize_step = True + +# eval +cfg.eval_ddp = True +cfg.calc_metric = True +cfg.calc_metric_epochs = 1 +cfg.eval_steps = 0 +cfg.eval_epochs = 1 +cfg.save_pp_csv = True + + +# ressources +cfg.find_unused_parameters = False +cfg.grad_accumulation = 1 +cfg.syncbn = False +cfg.gpu = 0 +cfg.dp = False +cfg.num_workers = 8 +cfg.drop_last = True +cfg.save_checkpoint = True +cfg.save_only_last_ckpt = False +cfg.save_weights_only = False + +# logging, +cfg.neptune_project = None +cfg.neptune_connection_mode = "debug" +cfg.save_first_batch = False +cfg.save_first_batch_preds = False +cfg.clip_mode = "norm" +cfg.data_sample = -1 +cfg.track_grad_norm = True +cfg.grad_norm_type = 2. +cfg.track_weight_norm = True +cfg.norm_eps = 1e-4 +cfg.disable_tqdm = False + + + + +# paths + +cfg.data_folder = '/mount/cryo/data/czii-cryo-et-object-identification/train/static/ExperimentRuns/' +cfg.train_df = 'train_folded_v1.csv' + + +# stages +cfg.test = False +cfg.train = True +cfg.train_val = False + +#logging +cfg.neptune_project = None +cfg.neptune_connection_mode = "async" + +#model +cfg.model = "mdl_1" +cfg.mixup_p = 1. +cfg.mixup_beta = 1. +cfg.in_channels = 1 +cfg.pretrained = False + +#data +cfg.dataset = "ds_1" +cfg.classes = ['apo-ferritin','beta-amylase','beta-galactosidase','ribosome','thyroglobulin','virus-like-particle'] +cfg.n_classes = len(cfg.classes) + +cfg.post_process_pipeline = 'pp_1' +cfg.metric = 'metric_1' + + + +cfg.particle_radi = {'apo-ferritin':60, + 'beta-amylase':65, + 'beta-galactosidase':90, + 'ribosome':150, + 'thyroglobulin':130, + 'virus-like-particle':135 + } + +cfg.voxel_spacing = 10.0 + + +# OPTIMIZATION & SCHEDULE + +cfg.fold = 0 +cfg.epochs = 10 + +cfg.lr = 1e-3 +cfg.optimizer = "Adam" +cfg.weight_decay = 0. +cfg.warmup = 0. +cfg.batch_size = 8 +cfg.batch_size_val = 16 +cfg.sub_batch_size = 4 +cfg.roi_size = [96,96,96] +cfg.train_sub_epochs = 1112 +cfg.val_sub_epochs = 1 +cfg.mixed_precision = False +cfg.bf16 = True +cfg.force_fp16 = True +cfg.pin_memory = False +cfg.grad_accumulation = 1. +cfg.num_workers = 8 + + + + + + +#Saving +cfg.save_weights_only = True +cfg.save_only_last_ckpt = False +cfg.save_val_data = False +cfg.save_checkpoint=True +cfg.save_pp_csv = False + + + +cfg.static_transforms = static_transforms = mt.Compose([mt.EnsureChannelFirstd(keys=["image"], channel_dim="no_channel"),mt.NormalizeIntensityd(keys="image"),]) +cfg.train_aug = mt.Compose([mt.RandSpatialCropSamplesd(keys=["image", "label"], + roi_size=cfg.roi_size, + num_samples=cfg.sub_batch_size), + mt.RandFlipd( + keys=["image", "label"], + prob=0.5, + spatial_axis=0, + ), + mt.RandFlipd( + keys=["image", "label"], + prob=0.5, + spatial_axis=1, + ), + mt.RandFlipd( + keys=["image", "label"], + prob=0.5, + spatial_axis=2, + ), + mt.RandRotate90d( + keys=["image", "label"], + prob=0.75, + max_k=3, + spatial_axes=(0, 1), + ), + mt.RandRotated(keys=["image", "label"], prob=0.5,range_x=0.78,range_y=0.,range_z=0., padding_mode='reflection') + + ]) + +cfg.val_aug = mt.Compose([mt.GridPatchd(keys=["image","label"],patch_size=cfg.roi_size, pad_mode='reflect')]) + + + +basic_cfg = cfg diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py new file mode 100644 index 0000000000..c417c50926 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py @@ -0,0 +1,92 @@ +import os +import torch +import numpy as np +from torch.utils.data import Dataset, DataLoader +import zarr +from tqdm import tqdm + +def batch_to_device(batch, device): + batch_dict = {key: batch[key].to(device) for key in batch} + return batch_dict + +def collate_fn(batch): + + keys = batch[0].keys() + batch_dict = {key:torch.cat([b[key] for b in batch]) for key in keys} + return batch_dict + +tr_collate_fn = collate_fn +val_collate_fn = collate_fn + +import monai.data as md +import monai.transforms as mt + + +class CustomDataset(Dataset): + def __init__(self, df, cfg, aug, mode="train"): + + self.cfg = cfg + self.mode = mode + self.df = df + self.experiment_df = self.df.drop_duplicates(subset='experiment').copy() + self.exp_dict = self.df.groupby('experiment') + self.class2id = {c:i for i,c in enumerate(cfg.classes)} + self.n_classes = len(cfg.classes) + self.data_folder = cfg.data_folder + + + self.random_transforms = aug + data = [self.load_one(img_id) for img_id in tqdm(self.experiment_df['experiment'].values)] + data = md.CacheDataset(data=data, transform=cfg.static_transforms, cache_rate=1.0) + + if self.mode == 'train': + self.monai_ds = md.Dataset(data=data, transform=self.random_transforms) + self.sub_epochs = cfg.train_sub_epochs + self.len = len(self.monai_ds) * self.sub_epochs + else: + self.monai_ds = md.CacheDataset(data=data, transform=self.random_transforms, cache_rate=1.0)[0] + self.sub_epochs = cfg.val_sub_epochs + self.len = len(self.monai_ds['image']) + + def __getitem__(self, idx): + + if self.mode =='train': + monai_dict = self.monai_ds[idx//self.sub_epochs] + feature_dict = { + "input": torch.stack([item['image'] for item in monai_dict]), + "target": torch.stack([item['label'] for item in monai_dict]), + } + + else: + monai_dict = {k:self.monai_ds[k][idx] for k in self.monai_ds} + monai_dict['location'] = torch.from_numpy(self.monai_ds['image'].meta['location'][:,idx]) + feature_dict = { + "input": torch.stack([item['image'] for item in [monai_dict]]), + "location": torch.stack([item['location'] for item in [monai_dict]]), + "target": torch.stack([item['label'] for item in [monai_dict]]), + } + + return feature_dict + + def __len__(self): + return self.len + + def load_one(self, experiment_id): + + + img_fp = f'{self.data_folder}{experiment_id}' + try: + with zarr.open(img_fp + '/VoxelSpacing10.000/denoised.zarr') as zf: + img = np.array(zf[0]).transpose(2,1,0) + # img = np.array(zarr.open(img_fp + '/VoxelSpacing10.000/denoised.zarr')[0]).transpose(2,1,0) + except Exception as e: + print(e) + + centers = self.exp_dict.get_group(experiment_id)[['x','y','z']].values / 10 + classes = self.exp_dict.get_group(experiment_id)['particle_type'].map(self.class2id).values + mask = np.zeros((self.n_classes,) + img.shape[-3:]) + mask[classes, centers[:,0].astype(int), centers[:,1].astype(int), centers[:,2].astype(int)] = 1 + return {'image':img, 'label':mask} + + + diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py new file mode 100644 index 0000000000..b1615af667 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py @@ -0,0 +1,237 @@ +import numpy as np +import torch +from sklearn.metrics import roc_auc_score + +""" +Derived from: +https://github.com/cellcanvas/album-catalog/blob/main/solutions/copick/compare-picks/solution.py +""" + +import numpy as np +import pandas as pd + +from scipy.spatial import KDTree + + +class ParticipantVisibleError(Exception): + pass + + +def compute_metrics(reference_points, reference_radius, candidate_points): + num_reference_particles = len(reference_points) + num_candidate_particles = len(candidate_points) + + if len(reference_points) == 0: + return 0, num_candidate_particles, 0 + + if len(candidate_points) == 0: + return 0, 0, num_reference_particles + + ref_tree = KDTree(reference_points) + candidate_tree = KDTree(candidate_points) + raw_matches = candidate_tree.query_ball_tree(ref_tree, r=reference_radius) + matches_within_threshold = [] + for match in raw_matches: + matches_within_threshold.extend(match) + # Prevent submitting multiple matches per particle. + # This won't be be strictly correct in the (extremely rare) case where true particles + # are very close to each other. + matches_within_threshold = set(matches_within_threshold) + tp = int(len(matches_within_threshold)) + fp = int(num_candidate_particles - tp) + fn = int(num_reference_particles - tp) + return tp, fp, fn + + +def score( + solution: pd.DataFrame, + submission: pd.DataFrame, + row_id_column_name: str, + distance_multiplier: float, + beta: int, + weighted=True, +) -> float: + ''' + F_beta + - a true positive occurs when + - (a) the predicted location is within a threshold of the particle radius, and + - (b) the correct `particle_type` is specified + - raw results (TP, FP, FN) are aggregated across all experiments for each particle type + - f_beta is calculated for each particle type + - individual f_beta scores are weighted by particle type for final score + ''' + + particle_radius = { + 'apo-ferritin': 60, + 'beta-amylase': 65, + 'beta-galactosidase': 90, + 'ribosome': 150, + 'thyroglobulin': 130, + 'virus-like-particle': 135, + } + + weights = { + 'apo-ferritin': 1, + 'beta-amylase': 0, + 'beta-galactosidase': 2, + 'ribosome': 1, + 'thyroglobulin': 2, + 'virus-like-particle': 1, + } + + particle_radius = {k: v * distance_multiplier for k, v in particle_radius.items()} + + # Filter submission to only contain experiments found in the solution split + split_experiments = set(solution['experiment'].unique()) + submission = submission.loc[submission['experiment'].isin(split_experiments)] + + # Only allow known particle types + if not set(submission['particle_type'].unique()).issubset(set(weights.keys())): + raise ParticipantVisibleError('Unrecognized `particle_type`.') + + assert solution.duplicated(subset=['experiment', 'x', 'y', 'z']).sum() == 0 + assert particle_radius.keys() == weights.keys() + + results = {} + for particle_type in solution['particle_type'].unique(): + results[particle_type] = { + 'total_tp': 0, + 'total_fp': 0, + 'total_fn': 0, + } + + for experiment in split_experiments: + for particle_type in solution['particle_type'].unique(): + reference_radius = particle_radius[particle_type] + select = (solution['experiment'] == experiment) & (solution['particle_type'] == particle_type) + reference_points = solution.loc[select, ['x', 'y', 'z']].values + + select = (submission['experiment'] == experiment) & (submission['particle_type'] == particle_type) + candidate_points = submission.loc[select, ['x', 'y', 'z']].values + + if len(reference_points) == 0: + reference_points = np.array([]) + reference_radius = 1 + + if len(candidate_points) == 0: + candidate_points = np.array([]) + + tp, fp, fn = compute_metrics(reference_points, reference_radius, candidate_points) + + results[particle_type]['total_tp'] += tp + results[particle_type]['total_fp'] += fp + results[particle_type]['total_fn'] += fn + + fbetas = [] + fbeta_weights = [] + particle_types = [] + for particle_type, totals in results.items(): + tp = totals['total_tp'] + fp = totals['total_fp'] + fn = totals['total_fn'] + + precision = tp / (tp + fp) if tp + fp > 0 else 0 + recall = tp / (tp + fn) if tp + fn > 0 else 0 + fbeta = (1 + beta**2) * (precision * recall) / (beta**2 * precision + recall) if (precision + recall) > 0 else 0.0 + fbetas += [fbeta] + fbeta_weights += [weights.get(particle_type, 1.0)] + particle_types += [particle_type] + + if weighted: + aggregate_fbeta = np.average(fbetas,weights=fbeta_weights) + else: + aggregate_fbeta = np.mean(fbetas) + + return aggregate_fbeta, dict(zip(particle_types,fbetas)) + +def calc_metric(cfg, pp_out, val_df, pre="val"): + + particles = cfg.classes + pred_df = pp_out + + solution = val_df.copy() + solution['id'] = range(len(solution)) + + submission = pred_df.copy() + submission['experiment'] = solution['experiment'].unique()[0] + submission['id'] = range(len(submission)) + +# score003 = score( +# solution.copy(), +# submission[submission['conf']>0.03].copy(), +# row_id_column_name = 'id', +# distance_multiplier=0.5, +# beta=4)[0] +# print('score003',score003) + + best_ths = [] + for p in particles: + sol0a = solution[solution['particle_type']==p].copy() + sub0a = submission[submission['particle_type']==p].copy() + scores = [] + ths = np.arange(0,0.5,0.005) + for c in ths: + scores += [score( + sol0a.copy(), + sub0a[sub0a['conf']>c].copy(), + row_id_column_name = 'id', + distance_multiplier=0.5, + beta=4,weighted = False)[0]] + best_th = ths[np.argmax(scores)] + best_ths += [best_th] + + submission_pp = [] + for th, p in zip(best_ths,particles): + submission_pp += [submission[(submission['particle_type']==p) & (submission['conf']>th)].copy()] + submission_pp = pd.concat(submission_pp) + + score_pp, particle_scores = score( + solution[solution['particle_type']!='beta-amylase'].copy(), + submission_pp.copy(), + row_id_column_name = 'id', + distance_multiplier=0.5, + beta=4) + + result = {'score_' + k: v for k,v in particle_scores.items()} + result['score'] = score_pp +# print(result) + return result +# # if isinstance(pred_df,list): +# # pred_df,gt_df = pred_df +# # else: +# # gt_df = None + +# y_true = val_df['score'].values +# y_pred = val_data['preds'].cpu().numpy() +# score = get_score(y_true.flatten(), y_pred.flatten()) +# # print(score) + +# # df['score'] = df['location'].apply(ast.literal_eval) +# # df['span'] = df['location'].apply(location_to_span) +# # spans_true = df['span'].values + +# # df_pred = pred_df.copy() +# # # df_pred['location'] = df_pred['location'].apply(ast.literal_eval) +# # df_pred['span'] = df_pred['pred_location'].apply(pred_location_to_span) +# # spans_pred = df_pred['span'].values + +# # score = span_micro_f1(spans_pred, spans_true) + +# if hasattr(cfg, "neptune_run"): +# cfg.neptune_run[f"{pre}/score/"].log(score, step=cfg.curr_step) +# print(f"{pre} score: {score:.6}") +# # else: +# # return score + +# # if gt_df is not None: +# # df_pred = gt_df.copy() +# # df_pred['span'] = df_pred['pred_location'].apply(pred_location_to_span) +# # spans_pred = df_pred['span'].values + +# # score = span_micro_f1(spans_pred, spans_true) + +# # if hasattr(cfg, "neptune_run"): +# # cfg.neptune_run[f"{pre}/score_debug/"].log(score, step=cfg.curr_step) +# # # print(f"{pre} score_debug: {score:.6}") +# return score + diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py new file mode 100644 index 0000000000..fec46d8169 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py @@ -0,0 +1,318 @@ +import torch +import torch.nn.functional as F +from torch import nn +from torch.nn.parameter import Parameter +from torch.distributions import Beta +from monai.networks.nets.flexible_unet import SegmentationHead, UNetDecoder, FLEXUNET_BACKBONE + + +class PatchedUNetDecoder(UNetDecoder): + + """add functionality to output all feature maps""" + + def forward(self, features: list[torch.Tensor], skip_connect: int = 4): + skips = features[:-1][::-1] + features = features[1:][::-1] + + out = [] + x = features[0] + out += [x] + for i, block in enumerate(self.blocks): + if i < skip_connect: + skip = skips[i] + else: + skip = None + x = block(x, skip) + out += [x] + return out + + +class FlexibleUNet(nn.Module): + """ + A flexible implementation of UNet-like encoder-decoder architecture. + + (Adjusted to support PatchDecoder and multi segmentation heads) + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + backbone: str, + pretrained: bool = False, + decoder_channels: tuple = (256, 128, 64, 32, 16), + spatial_dims: int = 2, + norm: str | tuple = ("batch", {"eps": 1e-3, "momentum": 0.1}), + act: str | tuple = ("relu", {"inplace": True}), + dropout: float | tuple = 0.0, + decoder_bias: bool = False, + upsample: str = "nontrainable", + pre_conv: str = "default", + interp_mode: str = "nearest", + is_pad: bool = True, + ) -> None: + """ + A flexible implement of UNet, in which the backbone/encoder can be replaced with + any efficient or residual network. Currently the input must have a 2 or 3 spatial dimension + and the spatial size of each dimension must be a multiple of 32 if is_pad parameter + is False. + Please notice each output of backbone must be 2x downsample in spatial dimension + of last output. For example, if given a 512x256 2D image and a backbone with 4 outputs. + Spatial size of each encoder output should be 256x128, 128x64, 64x32 and 32x16. + + Args: + in_channels: number of input channels. + out_channels: number of output channels. + backbone: name of backbones to initialize, only support efficientnet and resnet right now, + can be from [efficientnet-b0, ..., efficientnet-b8, efficientnet-l2, resnet10, ..., resnet200]. + pretrained: whether to initialize pretrained weights. ImageNet weights are available for efficient networks + if spatial_dims=2 and batch norm is used. MedicalNet weights are available for residual networks + if spatial_dims=3 and in_channels=1. Default to False. + decoder_channels: number of output channels for all feature maps in decoder. + `len(decoder_channels)` should equal to `len(encoder_channels) - 1`,default + to (256, 128, 64, 32, 16). + spatial_dims: number of spatial dimensions, default to 2. + norm: normalization type and arguments, default to ("batch", {"eps": 1e-3, + "momentum": 0.1}). + act: activation type and arguments, default to ("relu", {"inplace": True}). + dropout: dropout ratio, default to 0.0. + decoder_bias: whether to have a bias term in decoder's convolution blocks. + upsample: upsampling mode, available options are``"deconv"``, ``"pixelshuffle"``, + ``"nontrainable"``. + pre_conv:a conv block applied before upsampling. Only used in the "nontrainable" or + "pixelshuffle" mode, default to `default`. + interp_mode: {``"nearest"``, ``"linear"``, ``"bilinear"``, ``"bicubic"``, ``"trilinear"``} + Only used in the "nontrainable" mode. + is_pad: whether to pad upsampling features to fit features from encoder. Default to True. + If this parameter is set to "True", the spatial dim of network input can be arbitrary + size, which is not supported by TensorRT. Otherwise, it must be a multiple of 32. + """ + super().__init__() + + if backbone not in FLEXUNET_BACKBONE.register_dict: + raise ValueError( + f"invalid model_name {backbone} found, must be one of {FLEXUNET_BACKBONE.register_dict.keys()}." + ) + + if spatial_dims not in (2, 3): + raise ValueError("spatial_dims can only be 2 or 3.") + + encoder = FLEXUNET_BACKBONE.register_dict[backbone] + self.backbone = backbone + self.spatial_dims = spatial_dims + encoder_parameters = encoder["parameter"] + if not ( + ("spatial_dims" in encoder_parameters) + and ("in_channels" in encoder_parameters) + and ("pretrained" in encoder_parameters) + ): + raise ValueError("The backbone init method must have spatial_dims, in_channels and pretrained parameters.") + encoder_feature_num = encoder["feature_number"] + if encoder_feature_num > 5: + raise ValueError("Flexible unet can only accept no more than 5 encoder feature maps.") + + decoder_channels = decoder_channels[:encoder_feature_num] + self.skip_connect = encoder_feature_num - 1 + encoder_parameters.update({"spatial_dims": spatial_dims, "in_channels": in_channels, "pretrained": pretrained}) + encoder_channels = tuple([in_channels] + list(encoder["feature_channel"])) + encoder_type = encoder["type"] + self.encoder = encoder_type(**encoder_parameters) + print(decoder_channels) + + + + self.decoder = PatchedUNetDecoder( + spatial_dims=spatial_dims, + encoder_channels=encoder_channels, + decoder_channels=decoder_channels, + act=act, + norm=norm, + dropout=dropout, + bias=decoder_bias, + upsample=upsample, + interp_mode=interp_mode, + pre_conv=pre_conv, + align_corners=None, + is_pad=is_pad, + ) + self.segmentation_heads = nn.ModuleList([SegmentationHead( + spatial_dims=spatial_dims, + in_channels=decoder_channel, + out_channels=out_channels + 1, + kernel_size=3, + act=None, + ) for decoder_channel in decoder_channels[:-1]]) + + def forward(self, inputs: torch.Tensor): + + x = inputs + enc_out = self.encoder(x) + decoder_out = self.decoder(enc_out, self.skip_connect)[1:-1] + x_seg = [self.segmentation_heads[i](decoder_out[i]) for i in range(len(decoder_out))] + + return x_seg + + + +def count_parameters(model): + return sum(p.numel() for p in model.parameters() if p.requires_grad) + +def human_format(num): + num = float('{:.3g}'.format(num)) + magnitude = 0 + while abs(num) >= 1000: + magnitude += 1 + num /= 1000.0 + return '{}{}'.format('{:f}'.format(num).rstrip('0').rstrip('.'), ['', 'K', 'M', 'B', 'T'][magnitude]) + + + + + +class DenseCrossEntropy(nn.Module): + def __init__(self, class_weights=None): + super(DenseCrossEntropy, self).__init__() + + self.class_weights = class_weights + + def forward(self, x, target): + x = x.float() + target = target.float() + logprobs = torch.nn.functional.log_softmax(x, dim=1, dtype=torch.float) + + loss = -logprobs * target + + class_losses = loss.mean((0,2,3,4)) + if self.class_weights is not None: + loss = (class_losses * self.class_weights.to(class_losses.device)).sum() #/ class_weights.sum() + else: + + loss = class_losses.sum() + return loss, class_losses + +class Mixup(nn.Module): + def __init__(self, mix_beta, mixadd=False): + + super(Mixup, self).__init__() + self.beta_distribution = Beta(mix_beta, mix_beta) + self.mixadd = mixadd + + def forward(self, X, Y, Z=None): + + bs = X.shape[0] + n_dims = len(X.shape) + perm = torch.randperm(bs) + coeffs = self.beta_distribution.rsample(torch.Size((bs,))).to(X.device) + X_coeffs = coeffs.view((-1,) + (1,)*(X.ndim-1)) + Y_coeffs = coeffs.view((-1,) + (1,)*(Y.ndim-1)) + + X = X_coeffs * X + (1-X_coeffs) * X[perm] + + if self.mixadd: + Y = (Y + Y[perm]).clip(0, 1) + else: + Y = Y_coeffs * Y + (1 - Y_coeffs) * Y[perm] + + if Z: + return X, Y, Z + + return X, Y + +def to_ce_target(y): + # bs, c, h, w, d + y_bg = 1 - y.sum(1, keepdim=True).clamp(0, 1) + y = torch.cat([y,y_bg], 1) + y = y / y.sum(1, keepdim=True) + return y + +class Net(nn.Module): + + def __init__(self, cfg): + super(Net, self).__init__() + + self.cfg = cfg + self.n_classes = cfg.n_classes + self.classes = cfg.classes + + self.backbone = FlexibleUNet(**cfg.backbone_args) + + + self.mixup = Mixup(cfg.mixup_beta) + + print(f'Net parameters: {human_format(count_parameters(self))}') + self.lvl_weights = torch.from_numpy(cfg.lvl_weights) + self.loss_fn = DenseCrossEntropy(class_weights=torch.from_numpy(cfg.class_weights)) + + def forward(self, batch): + + x = batch['input'] + if "target" in batch.keys(): + y = batch["target"] + if self.training: + if torch.rand(1)[0] < self.cfg.mixup_p: + x, y = self.mixup(x,y) + out = self.backbone(x) + + + + outputs = {} + + if "target" in batch.keys(): + ys = [F.adaptive_max_pool3d(y, item.shape[-3:]) for item in out] + losses = torch.stack([self.loss_fn(out[i], to_ce_target(ys[i]))[0] for i in range(len(out))]) + lvl_weights = self.lvl_weights.to(losses.device) + loss = (losses * lvl_weights).sum() / lvl_weights.sum() + outputs['loss'] = loss + if not self.training: + outputs["logits"] = out[-1] + if 'location' in batch: + outputs["location"] = batch['location'] + + return outputs + + +class TestNet(nn.Module): + + def __init__(self, **backbone_args): + super(TestNet, self).__init__() + + self.backbone = FlexibleUNet(**backbone_args) + + + def forward(self, x): + #x shape is bs, c, h, w, d + out = self.backbone(x) + #out shape is bs, 7, h//2, w//2, d//2 + logits = out[-1] # for heatmap do softmax + reorder classes .softmax(1)[:,[0,2,3,4,5,1]] + return logits + + +# import torch +# import monai.transforms as mt +# import zarr +# import numpy as np + +# def preprocess_img(zarr_fn, transforms): +# img = np.array(zarr.open(zarr_fn)[0]).transpose(2,1,0) +# img = transforms({'image':img})['image'] +# return img + +# backbone_args = dict(spatial_dims=3, +# in_channels=1, +# out_channels=6, +# backbone='resnet34', +# pretrained=False) + +# net = TestNet(**backbone_args) +# sd = torch.load(CKPT_FILE)['model'] +# net.eval().cuda() +# net.load_state_dict(sd) + +# static_transforms = mt.Compose([mt.EnsureChannelFirstd(keys=["image"], channel_dim="no_channel"),mt.NormalizeIntensityd(keys="image"),]) +# zarr_fn = '/mount/cryo/data/czii-cryo-et-object-identification/train/static/ExperimentRuns/TS_5_4/VoxelSpacing10.000/denoised.zarr' +# img = preprocess_img(zarr_fn,static_transforms) # torch.Size([1, 630, 630, 184]) + +# patch = img[None, :, :96, :96, :96] # torch.Size([1, ,1 96, 96, 96]) + +# logits = net(patch.cuda()) +# proba_heatmap = logits.softmax(1)[:,[0,2,3,4,5,1]] \ No newline at end of file diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py new file mode 100644 index 0000000000..3be9890e54 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py @@ -0,0 +1,62 @@ + + +import pandas as pd +import torch +from torch.nn import functional as F +from tqdm import tqdm + +import torch +from torch import nn + + + +def simple_nms(scores, nms_radius: int): + """ Fast Non-maximum suppression to remove nearby points """ + assert(nms_radius >= 0) + + def max_pool(x): + return torch.nn.functional.max_pool3d(x, kernel_size=nms_radius*2+1, stride=1, padding=nms_radius) + + zeros = torch.zeros_like(scores) + max_mask = scores == max_pool(scores) + return torch.where(max_mask, scores, zeros) + +def reconstruct(img, locations, out_size, crop_size): + reconstructed_img = torch.zeros(out_size) + + for i in range(img.shape[0]): + reconstructed_img[:,locations[0][i]:locations[0][i]+crop_size[0], + locations[1][i]:locations[1][i]+crop_size[1], + locations[2][i]:locations[2][i]+crop_size[2], + ] = img[i,:] + return reconstructed_img + + +def post_process_pipeline(cfg, val_data, val_df): + + img = val_data['logits'] + img = torch.nn.functional.interpolate(img, size=(cfg.roi_size[0],cfg.roi_size[1],cfg.roi_size[2]), mode='trilinear', align_corners=False) + locations = val_data['location'] + out_size = [cfg.n_classes + 1] + [l.item()+r for l,r in zip(locations.max(0)[0],cfg.roi_size)] + rec_img = reconstruct(img, locations.permute(1,0), out_size=out_size, crop_size=cfg.roi_size) + s = rec_img.shape[-3:] + rec_img = torch.nn.functional.interpolate(rec_img[None], size=(s[0]//2,s[1]//2,s[2]//2), mode='trilinear', align_corners=False)[0] + preds = rec_img.softmax(0)[:-1] + + pred_df = [] + + for i,p in enumerate(cfg.classes): + p1 = preds[i][None,].cuda() + y = simple_nms(p1, nms_radius=int(0.5 * cfg.particle_radi[p]/10)) + kps = torch.where(y>0) + xyz = torch.stack(kps[1:],-1) * 10 * 2 + conf = y[kps] + pred_df_ = pd.DataFrame(xyz.cpu().numpy(),columns=['x','y','z']) + pred_df_['particle_type'] = p + pred_df_['conf'] = conf.cpu().numpy() +# pred_df_['experiment'] = experiments[fold] + pred_df += [pred_df_] + pred_df = pd.concat(pred_df) + pred_df = pred_df[(pred_df['x']<6300) & (pred_df['y']<6300)& (pred_df['z']<1840) & (pred_df['conf']>0.01)].copy() + pred_df.to_csv(f"{cfg.output_dir}/fold{cfg.fold}/val_pred_df_seed{cfg.seed}.csv",index=False) + return pred_df diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt b/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt new file mode 100644 index 0000000000..07d6d142fa --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt @@ -0,0 +1,10 @@ +kaggle +optuna +boto3 +neptune +zarr +albumentations==1.4.21 +opencv-python==4.5.5.64 +timm==1.0.11 +monai==1.4.0 +mrcfile \ No newline at end of file diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py new file mode 100644 index 0000000000..fe6002fe6e --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py @@ -0,0 +1,469 @@ +import numpy as np +import pandas as pd +import importlib +import sys +from tqdm import tqdm +import gc +import argparse +import torch +import math +try: + from torch.amp import GradScaler, autocast +except: + from torch.cuda.amp import GradScaler, autocast +from torch.nn.parallel import DistributedDataParallel as NativeDDP +from collections import defaultdict + +from utils import ( + sync_across_gpus, + set_seed, + get_model, + create_checkpoint, + load_checkpoint, + get_data, + get_dataset, + get_dataloader, + calc_grad_norm, + calc_weight_norm, +) +from utils import ( + get_optimizer, + get_scheduler, + setup_neptune, + upload_s3, +) + + +from copy import copy +import os + +os.environ["OMP_NUM_THREADS"] = "1" +os.environ["MKL_NUM_THREADS"] = "1" +os.environ["OPENBLAS_NUM_THREADS"] = "1" +os.environ["VECLIB_MAXIMUM_THREADS"] = "1" +os.environ["NUMEXPR_NUM_THREADS"] = "1" +os.environ["TOKENIZERS_PARALLELISM"] = "false" + +try: + import cv2 + cv2.setNumThreads(0) +except: + print('no cv2 installed, running without') + + +sys.path.append("configs") +sys.path.append("models") +sys.path.append("data") +sys.path.append("postprocess") +sys.path.append("metrics") + + +def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): + saved_images = False + model.eval() + torch.set_grad_enabled(False) + + # store information for evaluation + val_data = defaultdict(list) + val_score =0 + for ind_, data in enumerate(tqdm(val_dataloader, disable=(cfg.local_rank != 0) | cfg.disable_tqdm)): + + batch = cfg.batch_to_device(data, cfg.device) + + if cfg.mixed_precision: + with autocast('cuda'): + output = model(batch) + else: + output = model(batch) + + if (cfg.local_rank == 0) and (cfg.calc_metric) and (((curr_epoch + 1) % cfg.calc_metric_epochs) == 0): + # per batch calculations + pass + + if (not saved_images) & (cfg.save_first_batch_preds): + save_first_batch_preds(batch, output, cfg) + saved_images = True + + for key, val in output.items(): + val_data[key] += [output[key]] + + for key, val in output.items(): + value = val_data[key] + if isinstance(value[0], list): + val_data[key] = [item for sublist in value for item in sublist] + + else: + if len(value[0].shape) == 0: + val_data[key] = torch.stack(value) + else: + val_data[key] = torch.cat(value, dim=0) + + if (cfg.local_rank == 0) and (cfg.calc_metric) and (((curr_epoch + 1) % cfg.calc_metric_epochs) == 0): + pass + + if cfg.distributed and cfg.eval_ddp: + for key, val in output.items(): + val_data[key] = sync_across_gpus(val_data[key], cfg.world_size) + + if cfg.local_rank == 0: + if cfg.save_val_data: + if cfg.distributed: + for k, v in val_data.items(): + val_data[k] = v[: len(val_dataloader.dataset)] + torch.save(val_data, f"{cfg.output_dir}/fold{cfg.fold}/{pre}_data_seed{cfg.seed}.pth") + + loss_names = [key for key in output if 'loss' in key] + loss_names += [key for key in output if 'score' in key] + for k in loss_names: + if cfg.local_rank == 0 and k in val_data: + losses = val_data[k].cpu().numpy() + loss = np.mean(losses) + + print(f"Mean {pre}_{k}", loss) + if cfg.neptune_run: + if not math.isinf(loss) and not math.isnan(loss): + cfg.neptune_run[f"{pre}/{k}"].log(loss, step=cfg.curr_step) + + + if (cfg.local_rank == 0) and (cfg.calc_metric) and (((curr_epoch + 1) % cfg.calc_metric_epochs) == 0): + + val_df = val_dataloader.dataset.df + pp_out = cfg.post_process_pipeline(cfg, val_data, val_df) + val_score = cfg.calc_metric(cfg, pp_out, val_df, pre) + if type(val_score)!=dict: + val_score = {f'score':val_score} + + for k, v in val_score.items(): + print(f"{pre}_{k}: {v:.3f}") + if cfg.neptune_run: + if not math.isinf(v) and not math.isnan(v): + cfg.neptune_run[f"{pre}/{k}"].log(v, step=cfg.curr_step) + + if cfg.distributed: + torch.distributed.barrier() + +# print("EVAL FINISHED") + + return val_score + + +def train(cfg): + # set seed + if cfg.seed < 0: + cfg.seed = np.random.randint(1_000_000) + print("seed", cfg.seed) + + + if cfg.distributed: + + cfg.local_rank = int(os.environ["LOCAL_RANK"]) + device = "cuda:%d" % cfg.local_rank + cfg.device = device + + + + torch.cuda.set_device(cfg.local_rank) + torch.distributed.init_process_group(backend="nccl", init_method="env://") + cfg.world_size = torch.distributed.get_world_size() + cfg.rank = torch.distributed.get_rank() +# print("Training in distributed mode with multiple processes, 1 GPU per process.") + print(f"Process {cfg.rank}, total {cfg.world_size}, local rank {cfg.local_rank}.") + cfg.group = torch.distributed.new_group(np.arange(cfg.world_size)) +# print("Group", cfg.group) + + # syncing the random seed + cfg.seed = int( + sync_across_gpus(torch.Tensor([cfg.seed]).to(device), cfg.world_size) + .detach() + .cpu() + .numpy()[0] + ) # + + print(f"LOCAL_RANK {cfg.local_rank}, device {device}, seed {cfg.seed}") + + else: + cfg.local_rank = 0 + cfg.world_size = 1 + cfg.rank = 0 # global rank + + device = "cuda:%d" % cfg.gpu + cfg.device = device + + set_seed(cfg.seed) + + if cfg.local_rank == 0: + cfg.neptune_run = setup_neptune(cfg) + + train_df, val_df, test_df = get_data(cfg) + + train_dataset = get_dataset(train_df, cfg, mode='train') + train_dataloader = get_dataloader(train_dataset, cfg, mode='train') + + val_dataset = get_dataset(val_df, cfg, mode='val') + val_dataloader = get_dataloader(val_dataset, cfg, mode='val') + + if cfg.test: + test_dataset = get_dataset(test_df, cfg, mode='test') + test_dataloader = get_dataloader(test_dataset, cfg, mode='test') + + if cfg.train_val: + train_val_dataset = get_dataset(train_df, cfg, mode='val') + train_val_dataloader = get_dataloader(train_val_dataset, cfg, 'val') + + model = get_model(cfg, train_dataset) + if cfg.compile_model: + print('compiling model') + model = torch.compile(model) + model.to(device) + + if cfg.distributed: + + if cfg.syncbn: + model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model) + + model = NativeDDP( + model, device_ids=[cfg.local_rank], find_unused_parameters=cfg.find_unused_parameters + ) + + total_steps = len(train_dataset) + if train_dataloader.sampler is not None: + if 'WeightedRandomSampler' in str(train_dataloader.sampler.__class__): + total_steps = train_dataloader.sampler.num_samples + + optimizer = get_optimizer(model, cfg) + scheduler = get_scheduler(cfg, optimizer, total_steps) + + if cfg.mixed_precision: + scaler = GradScaler() + else: + scaler = None + + cfg.curr_step = 0 + i = 0 + best_val_loss = np.inf + optimizer.zero_grad() + total_grad_norm = None + total_weight_norm = None + total_grad_norm_after_clip = None + + for epoch in range(cfg.epochs): + + set_seed(cfg.seed + epoch + cfg.local_rank) + + cfg.curr_epoch = epoch + if cfg.local_rank == 0: + print("EPOCH:", epoch) + + + if cfg.distributed: + train_dataloader.sampler.set_epoch(epoch) + + progress_bar = tqdm(range(len(train_dataloader)),disable=cfg.disable_tqdm) + tr_it = iter(train_dataloader) + + losses = [] + + gc.collect() + + if cfg.train: + # ==== TRAIN LOOP + for itr in progress_bar: + i += 1 + + cfg.curr_step += cfg.batch_size * cfg.world_size + + try: + data = next(tr_it) + except Exception as e: + print(e) + print("DATA FETCH ERROR") + # continue + + + model.train() + torch.set_grad_enabled(True) + + + batch = cfg.batch_to_device(data, device) + + if cfg.mixed_precision: + with autocast('cuda'): + output_dict = model(batch) + else: + if cfg.bf16: + with autocast('cuda',dtype=torch.bfloat16): + output_dict = model(batch) + else: + output_dict = model(batch) + + loss = output_dict["loss"] + + losses.append(loss.item()) + + if cfg.grad_accumulation >1: + loss /= cfg.grad_accumulation + + # Backward pass + + if cfg.mixed_precision: + scaler.scale(loss).backward() + + if i % cfg.grad_accumulation == 0: + if (cfg.track_grad_norm) or (cfg.clip_grad > 0): + scaler.unscale_(optimizer) + if cfg.track_grad_norm: + total_grad_norm = calc_grad_norm(model.parameters(), cfg.grad_norm_type) + if cfg.clip_grad > 0: + torch.nn.utils.clip_grad_norm_(model.parameters(), cfg.clip_grad) + if cfg.track_grad_norm: + total_grad_norm_after_clip = calc_grad_norm(model.parameters(), cfg.grad_norm_type) + scaler.step(optimizer) + scaler.update() + optimizer.zero_grad() + + else: + + loss.backward() + if i % cfg.grad_accumulation == 0: + if cfg.track_grad_norm: + total_grad_norm = calc_grad_norm(model.parameters()) + if cfg.clip_grad > 0: + torch.nn.utils.clip_grad_norm_(model.parameters(), cfg.clip_grad) + if cfg.track_grad_norm: + total_grad_norm_after_clip = calc_grad_norm(model.parameters(), cfg.grad_norm_type) + if cfg.track_weight_norm: + total_weight_norm = calc_weight_norm(model.parameters(), cfg.grad_norm_type) + optimizer.step() + optimizer.zero_grad() + # print(optimizer.state_dict()) + # break + + if cfg.distributed: + torch.cuda.synchronize() + + if scheduler is not None: + scheduler.step() + + if cfg.local_rank == 0 and cfg.curr_step % cfg.batch_size == 0: + + loss_names = [key for key in output_dict if 'loss' in key] + if cfg.neptune_run: + for l in loss_names: + v = output_dict[l].item() + if not math.isinf(v) and not math.isnan(v): + cfg.neptune_run[f"train/{l}"].log(value=v, step=cfg.curr_step) + cfg.neptune_run["lr"].log(value=optimizer.param_groups[0]["lr"], step=cfg.curr_step) + if total_grad_norm is not None: + cfg.neptune_run["total_grad_norm"].log(value=total_grad_norm.item(), step=cfg.curr_step) + cfg.neptune_run["total_grad_norm_after_clip"].log(value=total_grad_norm_after_clip.item(), step=cfg.curr_step) + if total_weight_norm is not None: + cfg.neptune_run["total_weight_norm"].log(value=total_weight_norm.item(), step=cfg.curr_step) + progress_bar.set_description(f"loss: {np.mean(losses[-10:]):.4f}") + + if cfg.eval_steps != 0: + if i % cfg.eval_steps == 0: + if cfg.distributed and cfg.eval_ddp: + val_loss = run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=epoch) + else: + if cfg.local_rank == 0: + val_loss = run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=epoch) + else: + val_score = 0 + + print(f"Mean train_loss {np.mean(losses):.4f}") + + if cfg.distributed: + torch.cuda.synchronize() + if cfg.force_fp16: + model = model.half().float() + if cfg.val: + + if (epoch + 1) % cfg.eval_epochs == 0 or (epoch + 1) == cfg.epochs: + if cfg.distributed and cfg.eval_ddp: + val_score = run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=epoch) + else: + if cfg.local_rank == 0: + val_score = run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=epoch) + else: + val_score = 0 + + if cfg.train_val == True: + if (epoch + 1) % cfg.eval_train_epochs == 0 or (epoch + 1) == cfg.epochs: + if cfg.distributed and cfg.eval_ddp: + _ = get_preds(model, train_val_dataloader, cfg, pre=cfg.pre_train_val) + + else: + if cfg.local_rank == 0: + _ = get_preds(model, train_val_dataloader, cfg, pre=cfg.pre_train_val) + + + if cfg.distributed: + torch.distributed.barrier() + + if (cfg.local_rank == 0) and (cfg.epochs > 0) and (cfg.save_checkpoint): + if not cfg.save_only_last_ckpt: + checkpoint = create_checkpoint(cfg, model, optimizer, epoch, scheduler=scheduler, scaler=scaler) + + torch.save( + checkpoint, f"{cfg.output_dir}/fold{cfg.fold}/checkpoint_last_seed{cfg.seed}.pth" + ) + + if (cfg.local_rank == 0) and (cfg.epochs > 0) and (cfg.save_checkpoint): + checkpoint = create_checkpoint(cfg, model, optimizer, epoch, scheduler=scheduler, scaler=scaler) + + torch.save( + checkpoint, f"{cfg.output_dir}/fold{cfg.fold}/checkpoint_last_seed{cfg.seed}.pth" + ) + + if cfg.test: + run_eval(model, test_dataloader, test_df, cfg, pre="test") + + return val_score + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="") + + parser.add_argument("-C", "--config", help="config filename") + parser.add_argument("-D", "--debug", action='store_true', help="debugging True/ False") + parser_args, other_args = parser.parse_known_args(sys.argv) + + cfg = copy(importlib.import_module(parser_args.config).cfg) + + if parser_args.debug: + print('debug mode') + cfg.neptune_connection_mode = 'debug' + + + # overwrite params in config with additional args + if len(other_args) > 1: + other_args = {k.replace('-',''):v for k, v in zip(other_args[1::2], other_args[2::2])} + + for key in other_args: + if key in cfg.__dict__: + + print(f'overwriting cfg.{key}: {cfg.__dict__[key]} -> {other_args[key]}') + cfg_type = type(cfg.__dict__[key]) + if other_args[key] == 'None': + cfg.__dict__[key] = None + elif cfg_type == bool: + cfg.__dict__[key] = other_args[key] == 'True' + elif cfg_type == type(None): + cfg.__dict__[key] = other_args[key] + else: + cfg.__dict__[key] = cfg_type(other_args[key]) + + + os.makedirs(str(cfg.output_dir + f"/fold{cfg.fold}/"), exist_ok=True) + + cfg.CustomDataset = importlib.import_module(cfg.dataset).CustomDataset + cfg.tr_collate_fn = importlib.import_module(cfg.dataset).tr_collate_fn + cfg.val_collate_fn = importlib.import_module(cfg.dataset).val_collate_fn + cfg.batch_to_device = importlib.import_module(cfg.dataset).batch_to_device + + cfg.post_process_pipeline = importlib.import_module(cfg.post_process_pipeline).post_process_pipeline + cfg.calc_metric = importlib.import_module(cfg.metric).calc_metric + + result = train(cfg) + print(result) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/train_folded_v1.csv b/competitions/kaggle/Cryo-ET/1st_place_solution/train_folded_v1.csv new file mode 100644 index 0000000000..3b4c536697 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/train_folded_v1.csv @@ -0,0 +1,1270 @@ +x,y,z,particle_type,experiment,fold +5417.091,1120.574,567.527,beta-amylase,TS_5_4,0 +4340.0,1220.0,365.0,beta-amylase,TS_5_4,0 +4908.973,1745.891,279.668,beta-amylase,TS_5_4,0 +5153.2,4595.2,420.8,beta-amylase,TS_5_4,0 +1500.0,4212.222,1016.667,beta-amylase,TS_5_4,0 +1068.16,3620.736,1033.129,beta-amylase,TS_5_4,0 +1025.185,2153.333,1215.556,beta-amylase,TS_5_4,0 +1750.0,2648.75,881.25,beta-amylase,TS_5_4,0 +2609.328,2513.109,1131.555,beta-amylase,TS_5_4,0 +3634.655,3226.691,1280.655,beta-amylase,TS_5_4,0 +1959.181,701.502,371.229,beta-galactosidase,TS_5_4,0 +2135.713,4459.195,936.655,beta-galactosidase,TS_5_4,0 +3449.782,5052.93,511.041,beta-galactosidase,TS_5_4,0 +4623.597,3652.095,1161.66,beta-galactosidase,TS_5_4,0 +1390.238,4121.667,570.595,beta-galactosidase,TS_5_4,0 +1317.617,2292.128,1089.064,beta-galactosidase,TS_5_4,0 +3540.625,1851.25,50.0,beta-galactosidase,TS_5_4,0 +5421.651,1158.868,563.302,beta-galactosidase,TS_5_4,0 +5132.863,4594.658,436.41,beta-galactosidase,TS_5_4,0 +455.241,2838.414,623.402,beta-galactosidase,TS_5_4,0 +766.848,1452.913,700.413,beta-galactosidase,TS_5_4,0 +4048.927,5220.881,1249.54,beta-galactosidase,TS_5_4,0 +4601.271,601.066,600.934,ribosome,TS_5_4,0 +4803.789,455.425,514.016,ribosome,TS_5_4,0 +4715.436,825.374,802.166,ribosome,TS_5_4,0 +5003.275,782.745,802.48,ribosome,TS_5_4,0 +710.459,3815.845,1405.435,ribosome,TS_5_4,0 +2421.852,1141.637,663.19,ribosome,TS_5_4,0 +691.64,897.383,288.35,ribosome,TS_5_4,0 +948.219,2155.294,372.13,ribosome,TS_5_4,0 +1910.761,755.539,1052.241,ribosome,TS_5_4,0 +1610.634,232.123,1068.46,ribosome,TS_5_4,0 +4217.488,4192.518,941.839,ribosome,TS_5_4,0 +4725.193,4090.105,1110.456,ribosome,TS_5_4,0 +2545.405,3069.551,1119.704,ribosome,TS_5_4,0 +4091.497,2159.453,874.047,ribosome,TS_5_4,0 +3986.554,1885.673,744.371,ribosome,TS_5_4,0 +3822.144,1883.156,1000.826,ribosome,TS_5_4,0 +2803.199,2701.106,336.099,ribosome,TS_5_4,0 +3189.676,2565.629,306.801,ribosome,TS_5_4,0 +3526.519,2317.862,1059.979,ribosome,TS_5_4,0 +4632.091,2990.234,244.138,ribosome,TS_5_4,0 +4620.799,2802.16,513.43,ribosome,TS_5_4,0 +4809.673,2925.589,730.053,ribosome,TS_5_4,0 +5072.909,2902.993,763.206,ribosome,TS_5_4,0 +4790.666,2517.002,489.436,ribosome,TS_5_4,0 +4805.633,2619.184,874.657,ribosome,TS_5_4,0 +5056.98,2532.435,877.002,ribosome,TS_5_4,0 +3397.301,4660.778,1309.286,ribosome,TS_5_4,0 +2843.556,6150.71,818.754,ribosome,TS_5_4,0 +4195.391,3961.637,866.076,ribosome,TS_5_4,0 +4142.395,2205.03,1126.4,ribosome,TS_5_4,0 +4356.021,3945.454,1156.437,ribosome,TS_5_4,0 +4527.706,221.468,279.908,thyroglobulin,TS_5_4,0 +5458.275,3743.418,332.075,thyroglobulin,TS_5_4,0 +5086.266,5378.977,411.051,thyroglobulin,TS_5_4,0 +2091.242,489.208,433.742,thyroglobulin,TS_5_4,0 +4338.644,1822.464,455.595,thyroglobulin,TS_5_4,0 +1310.154,4513.909,442.042,thyroglobulin,TS_5_4,0 +1585.642,3326.048,517.486,thyroglobulin,TS_5_4,0 +2293.097,1869.88,555.296,thyroglobulin,TS_5_4,0 +3469.929,3753.49,782.101,thyroglobulin,TS_5_4,0 +2439.53,3199.849,797.353,thyroglobulin,TS_5_4,0 +3088.296,2349.548,839.11,thyroglobulin,TS_5_4,0 +756.786,3307.765,923.717,thyroglobulin,TS_5_4,0 +3095.628,5843.591,947.596,thyroglobulin,TS_5_4,0 +2273.228,1492.983,949.712,thyroglobulin,TS_5_4,0 +5121.456,5468.983,1005.202,thyroglobulin,TS_5_4,0 +474.398,3091.932,1076.289,thyroglobulin,TS_5_4,0 +1541.559,5808.044,1379.853,thyroglobulin,TS_5_4,0 +441.346,2266.635,835.865,thyroglobulin,TS_5_4,0 +873.75,2086.875,1055.312,thyroglobulin,TS_5_4,0 +2405.172,6025.0,563.966,thyroglobulin,TS_5_4,0 +1990.0,5790.0,870.0,thyroglobulin,TS_5_4,0 +3087.172,5274.276,700.759,thyroglobulin,TS_5_4,0 +2917.805,3958.293,730.732,thyroglobulin,TS_5_4,0 +3419.647,2830.627,459.333,thyroglobulin,TS_5_4,0 +5090.0,573.478,122.174,thyroglobulin,TS_5_4,0 +2175.491,3100.549,452.861,thyroglobulin,TS_5_4,0 +2335.068,4957.432,703.716,thyroglobulin,TS_5_4,0 +2752.287,5760.574,881.249,thyroglobulin,TS_5_4,0 +2613.583,2253.208,884.042,thyroglobulin,TS_5_4,0 +534.129,1666.452,1038.0,thyroglobulin,TS_5_4,0 +626.933,1151.091,637.139,virus-like-particle,TS_5_4,0 +1480.552,1278.98,820.227,virus-like-particle,TS_5_4,0 +1251.324,5451.893,735.631,virus-like-particle,TS_5_4,0 +117.204,5393.268,923.733,virus-like-particle,TS_5_4,0 +672.259,3039.65,711.322,virus-like-particle,TS_5_4,0 +2620.699,3741.685,865.897,virus-like-particle,TS_5_4,0 +2636.539,4214.98,965.41,virus-like-particle,TS_5_4,0 +3137.396,3572.46,372.914,virus-like-particle,TS_5_4,0 +3294.133,3027.464,674.07,virus-like-particle,TS_5_4,0 +2997.686,4948.218,1169.375,virus-like-particle,TS_5_4,0 +3435.851,6177.824,1016.473,virus-like-particle,TS_5_4,0 +468.514,5915.906,604.167,apo-ferritin,TS_5_4,0 +5674.694,1114.354,565.068,apo-ferritin,TS_5_4,0 +5744.509,1049.172,653.712,apo-ferritin,TS_5_4,0 +5880.769,1125.348,579.56,apo-ferritin,TS_5_4,0 +4661.667,1269.497,810.409,apo-ferritin,TS_5_4,0 +4593.498,1195.874,877.982,apo-ferritin,TS_5_4,0 +4569.735,1249.967,735.033,apo-ferritin,TS_5_4,0 +4524.726,1359.207,763.445,apo-ferritin,TS_5_4,0 +1072.449,2963.265,384.014,apo-ferritin,TS_5_4,0 +1068.182,3024.848,515.628,apo-ferritin,TS_5_4,0 +995.294,3029.412,407.899,apo-ferritin,TS_5_4,0 +969.749,3086.695,557.95,apo-ferritin,TS_5_4,0 +1088.28,3115.16,609.8,apo-ferritin,TS_5_4,0 +1130.0,3130.0,795.0,apo-ferritin,TS_5_4,0 +891.818,2977.273,510.606,apo-ferritin,TS_5_4,0 +924.121,3210.767,736.134,apo-ferritin,TS_5_4,0 +1663.767,3311.767,1051.7,apo-ferritin,TS_5_4,0 +5870.268,5131.104,76.321,apo-ferritin,TS_5_4,0 +4807.948,4949.03,1039.478,apo-ferritin,TS_5_4,0 +4837.967,5049.35,1030.081,apo-ferritin,TS_5_4,0 +4714.452,5064.976,1077.238,apo-ferritin,TS_5_4,0 +5062.45,5082.947,1205.927,apo-ferritin,TS_5_4,0 +4829.392,4473.384,208.707,apo-ferritin,TS_5_4,0 +3684.954,4537.92,639.235,apo-ferritin,TS_5_4,0 +3738.736,4124.944,1182.974,apo-ferritin,TS_5_4,0 +2682.13,3630.154,642.963,apo-ferritin,TS_5_4,0 +2801.725,1630.415,204.473,apo-ferritin,TS_5_4,0 +2504.12,2311.852,267.454,apo-ferritin,TS_5_4,0 +2499.282,2197.394,298.989,apo-ferritin,TS_5_4,0 +2626.828,2295.534,254.337,apo-ferritin,TS_5_4,0 +2552.162,2711.815,416.023,apo-ferritin,TS_5_4,0 +609.824,4421.479,678.38,apo-ferritin,TS_5_4,0 +521.952,4275.286,650.429,apo-ferritin,TS_5_4,0 +612.529,4271.092,494.828,apo-ferritin,TS_5_4,0 +4657.21,5130.873,989.265,apo-ferritin,TS_5_4,0 +1151.918,3210.74,696.075,apo-ferritin,TS_5_4,0 +1038.494,3201.114,695.662,apo-ferritin,TS_5_4,0 +1059.84,3172.472,836.249,apo-ferritin,TS_5_4,0 +1066.853,3207.492,585.938,apo-ferritin,TS_5_4,0 +2340.306,2283.227,345.429,apo-ferritin,TS_5_4,0 +2349.924,2329.502,485.604,apo-ferritin,TS_5_4,0 +3779.642,4198.01,1296.612,apo-ferritin,TS_5_4,0 +611.797,4393.753,515.641,apo-ferritin,TS_5_4,0 +557.665,4367.989,605.753,apo-ferritin,TS_5_4,0 +5715.014,4998.216,115.143,apo-ferritin,TS_5_4,0 +5748.253,5115.846,108.033,apo-ferritin,TS_5_4,0 +320.256,5482.051,1088.205,beta-amylase,TS_86_3,1 +4290.0,2290.0,1050.0,beta-amylase,TS_86_3,1 +4268.588,1190.235,706.118,beta-amylase,TS_86_3,1 +3693.165,1512.658,849.62,beta-amylase,TS_86_3,1 +4384.167,445.833,1119.167,beta-amylase,TS_86_3,1 +5437.751,1527.337,824.201,beta-amylase,TS_86_3,1 +5509.412,4910.588,701.569,beta-amylase,TS_86_3,1 +2247.754,378.116,1050.217,beta-amylase,TS_86_3,1 +1188.393,2327.798,696.845,beta-amylase,TS_86_3,1 +348.342,3368.995,520.0,beta-galactosidase,TS_86_3,1 +3111.559,1404.395,597.264,beta-galactosidase,TS_86_3,1 +5128.266,4932.229,585.512,beta-galactosidase,TS_86_3,1 +3984.278,423.056,725.944,beta-galactosidase,TS_86_3,1 +2763.251,3038.958,855.062,beta-galactosidase,TS_86_3,1 +4073.83,3599.172,1070.207,beta-galactosidase,TS_86_3,1 +4463.183,1681.903,1096.54,beta-galactosidase,TS_86_3,1 +1304.049,2964.441,1180.928,beta-galactosidase,TS_86_3,1 +5695.682,831.818,827.273,beta-galactosidase,TS_86_3,1 +2369.583,5805.208,949.167,beta-galactosidase,TS_86_3,1 +1808.132,5982.125,1006.52,beta-galactosidase,TS_86_3,1 +1549.167,3946.437,574.713,beta-galactosidase,TS_86_3,1 +4913.909,2197.738,878.413,beta-galactosidase,TS_86_3,1 +565.0,1044.148,1032.481,beta-galactosidase,TS_86_3,1 +1908.833,229.833,948.167,beta-galactosidase,TS_86_3,1 +2474.59,4055.164,488.224,beta-galactosidase,TS_86_3,1 +4085.02,1689.352,644.332,beta-galactosidase,TS_86_3,1 +2511.429,480.0,807.619,beta-galactosidase,TS_86_3,1 +4450.0,1356.667,876.667,beta-galactosidase,TS_86_3,1 +2000.686,1232.08,923.473,beta-galactosidase,TS_86_3,1 +3552.92,3973.993,1034.795,beta-galactosidase,TS_86_3,1 +5021.325,3944.498,1080.0,beta-galactosidase,TS_86_3,1 +2745.385,872.86,1044.339,beta-galactosidase,TS_86_3,1 +4662.299,5907.258,945.084,ribosome,TS_86_3,1 +4504.116,5758.297,1102.158,ribosome,TS_86_3,1 +4462.021,6207.162,999.208,ribosome,TS_86_3,1 +4777.299,6156.435,1063.849,ribosome,TS_86_3,1 +4666.205,5215.513,747.842,ribosome,TS_86_3,1 +4438.999,4935.4,788.27,ribosome,TS_86_3,1 +5252.162,4295.573,849.171,ribosome,TS_86_3,1 +5689.451,4321.102,893.643,ribosome,TS_86_3,1 +6095.703,4218.054,560.215,ribosome,TS_86_3,1 +4798.895,4427.67,644.16,ribosome,TS_86_3,1 +4790.113,4162.789,708.444,ribosome,TS_86_3,1 +4327.26,4594.434,562.929,ribosome,TS_86_3,1 +4530.23,4362.667,507.066,ribosome,TS_86_3,1 +4685.378,4744.749,747.362,ribosome,TS_86_3,1 +4546.785,4603.197,1000.327,ribosome,TS_86_3,1 +5815.688,5910.474,1112.865,ribosome,TS_86_3,1 +3558.463,2113.569,1179.779,ribosome,TS_86_3,1 +2024.391,3063.731,968.932,ribosome,TS_86_3,1 +1917.914,2905.379,1174.505,ribosome,TS_86_3,1 +1649.253,2893.382,1100.787,ribosome,TS_86_3,1 +1665.413,3181.988,1083.413,ribosome,TS_86_3,1 +1563.059,6022.91,504.966,ribosome,TS_86_3,1 +1217.9,3972.649,1086.719,ribosome,TS_86_3,1 +1352.382,4356.122,751.612,ribosome,TS_86_3,1 +1213.789,4280.218,1050.137,ribosome,TS_86_3,1 +1444.336,4663.022,1049.317,ribosome,TS_86_3,1 +1974.574,4645.358,1227.115,ribosome,TS_86_3,1 +1764.31,4357.96,1082.894,ribosome,TS_86_3,1 +1677.802,4682.915,728.72,ribosome,TS_86_3,1 +615.248,5884.541,961.487,ribosome,TS_86_3,1 +253.933,5845.176,596.784,ribosome,TS_86_3,1 +238.655,5198.089,1175.451,ribosome,TS_86_3,1 +571.25,4918.854,1019.915,ribosome,TS_86_3,1 +536.338,5199.648,1025.404,ribosome,TS_86_3,1 +559.463,4480.427,1114.371,ribosome,TS_86_3,1 +420.204,4238.479,1147.024,ribosome,TS_86_3,1 +92.928,4298.622,1089.994,ribosome,TS_86_3,1 +5020.213,4244.872,791.973,ribosome,TS_86_3,1 +1452.097,5112.11,1022.865,ribosome,TS_86_3,1 +4438.741,4920.707,515.641,ribosome,TS_86_3,1 +4518.449,4937.775,1036.288,ribosome,TS_86_3,1 +4565.882,4348.095,775.964,ribosome,TS_86_3,1 +5572.189,4125.515,785.977,ribosome,TS_86_3,1 +5818.377,4480.871,856.064,ribosome,TS_86_3,1 +5938.142,4347.523,1016.263,ribosome,TS_86_3,1 +5416.05,4499.133,856.064,ribosome,TS_86_3,1 +5931.509,4108.851,866.076,ribosome,TS_86_3,1 +1959.59,4591.854,903.764,ribosome,TS_86_3,1 +1358.159,4893.146,936.164,ribosome,TS_86_3,1 +1285.497,4093.274,632.591,ribosome,TS_86_3,1 +1297.423,4036.911,866.076,ribosome,TS_86_3,1 +1633.782,4376.815,745.927,ribosome,TS_86_3,1 +616.559,5883.402,685.852,ribosome,TS_86_3,1 +513.851,5639.86,836.039,ribosome,TS_86_3,1 +4341.534,4578.56,816.014,ribosome,TS_86_3,1 +3491.458,1964.375,301.542,thyroglobulin,TS_86_3,1 +1059.895,338.01,466.754,thyroglobulin,TS_86_3,1 +5583.535,5055.373,522.266,thyroglobulin,TS_86_3,1 +3753.362,3195.014,497.106,thyroglobulin,TS_86_3,1 +4845.469,5984.182,512.044,thyroglobulin,TS_86_3,1 +3528.827,6079.586,566.737,thyroglobulin,TS_86_3,1 +4527.884,512.538,643.163,thyroglobulin,TS_86_3,1 +5107.291,5147.205,654.024,thyroglobulin,TS_86_3,1 +4062.524,5536.16,724.915,thyroglobulin,TS_86_3,1 +4426.324,5717.765,731.724,thyroglobulin,TS_86_3,1 +381.507,338.051,836.647,thyroglobulin,TS_86_3,1 +4184.942,666.409,809.653,thyroglobulin,TS_86_3,1 +1339.037,3068.829,802.446,thyroglobulin,TS_86_3,1 +2420.207,148.637,863.583,thyroglobulin,TS_86_3,1 +4547.914,2728.749,949.698,thyroglobulin,TS_86_3,1 +1358.067,798.14,896.974,thyroglobulin,TS_86_3,1 +5038.553,1656.659,993.759,thyroglobulin,TS_86_3,1 +3840.899,5011.353,972.844,thyroglobulin,TS_86_3,1 +4325.738,386.706,1027.049,thyroglobulin,TS_86_3,1 +3082.518,2718.95,1080.23,thyroglobulin,TS_86_3,1 +1856.208,5223.976,1135.201,thyroglobulin,TS_86_3,1 +1212.201,2091.175,1244.156,thyroglobulin,TS_86_3,1 +5353.462,5348.654,880.096,thyroglobulin,TS_86_3,1 +2431.176,5760.392,683.137,thyroglobulin,TS_86_3,1 +5853.226,2606.774,184.839,thyroglobulin,TS_86_3,1 +3131.439,1631.365,1285.867,thyroglobulin,TS_86_3,1 +4843.009,1838.761,1023.053,thyroglobulin,TS_86_3,1 +3472.222,325.556,845.556,thyroglobulin,TS_86_3,1 +3170.0,370.0,940.0,thyroglobulin,TS_86_3,1 +2042.034,923.559,1130.339,thyroglobulin,TS_86_3,1 +2640.654,2140.759,734.476,thyroglobulin,TS_86_3,1 +1979.6,2052.4,692.8,thyroglobulin,TS_86_3,1 +3782.069,3580.345,988.621,thyroglobulin,TS_86_3,1 +394.643,2931.786,1176.786,thyroglobulin,TS_86_3,1 +2799.672,5092.022,408.47,thyroglobulin,TS_86_3,1 +4646.594,1023.948,467.896,thyroglobulin,TS_86_3,1 +1756.142,3207.48,651.26,thyroglobulin,TS_86_3,1 +2860.196,2950.392,609.216,thyroglobulin,TS_86_3,1 +1544.041,3483.215,690.914,thyroglobulin,TS_86_3,1 +5901.04,600.347,761.965,thyroglobulin,TS_86_3,1 +4904.507,2199.128,875.641,thyroglobulin,TS_86_3,1 +3333.953,3082.093,905.233,thyroglobulin,TS_86_3,1 +3923.75,5790.0,952.5,thyroglobulin,TS_86_3,1 +3840.601,1873.09,970.687,thyroglobulin,TS_86_3,1 +5164.483,2126.724,1004.655,thyroglobulin,TS_86_3,1 +154.098,4542.461,671.862,virus-like-particle,TS_86_3,1 +5162.937,6250.567,783.617,virus-like-particle,TS_86_3,1 +5478.422,5660.856,1001.878,virus-like-particle,TS_86_3,1 +6024.374,5816.326,630.589,virus-like-particle,TS_86_3,1 +5670.599,3861.008,454.412,virus-like-particle,TS_86_3,1 +6097.154,3365.906,615.748,virus-like-particle,TS_86_3,1 +5623.065,2769.741,432.341,virus-like-particle,TS_86_3,1 +3895.295,4743.936,724.292,virus-like-particle,TS_86_3,1 +3921.092,4158.7,972.267,virus-like-particle,TS_86_3,1 +2720.586,4840.967,1057.297,virus-like-particle,TS_86_3,1 +984.028,881.454,770.454,virus-like-particle,TS_86_3,1 +796.253,1232.922,956.509,virus-like-particle,TS_86_3,1 +1212.736,396.454,913.84,virus-like-particle,TS_86_3,1 +1234.637,1416.69,758.009,virus-like-particle,TS_86_3,1 +713.243,3300.755,729.3,virus-like-particle,TS_86_3,1 +449.787,3348.949,936.699,virus-like-particle,TS_86_3,1 +1024.638,2983.1,649.246,virus-like-particle,TS_86_3,1 +640.315,2221.381,868.266,virus-like-particle,TS_86_3,1 +3443.658,858.443,609.676,virus-like-particle,TS_86_3,1 +2733.056,387.938,935.431,virus-like-particle,TS_86_3,1 +3993.256,185.537,625.067,virus-like-particle,TS_86_3,1 +2891.009,1717.577,755.086,virus-like-particle,TS_86_3,1 +2785.126,2361.186,1008.411,virus-like-particle,TS_86_3,1 +4638.201,1844.865,491.223,virus-like-particle,TS_86_3,1 +4683.103,1546.998,899.626,virus-like-particle,TS_86_3,1 +3563.17,2650.076,660.386,virus-like-particle,TS_86_3,1 +3994.606,2797.533,839.291,virus-like-particle,TS_86_3,1 +3829.926,2129.623,859.549,virus-like-particle,TS_86_3,1 +1905.062,2172.381,978.064,virus-like-particle,TS_86_3,1 +3870.343,4952.714,1261.6,apo-ferritin,TS_86_3,1 +4130.897,5422.292,501.86,apo-ferritin,TS_86_3,1 +2735.0,4668.447,520.291,apo-ferritin,TS_86_3,1 +2649.615,4690.615,600.923,apo-ferritin,TS_86_3,1 +2665.353,4810.641,612.019,apo-ferritin,TS_86_3,1 +2957.522,4630.03,489.701,apo-ferritin,TS_86_3,1 +2920.264,4718.238,659.559,apo-ferritin,TS_86_3,1 +2996.667,4730.0,436.667,apo-ferritin,TS_86_3,1 +2957.199,4845.319,394.858,apo-ferritin,TS_86_3,1 +2858.555,4515.665,523.699,apo-ferritin,TS_86_3,1 +2802.396,4464.345,937.859,apo-ferritin,TS_86_3,1 +3636.645,5702.839,646.419,apo-ferritin,TS_86_3,1 +349.225,4387.254,639.613,apo-ferritin,TS_86_3,1 +1421.215,4837.57,473.209,apo-ferritin,TS_86_3,1 +1516.687,4884.498,530.122,apo-ferritin,TS_86_3,1 +1344.405,5016.071,464.077,apo-ferritin,TS_86_3,1 +1615.455,5224.058,418.604,apo-ferritin,TS_86_3,1 +1529.628,5302.061,442.027,apo-ferritin,TS_86_3,1 +1744.252,5121.395,462.245,apo-ferritin,TS_86_3,1 +1289.961,4419.807,498.842,apo-ferritin,TS_86_3,1 +368.896,5304.785,611.472,apo-ferritin,TS_86_3,1 +349.415,5404.185,663.938,apo-ferritin,TS_86_3,1 +737.13,5657.546,451.944,apo-ferritin,TS_86_3,1 +1152.315,5759.259,463.056,apo-ferritin,TS_86_3,1 +1223.792,5738.917,536.083,apo-ferritin,TS_86_3,1 +1111.487,5848.885,503.234,apo-ferritin,TS_86_3,1 +2024.386,2430.912,928.211,apo-ferritin,TS_86_3,1 +1885.083,2451.761,899.07,apo-ferritin,TS_86_3,1 +1635.436,2052.785,702.483,apo-ferritin,TS_86_3,1 +764.053,3009.015,780.114,apo-ferritin,TS_86_3,1 +805.603,2963.582,867.943,apo-ferritin,TS_86_3,1 +812.748,562.691,574.079,apo-ferritin,TS_86_3,1 +2398.017,1136.609,1115.057,apo-ferritin,TS_86_3,1 +2339.747,1240.886,1132.057,apo-ferritin,TS_86_3,1 +2423.333,1373.903,1099.801,apo-ferritin,TS_86_3,1 +1882.737,1238.321,1279.672,apo-ferritin,TS_86_3,1 +2886.727,662.649,515.325,apo-ferritin,TS_86_3,1 +3243.811,189.939,1204.756,apo-ferritin,TS_86_3,1 +5632.418,4832.451,852.157,apo-ferritin,TS_86_3,1 +5571.34,4880.069,923.814,apo-ferritin,TS_86_3,1 +5786.293,5116.573,858.037,apo-ferritin,TS_86_3,1 +5775.152,4995.212,878.485,apo-ferritin,TS_86_3,1 +4335.3,3517.367,1224.1,apo-ferritin,TS_86_3,1 +4765.574,621.401,780.112,apo-ferritin,TS_86_3,1 +4728.462,597.308,964.615,apo-ferritin,TS_86_3,1 +4784.136,555.39,853.254,apo-ferritin,TS_86_3,1 +3028.839,2861.645,444.29,apo-ferritin,TS_86_3,1 +3098.086,3170.695,1146.425,apo-ferritin,TS_86_3,1 +3192.802,3247.743,1146.425,apo-ferritin,TS_86_3,1 +3123.334,3157.011,1266.574,apo-ferritin,TS_86_3,1 +3197.58,5525.735,1296.612,apo-ferritin,TS_86_3,1 +1912.78,3850.979,1236.537,apo-ferritin,TS_86_3,1 +3760.282,4956.3,1186.475,apo-ferritin,TS_86_3,1 +982.232,4714.162,475.591,apo-ferritin,TS_86_3,1 +996.927,4489.487,475.591,apo-ferritin,TS_86_3,1 +745.902,4018.275,615.765,apo-ferritin,TS_86_3,1 +375.196,4211.727,615.765,apo-ferritin,TS_86_3,1 +258.592,4183.515,645.803,apo-ferritin,TS_86_3,1 +2084.869,5220.131,435.541,apo-ferritin,TS_86_3,1 +718.028,4094.611,565.703,apo-ferritin,TS_86_3,1 +457.252,4137.12,505.628,apo-ferritin,TS_86_3,1 +1361.928,5703.278,435.541,apo-ferritin,TS_86_3,1 +1112.671,5943.359,455.566,apo-ferritin,TS_86_3,1 +69.928,5362.53,555.691,apo-ferritin,TS_86_3,1 +3824.768,5064.901,676.026,beta-amylase,TS_69_2,2 +5166.324,3905.441,924.118,beta-amylase,TS_69_2,2 +5123.125,5389.375,945.625,beta-amylase,TS_69_2,2 +4407.778,6266.944,1228.333,beta-amylase,TS_69_2,2 +350.0,1220.0,1210.0,beta-amylase,TS_69_2,2 +1567.122,2131.079,687.05,beta-amylase,TS_69_2,2 +1466.143,913.857,1258.714,beta-amylase,TS_69_2,2 +2791.339,678.571,1164.821,beta-amylase,TS_69_2,2 +4378.638,762.894,913.191,beta-amylase,TS_69_2,2 +3569.083,427.11,935.688,beta-amylase,TS_69_2,2 +4442.424,2286.136,669.773,beta-amylase,TS_69_2,2 +4263.574,3363.976,1001.928,beta-amylase,TS_69_2,2 +3124.091,2349.758,914.818,beta-galactosidase,TS_69_2,2 +3104.737,116.316,893.158,beta-galactosidase,TS_69_2,2 +1482.15,1976.86,809.963,beta-galactosidase,TS_69_2,2 +4736.857,1633.5,1281.857,beta-galactosidase,TS_69_2,2 +5440.842,2153.316,1065.263,beta-galactosidase,TS_69_2,2 +5829.167,3585.0,1234.167,beta-galactosidase,TS_69_2,2 +5934.359,4038.974,711.538,beta-galactosidase,TS_69_2,2 +5565.034,3118.993,770.604,beta-galactosidase,TS_69_2,2 +1786.416,1087.714,590.104,beta-galactosidase,TS_69_2,2 +5058.789,2709.421,670.105,beta-galactosidase,TS_69_2,2 +1884.097,2071.736,719.583,beta-galactosidase,TS_69_2,2 +5685.556,4460.606,717.172,beta-galactosidase,TS_69_2,2 +4895.506,2074.177,754.272,beta-galactosidase,TS_69_2,2 +3093.723,2528.936,923.617,beta-galactosidase,TS_69_2,2 +5015.0,1340.263,944.649,beta-galactosidase,TS_69_2,2 +5835.63,5542.991,1125.63,beta-galactosidase,TS_69_2,2 +6006.82,5328.856,808.633,ribosome,TS_69_2,2 +5957.767,5571.663,706.068,ribosome,TS_69_2,2 +5618.836,6161.76,693.695,ribosome,TS_69_2,2 +1127.794,187.938,537.361,ribosome,TS_69_2,2 +1767.82,3213.929,1001.62,ribosome,TS_69_2,2 +1916.347,3426.268,1005.688,ribosome,TS_69_2,2 +1171.606,3275.311,1023.63,ribosome,TS_69_2,2 +1475.204,3418.907,1062.102,ribosome,TS_69_2,2 +855.205,1905.764,988.053,ribosome,TS_69_2,2 +1041.483,1639.013,1025.697,ribosome,TS_69_2,2 +588.724,1717.082,887.573,ribosome,TS_69_2,2 +1158.38,2417.84,824.652,ribosome,TS_69_2,2 +3944.471,877.964,875.367,ribosome,TS_69_2,2 +3800.311,509.266,1148.402,ribosome,TS_69_2,2 +5080.617,763.376,705.302,ribosome,TS_69_2,2 +4661.842,201.026,1140.471,ribosome,TS_69_2,2 +4909.496,342.802,905.927,ribosome,TS_69_2,2 +3032.898,1801.65,980.107,ribosome,TS_69_2,2 +3281.41,1786.652,1039.956,ribosome,TS_69_2,2 +3263.628,3307.453,718.927,ribosome,TS_69_2,2 +3204.775,3028.787,664.15,ribosome,TS_69_2,2 +3411.387,3674.855,1015.965,ribosome,TS_69_2,2 +2957.875,3215.688,651.509,ribosome,TS_69_2,2 +2988.816,2839.513,747.089,ribosome,TS_69_2,2 +3759.918,3733.779,875.956,ribosome,TS_69_2,2 +4064.384,3855.712,1073.271,ribosome,TS_69_2,2 +4093.64,2806.907,992.895,ribosome,TS_69_2,2 +4553.26,2854.694,934.778,ribosome,TS_69_2,2 +4833.247,2785.558,1008.777,ribosome,TS_69_2,2 +4982.25,3012.384,1141.249,ribosome,TS_69_2,2 +4746.456,3247.044,989.765,ribosome,TS_69_2,2 +6205.712,3190.031,1166.83,ribosome,TS_69_2,2 +5678.07,3034.789,1201.75,ribosome,TS_69_2,2 +5473.934,5538.159,1008.782,ribosome,TS_69_2,2 +1863.411,1820.198,1074.291,ribosome,TS_69_2,2 +4310.582,2600.476,966.971,ribosome,TS_69_2,2 +3825.977,2568.575,1072.554,ribosome,TS_69_2,2 +1697.664,2484.89,553.359,thyroglobulin,TS_69_2,2 +1083.746,4336.916,660.712,thyroglobulin,TS_69_2,2 +2535.083,3739.817,626.862,thyroglobulin,TS_69_2,2 +719.11,2318.413,647.963,thyroglobulin,TS_69_2,2 +270.918,4900.032,677.824,thyroglobulin,TS_69_2,2 +5371.849,897.815,674.958,thyroglobulin,TS_69_2,2 +5539.777,1677.178,752.772,thyroglobulin,TS_69_2,2 +2021.719,797.706,864.533,thyroglobulin,TS_69_2,2 +2367.94,978.443,867.55,thyroglobulin,TS_69_2,2 +5605.799,1623.846,1155.562,thyroglobulin,TS_69_2,2 +843.974,3537.848,538.51,thyroglobulin,TS_69_2,2 +714.545,3129.091,673.636,thyroglobulin,TS_69_2,2 +1456.572,4357.201,783.836,thyroglobulin,TS_69_2,2 +4435.591,5401.685,874.624,thyroglobulin,TS_69_2,2 +4315.138,4204.558,650.083,thyroglobulin,TS_69_2,2 +3988.304,3924.261,623.783,thyroglobulin,TS_69_2,2 +5049.901,4123.515,1092.673,thyroglobulin,TS_69_2,2 +5901.79,5390.067,1034.295,thyroglobulin,TS_69_2,2 +4435.398,6184.513,718.702,thyroglobulin,TS_69_2,2 +4195.77,2991.516,666.993,thyroglobulin,TS_69_2,2 +5720.0,1579.247,825.146,thyroglobulin,TS_69_2,2 +3050.0,2350.0,810.0,thyroglobulin,TS_69_2,2 +2991.25,1755.417,616.25,thyroglobulin,TS_69_2,2 +3004.667,1165.667,938.333,thyroglobulin,TS_69_2,2 +2035.125,145.5,1198.875,thyroglobulin,TS_69_2,2 +2021.887,1799.321,698.34,thyroglobulin,TS_69_2,2 +763.441,469.624,885.591,thyroglobulin,TS_69_2,2 +6229.592,394.49,1286.327,thyroglobulin,TS_69_2,2 +1369.833,5924.057,828.807,thyroglobulin,TS_69_2,2 +785.587,2630.0,576.201,thyroglobulin,TS_69_2,2 +3362.941,695.882,620.588,thyroglobulin,TS_69_2,2 +3625.942,4047.846,729.154,thyroglobulin,TS_69_2,2 +2618.947,369.774,749.173,thyroglobulin,TS_69_2,2 +5679.653,4176.597,1108.889,thyroglobulin,TS_69_2,2 +235.918,2967.62,799.798,virus-like-particle,TS_69_2,2 +516.962,4021.185,878.484,virus-like-particle,TS_69_2,2 +2832.249,750.827,654.317,virus-like-particle,TS_69_2,2 +2587.613,1360.659,814.115,virus-like-particle,TS_69_2,2 +5063.363,1732.215,951.33,virus-like-particle,TS_69_2,2 +5930.927,978.082,911.592,virus-like-particle,TS_69_2,2 +3603.379,2385.445,924.877,virus-like-particle,TS_69_2,2 +5125.065,5681.883,888.948,virus-like-particle,TS_69_2,2 +5924.779,3598.073,941.761,virus-like-particle,TS_69_2,2 +770.625,1111.161,1088.795,apo-ferritin,TS_69_2,2 +828.291,1201.673,1153.745,apo-ferritin,TS_69_2,2 +668.986,1041.449,1102.246,apo-ferritin,TS_69_2,2 +834.049,592.958,698.099,apo-ferritin,TS_69_2,2 +81.893,2152.929,543.179,apo-ferritin,TS_69_2,2 +5194.716,2277.793,916.254,apo-ferritin,TS_69_2,2 +2720.996,975.578,759.681,apo-ferritin,TS_69_2,2 +409.224,5385.982,819.132,apo-ferritin,TS_69_2,2 +470.992,5471.736,826.116,apo-ferritin,TS_69_2,2 +1340.353,5860.929,518.237,apo-ferritin,TS_69_2,2 +6150.553,4248.018,598.848,apo-ferritin,TS_69_2,2 +4116.256,6243.555,550.142,apo-ferritin,TS_69_2,2 +1695.969,4701.783,576.473,apo-ferritin,TS_69_2,2 +1639.36,4850.438,1063.3,apo-ferritin,TS_69_2,2 +4775.286,5015.714,581.514,apo-ferritin,TS_69_2,2 +4844.739,4397.387,782.334,apo-ferritin,TS_69_2,2 +3192.063,3826.044,607.451,apo-ferritin,TS_69_2,2 +3809.442,4198.247,561.673,apo-ferritin,TS_69_2,2 +3583.403,4366.702,603.141,apo-ferritin,TS_69_2,2 +3476.717,4383.102,650.181,apo-ferritin,TS_69_2,2 +3346.316,4185.936,586.959,apo-ferritin,TS_69_2,2 +3356.477,4340.341,649.659,apo-ferritin,TS_69_2,2 +3288.077,4440.594,634.301,apo-ferritin,TS_69_2,2 +3227.491,4544.364,579.127,apo-ferritin,TS_69_2,2 +3986.778,4435.444,572.259,apo-ferritin,TS_69_2,2 +3838.352,3983.448,633.065,apo-ferritin,TS_69_2,2 +4414.178,3489.079,1138.289,apo-ferritin,TS_69_2,2 +2864.757,2420.825,1037.476,apo-ferritin,TS_69_2,2 +2965.475,2392.793,1081.285,apo-ferritin,TS_69_2,2 +2450.507,2722.905,999.426,apo-ferritin,TS_69_2,2 +3374.729,4537.176,585.728,apo-ferritin,TS_69_2,2 +3463.362,4759.63,585.728,apo-ferritin,TS_69_2,2 +3280.785,4270.741,665.828,apo-ferritin,TS_69_2,2 +3738.087,4294.279,665.828,apo-ferritin,TS_69_2,2 +3811.914,4381.302,616.461,apo-ferritin,TS_69_2,2 +2913.77,5921.278,867.604,beta-amylase,TS_99_9,3 +2806.057,5772.0,1138.4,beta-amylase,TS_99_9,3 +1676.875,5103.125,1180.0,beta-amylase,TS_99_9,3 +579.608,4457.059,1065.686,beta-amylase,TS_99_9,3 +920.0,5645.0,1530.0,beta-amylase,TS_99_9,3 +4033.412,3769.948,1173.543,beta-amylase,TS_99_9,3 +2777.874,3491.181,478.15,beta-amylase,TS_99_9,3 +1885.833,236.667,184.167,beta-amylase,TS_99_9,3 +1999.49,2273.469,310.663,beta-amylase,TS_99_9,3 +2345.73,2017.64,996.966,beta-amylase,TS_99_9,3 +2080.0,1815.0,311.275,beta-amylase,TS_99_9,3 +3379.651,1627.093,693.682,beta-amylase,TS_99_9,3 +2832.0,1580.0,670.0,beta-amylase,TS_99_9,3 +3680.78,2053.756,450.488,beta-amylase,TS_99_9,3 +1990.0,2810.0,730.0,beta-amylase,TS_99_9,3 +3614.741,2640.948,742.026,beta-amylase,TS_99_9,3 +1571.774,1085.222,661.613,beta-amylase,TS_99_9,3 +1440.667,1001.667,790.333,beta-amylase,TS_99_9,3 +2501.623,951.948,806.623,beta-amylase,TS_99_9,3 +730.0,1848.333,921.667,beta-amylase,TS_99_9,3 +4443.627,6262.353,945.882,beta-amylase,TS_99_9,3 +5628.926,4904.558,596.945,beta-galactosidase,TS_99_9,3 +5173.756,5124.78,558.683,beta-galactosidase,TS_99_9,3 +3510.155,195.58,743.106,beta-galactosidase,TS_99_9,3 +5368.571,3285.0,924.286,beta-galactosidase,TS_99_9,3 +5870.914,2555.995,402.796,beta-galactosidase,TS_99_9,3 +5692.178,363.77,822.506,beta-galactosidase,TS_99_9,3 +4330.594,227.707,232.155,beta-galactosidase,TS_99_9,3 +2962.0,1081.667,524.222,beta-galactosidase,TS_99_9,3 +2595.909,762.727,660.0,beta-galactosidase,TS_99_9,3 +2189.81,932.048,518.571,beta-galactosidase,TS_99_9,3 +2145.786,1449.929,283.357,beta-galactosidase,TS_99_9,3 +485.221,2212.794,529.044,beta-galactosidase,TS_99_9,3 +761.031,2527.937,656.812,beta-galactosidase,TS_99_9,3 +1503.711,5532.695,1127.148,beta-galactosidase,TS_99_9,3 +3130.0,3430.0,325.0,beta-galactosidase,TS_99_9,3 +1542.335,4102.275,970.0,beta-galactosidase,TS_99_9,3 +430.436,5762.282,972.349,beta-galactosidase,TS_99_9,3 +6028.531,1814.84,489.704,beta-galactosidase,TS_99_9,3 +5142.516,1120.644,610.598,beta-galactosidase,TS_99_9,3 +2907.5,3320.0,580.0,beta-galactosidase,TS_99_9,3 +3112.493,2771.804,800.133,beta-galactosidase,TS_99_9,3 +2058.911,3929.241,786.634,beta-galactosidase,TS_99_9,3 +807.718,4597.49,1060.788,beta-galactosidase,TS_99_9,3 +5390.233,5613.953,1080.233,beta-galactosidase,TS_99_9,3 +3319.52,5476.3,1014.511,ribosome,TS_99_9,3 +3106.079,5574.209,1186.11,ribosome,TS_99_9,3 +3676.997,5271.281,890.752,ribosome,TS_99_9,3 +3717.18,5455.125,1103.21,ribosome,TS_99_9,3 +3525.117,5614.423,1112.576,ribosome,TS_99_9,3 +3535.323,5008.014,1059.689,ribosome,TS_99_9,3 +2883.189,5382.516,1133.506,ribosome,TS_99_9,3 +2116.336,5189.631,1027.456,ribosome,TS_99_9,3 +2328.909,5130.186,1253.405,ribosome,TS_99_9,3 +1947.981,5391.709,1313.177,ribosome,TS_99_9,3 +2156.805,5609.771,1273.522,ribosome,TS_99_9,3 +2509.295,5370.443,1185.571,ribosome,TS_99_9,3 +2719.011,5096.74,1263.963,ribosome,TS_99_9,3 +3176.477,5049.588,1126.434,ribosome,TS_99_9,3 +4934.962,6071.261,947.376,ribosome,TS_99_9,3 +4999.826,5866.993,1122.244,ribosome,TS_99_9,3 +4927.254,5595.834,1145.519,ribosome,TS_99_9,3 +5615.95,4909.438,989.317,ribosome,TS_99_9,3 +5336.302,4963.521,1045.105,ribosome,TS_99_9,3 +5279.465,4564.465,417.07,ribosome,TS_99_9,3 +4916.58,5065.955,1120.492,ribosome,TS_99_9,3 +411.526,315.634,370.328,ribosome,TS_99_9,3 +430.518,110.647,133.701,ribosome,TS_99_9,3 +702.557,115.463,165.955,ribosome,TS_99_9,3 +1055.336,457.84,237.445,ribosome,TS_99_9,3 +1152.917,482.744,468.791,ribosome,TS_99_9,3 +1424.626,537.985,227.71,ribosome,TS_99_9,3 +852.272,670.775,246.253,ribosome,TS_99_9,3 +1135.8,1200.179,278.969,ribosome,TS_99_9,3 +833.018,1211.915,291.627,ribosome,TS_99_9,3 +521.382,991.567,232.765,ribosome,TS_99_9,3 +724.625,906.686,436.445,ribosome,TS_99_9,3 +971.899,924.657,189.731,ribosome,TS_99_9,3 +309.802,1409.819,659.974,ribosome,TS_99_9,3 +544.924,1197.59,831.877,ribosome,TS_99_9,3 +539.079,1402.459,906.531,ribosome,TS_99_9,3 +1131.172,171.919,184.836,ribosome,TS_99_9,3 +271.44,4207.943,1181.704,ribosome,TS_99_9,3 +642.942,3879.471,1147.333,ribosome,TS_99_9,3 +559.039,3663.243,1180.24,ribosome,TS_99_9,3 +807.849,4177.77,1283.998,ribosome,TS_99_9,3 +283.913,3819.565,1156.087,ribosome,TS_99_9,3 +256.686,5246.703,1066.913,ribosome,TS_99_9,3 +490.506,5275.121,1291.868,ribosome,TS_99_9,3 +882.72,5136.226,1305.376,ribosome,TS_99_9,3 +2003.082,3328.973,1001.08,ribosome,TS_99_9,3 +1873.277,3543.216,678.046,ribosome,TS_99_9,3 +1118.468,3619.591,1267.875,ribosome,TS_99_9,3 +956.16,2418.385,833.722,ribosome,TS_99_9,3 +842.784,2288.479,463.814,ribosome,TS_99_9,3 +1008.88,2721.708,938.224,ribosome,TS_99_9,3 +1534.252,2653.747,894.814,ribosome,TS_99_9,3 +1766.353,2563.641,1128.742,ribosome,TS_99_9,3 +1713.554,2870.877,1082.887,ribosome,TS_99_9,3 +2887.071,2920.025,1138.122,ribosome,TS_99_9,3 +5283.409,1249.497,917.141,ribosome,TS_99_9,3 +5762.274,1749.037,772.82,ribosome,TS_99_9,3 +5903.842,1565.63,977.371,ribosome,TS_99_9,3 +5482.753,1827.704,848.819,ribosome,TS_99_9,3 +4787.941,1452.551,904.813,ribosome,TS_99_9,3 +4561.989,1641.32,954.457,ribosome,TS_99_9,3 +4166.224,1356.023,1062.256,ribosome,TS_99_9,3 +4306.506,2049.676,1092.797,ribosome,TS_99_9,3 +258.14,5412.14,1270.491,ribosome,TS_99_9,3 +3763.052,5198.677,1156.437,ribosome,TS_99_9,3 +638.516,1499.883,279.57,thyroglobulin,TS_99_9,3 +2038.81,214.041,361.807,thyroglobulin,TS_99_9,3 +1504.54,3662.02,566.838,thyroglobulin,TS_99_9,3 +2166.658,2000.169,547.046,thyroglobulin,TS_99_9,3 +656.526,1988.951,597.846,thyroglobulin,TS_99_9,3 +3082.001,2240.364,645.634,thyroglobulin,TS_99_9,3 +2504.842,4565.01,628.011,thyroglobulin,TS_99_9,3 +5788.387,2978.892,692.567,thyroglobulin,TS_99_9,3 +5360.187,4157.772,727.216,thyroglobulin,TS_99_9,3 +2245.603,1789.767,785.667,thyroglobulin,TS_99_9,3 +1398.087,3775.91,1004.103,thyroglobulin,TS_99_9,3 +3817.136,1389.497,1017.085,thyroglobulin,TS_99_9,3 +1229.988,2017.224,1143.411,thyroglobulin,TS_99_9,3 +2169.349,329.172,1145.444,thyroglobulin,TS_99_9,3 +4890.0,4570.0,1210.0,thyroglobulin,TS_99_9,3 +4649.024,4763.902,624.268,thyroglobulin,TS_99_9,3 +5513.22,4635.311,759.492,thyroglobulin,TS_99_9,3 +4347.313,4142.09,1080.597,thyroglobulin,TS_99_9,3 +3747.657,4574.435,1079.079,thyroglobulin,TS_99_9,3 +5409.036,5614.819,878.072,thyroglobulin,TS_99_9,3 +5141.848,2562.391,982.609,thyroglobulin,TS_99_9,3 +4465.707,2667.488,1096.463,thyroglobulin,TS_99_9,3 +3957.731,2284.118,393.025,thyroglobulin,TS_99_9,3 +3382.5,2805.0,1050.0,thyroglobulin,TS_99_9,3 +3261.336,2442.708,1020.181,thyroglobulin,TS_99_9,3 +2357.384,3572.778,1085.972,thyroglobulin,TS_99_9,3 +1649.074,3786.667,624.074,thyroglobulin,TS_99_9,3 +1905.325,3728.049,1032.276,thyroglobulin,TS_99_9,3 +1498.586,4220.741,1323.401,thyroglobulin,TS_99_9,3 +2345.0,5647.5,895.0,thyroglobulin,TS_99_9,3 +2303.101,5998.481,899.873,thyroglobulin,TS_99_9,3 +3525.787,183.38,733.264,thyroglobulin,TS_99_9,3 +2731.186,1063.66,237.062,thyroglobulin,TS_99_9,3 +1054.579,3043.832,715.813,thyroglobulin,TS_99_9,3 +430.0,2690.0,930.0,thyroglobulin,TS_99_9,3 +1503.692,1698.051,288.667,thyroglobulin,TS_99_9,3 +676.591,834.773,70.909,thyroglobulin,TS_99_9,3 +492.857,798.312,686.104,thyroglobulin,TS_99_9,3 +452.931,1866.293,293.448,thyroglobulin,TS_99_9,3 +1604.444,695.079,1036.984,thyroglobulin,TS_99_9,3 +2620.97,1504.97,424.606,thyroglobulin,TS_99_9,3 +2576.97,774.646,642.323,thyroglobulin,TS_99_9,3 +2487.147,4256.221,704.01,thyroglobulin,TS_99_9,3 +2280.316,3380.752,791.189,thyroglobulin,TS_99_9,3 +2790.0,3945.0,790.0,thyroglobulin,TS_99_9,3 +1110.308,5734.615,862.154,thyroglobulin,TS_99_9,3 +3148.098,3234.683,952.634,thyroglobulin,TS_99_9,3 +1541.778,4082.63,989.996,thyroglobulin,TS_99_9,3 +1267.073,4858.78,1068.293,thyroglobulin,TS_99_9,3 +2766.389,185.542,510.129,virus-like-particle,TS_99_9,3 +3084.893,487.084,760.823,virus-like-particle,TS_99_9,3 +3693.973,2932.983,620.271,virus-like-particle,TS_99_9,3 +3010.666,2469.137,884.272,virus-like-particle,TS_99_9,3 +556.664,3167.112,664.183,virus-like-particle,TS_99_9,3 +289.388,4113.399,765.344,virus-like-particle,TS_99_9,3 +981.03,1910.541,780.365,virus-like-particle,TS_99_9,3 +1566.401,4710.311,791.778,virus-like-particle,TS_99_9,3 +2010.056,4752.618,1057.078,virus-like-particle,TS_99_9,3 +2244.068,4310.063,959.548,virus-like-particle,TS_99_9,3 +804.27,5817.135,579.493,virus-like-particle,TS_99_9,3 +4198.228,5534.578,858.169,virus-like-particle,TS_99_9,3 +5698.538,3331.427,806.002,virus-like-particle,TS_99_9,3 +6072.464,4038.0,715.679,apo-ferritin,TS_99_9,3 +5967.452,4228.213,1124.601,apo-ferritin,TS_99_9,3 +5847.622,5066.71,678.893,apo-ferritin,TS_99_9,3 +472.853,5632.618,580.676,apo-ferritin,TS_99_9,3 +564.507,5604.88,678.16,apo-ferritin,TS_99_9,3 +890.414,5615.759,467.69,apo-ferritin,TS_99_9,3 +5613.561,672.73,265.579,apo-ferritin,TS_99_9,3 +458.087,1972.013,765.906,apo-ferritin,TS_99_9,3 +1284.111,1752.411,250.87,apo-ferritin,TS_99_9,3 +1284.136,1713.797,367.525,apo-ferritin,TS_99_9,3 +1233.112,2053.458,251.902,apo-ferritin,TS_99_9,3 +1194.107,1911.285,267.179,apo-ferritin,TS_99_9,3 +5543.161,2879.598,408.42,apo-ferritin,TS_99_9,3 +5485.217,2774.62,411.413,apo-ferritin,TS_99_9,3 +5448.11,3486.46,985.533,apo-ferritin,TS_99_9,3 +3529.611,2919.728,380.389,apo-ferritin,TS_99_9,3 +3438.216,3010.29,397.925,apo-ferritin,TS_99_9,3 +4251.254,845.627,720.143,apo-ferritin,TS_99_9,3 +4592.044,1321.788,322.883,apo-ferritin,TS_99_9,3 +4250.071,1837.26,319.929,apo-ferritin,TS_99_9,3 +4395.13,1892.857,352.013,apo-ferritin,TS_99_9,3 +3670.295,1851.882,328.745,apo-ferritin,TS_99_9,3 +3287.917,1286.28,282.56,apo-ferritin,TS_99_9,3 +2967.331,1646.264,312.781,apo-ferritin,TS_99_9,3 +2987.912,1729.794,397.471,apo-ferritin,TS_99_9,3 +652.0,4574.214,416.571,apo-ferritin,TS_99_9,3 +1032.835,4506.417,1003.228,apo-ferritin,TS_99_9,3 +1100.435,4449.249,1078.617,apo-ferritin,TS_99_9,3 +929.932,4524.384,915.137,apo-ferritin,TS_99_9,3 +1189.237,4882.468,464.122,apo-ferritin,TS_99_9,3 +1649.873,4985.285,473.544,apo-ferritin,TS_99_9,3 +2165.042,4617.759,846.807,apo-ferritin,TS_99_9,3 +2215.952,4694.502,928.61,apo-ferritin,TS_99_9,3 +2276.394,4608.261,802.558,apo-ferritin,TS_99_9,3 +2137.354,3437.938,600.0,apo-ferritin,TS_99_9,3 +1685.81,3815.229,1354.985,apo-ferritin,TS_99_9,3 +1962.647,358.235,376.029,beta-amylase,TS_73_6,4 +2212.609,450.632,641.739,beta-amylase,TS_73_6,4 +2177.5,205.0,907.5,beta-amylase,TS_73_6,4 +943.718,1273.397,132.821,beta-amylase,TS_73_6,4 +2271.379,1911.034,546.293,beta-amylase,TS_73_6,4 +4376.951,2160.122,396.829,beta-amylase,TS_73_6,4 +3761.98,2860.0,1021.287,beta-amylase,TS_73_6,4 +2534.933,3468.622,646.267,beta-amylase,TS_73_6,4 +1948.482,3600.052,965.864,beta-amylase,TS_73_6,4 +1949.783,4223.261,241.304,beta-amylase,TS_73_6,4 +1838.74,5085.118,893.386,beta-amylase,TS_73_6,4 +270.861,5594.658,815.646,beta-amylase,TS_73_6,4 +2285.357,2137.571,596.214,beta-galactosidase,TS_73_6,4 +4008.603,383.309,798.529,beta-galactosidase,TS_73_6,4 +839.231,1602.692,1088.077,beta-galactosidase,TS_73_6,4 +188.815,2167.37,769.741,beta-galactosidase,TS_73_6,4 +742.788,2808.077,246.058,beta-galactosidase,TS_73_6,4 +3552.222,228.148,556.481,beta-galactosidase,TS_73_6,4 +4341.167,3756.5,532.667,beta-galactosidase,TS_73_6,4 +3670.099,2118.144,520.99,beta-galactosidase,TS_73_6,4 +2349.623,474.663,334.96,beta-galactosidase,TS_73_6,4 +3725.132,3520.212,537.937,beta-galactosidase,TS_73_6,4 +2132.535,2661.582,689.108,beta-galactosidase,TS_73_6,4 +388.433,3230.336,768.209,beta-galactosidase,TS_73_6,4 +4555.0,3188.75,868.75,beta-galactosidase,TS_73_6,4 +277.276,3258.78,897.378,beta-galactosidase,TS_73_6,4 +5812.21,954.718,134.827,ribosome,TS_73_6,4 +5598.248,890.665,230.051,ribosome,TS_73_6,4 +5869.463,488.783,285.901,ribosome,TS_73_6,4 +5850.904,575.293,735.879,ribosome,TS_73_6,4 +5940.845,736.687,114.293,ribosome,TS_73_6,4 +5600.385,584.159,361.892,ribosome,TS_73_6,4 +5755.45,2294.051,667.695,ribosome,TS_73_6,4 +6045.567,2430.597,715.623,ribosome,TS_73_6,4 +5923.825,2647.136,880.662,ribosome,TS_73_6,4 +5612.53,2504.846,757.101,ribosome,TS_73_6,4 +2975.042,5637.426,1086.291,ribosome,TS_73_6,4 +2783.415,5731.957,917.101,ribosome,TS_73_6,4 +3067.628,5861.795,977.738,ribosome,TS_73_6,4 +6067.043,3899.28,953.521,ribosome,TS_73_6,4 +4227.966,4408.917,1033.563,ribosome,TS_73_6,4 +4124.483,4142.615,1148.458,ribosome,TS_73_6,4 +3880.999,3552.474,1003.724,ribosome,TS_73_6,4 +4595.975,3413.592,1095.442,ribosome,TS_73_6,4 +2492.225,4304.511,735.979,ribosome,TS_73_6,4 +2141.774,4385.963,846.593,ribosome,TS_73_6,4 +2316.45,4391.133,1066.221,ribosome,TS_73_6,4 +2282.211,4170.642,807.039,ribosome,TS_73_6,4 +2155.564,4599.751,1226.02,ribosome,TS_73_6,4 +1845.342,4530.955,856.591,ribosome,TS_73_6,4 +1698.475,4467.553,1310.344,ribosome,TS_73_6,4 +2963.526,4115.076,947.772,ribosome,TS_73_6,4 +3230.411,3924.05,903.202,ribosome,TS_73_6,4 +3501.536,3769.701,1211.88,ribosome,TS_73_6,4 +4136.679,1185.915,793.199,ribosome,TS_73_6,4 +4244.615,1461.256,917.947,ribosome,TS_73_6,4 +4401.817,1002.721,835.005,ribosome,TS_73_6,4 +3898.451,1614.824,1008.257,ribosome,TS_73_6,4 +3506.524,1792.811,1036.299,ribosome,TS_73_6,4 +3056.445,2174.747,1036.756,ribosome,TS_73_6,4 +2259.0,1224.141,715.852,ribosome,TS_73_6,4 +2311.395,1129.953,423.085,ribosome,TS_73_6,4 +2652.943,1048.475,883.276,ribosome,TS_73_6,4 +2435.147,921.61,1049.207,ribosome,TS_73_6,4 +2677.436,868.552,342.294,ribosome,TS_73_6,4 +2587.987,1253.328,771.87,ribosome,TS_73_6,4 +2214.444,2556.385,1187.927,ribosome,TS_73_6,4 +2213.117,2918.397,1252.563,ribosome,TS_73_6,4 +1535.442,3177.381,1205.003,ribosome,TS_73_6,4 +1010.903,3442.274,1142.175,ribosome,TS_73_6,4 +3126.686,4057.299,1156.437,ribosome,TS_73_6,4 +2182.44,1225.034,1076.338,ribosome,TS_73_6,4 +1841.78,1057.592,258.848,thyroglobulin,TS_73_6,4 +2387.281,2326.345,331.932,thyroglobulin,TS_73_6,4 +3242.6,452.621,489.296,thyroglobulin,TS_73_6,4 +409.479,1796.38,525.534,thyroglobulin,TS_73_6,4 +4582.511,1738.01,569.479,thyroglobulin,TS_73_6,4 +1402.102,2384.13,834.912,thyroglobulin,TS_73_6,4 +728.817,4151.187,967.208,thyroglobulin,TS_73_6,4 +6183.204,996.602,946.99,thyroglobulin,TS_73_6,4 +2025.333,4083.449,1013.484,thyroglobulin,TS_73_6,4 +3103.444,4679.447,1043.352,thyroglobulin,TS_73_6,4 +2073.311,3496.993,1163.75,thyroglobulin,TS_73_6,4 +743.959,238.379,1177.853,thyroglobulin,TS_73_6,4 +524.99,5021.579,1300.106,thyroglobulin,TS_73_6,4 +2870.0,1590.0,215.0,thyroglobulin,TS_73_6,4 +4842.5,412.5,438.333,thyroglobulin,TS_73_6,4 +901.401,5325.541,774.427,thyroglobulin,TS_73_6,4 +1885.771,2380.697,668.159,thyroglobulin,TS_73_6,4 +1875.447,2614.342,921.947,thyroglobulin,TS_73_6,4 +608.235,2829.608,478.693,thyroglobulin,TS_73_6,4 +910.265,3239.47,907.848,thyroglobulin,TS_73_6,4 +550.896,1484.776,625.075,thyroglobulin,TS_73_6,4 +4329.907,3719.346,547.009,thyroglobulin,TS_73_6,4 +4524.646,1289.394,73.081,thyroglobulin,TS_73_6,4 +2349.196,3677.366,735.089,thyroglobulin,TS_73_6,4 +3302.378,1831.524,794.207,thyroglobulin,TS_73_6,4 +448.929,2528.214,811.667,thyroglobulin,TS_73_6,4 +2694.397,4488.369,818.369,thyroglobulin,TS_73_6,4 +3452.853,2717.301,962.577,thyroglobulin,TS_73_6,4 +3023.031,6098.949,542.343,virus-like-particle,TS_73_6,4 +2133.513,5544.092,839.866,virus-like-particle,TS_73_6,4 +5175.693,1206.756,215.022,virus-like-particle,TS_73_6,4 +4849.137,666.384,412.947,virus-like-particle,TS_73_6,4 +6161.707,2651.433,465.402,virus-like-particle,TS_73_6,4 +6042.997,3306.628,713.333,virus-like-particle,TS_73_6,4 +5654.074,2942.728,61.945,virus-like-particle,TS_73_6,4 +6013.231,1871.433,680.048,virus-like-particle,TS_73_6,4 +2836.001,455.2,275.173,virus-like-particle,TS_73_6,4 +2482.763,295.84,690.206,virus-like-particle,TS_73_6,4 +3530.063,771.996,413.662,virus-like-particle,TS_73_6,4 +2687.924,2725.328,632.393,virus-like-particle,TS_73_6,4 +2782.653,1819.135,1019.265,virus-like-particle,TS_73_6,4 +1002.78,2972.855,700.217,virus-like-particle,TS_73_6,4 +679.125,2770.252,855.0,virus-like-particle,TS_73_6,4 +1379.817,2702.33,783.111,virus-like-particle,TS_73_6,4 +985.214,2133.973,598.405,virus-like-particle,TS_73_6,4 +1436.036,1368.571,989.781,virus-like-particle,TS_73_6,4 +1636.918,3798.904,1006.075,virus-like-particle,TS_73_6,4 +3298.877,4560.393,515.641,virus-like-particle,TS_73_6,4 +3093.113,4729.102,645.803,virus-like-particle,TS_73_6,4 +3343.273,4722.883,735.915,virus-like-particle,TS_73_6,4 +268.662,4730.318,916.115,apo-ferritin,TS_73_6,4 +238.946,4853.061,909.898,apo-ferritin,TS_73_6,4 +83.114,5729.56,1219.524,apo-ferritin,TS_73_6,4 +582.143,2769.968,1076.364,apo-ferritin,TS_73_6,4 +510.389,2157.244,362.438,apo-ferritin,TS_73_6,4 +619.176,1991.61,520.449,apo-ferritin,TS_73_6,4 +1245.704,2002.993,743.873,apo-ferritin,TS_73_6,4 +1290.839,1518.942,797.372,apo-ferritin,TS_73_6,4 +1328.307,1547.677,653.425,apo-ferritin,TS_73_6,4 +1352.893,1445.597,725.912,apo-ferritin,TS_73_6,4 +993.954,1705.229,366.405,apo-ferritin,TS_73_6,4 +751.138,1426.892,458.677,apo-ferritin,TS_73_6,4 +2350.062,2564.704,216.978,apo-ferritin,TS_73_6,4 +1210.503,5687.487,1217.588,apo-ferritin,TS_73_6,4 +1134.771,5679.086,898.514,apo-ferritin,TS_73_6,4 +1385.017,5077.747,364.983,apo-ferritin,TS_73_6,4 +1501.315,5047.829,332.783,apo-ferritin,TS_73_6,4 +5980.404,4244.379,478.416,apo-ferritin,TS_73_6,4 +5388.235,4480.0,84.706,apo-ferritin,TS_73_6,4 +5377.5,4507.5,85.833,apo-ferritin,TS_73_6,4 +5395.425,4746.993,39.085,apo-ferritin,TS_73_6,4 +5415.078,4630.078,54.219,apo-ferritin,TS_73_6,4 +5269.381,4808.761,43.717,apo-ferritin,TS_73_6,4 +5704.167,3290.0,85.833,apo-ferritin,TS_73_6,4 +5543.821,3528.374,47.154,apo-ferritin,TS_73_6,4 +5303.962,3755.786,58.176,apo-ferritin,TS_73_6,4 +1643.592,547.51,582.98,apo-ferritin,TS_73_6,4 +1725.534,494.356,644.849,apo-ferritin,TS_73_6,4 +3173.276,4450.517,1039.138,apo-ferritin,TS_73_6,4 +3121.796,4434.709,264.563,apo-ferritin,TS_73_6,4 +3009.867,4384.053,285.76,apo-ferritin,TS_73_6,4 +2947.351,4494.183,306.881,apo-ferritin,TS_73_6,4 +3410.451,4713.045,295.94,apo-ferritin,TS_73_6,4 +2367.675,4872.213,269.412,apo-ferritin,TS_73_6,4 +2273.256,4937.781,298.732,apo-ferritin,TS_73_6,4 +2295.068,4704.11,382.363,apo-ferritin,TS_73_6,4 +2309.494,4590.127,344.557,apo-ferritin,TS_73_6,4 +2345.93,4470.807,329.193,apo-ferritin,TS_73_6,4 +2468.306,4779.707,273.485,apo-ferritin,TS_73_6,4 +2381.72,4060.32,1021.0,apo-ferritin,TS_73_6,4 +2428.114,4271.673,1335.552,apo-ferritin,TS_73_6,4 +4396.744,5082.692,132.436,apo-ferritin,TS_73_6,4 +4620.519,2441.364,105.357,apo-ferritin,TS_73_6,4 +4618.52,2364.605,202.105,apo-ferritin,TS_73_6,4 +4639.096,2192.289,61.386,apo-ferritin,TS_73_6,4 +4635.698,2262.0,151.66,apo-ferritin,TS_73_6,4 +4192.5,2611.806,149.769,apo-ferritin,TS_73_6,4 +4239.923,2540.192,172.222,apo-ferritin,TS_73_6,4 +4286.667,2535.149,605.498,apo-ferritin,TS_73_6,4 +4349.217,2580.569,697.082,apo-ferritin,TS_73_6,4 +4393.977,2435.568,552.273,apo-ferritin,TS_73_6,4 +5024.323,2631.095,824.726,apo-ferritin,TS_73_6,4 +3229.887,3158.038,1059.283,apo-ferritin,TS_73_6,4 +3335.99,3213.02,1097.624,apo-ferritin,TS_73_6,4 +3086.108,1940.784,475.514,apo-ferritin,TS_73_6,4 +2990.879,1990.733,521.832,apo-ferritin,TS_73_6,4 +3155.737,2509.436,149.875,apo-ferritin,TS_73_6,4 +3287.952,2245.186,149.096,apo-ferritin,TS_73_6,4 +3383.0,2800.036,194.679,apo-ferritin,TS_73_6,4 +3615.771,1045.806,224.552,apo-ferritin,TS_73_6,4 +3646.689,1782.23,88.311,apo-ferritin,TS_73_6,4 +3585.105,1694.895,99.247,apo-ferritin,TS_73_6,4 +3807.282,2263.107,561.197,apo-ferritin,TS_73_6,4 +4331.237,779.541,209.293,apo-ferritin,TS_73_6,4 +4393.259,885.599,236.462,apo-ferritin,TS_73_6,4 +3621.489,341.667,73.652,apo-ferritin,TS_73_6,4 +2798.99,1315.505,241.045,apo-ferritin,TS_73_6,4 +2536.024,3417.62,256.355,apo-ferritin,TS_73_6,4 +1334.486,3988.322,802.226,apo-ferritin,TS_73_6,4 +1085.929,5158.59,465.579,apo-ferritin,TS_73_6,4 +1177.73,5141.064,425.529,apo-ferritin,TS_73_6,4 +1178.346,5227.0,345.429,apo-ferritin,TS_73_6,4 +1504.843,5180.322,355.442,apo-ferritin,TS_73_6,4 +5058.617,2754.639,826.027,apo-ferritin,TS_73_6,4 +3167.563,2035.642,455.566,apo-ferritin,TS_73_6,4 +1573.409,390.881,495.616,apo-ferritin,TS_73_6,4 +1634.084,507.488,455.566,apo-ferritin,TS_73_6,4 +1545.135,468.754,595.74,apo-ferritin,TS_73_6,4 +1584.637,454.698,685.852,apo-ferritin,TS_73_6,4 +1779.966,580.292,485.604,apo-ferritin,TS_73_6,4 +1582.199,582.679,355.442,apo-ferritin,TS_73_6,4 +2192.89,171.392,495.616,apo-ferritin,TS_73_6,4 +2076.604,200.819,575.716,apo-ferritin,TS_73_6,4 +2189.662,117.874,595.74,apo-ferritin,TS_73_6,4 +1957.082,145.518,465.579,apo-ferritin,TS_73_6,4 +1944.65,161.916,585.728,apo-ferritin,TS_73_6,4 +3270.772,3235.528,1016.263,apo-ferritin,TS_73_6,4 +3421.573,1462.485,305.38,apo-ferritin,TS_73_6,4 +3221.982,2918.325,265.33,apo-ferritin,TS_73_6,4 +3391.454,2889.264,335.417,apo-ferritin,TS_73_6,4 +3927.906,4070.523,225.28,apo-ferritin,TS_73_6,4 +4263.185,4747.497,225.28,apo-ferritin,TS_73_6,4 +4122.371,4852.662,225.28,apo-ferritin,TS_73_6,4 +4236.513,2479.339,515.641,apo-ferritin,TS_73_6,4 +4309.621,2389.686,485.604,apo-ferritin,TS_73_6,4 +4806.667,980.0,211.111,beta-amylase,TS_6_4,5 +2405.9,2058.159,762.887,beta-amylase,TS_6_4,5 +1489.928,1645.29,797.246,beta-amylase,TS_6_4,5 +4400.989,5358.575,751.563,beta-amylase,TS_6_4,5 +3791.75,4560.917,811.0,beta-amylase,TS_6_4,5 +3495.477,4704.215,901.938,beta-amylase,TS_6_4,5 +3876.774,5060.387,1484.194,beta-amylase,TS_6_4,5 +4501.951,4290.139,1074.704,beta-amylase,TS_6_4,5 +1318.542,5045.417,1136.875,beta-amylase,TS_6_4,5 +804.615,1977.846,489.385,beta-galactosidase,TS_6_4,5 +2134.942,3647.023,657.082,beta-galactosidase,TS_6_4,5 +5361.881,5472.376,1032.772,beta-galactosidase,TS_6_4,5 +4130.0,3716.667,465.0,beta-galactosidase,TS_6_4,5 +3072.5,4610.0,1090.0,beta-galactosidase,TS_6_4,5 +2151.626,4674.959,1189.35,beta-galactosidase,TS_6_4,5 +2367.485,5196.81,587.178,beta-galactosidase,TS_6_4,5 +2680.0,2415.476,982.381,beta-galactosidase,TS_6_4,5 +250.577,1621.923,850.962,beta-galactosidase,TS_6_4,5 +1322.147,2720.516,952.283,beta-galactosidase,TS_6_4,5 +1514.91,1642.699,821.722,beta-galactosidase,TS_6_4,5 +4483.644,4570.968,1089.313,beta-galactosidase,TS_6_4,5 +5274.903,5288.121,619.798,ribosome,TS_6_4,5 +5493.057,5181.127,726.624,ribosome,TS_6_4,5 +5656.951,5168.655,421.572,ribosome,TS_6_4,5 +5559.324,5426.553,556.878,ribosome,TS_6_4,5 +5312.439,4964.156,602.574,ribosome,TS_6_4,5 +5130.837,5121.036,631.702,ribosome,TS_6_4,5 +5453.083,4696.154,504.224,ribosome,TS_6_4,5 +5635.855,4803.903,706.456,ribosome,TS_6_4,5 +5106.838,4835.263,619.225,ribosome,TS_6_4,5 +5734.44,5547.349,1265.455,ribosome,TS_6_4,5 +5571.792,5790.483,1296.079,ribosome,TS_6_4,5 +285.661,4791.067,779.819,ribosome,TS_6_4,5 +229.331,4316.582,690.387,ribosome,TS_6_4,5 +232.042,4432.901,1135.128,ribosome,TS_6_4,5 +348.532,4570.763,717.301,ribosome,TS_6_4,5 +97.34,4661.182,1144.298,ribosome,TS_6_4,5 +143.939,5029.347,732.139,ribosome,TS_6_4,5 +1273.648,4386.703,631.656,ribosome,TS_6_4,5 +1223.64,4186.768,819.895,ribosome,TS_6_4,5 +1236.034,4674.307,668.476,ribosome,TS_6_4,5 +650.107,3282.821,1144.294,ribosome,TS_6_4,5 +830.635,3537.878,963.135,ribosome,TS_6_4,5 +903.085,3391.971,1118.573,ribosome,TS_6_4,5 +630.784,3622.607,1171.482,ribosome,TS_6_4,5 +440.984,3866.177,643.857,ribosome,TS_6_4,5 +583.565,4051.159,828.723,ribosome,TS_6_4,5 +759.417,4295.623,734.899,ribosome,TS_6_4,5 +898.55,4141.046,883.996,ribosome,TS_6_4,5 +868.611,3893.736,743.916,ribosome,TS_6_4,5 +802.156,4625.141,708.375,ribosome,TS_6_4,5 +326.143,3537.823,937.014,ribosome,TS_6_4,5 +1504.444,3275.214,1257.063,ribosome,TS_6_4,5 +2644.576,4431.906,990.296,ribosome,TS_6_4,5 +2444.258,4567.69,1141.142,ribosome,TS_6_4,5 +2468.644,4101.445,1115.899,ribosome,TS_6_4,5 +3078.08,6072.239,1009.897,ribosome,TS_6_4,5 +3015.779,5943.612,1281.03,ribosome,TS_6_4,5 +3702.491,5596.93,1336.535,ribosome,TS_6_4,5 +1997.267,1411.194,1149.58,ribosome,TS_6_4,5 +884.666,2137.612,880.833,ribosome,TS_6_4,5 +663.969,2316.198,1203.572,ribosome,TS_6_4,5 +914.745,2431.255,1268.81,ribosome,TS_6_4,5 +394.061,658.244,750.429,ribosome,TS_6_4,5 +921.916,278.031,597.735,ribosome,TS_6_4,5 +1004.534,86.606,524.534,ribosome,TS_6_4,5 +1284.212,412.471,737.595,ribosome,TS_6_4,5 +1370.425,534.452,920.29,ribosome,TS_6_4,5 +619.064,850.866,358.126,ribosome,TS_6_4,5 +1015.725,640.198,622.387,ribosome,TS_6_4,5 +896.557,967.706,588.803,ribosome,TS_6_4,5 +1017.985,990.96,798.864,ribosome,TS_6_4,5 +898.732,759.425,1040.914,ribosome,TS_6_4,5 +1860.317,365.754,336.113,ribosome,TS_6_4,5 +1689.471,123.33,410.727,ribosome,TS_6_4,5 +886.465,1363.99,1164.549,ribosome,TS_6_4,5 +5007.198,2893.632,998.903,ribosome,TS_6_4,5 +4847.396,2618.105,908.936,ribosome,TS_6_4,5 +5318.939,2139.613,824.39,ribosome,TS_6_4,5 +5186.69,2325.778,977.738,ribosome,TS_6_4,5 +4891.054,1969.255,839.404,ribosome,TS_6_4,5 +4700.372,2111.558,1051.613,ribosome,TS_6_4,5 +4585.681,1839.145,1089.516,ribosome,TS_6_4,5 +4545.69,1538.897,425.389,ribosome,TS_6_4,5 +4576.092,1520.016,1009.884,ribosome,TS_6_4,5 +4654.602,1197.481,806.34,ribosome,TS_6_4,5 +2926.093,2721.038,955.726,ribosome,TS_6_4,5 +2928.412,2194.561,1051.608,ribosome,TS_6_4,5 +2930.904,1857.678,1081.158,ribosome,TS_6_4,5 +2971.267,127.642,534.313,ribosome,TS_6_4,5 +3098.923,2941.184,836.926,ribosome,TS_6_4,5 +3294.228,6090.415,1326.649,ribosome,TS_6_4,5 +5736.406,6034.086,1176.462,ribosome,TS_6_4,5 +503.142,393.241,485.604,ribosome,TS_6_4,5 +5336.84,2766.878,946.176,ribosome,TS_6_4,5 +5251.785,2090.452,490.516,thyroglobulin,TS_6_4,5 +5704.207,2059.378,481.002,thyroglobulin,TS_6_4,5 +5926.681,1787.182,496.498,thyroglobulin,TS_6_4,5 +4967.648,2249.978,522.505,thyroglobulin,TS_6_4,5 +1242.919,1464.644,581.353,thyroglobulin,TS_6_4,5 +3547.616,4770.646,595.253,thyroglobulin,TS_6_4,5 +4756.011,4447.041,581.386,thyroglobulin,TS_6_4,5 +3899.562,5206.682,624.795,thyroglobulin,TS_6_4,5 +4818.823,5953.736,667.288,thyroglobulin,TS_6_4,5 +910.287,2806.175,729.882,thyroglobulin,TS_6_4,5 +4305.698,3168.196,757.859,thyroglobulin,TS_6_4,5 +2447.604,3079.107,791.723,thyroglobulin,TS_6_4,5 +3238.774,4832.551,929.867,thyroglobulin,TS_6_4,5 +316.535,1807.724,1089.125,thyroglobulin,TS_6_4,5 +2022.746,4815.141,1054.014,thyroglobulin,TS_6_4,5 +6060.0,5590.0,150.0,thyroglobulin,TS_6_4,5 +1479.773,2517.727,424.545,thyroglobulin,TS_6_4,5 +1770.114,2095.398,713.92,thyroglobulin,TS_6_4,5 +1732.632,2841.579,1069.043,thyroglobulin,TS_6_4,5 +867.222,1503.333,658.333,thyroglobulin,TS_6_4,5 +1668.75,1150.0,758.75,thyroglobulin,TS_6_4,5 +1990.417,5721.667,858.333,thyroglobulin,TS_6_4,5 +2613.889,3310.0,727.778,thyroglobulin,TS_6_4,5 +6035.196,3635.735,1175.735,thyroglobulin,TS_6_4,5 +4056.946,3386.256,432.266,thyroglobulin,TS_6_4,5 +3753.041,5168.249,1048.157,thyroglobulin,TS_6_4,5 +6000.663,1943.878,948.52,thyroglobulin,TS_6_4,5 +3553.393,2129.821,561.696,thyroglobulin,TS_6_4,5 +4490.0,4330.0,747.5,thyroglobulin,TS_6_4,5 +5785.405,4142.342,1002.77,thyroglobulin,TS_6_4,5 +911.29,5638.402,671.21,virus-like-particle,TS_6_4,5 +122.332,5627.746,1066.367,virus-like-particle,TS_6_4,5 +1452.865,4795.545,1116.066,virus-like-particle,TS_6_4,5 +5580.108,1240.86,692.222,virus-like-particle,TS_6_4,5 +4765.58,3469.964,689.813,virus-like-particle,TS_6_4,5 +5088.704,4120.923,981.513,virus-like-particle,TS_6_4,5 +4268.076,2814.277,815.446,virus-like-particle,TS_6_4,5 +5211.319,5766.513,877.832,virus-like-particle,TS_6_4,5 +4509.57,5139.077,1161.95,virus-like-particle,TS_6_4,5 +6045.947,2340.359,745.927,virus-like-particle,TS_6_4,5 +616.51,2880.471,1294.039,apo-ferritin,TS_6_4,5 +1099.033,1820.423,371.571,apo-ferritin,TS_6_4,5 +1019.831,1859.831,400.424,apo-ferritin,TS_6_4,5 +959.708,1708.149,606.039,apo-ferritin,TS_6_4,5 +1010.329,1758.816,718.52,apo-ferritin,TS_6_4,5 +6116.31,2609.963,528.155,apo-ferritin,TS_6_4,5 +761.116,4901.459,1025.15,apo-ferritin,TS_6_4,5 +969.876,4757.709,1085.418,apo-ferritin,TS_6_4,5 +1648.828,4760.276,997.759,apo-ferritin,TS_6_4,5 +4984.373,5655.125,993.835,apo-ferritin,TS_6_4,5 +4320.826,5556.391,694.862,apo-ferritin,TS_6_4,5 +3683.627,5175.882,813.922,apo-ferritin,TS_6_4,5 +3448.962,5244.962,861.423,apo-ferritin,TS_6_4,5 +4215.917,4821.592,1046.194,apo-ferritin,TS_6_4,5 +2094.233,3235.89,953.497,apo-ferritin,TS_6_4,5 +4793.069,3457.834,1102.852,apo-ferritin,TS_6_4,5 +4804.171,3440.284,1208.91,apo-ferritin,TS_6_4,5 +4550.54,2147.492,218.857,apo-ferritin,TS_6_4,5 +4495.06,2052.976,296.726,apo-ferritin,TS_6_4,5 +4471.074,2131.577,383.456,apo-ferritin,TS_6_4,5 +4126.963,1933.063,190.969,apo-ferritin,TS_6_4,5 +3576.426,2612.49,304.859,apo-ferritin,TS_6_4,5 +3634.286,2431.136,572.418,apo-ferritin,TS_6_4,5 +3534.854,2407.485,425.439,apo-ferritin,TS_6_4,5 +4094.142,2567.337,295.148,apo-ferritin,TS_6_4,5 +4114.734,2446.095,331.538,apo-ferritin,TS_6_4,5 +3894.125,2456.271,258.185,apo-ferritin,TS_6_4,5 +3752.045,2505.17,411.989,apo-ferritin,TS_6_4,5 +3729.36,2566.163,517.093,apo-ferritin,TS_6_4,5 +3836.309,2673.691,474.128,apo-ferritin,TS_6_4,5 +3846.464,2570.493,545.246,apo-ferritin,TS_6_4,5 +3942.417,2660.695,413.609,apo-ferritin,TS_6_4,5 +3965.042,2591.765,574.37,apo-ferritin,TS_6_4,5 +4067.936,2609.929,620.107,apo-ferritin,TS_6_4,5 +4009.57,2947.043,412.634,apo-ferritin,TS_6_4,5 +4095.423,2885.423,469.801,apo-ferritin,TS_6_4,5 +4037.127,2899.179,280.336,apo-ferritin,TS_6_4,5 +4115.619,2817.426,332.054,apo-ferritin,TS_6_4,5 +4036.695,2773.898,490.254,apo-ferritin,TS_6_4,5 +4132.45,2712.15,541.35,apo-ferritin,TS_6_4,5 +3913.495,2803.827,494.974,apo-ferritin,TS_6_4,5 +4211.583,3077.606,520.463,apo-ferritin,TS_6_4,5 +4120.404,3128.081,582.424,apo-ferritin,TS_6_4,5 +4356.198,2834.341,298.892,apo-ferritin,TS_6_4,5 +4282.932,2913.759,318.195,apo-ferritin,TS_6_4,5 +4243.778,2985.682,420.398,apo-ferritin,TS_6_4,5 +4172.383,2955.235,626.173,apo-ferritin,TS_6_4,5 +4054.277,3025.843,547.47,apo-ferritin,TS_6_4,5 +4070.927,2541.192,935.695,apo-ferritin,TS_6_4,5 +3357.295,2074.139,225.328,apo-ferritin,TS_6_4,5 +2153.09,846.979,252.847,apo-ferritin,TS_6_4,5 +3596.958,2593.013,415.516,apo-ferritin,TS_6_4,5 +3486.946,2616.549,415.516,apo-ferritin,TS_6_4,5 +3631.179,2496.627,415.516,apo-ferritin,TS_6_4,5 +776.293,4893.666,1196.487,apo-ferritin,TS_6_4,5 +940.013,4882.972,1046.3,apo-ferritin,TS_6_4,5 +843.54,4841.021,1116.388,apo-ferritin,TS_6_4,5 +2326.455,5821.083,917.063,apo-ferritin,TS_6_4,5 +3850.0,1010.0,310.0,beta-amylase,TS_6_6,6 +4063.606,1651.538,239.038,beta-amylase,TS_6_6,6 +3229.286,1990.0,745.714,beta-amylase,TS_6_6,6 +3367.549,1766.986,676.789,beta-amylase,TS_6_6,6 +3241.333,2196.667,1108.667,beta-amylase,TS_6_6,6 +2695.942,1422.556,380.064,beta-amylase,TS_6_6,6 +2657.277,1564.851,477.787,beta-amylase,TS_6_6,6 +402.895,4085.0,1514.474,beta-amylase,TS_6_6,6 +1837.341,4133.064,835.607,beta-amylase,TS_6_6,6 +1485.96,3785.563,277.881,beta-amylase,TS_6_6,6 +2183.235,3039.118,670.588,beta-amylase,TS_6_6,6 +5169.63,5237.037,709.259,beta-amylase,TS_6_6,6 +4910.0,4665.714,738.857,beta-amylase,TS_6_6,6 +4580.0,5189.756,1065.528,beta-amylase,TS_6_6,6 +5524.088,3251.226,619.877,beta-galactosidase,TS_6_6,6 +5046.434,1586.382,988.516,beta-galactosidase,TS_6_6,6 +4640.0,1865.0,685.0,beta-galactosidase,TS_6_6,6 +1777.5,3650.417,600.0,beta-galactosidase,TS_6_6,6 +1343.732,5939.859,1039.93,beta-galactosidase,TS_6_6,6 +4634.583,4465.417,806.667,beta-galactosidase,TS_6_6,6 +4791.0,4911.6,473.6,beta-galactosidase,TS_6_6,6 +3827.744,5276.792,487.469,beta-galactosidase,TS_6_6,6 +4020.131,3614.629,916.419,beta-galactosidase,TS_6_6,6 +4002.222,5008.421,1137.719,beta-galactosidase,TS_6_6,6 +2440.282,2449.981,1140.433,beta-galactosidase,TS_6_6,6 +773.644,4975.664,1499.213,ribosome,TS_6_6,6 +1029.655,5119.687,1332.989,ribosome,TS_6_6,6 +2106.09,4846.302,1289.305,ribosome,TS_6_6,6 +2843.254,5123.492,1377.645,ribosome,TS_6_6,6 +3676.182,4058.335,1228.49,ribosome,TS_6_6,6 +5697.427,1950.562,972.48,ribosome,TS_6_6,6 +2452.111,3000.991,1108.533,ribosome,TS_6_6,6 +4305.75,4499.335,1214.481,ribosome,TS_6_6,6 +3740.366,4541.466,1101.761,ribosome,TS_6_6,6 +5405.801,2023.684,725.902,ribosome,TS_6_6,6 +5432.587,1978.296,1026.276,ribosome,TS_6_6,6 +5343.945,2419.647,826.027,ribosome,TS_6_6,6 +5182.757,2111.002,826.027,ribosome,TS_6_6,6 +5442.264,2473.504,1116.388,ribosome,TS_6_6,6 +5702.357,2282.812,916.139,ribosome,TS_6_6,6 +5156.231,2251.003,1036.288,ribosome,TS_6_6,6 +4984.874,4435.309,1086.35,ribosome,TS_6_6,6 +4816.361,4198.518,1076.338,ribosome,TS_6_6,6 +4333.457,4506.473,933.065,ribosome,TS_6_6,6 +3821.058,4739.232,976.213,ribosome,TS_6_6,6 +4248.584,4224.388,1176.462,ribosome,TS_6_6,6 +782.628,5388.786,1436.786,ribosome,TS_6_6,6 +585.528,5169.929,1436.786,ribosome,TS_6_6,6 +4709.027,5996.216,316.811,thyroglobulin,TS_6_6,6 +2822.785,2974.861,443.679,thyroglobulin,TS_6_6,6 +2974.63,5786.818,491.11,thyroglobulin,TS_6_6,6 +4622.227,4238.002,506.417,thyroglobulin,TS_6_6,6 +1743.59,605.843,529.632,thyroglobulin,TS_6_6,6 +4492.1,2145.4,584.3,thyroglobulin,TS_6_6,6 +2408.626,2515.879,632.912,thyroglobulin,TS_6_6,6 +6004.797,1528.455,670.081,thyroglobulin,TS_6_6,6 +6065.226,782.511,706.026,thyroglobulin,TS_6_6,6 +3444.054,1444.976,785.389,thyroglobulin,TS_6_6,6 +4327.106,2286.262,879.085,thyroglobulin,TS_6_6,6 +2092.183,1968.936,946.212,thyroglobulin,TS_6_6,6 +2141.26,4493.054,956.843,thyroglobulin,TS_6_6,6 +5348.617,5636.545,943.527,thyroglobulin,TS_6_6,6 +2505.834,1629.141,1118.847,thyroglobulin,TS_6_6,6 +760.567,4157.394,1146.652,thyroglobulin,TS_6_6,6 +228.897,2923.352,1286.104,thyroglobulin,TS_6_6,6 +524.918,2713.886,1291.522,thyroglobulin,TS_6_6,6 +3302.056,834.556,1336.967,thyroglobulin,TS_6_6,6 +586.238,3201.857,1507.786,thyroglobulin,TS_6_6,6 +3750.577,1852.692,950.385,thyroglobulin,TS_6_6,6 +3766.612,1578.415,588.798,thyroglobulin,TS_6_6,6 +5407.907,1216.163,407.558,thyroglobulin,TS_6_6,6 +3113.699,4522.603,960.342,thyroglobulin,TS_6_6,6 +3466.387,4875.21,408.571,thyroglobulin,TS_6_6,6 +1856.175,4545.519,1185.519,thyroglobulin,TS_6_6,6 +922.5,5110.0,1055.0,thyroglobulin,TS_6_6,6 +1184.054,5135.135,337.568,thyroglobulin,TS_6_6,6 +2511.073,1898.707,818.244,thyroglobulin,TS_6_6,6 +4817.354,4860.628,979.955,thyroglobulin,TS_6_6,6 +4461.316,3580.702,1067.544,thyroglobulin,TS_6_6,6 +2947.812,1297.5,1109.688,thyroglobulin,TS_6_6,6 +4719.918,1853.143,1229.265,thyroglobulin,TS_6_6,6 +1543.079,3649.778,1353.424,thyroglobulin,TS_6_6,6 +970.18,3473.559,1473.604,thyroglobulin,TS_6_6,6 +4113.927,1540.028,957.754,virus-like-particle,TS_6_6,6 +4205.011,514.238,377.245,virus-like-particle,TS_6_6,6 +3546.662,996.468,1199.592,virus-like-particle,TS_6_6,6 +238.569,3475.323,722.892,virus-like-particle,TS_6_6,6 +404.421,2707.26,970.408,virus-like-particle,TS_6_6,6 +5686.068,5829.641,364.486,virus-like-particle,TS_6_6,6 +5086.352,6157.563,696.444,virus-like-particle,TS_6_6,6 +4657.586,5875.914,627.971,virus-like-particle,TS_6_6,6 +5827.109,4997.341,854.623,virus-like-particle,TS_6_6,6 +5157.398,4441.079,683.717,virus-like-particle,TS_6_6,6 +5278.371,3038.921,406.17,virus-like-particle,TS_6_6,6 +3005.1,4116.745,533.11,virus-like-particle,TS_6_6,6 +2829.054,4175.449,828.66,virus-like-particle,TS_6_6,6 +3248.07,4513.06,582.863,virus-like-particle,TS_6_6,6 +2609.876,4569.876,1169.759,virus-like-particle,TS_6_6,6 +2213.287,4135.017,1286.851,virus-like-particle,TS_6_6,6 +3303.905,5697.825,789.744,virus-like-particle,TS_6_6,6 +1008.748,5949.213,1077.303,virus-like-particle,TS_6_6,6 +5749.052,3911.392,275.342,virus-like-particle,TS_6_6,6 +1916.83,3311.797,754.673,apo-ferritin,TS_6_6,6 +1996.861,3231.277,803.577,apo-ferritin,TS_6_6,6 +2206.512,2975.302,1179.674,apo-ferritin,TS_6_6,6 +285.292,1379.331,417.577,apo-ferritin,TS_6_6,6 +753.781,2633.219,973.094,apo-ferritin,TS_6_6,6 +726.176,2559.49,1473.314,apo-ferritin,TS_6_6,6 +747.829,2630.698,1549.302,apo-ferritin,TS_6_6,6 +1123.151,2698.296,725.852,apo-ferritin,TS_6_6,6 +1234.692,2994.751,920.469,apo-ferritin,TS_6_6,6 +332.707,3382.279,1241.909,apo-ferritin,TS_6_6,6 +766.772,3170.667,1543.544,apo-ferritin,TS_6_6,6 +1569.207,2394.552,1299.69,apo-ferritin,TS_6_6,6 +818.107,3625.562,671.657,apo-ferritin,TS_6_6,6 +1146.957,1508.462,679.398,apo-ferritin,TS_6_6,6 +4586.585,476.585,763.78,apo-ferritin,TS_6_6,6 +4528.663,551.489,773.374,apo-ferritin,TS_6_6,6 +4905.12,1090.843,266.175,apo-ferritin,TS_6_6,6 +5344.971,446.0,60.429,apo-ferritin,TS_6_6,6 +5250.109,3132.92,842.628,apo-ferritin,TS_6_6,6 +5066.127,1954.349,293.048,apo-ferritin,TS_6_6,6 +5671.396,2753.784,797.838,apo-ferritin,TS_6_6,6 +5794.353,2457.302,441.691,apo-ferritin,TS_6_6,6 +3355.743,2365.446,1138.614,apo-ferritin,TS_6_6,6 +3101.77,1278.918,264.197,apo-ferritin,TS_6_6,6 +2705.768,1889.625,1354.642,apo-ferritin,TS_6_6,6 +3109.837,1800.782,954.072,apo-ferritin,TS_6_6,6 +4212.422,2013.806,305.882,apo-ferritin,TS_6_6,6 +4057.61,5767.868,576.581,apo-ferritin,TS_6_6,6 +3153.077,4790.538,460.577,apo-ferritin,TS_6_6,6 +2977.845,4634.696,444.254,apo-ferritin,TS_6_6,6 +3332.697,5251.364,720.576,apo-ferritin,TS_6_6,6 +3442.903,5827.204,1080.502,apo-ferritin,TS_6_6,6 +318.185,5000.579,551.158,apo-ferritin,TS_6_6,6 +1776.173,4887.716,1019.877,apo-ferritin,TS_6_6,6 +3578.705,3480.026,1206.5,apo-ferritin,TS_6_6,6 +5680.786,2759.578,936.164,apo-ferritin,TS_6_6,6 +3432.444,1208.43,826.027,apo-ferritin,TS_6_6,6 +3074.149,1859.506,846.052,apo-ferritin,TS_6_6,6 +1949.78,3068.74,1156.437,apo-ferritin,TS_6_6,6 +3687.965,1751.484,1296.612,apo-ferritin,TS_6_6,6 +3684.71,1654.009,1306.624,apo-ferritin,TS_6_6,6 diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py new file mode 100644 index 0000000000..93fb7c3358 --- /dev/null +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py @@ -0,0 +1,581 @@ + +import random +import os +import numpy as np +import pandas as pd +import torch +from torch.utils.data import Sampler, RandomSampler, SequentialSampler, DataLoader, WeightedRandomSampler +from torch import nn, optim +# from torch.optim import AdamW +from torch.optim.lr_scheduler import LambdaLR +from torch.optim import lr_scheduler +import importlib +import math +import neptune +from neptune.utils import stringify_unsupported + +import logging +import pickle + + + +def get_linear_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps, last_epoch=-1): + """ + from https://github.com/huggingface/transformers/blob/main/src/transformers/optimization.py + Create a schedule with a learning rate that decreases linearly from the initial lr set in the optimizer to 0, after + a warmup period during which it increases linearly from 0 to the initial lr set in the optimizer. + Args: + optimizer ([`~torch.optim.Optimizer`]): + The optimizer for which to schedule the learning rate. + num_warmup_steps (`int`): + The number of steps for the warmup phase. + num_training_steps (`int`): + The total number of training steps. + last_epoch (`int`, *optional*, defaults to -1): + The index of the last epoch when resuming training. + Return: + `torch.optim.lr_scheduler.LambdaLR` with the appropriate schedule. + """ + + def lr_lambda(current_step: int): + if current_step < num_warmup_steps: + return float(current_step) / float(max(1, num_warmup_steps)) + return max( + 0.0, float(num_training_steps - current_step) / float(max(1, num_training_steps - num_warmup_steps)) + ) + + return LambdaLR(optimizer, lr_lambda, last_epoch) + + +def get_cosine_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps, num_cycles= 0.5, last_epoch= -1): + """ + from https://github.com/huggingface/transformers/blob/main/src/transformers/optimization.py + Create a schedule with a learning rate that decreases following the values of the cosine function between the + initial lr set in the optimizer to 0, after a warmup period during which it increases linearly between 0 and the + initial lr set in the optimizer. + Args: + optimizer ([`~torch.optim.Optimizer`]): + The optimizer for which to schedule the learning rate. + num_warmup_steps (`int`): + The number of steps for the warmup phase. + num_training_steps (`int`): + The total number of training steps. + num_cycles (`float`, *optional*, defaults to 0.5): + The number of waves in the cosine schedule (the defaults is to just decrease from the max value to 0 + following a half-cosine). + last_epoch (`int`, *optional*, defaults to -1): + The index of the last epoch when resuming training. + Return: + `torch.optim.lr_scheduler.LambdaLR` with the appropriate schedule. + """ + + def lr_lambda(current_step): + if current_step < num_warmup_steps: + return float(current_step) / float(max(1, num_warmup_steps)) + progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps)) + return max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(num_cycles) * 2.0 * progress))) + + return LambdaLR(optimizer, lr_lambda, last_epoch) + + + + +def calc_grad_norm(parameters,norm_type=2.): + + if isinstance(parameters, torch.Tensor): + parameters = [parameters] + parameters = [p for p in parameters if p.grad is not None] + norm_type = float(norm_type) + if len(parameters) == 0: + return torch.tensor(0.) + device = parameters[0].grad.device + total_norm = torch.norm(torch.stack([torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]), norm_type) + if torch.logical_or(total_norm.isnan(), total_norm.isinf()): + total_norm = None + + return total_norm + +def calc_weight_norm(parameters,norm_type=2.): + + # l2_loss = 0 + # for param in parameters : + # l2_loss += 0.5 * torch.sum(param ** 2) + # return l2_loss + + if isinstance(parameters, torch.Tensor): + parameters = [parameters] + parameters = [p for p in parameters if p.grad is not None] + norm_type = float(norm_type) + if len(parameters) == 0: + return torch.tensor(0.) + device = parameters[0].grad.device + + + total_norm = torch.stack([torch.norm(p.detach(), norm_type).to(device) for p in parameters]).mean() + if torch.logical_or(total_norm.isnan(), total_norm.isinf()): + total_norm = None + + return total_norm + +class OrderedDistributedSampler(Sampler): + def __init__(self, dataset, num_replicas=None, rank=None): + if num_replicas is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + num_replicas = dist.get_world_size() + if rank is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + rank = dist.get_rank() + self.dataset = dataset + self.num_replicas = num_replicas + self.rank = rank + self.num_samples = int(math.ceil(len(self.dataset) * 1.0 / self.num_replicas)) + self.total_size = self.num_samples * self.num_replicas + + print("TOTAL SIZE", self.total_size) + + def __iter__(self): + indices = list(range(len(self.dataset))) + + # add extra samples to make it evenly divisible + indices += indices[: (self.total_size - len(indices))] + assert len(indices) == self.total_size + + # subsample + indices = indices[ + self.rank * self.num_samples : self.rank * self.num_samples + self.num_samples + ] + print( + "SAMPLES", + self.rank * self.num_samples, + self.rank * self.num_samples + self.num_samples, + ) + assert len(indices) == self.num_samples + + return iter(indices) + + def __len__(self): + return self.num_samples + + +def sync_across_gpus(t, world_size): + torch.distributed.barrier() + gather_t_tensor = [torch.ones_like(t) for _ in range(world_size)] + torch.distributed.all_gather(gather_t_tensor, t) + return torch.cat(gather_t_tensor) + + +def set_seed(seed=1234): + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.deterministic = False + torch.backends.cudnn.benchmark = True + + +def worker_init_fn(worker_id): + np.random.seed(np.random.get_state()[1][0] + worker_id) + + +def get_model(cfg, ds): + Net = importlib.import_module(cfg.model).Net + net = Net(cfg) + if cfg.pretrained_weights is not None: + if type(cfg.pretrained_weights) == list: + cfg.pretrained_weights = cfg.pretrained_weights[cfg.fold] + print(f'{cfg.local_rank}: loading weights from',cfg.pretrained_weights) + state_dict = torch.load(cfg.pretrained_weights, map_location='cpu') + if "model" in state_dict.keys(): + state_dict = state_dict['model'] + state_dict = {key.replace('module.',''):val for key,val in state_dict.items()} + if cfg.pop_weights is not None: + print(f'popping {cfg.pop_weights}') + to_pop = [] + for key in state_dict: + for item in cfg.pop_weights: + if item in key: + to_pop += [key] + for key in to_pop: + print(f'popping {key}') + state_dict.pop(key) + + net.load_state_dict(state_dict, strict=cfg.pretrained_weights_strict) + print(f'{cfg.local_rank}: weights loaded from',cfg.pretrained_weights) + + return net + + +def create_checkpoint(cfg, model, optimizer, epoch, scheduler=None, scaler=None): + + + state_dict = model.state_dict() + if cfg.save_weights_only: + checkpoint = {"model": state_dict} + return checkpoint + + checkpoint = { + "model": state_dict, + "optimizer": optimizer.state_dict(), + "epoch": epoch, + } + + if scheduler is not None: + checkpoint["scheduler"] = scheduler.state_dict() + + if scaler is not None: + checkpoint["scaler"] = scaler.state_dict() + return checkpoint + +def load_checkpoint(cfg, model, optimizer, scheduler=None, scaler=None): + + print(f'loading ckpt {cfg.resume_from}') + checkpoint = torch.load(cfg.resume_from, map_location='cpu') + model.load_state_dict(checkpoint['model']) + optimizer.load_state_dict(checkpoint['optimizer']) + scheduler_dict = checkpoint['scheduler'] + if scaler is not None: + scaler.load_state_dict(checkpoint['scaler']) + + epoch = checkpoint['epoch'] + return model, optimizer, scheduler_dict, scaler, epoch + + +def get_dataset(df, cfg, mode='train'): + + #modes train, val, index + print(f"Loading {mode} dataset") + + if mode == 'train': + dataset = get_train_dataset(df, cfg) +# elif mode == 'train_val': +# dataset = get_val_dataset(df, cfg) + elif mode == 'val': + dataset = get_val_dataset(df, cfg) + elif mode == 'test': + dataset = get_test_dataset(df, cfg) + else: + pass + return dataset + +def get_dataloader(ds, cfg, mode='train'): + + if mode == 'train': + dl = get_train_dataloader(ds, cfg) + elif mode =='val': + dl = get_val_dataloader(ds, cfg) + elif mode =='test': + dl = get_test_dataloader(ds, cfg) + return dl + + +def get_train_dataset(train_df, cfg): + + train_dataset = cfg.CustomDataset(train_df, cfg, aug=cfg.train_aug, mode="train") + if cfg.data_sample > 0: + train_dataset = torch.utils.data.Subset(train_dataset, np.arange(cfg.data_sample)) + return train_dataset + + + + +def get_train_dataloader(train_ds, cfg): + + if cfg.distributed: + sampler = torch.utils.data.distributed.DistributedSampler( + train_ds, num_replicas=cfg.world_size, rank=cfg.local_rank, shuffle=True, seed=cfg.seed + ) + else: + try: + if cfg.random_sampler_frac > 0: + + num_samples = int(len(train_ds) * cfg.random_sampler_frac) + sample_weights = train_ds.sample_weights + sampler = WeightedRandomSampler(sample_weights, num_samples= num_samples ) + else: + sampler = None + except: + sampler = None + + + if cfg.use_custom_batch_sampler: + sampler = RandomSampler(train_ds) + bsampler = CustomBatchSampler(sampler, batch_size =cfg.batch_size, drop_last=cfg.drop_last) + train_dataloader = DataLoader( + train_ds, + batch_sampler=bsampler, +# shuffle=(sampler is None), +# batch_size=cfg.batch_size, + num_workers=cfg.num_workers, + pin_memory=cfg.pin_memory, + collate_fn=cfg.tr_collate_fn, +# drop_last=cfg.drop_last, + worker_init_fn=worker_init_fn, + ) + else: + + + train_dataloader = DataLoader( + train_ds, + sampler=sampler, + shuffle=(sampler is None), + batch_size=cfg.batch_size, + num_workers=cfg.num_workers, + pin_memory=cfg.pin_memory, + collate_fn=cfg.tr_collate_fn, + drop_last=cfg.drop_last, + worker_init_fn=worker_init_fn, + ) + print(f"train: dataset {len(train_ds)}, dataloader {len(train_dataloader)}") + return train_dataloader + + +def get_val_dataset(val_df, cfg, allowed_targets=None): + val_dataset = cfg.CustomDataset(val_df, cfg, aug=cfg.val_aug, mode="val") + return val_dataset + + +def get_val_dataloader(val_ds, cfg): + + if cfg.distributed and cfg.eval_ddp: + sampler = OrderedDistributedSampler( + val_ds, num_replicas=cfg.world_size, rank=cfg.local_rank + ) + else: + sampler = SequentialSampler(val_ds) + + if cfg.batch_size_val is not None: + batch_size = cfg.batch_size_val + else: + batch_size = cfg.batch_size + val_dataloader = DataLoader( + val_ds, + sampler=sampler, + batch_size=batch_size, + num_workers=cfg.num_workers, + pin_memory=cfg.pin_memory, + collate_fn=cfg.val_collate_fn, + worker_init_fn=worker_init_fn, + ) + print(f"valid: dataset {len(val_ds)}, dataloader {len(val_dataloader)}") + return val_dataloader + + +def get_test_dataset(test_df, cfg): + test_dataset = cfg.CustomDataset(test_df, cfg, aug=cfg.val_aug, mode="test") + return test_dataset + + +def get_test_dataloader(test_ds, cfg): + + if cfg.distributed and cfg.eval_ddp: + sampler = OrderedDistributedSampler( + test_ds, num_replicas=cfg.world_size, rank=cfg.local_rank + ) + else: + sampler = SequentialSampler(test_ds) + + if cfg.batch_size_val is not None: + batch_size = cfg.batch_size_val + else: + batch_size = cfg.batch_size + test_dataloader = DataLoader( + test_ds, + sampler=sampler, + batch_size=batch_size, + num_workers=cfg.num_workers, + pin_memory=cfg.pin_memory, + collate_fn=cfg.val_collate_fn, + worker_init_fn=worker_init_fn, + ) + print(f"test: dataset {len(test_ds)}, dataloader {len(test_dataloader)}") + return test_dataloader + + + +def get_optimizer(model, cfg): + + params = model.parameters() + + if cfg.optimizer == "Adam": + optimizer = optim.Adam(params, lr=cfg.lr, weight_decay=cfg.weight_decay) + + elif cfg.optimizer == "AdamW_plus": + paras = list(model.named_parameters()) + no_decay = ["bias", "LayerNorm.bias"] + params = [{"params": [param for name, param in paras if (not any(nd in name for nd in no_decay))], + "lr": cfg.lr, + "weight_decay":cfg.weight_decay}, + {"params": [param for name, param in paras if (any(nd in name for nd in no_decay))], + "lr": cfg.lr, + "weight_decay":0.}, + ] + optimizer = optim.AdamW(params, lr=cfg.lr) + + + elif cfg.optimizer == "AdamW": + optimizer = optim.AdamW(params, lr=cfg.lr, weight_decay=cfg.weight_decay) + + elif cfg.optimizer == "SGD": + optimizer = optim.SGD( + params, + lr=cfg.lr, + momentum=cfg.sgd_momentum, + nesterov=cfg.sgd_nesterov, + weight_decay=cfg.weight_decay, + ) + + return optimizer + + + +def get_scheduler(cfg, optimizer, total_steps): + + if cfg.schedule == "steplr": + scheduler = optim.lr_scheduler.StepLR( + optimizer, + step_size=cfg.epochs_step * (total_steps // cfg.batch_size) // cfg.world_size, + gamma=0.5, + ) + elif cfg.schedule == "cosine": + scheduler = get_cosine_schedule_with_warmup( + optimizer, + num_warmup_steps=cfg.warmup * (total_steps // cfg.batch_size) // cfg.world_size, + num_training_steps=cfg.epochs * (total_steps // cfg.batch_size) // cfg.world_size, + num_cycles = cfg.num_cycles + ) + elif cfg.schedule == "linear": + scheduler = get_linear_schedule_with_warmup( + optimizer, + num_warmup_steps=0, + num_training_steps=cfg.epochs * (total_steps // cfg.batch_size) // cfg.world_size, + ) + + elif cfg.schedule == "CosineAnnealingLR": + T_max = int(np.ceil(0.5*total_steps)) + scheduler = lr_scheduler.CosineAnnealingLR(optimizer, + T_max=T_max, + eta_min=1e-8) + +# print("num_steps", (total_steps // cfg.batch_size) // cfg.world_size) + + + + else: + scheduler = None + + return scheduler + + +def setup_neptune(cfg): + neptune_run = None + if cfg.neptune_project: + neptune_run = neptune.init_run( + project=cfg.neptune_project, +# tags=cfg.tags, + mode=cfg.neptune_connection_mode, + capture_stdout=False, + capture_stderr=False, + source_files=[f'models/{cfg.model}.py',f'data/{cfg.dataset}.py',f'configs/{cfg.name}.py'] + ) + + + neptune_run["cfg"] = stringify_unsupported(cfg.__dict__) + + return neptune_run + + +def read_df(fn): + + if 'parquet' in fn: + df = pd.read_parquet(fn, engine = "fastparquet") + else: + df = pd.read_csv(fn) + return df + +def get_data(cfg): + + # setup dataset + if type(cfg.train_df) == list: + cfg.train_df = cfg.train_df[cfg.fold] + print(f"reading {cfg.train_df}") + df = read_df(cfg.train_df) + + if cfg.test: + test_df = read_df(cfg.test_df) + else: + test_df = None + + if cfg.val_df: + if type(cfg.val_df) == list: + cfg.val_df = cfg.val_df[cfg.fold] + val_df = read_df(cfg.val_df) + if cfg.fold > -1: + if 'fold' in val_df.columns: + val_df = val_df[val_df["fold"] == cfg.fold] + train_df = df[df["fold"] != cfg.fold] + else: + train_df = df + else: + train_df = df + else: + if cfg.fold == -1: + val_df = df[df["fold"] == 0] + else: + val_df = df[df["fold"] == cfg.fold] + + train_df = df[df["fold"] != cfg.fold] + + return train_df, val_df, test_df + + +def upload_s3(cfg): + from boto3.session import Session + import boto3 + BUCKET_NAME = cfg.s3_bucket_name + ACCESS_KEY = cfg.s3_access_key + SECRET_KEY = cfg.s3_secret_key + session = Session(aws_access_key_id=ACCESS_KEY, + aws_secret_access_key=SECRET_KEY) + s3 = session.resource('s3') + + s3.Bucket(BUCKET_NAME).upload_file(f"{cfg.output_dir}/fold{cfg.fold}/val_data_seed{cfg.seed}.pth", f"output/{cfg.name}/fold{cfg.fold}/val_data_seed{cfg.seed}.pth") + s3.Bucket(BUCKET_NAME).upload_file(f"{cfg.output_dir}/fold{cfg.fold}/test_data_seed{cfg.seed}.pth", f"output/{cfg.name}/fold{cfg.fold}/test_data_seed{cfg.seed}.pth") + s3.Bucket(BUCKET_NAME).upload_file(f"{cfg.output_dir}/fold{cfg.fold}/submission_seed{cfg.seed}.csv", f"output/{cfg.name}/fold{cfg.fold}/submission_seed{cfg.seed}.csv") + + +def flatten(t): + return [item for sublist in t for item in sublist] + +def set_pandas_display(): + pd.set_option('display.max_columns', None) + pd.set_option('display.max_rows',10000) + pd.set_option('display.width', 10000) + pd.set_option('display.float_format', lambda x: '%.3f' % x) + +def dumpobj(file, obj): + with open(file, 'wb') as handle: + pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL) + +def loadobj(file): + with open(file, 'rb') as handle: + return pickle.load(handle) + +def get_level(level_str): + ''' get level''' + l_names = {logging.getLevelName(lvl).lower(): lvl for lvl in [10, 20, 30, 40, 50]} # noqa + return l_names.get(level_str.lower(), logging.INFO) + +def get_logger(name, level_str): + ''' get logger''' + logger = logging.getLogger(name) + logger.setLevel(get_level(level_str)) + handler = logging.StreamHandler() + handler.setLevel(level_str) + handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) # pylint: disable=C0301 # noqa + logger.addHandler(handler) + + return logger + From 2ffe23d162a642acee2208f94a6e878023286717 Mon Sep 17 00:00:00 2001 From: christofhenkel Date: Mon, 24 Feb 2025 15:53:04 +0000 Subject: [PATCH 02/13] remove unnecessary dependencies --- .../Cryo-ET/1st_place_solution/README.md | 2 +- .../1st_place_solution/metrics/metric_1.py | 54 +------------------ .../1st_place_solution/postprocess/pp_1.py | 5 -- .../1st_place_solution/requirements.txt | 8 --- .../Cryo-ET/1st_place_solution/train.py | 30 ++--------- .../Cryo-ET/1st_place_solution/utils.py | 19 ------- 6 files changed, 7 insertions(+), 111 deletions(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index c32c47dd28..57270a67bf 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -66,7 +66,7 @@ We provide three different configurations which differ only in the used backbone ```python train.py -C cfg_resnet34 --output_dir WHATEVERISYOUROUTPUTDIR``` -This will save checkpoints under the specified WHATEVERISYOUROUTPUTDIR. +This will save checkpoints under the specified WHATEVERISYOUROUTPUTDIR when training is finished. By default models are trained using bfloat16 which requires a GPU capable of that. Alternatively you can set ```cfg.bf16=False``` or overwrite as flag ```--bf16 False``` when running ```train.py ```. ### Replicating 1st place solution (segmentation part) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py index b1615af667..69d6aae5b0 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py @@ -1,15 +1,7 @@ import numpy as np import torch from sklearn.metrics import roc_auc_score - -""" -Derived from: -https://github.com/cellcanvas/album-catalog/blob/main/solutions/copick/compare-picks/solution.py -""" - -import numpy as np import pandas as pd - from scipy.spatial import KDTree @@ -156,13 +148,6 @@ def calc_metric(cfg, pp_out, val_df, pre="val"): submission['experiment'] = solution['experiment'].unique()[0] submission['id'] = range(len(submission)) -# score003 = score( -# solution.copy(), -# submission[submission['conf']>0.03].copy(), -# row_id_column_name = 'id', -# distance_multiplier=0.5, -# beta=4)[0] -# print('score003',score003) best_ths = [] for p in particles: @@ -196,42 +181,5 @@ def calc_metric(cfg, pp_out, val_df, pre="val"): result['score'] = score_pp # print(result) return result -# # if isinstance(pred_df,list): -# # pred_df,gt_df = pred_df -# # else: -# # gt_df = None - -# y_true = val_df['score'].values -# y_pred = val_data['preds'].cpu().numpy() -# score = get_score(y_true.flatten(), y_pred.flatten()) -# # print(score) - -# # df['score'] = df['location'].apply(ast.literal_eval) -# # df['span'] = df['location'].apply(location_to_span) -# # spans_true = df['span'].values - -# # df_pred = pred_df.copy() -# # # df_pred['location'] = df_pred['location'].apply(ast.literal_eval) -# # df_pred['span'] = df_pred['pred_location'].apply(pred_location_to_span) -# # spans_pred = df_pred['span'].values - -# # score = span_micro_f1(spans_pred, spans_true) - -# if hasattr(cfg, "neptune_run"): -# cfg.neptune_run[f"{pre}/score/"].log(score, step=cfg.curr_step) -# print(f"{pre} score: {score:.6}") -# # else: -# # return score - -# # if gt_df is not None: -# # df_pred = gt_df.copy() -# # df_pred['span'] = df_pred['pred_location'].apply(pred_location_to_span) -# # spans_pred = df_pred['span'].values - -# # score = span_micro_f1(spans_pred, spans_true) - -# # if hasattr(cfg, "neptune_run"): -# # cfg.neptune_run[f"{pre}/score_debug/"].log(score, step=cfg.curr_step) -# # # print(f"{pre} score_debug: {score:.6}") -# return score + diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py index 3be9890e54..796b626f95 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py @@ -1,5 +1,3 @@ - - import pandas as pd import torch from torch.nn import functional as F @@ -8,8 +6,6 @@ import torch from torch import nn - - def simple_nms(scores, nms_radius: int): """ Fast Non-maximum suppression to remove nearby points """ assert(nms_radius >= 0) @@ -54,7 +50,6 @@ def post_process_pipeline(cfg, val_data, val_df): pred_df_ = pd.DataFrame(xyz.cpu().numpy(),columns=['x','y','z']) pred_df_['particle_type'] = p pred_df_['conf'] = conf.cpu().numpy() -# pred_df_['experiment'] = experiments[fold] pred_df += [pred_df_] pred_df = pd.concat(pred_df) pred_df = pred_df[(pred_df['x']<6300) & (pred_df['y']<6300)& (pred_df['z']<1840) & (pred_df['conf']>0.01)].copy() diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt b/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt index 07d6d142fa..65331e9981 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt @@ -1,10 +1,2 @@ -kaggle -optuna -boto3 -neptune zarr -albumentations==1.4.21 -opencv-python==4.5.5.64 -timm==1.0.11 monai==1.4.0 -mrcfile \ No newline at end of file diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py index fe6002fe6e..ad22756910 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py @@ -29,8 +29,6 @@ from utils import ( get_optimizer, get_scheduler, - setup_neptune, - upload_s3, ) @@ -120,9 +118,7 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): loss = np.mean(losses) print(f"Mean {pre}_{k}", loss) - if cfg.neptune_run: - if not math.isinf(loss) and not math.isnan(loss): - cfg.neptune_run[f"{pre}/{k}"].log(loss, step=cfg.curr_step) + if (cfg.local_rank == 0) and (cfg.calc_metric) and (((curr_epoch + 1) % cfg.calc_metric_epochs) == 0): @@ -135,9 +131,7 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): for k, v in val_score.items(): print(f"{pre}_{k}: {v:.3f}") - if cfg.neptune_run: - if not math.isinf(v) and not math.isnan(v): - cfg.neptune_run[f"{pre}/{k}"].log(v, step=cfg.curr_step) + if cfg.distributed: torch.distributed.barrier() @@ -191,8 +185,7 @@ def train(cfg): set_seed(cfg.seed) - if cfg.local_rank == 0: - cfg.neptune_run = setup_neptune(cfg) + train_df, val_df, test_df = get_data(cfg) @@ -347,17 +340,7 @@ def train(cfg): if cfg.local_rank == 0 and cfg.curr_step % cfg.batch_size == 0: loss_names = [key for key in output_dict if 'loss' in key] - if cfg.neptune_run: - for l in loss_names: - v = output_dict[l].item() - if not math.isinf(v) and not math.isnan(v): - cfg.neptune_run[f"train/{l}"].log(value=v, step=cfg.curr_step) - cfg.neptune_run["lr"].log(value=optimizer.param_groups[0]["lr"], step=cfg.curr_step) - if total_grad_norm is not None: - cfg.neptune_run["total_grad_norm"].log(value=total_grad_norm.item(), step=cfg.curr_step) - cfg.neptune_run["total_grad_norm_after_clip"].log(value=total_grad_norm_after_clip.item(), step=cfg.curr_step) - if total_weight_norm is not None: - cfg.neptune_run["total_weight_norm"].log(value=total_weight_norm.item(), step=cfg.curr_step) + progress_bar.set_description(f"loss: {np.mean(losses[-10:]):.4f}") if cfg.eval_steps != 0: @@ -426,14 +409,11 @@ def train(cfg): parser = argparse.ArgumentParser(description="") parser.add_argument("-C", "--config", help="config filename") - parser.add_argument("-D", "--debug", action='store_true', help="debugging True/ False") parser_args, other_args = parser.parse_known_args(sys.argv) cfg = copy(importlib.import_module(parser_args.config).cfg) - if parser_args.debug: - print('debug mode') - cfg.neptune_connection_mode = 'debug' + # overwrite params in config with additional args diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py index 93fb7c3358..36b5190ab2 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py @@ -1,4 +1,3 @@ - import random import os import numpy as np @@ -6,13 +5,10 @@ import torch from torch.utils.data import Sampler, RandomSampler, SequentialSampler, DataLoader, WeightedRandomSampler from torch import nn, optim -# from torch.optim import AdamW from torch.optim.lr_scheduler import LambdaLR from torch.optim import lr_scheduler import importlib import math -import neptune -from neptune.utils import stringify_unsupported import logging import pickle @@ -469,22 +465,7 @@ def get_scheduler(cfg, optimizer, total_steps): return scheduler -def setup_neptune(cfg): - neptune_run = None - if cfg.neptune_project: - neptune_run = neptune.init_run( - project=cfg.neptune_project, -# tags=cfg.tags, - mode=cfg.neptune_connection_mode, - capture_stdout=False, - capture_stderr=False, - source_files=[f'models/{cfg.model}.py',f'data/{cfg.dataset}.py',f'configs/{cfg.name}.py'] - ) - - - neptune_run["cfg"] = stringify_unsupported(cfg.__dict__) - return neptune_run def read_df(fn): From df958e7c3adb667daf4540429d89f4b80b18b9e7 Mon Sep 17 00:00:00 2001 From: christofhenkel Date: Mon, 24 Feb 2025 16:12:53 +0000 Subject: [PATCH 03/13] minor typos --- competitions/kaggle/Cryo-ET/1st_place_solution/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index 57270a67bf..e9c5c4dc20 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -49,7 +49,7 @@ Alternativly it can be downloaded using the kaggle API (which can be installed v ```kaggle competitions download -c czii-cryo-et-object-identification``` -and adjust path to it in ```configs/common_config.py``` with ```cfg.data_folder``` +and adjust path to it in ```configs/common_config.py``` with ```cfg.data_folder```. From 7ba7c6af3de45b9f935d2e35ff909ce7bd4a06df Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:19:30 +0000 Subject: [PATCH 04/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../Cryo-ET/1st_place_solution/README.md | 14 +- .../configs/cfg_effnetb3.py | 23 +- .../configs/cfg_resnet34.py | 20 +- .../configs/cfg_resnet34_ds.py | 18 +- .../configs/common_config.py | 125 +++++---- .../Cryo-ET/1st_place_solution/data/ds_1.py | 68 +++-- .../1st_place_solution/metrics/metric_1.py | 153 +++++------ .../1st_place_solution/models/mdl_1.py | 112 ++++---- .../1st_place_solution/postprocess/pp_1.py | 60 +++-- .../Cryo-ET/1st_place_solution/train.py | 123 ++++----- .../Cryo-ET/1st_place_solution/utils.py | 244 +++++++++--------- 11 files changed, 475 insertions(+), 485 deletions(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index e9c5c4dc20..c40853ce1d 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -29,15 +29,15 @@ To have a common environment its suggested to use the basic pytorch docker conta ```docker run nvcr.io/nvidia/pytorch:24.08-py3``` -2. Within the container clone this repository +2. Within the container clone this repository ``` -git clone https://github.com/ProjectMONAI/tutorials +git clone https://github.com/ProjectMONAI/tutorials cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ ``` -3. And install necessary additional pip packages via +3. And install necessary additional pip packages via ```pip install -r requirements.txt``` @@ -49,7 +49,7 @@ Alternativly it can be downloaded using the kaggle API (which can be installed v ```kaggle competitions download -c czii-cryo-et-object-identification``` -and adjust path to it in ```configs/common_config.py``` with ```cfg.data_folder```. +and adjust path to it in ```configs/common_config.py``` with ```cfg.data_folder```. @@ -62,7 +62,7 @@ We solve the competition with a 3D-segmentation approach leveraging [MONAI's Fle ![alt text](partly_Unet.png "Partly UNet") -We provide three different configurations which differ only in the used backbone and output feature maps. The configuration files are .py files and located under ```configs``` and share all other hyper-parameters. Each hyperparameter can be overwriten by adding a flag to the training command. To train a resnet34 version of our segmentation model simply run +We provide three different configurations which differ only in the used backbone and output feature maps. The configuration files are .py files and located under ```configs``` and share all other hyper-parameters. Each hyperparameter can be overwriten by adding a flag to the training command. To train a resnet34 version of our segmentation model simply run ```python train.py -C cfg_resnet34 --output_dir WHATEVERISYOUROUTPUTDIR``` @@ -71,7 +71,7 @@ By default models are trained using bfloat16 which requires a GPU capable of tha ### Replicating 1st place solution (segmentation part) -To train checkpoints necessary for replicating the segmentation part of the 1st place solution run training of 2x fullfits for each model. Thereby ```cfg.fold = -1``` results in training on all data, and using ```fold 0``` as validation. +To train checkpoints necessary for replicating the segmentation part of the 1st place solution run training of 2x fullfits for each model. Thereby ```cfg.fold = -1``` results in training on all data, and using ```fold 0``` as validation. ``` python train.py -C cfg_resnet34 --fold -1 python train.py -C cfg_resnet34 --fold -1 @@ -85,4 +85,4 @@ python train.py -C cfg_effnetb3 --fold -1 Inference after models are converted with torch jit is shown in our 1st place submission kaggle kernel. -https://www.kaggle.com/code/christofhenkel/cryo-et-1st-place-solution?scriptVersionId=223259615 \ No newline at end of file +https://www.kaggle.com/code/christofhenkel/cryo-et-1st-place-solution?scriptVersionId=223259615 diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py index 104b28d47a..55151aba93 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py @@ -9,15 +9,14 @@ cfg.name = os.path.basename(__file__).split(".")[0] cfg.output_dir = f"/mount/cryo/models/{os.path.basename(__file__).split('.')[0]}" -#model -cfg.backbone = 'efficientnet-b3' -cfg.backbone_args = dict(spatial_dims=3, - in_channels=cfg.in_channels, - out_channels=cfg.n_classes, - backbone=cfg.backbone, - pretrained=cfg.pretrained) -cfg.class_weights = np.array([64,64,64,64,64,64,1]) -cfg.lvl_weights = np.array([0,0,0,1]) - - - +# model +cfg.backbone = "efficientnet-b3" +cfg.backbone_args = dict( + spatial_dims=3, + in_channels=cfg.in_channels, + out_channels=cfg.n_classes, + backbone=cfg.backbone, + pretrained=cfg.pretrained, +) +cfg.class_weights = np.array([64, 64, 64, 64, 64, 64, 1]) +cfg.lvl_weights = np.array([0, 0, 0, 1]) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py index 4b3591f705..5f819efb00 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py @@ -10,13 +10,15 @@ cfg.output_dir = f"/mount/cryo/models/{os.path.basename(__file__).split('.')[0]}" -#model +# model -cfg.backbone = 'resnet34' -cfg.backbone_args = dict(spatial_dims=3, - in_channels=cfg.in_channels, - out_channels=cfg.n_classes, - backbone=cfg.backbone, - pretrained=cfg.pretrained) -cfg.class_weights = np.array([256,256,256,256,256,256,1]) -cfg.lvl_weights = np.array([0,0,0,1]) +cfg.backbone = "resnet34" +cfg.backbone_args = dict( + spatial_dims=3, + in_channels=cfg.in_channels, + out_channels=cfg.n_classes, + backbone=cfg.backbone, + pretrained=cfg.pretrained, +) +cfg.class_weights = np.array([256, 256, 256, 256, 256, 256, 1]) +cfg.lvl_weights = np.array([0, 0, 0, 1]) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py index d9dfa3c1a6..226658f91e 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py @@ -9,11 +9,13 @@ cfg.name = os.path.basename(__file__).split(".")[0] cfg.output_dir = f"/mount/cryo/models/{os.path.basename(__file__).split('.')[0]}" -cfg.backbone = 'resnet34' -cfg.backbone_args = dict(spatial_dims=3, - in_channels=cfg.in_channels, - out_channels=cfg.n_classes, - backbone=cfg.backbone, - pretrained=cfg.pretrained) -cfg.class_weights = np.array([64,64,64,64,64,64,1]) -cfg.lvl_weights = np.array([0,0,1,1]) +cfg.backbone = "resnet34" +cfg.backbone_args = dict( + spatial_dims=3, + in_channels=cfg.in_channels, + out_channels=cfg.n_classes, + backbone=cfg.backbone, + pretrained=cfg.pretrained, +) +cfg.class_weights = np.array([64, 64, 64, 64, 64, 64, 1]) +cfg.lvl_weights = np.array([0, 0, 1, 1]) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py index 5bc4f0a215..83e4de51d6 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py @@ -75,18 +75,16 @@ cfg.clip_mode = "norm" cfg.data_sample = -1 cfg.track_grad_norm = True -cfg.grad_norm_type = 2. +cfg.grad_norm_type = 2.0 cfg.track_weight_norm = True cfg.norm_eps = 1e-4 cfg.disable_tqdm = False - - # paths -cfg.data_folder = '/mount/cryo/data/czii-cryo-et-object-identification/train/static/ExperimentRuns/' -cfg.train_df = 'train_folded_v1.csv' +cfg.data_folder = "/mount/cryo/data/czii-cryo-et-object-identification/train/static/ExperimentRuns/" +cfg.train_df = "train_folded_v1.csv" # stages @@ -94,34 +92,34 @@ cfg.train = True cfg.train_val = False -#logging +# logging cfg.neptune_project = None cfg.neptune_connection_mode = "async" -#model +# model cfg.model = "mdl_1" -cfg.mixup_p = 1. -cfg.mixup_beta = 1. +cfg.mixup_p = 1.0 +cfg.mixup_beta = 1.0 cfg.in_channels = 1 cfg.pretrained = False -#data +# data cfg.dataset = "ds_1" -cfg.classes = ['apo-ferritin','beta-amylase','beta-galactosidase','ribosome','thyroglobulin','virus-like-particle'] +cfg.classes = ["apo-ferritin", "beta-amylase", "beta-galactosidase", "ribosome", "thyroglobulin", "virus-like-particle"] cfg.n_classes = len(cfg.classes) -cfg.post_process_pipeline = 'pp_1' -cfg.metric = 'metric_1' +cfg.post_process_pipeline = "pp_1" +cfg.metric = "metric_1" - -cfg.particle_radi = {'apo-ferritin':60, - 'beta-amylase':65, - 'beta-galactosidase':90, - 'ribosome':150, - 'thyroglobulin':130, - 'virus-like-particle':135 - } +cfg.particle_radi = { + "apo-ferritin": 60, + "beta-amylase": 65, + "beta-galactosidase": 90, + "ribosome": 150, + "thyroglobulin": 130, + "virus-like-particle": 135, +} cfg.voxel_spacing = 10.0 @@ -133,66 +131,67 @@ cfg.lr = 1e-3 cfg.optimizer = "Adam" -cfg.weight_decay = 0. -cfg.warmup = 0. +cfg.weight_decay = 0.0 +cfg.warmup = 0.0 cfg.batch_size = 8 cfg.batch_size_val = 16 cfg.sub_batch_size = 4 -cfg.roi_size = [96,96,96] +cfg.roi_size = [96, 96, 96] cfg.train_sub_epochs = 1112 cfg.val_sub_epochs = 1 cfg.mixed_precision = False cfg.bf16 = True cfg.force_fp16 = True cfg.pin_memory = False -cfg.grad_accumulation = 1. +cfg.grad_accumulation = 1.0 cfg.num_workers = 8 - - - - -#Saving +# Saving cfg.save_weights_only = True cfg.save_only_last_ckpt = False cfg.save_val_data = False -cfg.save_checkpoint=True +cfg.save_checkpoint = True cfg.save_pp_csv = False - -cfg.static_transforms = static_transforms = mt.Compose([mt.EnsureChannelFirstd(keys=["image"], channel_dim="no_channel"),mt.NormalizeIntensityd(keys="image"),]) -cfg.train_aug = mt.Compose([mt.RandSpatialCropSamplesd(keys=["image", "label"], - roi_size=cfg.roi_size, - num_samples=cfg.sub_batch_size), - mt.RandFlipd( - keys=["image", "label"], - prob=0.5, - spatial_axis=0, - ), - mt.RandFlipd( - keys=["image", "label"], - prob=0.5, - spatial_axis=1, - ), - mt.RandFlipd( - keys=["image", "label"], - prob=0.5, - spatial_axis=2, - ), - mt.RandRotate90d( - keys=["image", "label"], - prob=0.75, - max_k=3, - spatial_axes=(0, 1), - ), - mt.RandRotated(keys=["image", "label"], prob=0.5,range_x=0.78,range_y=0.,range_z=0., padding_mode='reflection') - - ]) - -cfg.val_aug = mt.Compose([mt.GridPatchd(keys=["image","label"],patch_size=cfg.roi_size, pad_mode='reflect')]) - +cfg.static_transforms = static_transforms = mt.Compose( + [ + mt.EnsureChannelFirstd(keys=["image"], channel_dim="no_channel"), + mt.NormalizeIntensityd(keys="image"), + ] +) +cfg.train_aug = mt.Compose( + [ + mt.RandSpatialCropSamplesd(keys=["image", "label"], roi_size=cfg.roi_size, num_samples=cfg.sub_batch_size), + mt.RandFlipd( + keys=["image", "label"], + prob=0.5, + spatial_axis=0, + ), + mt.RandFlipd( + keys=["image", "label"], + prob=0.5, + spatial_axis=1, + ), + mt.RandFlipd( + keys=["image", "label"], + prob=0.5, + spatial_axis=2, + ), + mt.RandRotate90d( + keys=["image", "label"], + prob=0.75, + max_k=3, + spatial_axes=(0, 1), + ), + mt.RandRotated( + keys=["image", "label"], prob=0.5, range_x=0.78, range_y=0.0, range_z=0.0, padding_mode="reflection" + ), + ] +) + +cfg.val_aug = mt.Compose([mt.GridPatchd(keys=["image", "label"], patch_size=cfg.roi_size, pad_mode="reflect")]) basic_cfg = cfg diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py index c417c50926..ac806e4d47 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py @@ -5,16 +5,19 @@ import zarr from tqdm import tqdm + def batch_to_device(batch, device): batch_dict = {key: batch[key].to(device) for key in batch} return batch_dict + def collate_fn(batch): - + keys = batch[0].keys() - batch_dict = {key:torch.cat([b[key] for b in batch]) for key in keys} + batch_dict = {key: torch.cat([b[key] for b in batch]) for key in keys} return batch_dict + tr_collate_fn = collate_fn val_collate_fn = collate_fn @@ -28,65 +31,60 @@ def __init__(self, df, cfg, aug, mode="train"): self.cfg = cfg self.mode = mode self.df = df - self.experiment_df = self.df.drop_duplicates(subset='experiment').copy() - self.exp_dict = self.df.groupby('experiment') - self.class2id = {c:i for i,c in enumerate(cfg.classes)} + self.experiment_df = self.df.drop_duplicates(subset="experiment").copy() + self.exp_dict = self.df.groupby("experiment") + self.class2id = {c: i for i, c in enumerate(cfg.classes)} self.n_classes = len(cfg.classes) self.data_folder = cfg.data_folder - self.random_transforms = aug - data = [self.load_one(img_id) for img_id in tqdm(self.experiment_df['experiment'].values)] + data = [self.load_one(img_id) for img_id in tqdm(self.experiment_df["experiment"].values)] data = md.CacheDataset(data=data, transform=cfg.static_transforms, cache_rate=1.0) - - if self.mode == 'train': + + if self.mode == "train": self.monai_ds = md.Dataset(data=data, transform=self.random_transforms) self.sub_epochs = cfg.train_sub_epochs self.len = len(self.monai_ds) * self.sub_epochs else: self.monai_ds = md.CacheDataset(data=data, transform=self.random_transforms, cache_rate=1.0)[0] self.sub_epochs = cfg.val_sub_epochs - self.len = len(self.monai_ds['image']) - + self.len = len(self.monai_ds["image"]) + def __getitem__(self, idx): - if self.mode =='train': - monai_dict = self.monai_ds[idx//self.sub_epochs] + if self.mode == "train": + monai_dict = self.monai_ds[idx // self.sub_epochs] feature_dict = { - "input": torch.stack([item['image'] for item in monai_dict]), - "target": torch.stack([item['label'] for item in monai_dict]), - } + "input": torch.stack([item["image"] for item in monai_dict]), + "target": torch.stack([item["label"] for item in monai_dict]), + } else: - monai_dict = {k:self.monai_ds[k][idx] for k in self.monai_ds} - monai_dict['location'] = torch.from_numpy(self.monai_ds['image'].meta['location'][:,idx]) + monai_dict = {k: self.monai_ds[k][idx] for k in self.monai_ds} + monai_dict["location"] = torch.from_numpy(self.monai_ds["image"].meta["location"][:, idx]) feature_dict = { - "input": torch.stack([item['image'] for item in [monai_dict]]), - "location": torch.stack([item['location'] for item in [monai_dict]]), - "target": torch.stack([item['label'] for item in [monai_dict]]), - } - + "input": torch.stack([item["image"] for item in [monai_dict]]), + "location": torch.stack([item["location"] for item in [monai_dict]]), + "target": torch.stack([item["label"] for item in [monai_dict]]), + } + return feature_dict def __len__(self): return self.len def load_one(self, experiment_id): - - img_fp = f'{self.data_folder}{experiment_id}' + img_fp = f"{self.data_folder}{experiment_id}" try: - with zarr.open(img_fp + '/VoxelSpacing10.000/denoised.zarr') as zf: - img = np.array(zf[0]).transpose(2,1,0) + with zarr.open(img_fp + "/VoxelSpacing10.000/denoised.zarr") as zf: + img = np.array(zf[0]).transpose(2, 1, 0) # img = np.array(zarr.open(img_fp + '/VoxelSpacing10.000/denoised.zarr')[0]).transpose(2,1,0) except Exception as e: print(e) - - centers = self.exp_dict.get_group(experiment_id)[['x','y','z']].values / 10 - classes = self.exp_dict.get_group(experiment_id)['particle_type'].map(self.class2id).values - mask = np.zeros((self.n_classes,) + img.shape[-3:]) - mask[classes, centers[:,0].astype(int), centers[:,1].astype(int), centers[:,2].astype(int)] = 1 - return {'image':img, 'label':mask} - - + centers = self.exp_dict.get_group(experiment_id)[["x", "y", "z"]].values / 10 + classes = self.exp_dict.get_group(experiment_id)["particle_type"].map(self.class2id).values + mask = np.zeros((self.n_classes,) + img.shape[-3:]) + mask[classes, centers[:, 0].astype(int), centers[:, 1].astype(int), centers[:, 2].astype(int)] = 1 + return {"image": img, "label": mask} diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py index 69d6aae5b0..68db35711c 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py @@ -36,14 +36,14 @@ def compute_metrics(reference_points, reference_radius, candidate_points): def score( - solution: pd.DataFrame, - submission: pd.DataFrame, - row_id_column_name: str, - distance_multiplier: float, - beta: int, - weighted=True, + solution: pd.DataFrame, + submission: pd.DataFrame, + row_id_column_name: str, + distance_multiplier: float, + beta: int, + weighted=True, ) -> float: - ''' + """ F_beta - a true positive occurs when - (a) the predicted location is within a threshold of the particle radius, and @@ -51,55 +51,55 @@ def score( - raw results (TP, FP, FN) are aggregated across all experiments for each particle type - f_beta is calculated for each particle type - individual f_beta scores are weighted by particle type for final score - ''' + """ particle_radius = { - 'apo-ferritin': 60, - 'beta-amylase': 65, - 'beta-galactosidase': 90, - 'ribosome': 150, - 'thyroglobulin': 130, - 'virus-like-particle': 135, + "apo-ferritin": 60, + "beta-amylase": 65, + "beta-galactosidase": 90, + "ribosome": 150, + "thyroglobulin": 130, + "virus-like-particle": 135, } weights = { - 'apo-ferritin': 1, - 'beta-amylase': 0, - 'beta-galactosidase': 2, - 'ribosome': 1, - 'thyroglobulin': 2, - 'virus-like-particle': 1, + "apo-ferritin": 1, + "beta-amylase": 0, + "beta-galactosidase": 2, + "ribosome": 1, + "thyroglobulin": 2, + "virus-like-particle": 1, } particle_radius = {k: v * distance_multiplier for k, v in particle_radius.items()} # Filter submission to only contain experiments found in the solution split - split_experiments = set(solution['experiment'].unique()) - submission = submission.loc[submission['experiment'].isin(split_experiments)] + split_experiments = set(solution["experiment"].unique()) + submission = submission.loc[submission["experiment"].isin(split_experiments)] # Only allow known particle types - if not set(submission['particle_type'].unique()).issubset(set(weights.keys())): - raise ParticipantVisibleError('Unrecognized `particle_type`.') + if not set(submission["particle_type"].unique()).issubset(set(weights.keys())): + raise ParticipantVisibleError("Unrecognized `particle_type`.") - assert solution.duplicated(subset=['experiment', 'x', 'y', 'z']).sum() == 0 + assert solution.duplicated(subset=["experiment", "x", "y", "z"]).sum() == 0 assert particle_radius.keys() == weights.keys() results = {} - for particle_type in solution['particle_type'].unique(): + for particle_type in solution["particle_type"].unique(): results[particle_type] = { - 'total_tp': 0, - 'total_fp': 0, - 'total_fn': 0, + "total_tp": 0, + "total_fp": 0, + "total_fn": 0, } for experiment in split_experiments: - for particle_type in solution['particle_type'].unique(): + for particle_type in solution["particle_type"].unique(): reference_radius = particle_radius[particle_type] - select = (solution['experiment'] == experiment) & (solution['particle_type'] == particle_type) - reference_points = solution.loc[select, ['x', 'y', 'z']].values + select = (solution["experiment"] == experiment) & (solution["particle_type"] == particle_type) + reference_points = solution.loc[select, ["x", "y", "z"]].values - select = (submission['experiment'] == experiment) & (submission['particle_type'] == particle_type) - candidate_points = submission.loc[select, ['x', 'y', 'z']].values + select = (submission["experiment"] == experiment) & (submission["particle_type"] == particle_type) + candidate_points = submission.loc[select, ["x", "y", "z"]].values if len(reference_points) == 0: reference_points = np.array([]) @@ -110,76 +110,81 @@ def score( tp, fp, fn = compute_metrics(reference_points, reference_radius, candidate_points) - results[particle_type]['total_tp'] += tp - results[particle_type]['total_fp'] += fp - results[particle_type]['total_fn'] += fn + results[particle_type]["total_tp"] += tp + results[particle_type]["total_fp"] += fp + results[particle_type]["total_fn"] += fn fbetas = [] fbeta_weights = [] particle_types = [] for particle_type, totals in results.items(): - tp = totals['total_tp'] - fp = totals['total_fp'] - fn = totals['total_fn'] + tp = totals["total_tp"] + fp = totals["total_fp"] + fn = totals["total_fn"] precision = tp / (tp + fp) if tp + fp > 0 else 0 recall = tp / (tp + fn) if tp + fn > 0 else 0 - fbeta = (1 + beta**2) * (precision * recall) / (beta**2 * precision + recall) if (precision + recall) > 0 else 0.0 + fbeta = ( + (1 + beta**2) * (precision * recall) / (beta**2 * precision + recall) if (precision + recall) > 0 else 0.0 + ) fbetas += [fbeta] fbeta_weights += [weights.get(particle_type, 1.0)] particle_types += [particle_type] - + if weighted: - aggregate_fbeta = np.average(fbetas,weights=fbeta_weights) + aggregate_fbeta = np.average(fbetas, weights=fbeta_weights) else: aggregate_fbeta = np.mean(fbetas) - - return aggregate_fbeta, dict(zip(particle_types,fbetas)) + + return aggregate_fbeta, dict(zip(particle_types, fbetas)) + def calc_metric(cfg, pp_out, val_df, pre="val"): - + particles = cfg.classes pred_df = pp_out - + solution = val_df.copy() - solution['id'] = range(len(solution)) - - submission = pred_df.copy() - submission['experiment'] = solution['experiment'].unique()[0] - submission['id'] = range(len(submission)) + solution["id"] = range(len(solution)) + submission = pred_df.copy() + submission["experiment"] = solution["experiment"].unique()[0] + submission["id"] = range(len(submission)) best_ths = [] for p in particles: - sol0a = solution[solution['particle_type']==p].copy() - sub0a = submission[submission['particle_type']==p].copy() + sol0a = solution[solution["particle_type"] == p].copy() + sub0a = submission[submission["particle_type"] == p].copy() scores = [] - ths = np.arange(0,0.5,0.005) + ths = np.arange(0, 0.5, 0.005) for c in ths: - scores += [score( - sol0a.copy(), - sub0a[sub0a['conf']>c].copy(), - row_id_column_name = 'id', - distance_multiplier=0.5, - beta=4,weighted = False)[0]] + scores += [ + score( + sol0a.copy(), + sub0a[sub0a["conf"] > c].copy(), + row_id_column_name="id", + distance_multiplier=0.5, + beta=4, + weighted=False, + )[0] + ] best_th = ths[np.argmax(scores)] best_ths += [best_th] - + submission_pp = [] - for th, p in zip(best_ths,particles): - submission_pp += [submission[(submission['particle_type']==p) & (submission['conf']>th)].copy()] + for th, p in zip(best_ths, particles): + submission_pp += [submission[(submission["particle_type"] == p) & (submission["conf"] > th)].copy()] submission_pp = pd.concat(submission_pp) - + score_pp, particle_scores = score( - solution[solution['particle_type']!='beta-amylase'].copy(), + solution[solution["particle_type"] != "beta-amylase"].copy(), submission_pp.copy(), - row_id_column_name = 'id', + row_id_column_name="id", distance_multiplier=0.5, - beta=4) - - result = {'score_' + k: v for k,v in particle_scores.items()} - result['score'] = score_pp -# print(result) - return result - + beta=4, + ) + result = {"score_" + k: v for k, v in particle_scores.items()} + result["score"] = score_pp + # print(result) + return result diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py index fec46d8169..f181fa04ce 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py @@ -7,9 +7,8 @@ class PatchedUNetDecoder(UNetDecoder): - """add functionality to output all feature maps""" - + def forward(self, features: list[torch.Tensor], skip_connect: int = 4): skips = features[:-1][::-1] features = features[1:][::-1] @@ -29,8 +28,8 @@ def forward(self, features: list[torch.Tensor], skip_connect: int = 4): class FlexibleUNet(nn.Module): """ - A flexible implementation of UNet-like encoder-decoder architecture. - + A flexible implementation of UNet-like encoder-decoder architecture. + (Adjusted to support PatchDecoder and multi segmentation heads) """ @@ -118,9 +117,7 @@ def __init__( encoder_type = encoder["type"] self.encoder = encoder_type(**encoder_parameters) print(decoder_channels) - - - + self.decoder = PatchedUNetDecoder( spatial_dims=spatial_dims, encoder_channels=encoder_channels, @@ -135,13 +132,18 @@ def __init__( align_corners=None, is_pad=is_pad, ) - self.segmentation_heads = nn.ModuleList([SegmentationHead( - spatial_dims=spatial_dims, - in_channels=decoder_channel, - out_channels=out_channels + 1, - kernel_size=3, - act=None, - ) for decoder_channel in decoder_channels[:-1]]) + self.segmentation_heads = nn.ModuleList( + [ + SegmentationHead( + spatial_dims=spatial_dims, + in_channels=decoder_channel, + out_channels=out_channels + 1, + kernel_size=3, + act=None, + ) + for decoder_channel in decoder_channels[:-1] + ] + ) def forward(self, inputs: torch.Tensor): @@ -153,20 +155,17 @@ def forward(self, inputs: torch.Tensor): return x_seg - def count_parameters(model): return sum(p.numel() for p in model.parameters() if p.requires_grad) + def human_format(num): - num = float('{:.3g}'.format(num)) + num = float("{:.3g}".format(num)) magnitude = 0 while abs(num) >= 1000: magnitude += 1 num /= 1000.0 - return '{}{}'.format('{:f}'.format(num).rstrip('0').rstrip('.'), ['', 'K', 'M', 'B', 'T'][magnitude]) - - - + return "{}{}".format("{:f}".format(num).rstrip("0").rstrip("."), ["", "K", "M", "B", "T"][magnitude]) class DenseCrossEntropy(nn.Module): @@ -174,22 +173,23 @@ def __init__(self, class_weights=None): super(DenseCrossEntropy, self).__init__() self.class_weights = class_weights - + def forward(self, x, target): x = x.float() target = target.float() logprobs = torch.nn.functional.log_softmax(x, dim=1, dtype=torch.float) loss = -logprobs * target - - class_losses = loss.mean((0,2,3,4)) + + class_losses = loss.mean((0, 2, 3, 4)) if self.class_weights is not None: - loss = (class_losses * self.class_weights.to(class_losses.device)).sum() #/ class_weights.sum() + loss = (class_losses * self.class_weights.to(class_losses.device)).sum() # / class_weights.sum() else: - + loss = class_losses.sum() return loss, class_losses + class Mixup(nn.Module): def __init__(self, mix_beta, mixadd=False): @@ -203,28 +203,30 @@ def forward(self, X, Y, Z=None): n_dims = len(X.shape) perm = torch.randperm(bs) coeffs = self.beta_distribution.rsample(torch.Size((bs,))).to(X.device) - X_coeffs = coeffs.view((-1,) + (1,)*(X.ndim-1)) - Y_coeffs = coeffs.view((-1,) + (1,)*(Y.ndim-1)) - - X = X_coeffs * X + (1-X_coeffs) * X[perm] + X_coeffs = coeffs.view((-1,) + (1,) * (X.ndim - 1)) + Y_coeffs = coeffs.view((-1,) + (1,) * (Y.ndim - 1)) + + X = X_coeffs * X + (1 - X_coeffs) * X[perm] if self.mixadd: Y = (Y + Y[perm]).clip(0, 1) else: Y = Y_coeffs * Y + (1 - Y_coeffs) * Y[perm] - + if Z: return X, Y, Z return X, Y - + + def to_ce_target(y): # bs, c, h, w, d y_bg = 1 - y.sum(1, keepdim=True).clamp(0, 1) - y = torch.cat([y,y_bg], 1) + y = torch.cat([y, y_bg], 1) y = y / y.sum(1, keepdim=True) return y + class Net(nn.Module): def __init__(self, cfg): @@ -233,40 +235,37 @@ def __init__(self, cfg): self.cfg = cfg self.n_classes = cfg.n_classes self.classes = cfg.classes - - self.backbone = FlexibleUNet(**cfg.backbone_args) + self.backbone = FlexibleUNet(**cfg.backbone_args) self.mixup = Mixup(cfg.mixup_beta) - - print(f'Net parameters: {human_format(count_parameters(self))}') + + print(f"Net parameters: {human_format(count_parameters(self))}") self.lvl_weights = torch.from_numpy(cfg.lvl_weights) - self.loss_fn = DenseCrossEntropy(class_weights=torch.from_numpy(cfg.class_weights)) - + self.loss_fn = DenseCrossEntropy(class_weights=torch.from_numpy(cfg.class_weights)) + def forward(self, batch): - x = batch['input'] + x = batch["input"] if "target" in batch.keys(): y = batch["target"] if self.training: if torch.rand(1)[0] < self.cfg.mixup_p: - x, y = self.mixup(x,y) + x, y = self.mixup(x, y) out = self.backbone(x) - - outputs = {} if "target" in batch.keys(): - ys = [F.adaptive_max_pool3d(y, item.shape[-3:]) for item in out] + ys = [F.adaptive_max_pool3d(y, item.shape[-3:]) for item in out] losses = torch.stack([self.loss_fn(out[i], to_ce_target(ys[i]))[0] for i in range(len(out))]) lvl_weights = self.lvl_weights.to(losses.device) loss = (losses * lvl_weights).sum() / lvl_weights.sum() - outputs['loss'] = loss + outputs["loss"] = loss if not self.training: outputs["logits"] = out[-1] - if 'location' in batch: - outputs["location"] = batch['location'] + if "location" in batch: + outputs["location"] = batch["location"] return outputs @@ -274,19 +273,18 @@ def forward(self, batch): class TestNet(nn.Module): def __init__(self, **backbone_args): - super(TestNet, self).__init__() - + super(TestNet, self).__init__() + self.backbone = FlexibleUNet(**backbone_args) - - + def forward(self, x): - #x shape is bs, c, h, w, d + # x shape is bs, c, h, w, d out = self.backbone(x) - #out shape is bs, 7, h//2, w//2, d//2 - logits = out[-1] # for heatmap do softmax + reorder classes .softmax(1)[:,[0,2,3,4,5,1]] + # out shape is bs, 7, h//2, w//2, d//2 + logits = out[-1] # for heatmap do softmax + reorder classes .softmax(1)[:,[0,2,3,4,5,1]] return logits - - + + # import torch # import monai.transforms as mt # import zarr @@ -297,7 +295,7 @@ def forward(self, x): # img = transforms({'image':img})['image'] # return img -# backbone_args = dict(spatial_dims=3, +# backbone_args = dict(spatial_dims=3, # in_channels=1, # out_channels=6, # backbone='resnet34', @@ -315,4 +313,4 @@ def forward(self, x): # patch = img[None, :, :96, :96, :96] # torch.Size([1, ,1 96, 96, 96]) # logits = net(patch.cuda()) -# proba_heatmap = logits.softmax(1)[:,[0,2,3,4,5,1]] \ No newline at end of file +# proba_heatmap = logits.softmax(1)[:,[0,2,3,4,5,1]] diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py index 796b626f95..db3e7fe8fa 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py @@ -6,52 +6,62 @@ import torch from torch import nn + def simple_nms(scores, nms_radius: int): - """ Fast Non-maximum suppression to remove nearby points """ - assert(nms_radius >= 0) + """Fast Non-maximum suppression to remove nearby points""" + assert nms_radius >= 0 def max_pool(x): - return torch.nn.functional.max_pool3d(x, kernel_size=nms_radius*2+1, stride=1, padding=nms_radius) + return torch.nn.functional.max_pool3d(x, kernel_size=nms_radius * 2 + 1, stride=1, padding=nms_radius) zeros = torch.zeros_like(scores) max_mask = scores == max_pool(scores) return torch.where(max_mask, scores, zeros) + def reconstruct(img, locations, out_size, crop_size): reconstructed_img = torch.zeros(out_size) - + for i in range(img.shape[0]): - reconstructed_img[:,locations[0][i]:locations[0][i]+crop_size[0], - locations[1][i]:locations[1][i]+crop_size[1], - locations[2][i]:locations[2][i]+crop_size[2], - ] = img[i,:] + reconstructed_img[ + :, + locations[0][i] : locations[0][i] + crop_size[0], + locations[1][i] : locations[1][i] + crop_size[1], + locations[2][i] : locations[2][i] + crop_size[2], + ] = img[i, :] return reconstructed_img def post_process_pipeline(cfg, val_data, val_df): - - img = val_data['logits'] - img = torch.nn.functional.interpolate(img, size=(cfg.roi_size[0],cfg.roi_size[1],cfg.roi_size[2]), mode='trilinear', align_corners=False) - locations = val_data['location'] - out_size = [cfg.n_classes + 1] + [l.item()+r for l,r in zip(locations.max(0)[0],cfg.roi_size)] - rec_img = reconstruct(img, locations.permute(1,0), out_size=out_size, crop_size=cfg.roi_size) + + img = val_data["logits"] + img = torch.nn.functional.interpolate( + img, size=(cfg.roi_size[0], cfg.roi_size[1], cfg.roi_size[2]), mode="trilinear", align_corners=False + ) + locations = val_data["location"] + out_size = [cfg.n_classes + 1] + [l.item() + r for l, r in zip(locations.max(0)[0], cfg.roi_size)] + rec_img = reconstruct(img, locations.permute(1, 0), out_size=out_size, crop_size=cfg.roi_size) s = rec_img.shape[-3:] - rec_img = torch.nn.functional.interpolate(rec_img[None], size=(s[0]//2,s[1]//2,s[2]//2), mode='trilinear', align_corners=False)[0] + rec_img = torch.nn.functional.interpolate( + rec_img[None], size=(s[0] // 2, s[1] // 2, s[2] // 2), mode="trilinear", align_corners=False + )[0] preds = rec_img.softmax(0)[:-1] - + pred_df = [] - for i,p in enumerate(cfg.classes): + for i, p in enumerate(cfg.classes): p1 = preds[i][None,].cuda() - y = simple_nms(p1, nms_radius=int(0.5 * cfg.particle_radi[p]/10)) - kps = torch.where(y>0) - xyz = torch.stack(kps[1:],-1) * 10 * 2 + y = simple_nms(p1, nms_radius=int(0.5 * cfg.particle_radi[p] / 10)) + kps = torch.where(y > 0) + xyz = torch.stack(kps[1:], -1) * 10 * 2 conf = y[kps] - pred_df_ = pd.DataFrame(xyz.cpu().numpy(),columns=['x','y','z']) - pred_df_['particle_type'] = p - pred_df_['conf'] = conf.cpu().numpy() + pred_df_ = pd.DataFrame(xyz.cpu().numpy(), columns=["x", "y", "z"]) + pred_df_["particle_type"] = p + pred_df_["conf"] = conf.cpu().numpy() pred_df += [pred_df_] pred_df = pd.concat(pred_df) - pred_df = pred_df[(pred_df['x']<6300) & (pred_df['y']<6300)& (pred_df['z']<1840) & (pred_df['conf']>0.01)].copy() - pred_df.to_csv(f"{cfg.output_dir}/fold{cfg.fold}/val_pred_df_seed{cfg.seed}.csv",index=False) + pred_df = pred_df[ + (pred_df["x"] < 6300) & (pred_df["y"] < 6300) & (pred_df["z"] < 1840) & (pred_df["conf"] > 0.01) + ].copy() + pred_df.to_csv(f"{cfg.output_dir}/fold{cfg.fold}/val_pred_df_seed{cfg.seed}.csv", index=False) return pred_df diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py index ad22756910..ca666e0f54 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py @@ -7,6 +7,7 @@ import argparse import torch import math + try: from torch.amp import GradScaler, autocast except: @@ -44,9 +45,10 @@ try: import cv2 + cv2.setNumThreads(0) except: - print('no cv2 installed, running without') + print("no cv2 installed, running without") sys.path.append("configs") @@ -63,13 +65,13 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): # store information for evaluation val_data = defaultdict(list) - val_score =0 + val_score = 0 for ind_, data in enumerate(tqdm(val_dataloader, disable=(cfg.local_rank != 0) | cfg.disable_tqdm)): batch = cfg.batch_to_device(data, cfg.device) if cfg.mixed_precision: - with autocast('cuda'): + with autocast("cuda"): output = model(batch) else: output = model(batch) @@ -77,7 +79,7 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): if (cfg.local_rank == 0) and (cfg.calc_metric) and (((curr_epoch + 1) % cfg.calc_metric_epochs) == 0): # per batch calculations pass - + if (not saved_images) & (cfg.save_first_batch_preds): save_first_batch_preds(batch, output, cfg) saved_images = True @@ -89,7 +91,7 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): value = val_data[key] if isinstance(value[0], list): val_data[key] = [item for sublist in value for item in sublist] - + else: if len(value[0].shape) == 0: val_data[key] = torch.stack(value) @@ -110,8 +112,8 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): val_data[k] = v[: len(val_dataloader.dataset)] torch.save(val_data, f"{cfg.output_dir}/fold{cfg.fold}/{pre}_data_seed{cfg.seed}.pth") - loss_names = [key for key in output if 'loss' in key] - loss_names += [key for key in output if 'score' in key] + loss_names = [key for key in output if "loss" in key] + loss_names += [key for key in output if "score" in key] for k in loss_names: if cfg.local_rank == 0 and k in val_data: losses = val_data[k].cpu().numpy() @@ -119,60 +121,51 @@ def run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=0): print(f"Mean {pre}_{k}", loss) - - if (cfg.local_rank == 0) and (cfg.calc_metric) and (((curr_epoch + 1) % cfg.calc_metric_epochs) == 0): val_df = val_dataloader.dataset.df pp_out = cfg.post_process_pipeline(cfg, val_data, val_df) val_score = cfg.calc_metric(cfg, pp_out, val_df, pre) - if type(val_score)!=dict: - val_score = {f'score':val_score} - + if type(val_score) != dict: + val_score = {f"score": val_score} + for k, v in val_score.items(): print(f"{pre}_{k}: {v:.3f}") - if cfg.distributed: torch.distributed.barrier() -# print("EVAL FINISHED") + # print("EVAL FINISHED") return val_score def train(cfg): - # set seed + # set seed if cfg.seed < 0: cfg.seed = np.random.randint(1_000_000) print("seed", cfg.seed) - if cfg.distributed: cfg.local_rank = int(os.environ["LOCAL_RANK"]) device = "cuda:%d" % cfg.local_rank cfg.device = device - - torch.cuda.set_device(cfg.local_rank) torch.distributed.init_process_group(backend="nccl", init_method="env://") cfg.world_size = torch.distributed.get_world_size() cfg.rank = torch.distributed.get_rank() -# print("Training in distributed mode with multiple processes, 1 GPU per process.") + # print("Training in distributed mode with multiple processes, 1 GPU per process.") print(f"Process {cfg.rank}, total {cfg.world_size}, local rank {cfg.local_rank}.") cfg.group = torch.distributed.new_group(np.arange(cfg.world_size)) -# print("Group", cfg.group) + # print("Group", cfg.group) # syncing the random seed cfg.seed = int( - sync_across_gpus(torch.Tensor([cfg.seed]).to(device), cfg.world_size) - .detach() - .cpu() - .numpy()[0] + sync_across_gpus(torch.Tensor([cfg.seed]).to(device), cfg.world_size).detach().cpu().numpy()[0] ) # - + print(f"LOCAL_RANK {cfg.local_rank}, device {device}, seed {cfg.seed}") else: @@ -185,27 +178,25 @@ def train(cfg): set_seed(cfg.seed) + train_df, val_df, test_df = get_data(cfg) + + train_dataset = get_dataset(train_df, cfg, mode="train") + train_dataloader = get_dataloader(train_dataset, cfg, mode="train") + val_dataset = get_dataset(val_df, cfg, mode="val") + val_dataloader = get_dataloader(val_dataset, cfg, mode="val") - train_df, val_df, test_df = get_data(cfg) - - train_dataset = get_dataset(train_df, cfg, mode='train') - train_dataloader = get_dataloader(train_dataset, cfg, mode='train') - - val_dataset = get_dataset(val_df, cfg, mode='val') - val_dataloader = get_dataloader(val_dataset, cfg, mode='val') - if cfg.test: - test_dataset = get_dataset(test_df, cfg, mode='test') - test_dataloader = get_dataloader(test_dataset, cfg, mode='test') + test_dataset = get_dataset(test_df, cfg, mode="test") + test_dataloader = get_dataloader(test_dataset, cfg, mode="test") if cfg.train_val: - train_val_dataset = get_dataset(train_df, cfg, mode='val') - train_val_dataloader = get_dataloader(train_val_dataset, cfg, 'val') + train_val_dataset = get_dataset(train_df, cfg, mode="val") + train_val_dataloader = get_dataloader(train_val_dataset, cfg, "val") model = get_model(cfg, train_dataset) if cfg.compile_model: - print('compiling model') + print("compiling model") model = torch.compile(model) model.to(device) @@ -214,13 +205,11 @@ def train(cfg): if cfg.syncbn: model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model) - model = NativeDDP( - model, device_ids=[cfg.local_rank], find_unused_parameters=cfg.find_unused_parameters - ) + model = NativeDDP(model, device_ids=[cfg.local_rank], find_unused_parameters=cfg.find_unused_parameters) total_steps = len(train_dataset) if train_dataloader.sampler is not None: - if 'WeightedRandomSampler' in str(train_dataloader.sampler.__class__): + if "WeightedRandomSampler" in str(train_dataloader.sampler.__class__): total_steps = train_dataloader.sampler.num_samples optimizer = get_optimizer(model, cfg) @@ -235,8 +224,8 @@ def train(cfg): i = 0 best_val_loss = np.inf optimizer.zero_grad() - total_grad_norm = None - total_weight_norm = None + total_grad_norm = None + total_weight_norm = None total_grad_norm_after_clip = None for epoch in range(cfg.epochs): @@ -244,14 +233,13 @@ def train(cfg): set_seed(cfg.seed + epoch + cfg.local_rank) cfg.curr_epoch = epoch - if cfg.local_rank == 0: + if cfg.local_rank == 0: print("EPOCH:", epoch) - if cfg.distributed: train_dataloader.sampler.set_epoch(epoch) - progress_bar = tqdm(range(len(train_dataloader)),disable=cfg.disable_tqdm) + progress_bar = tqdm(range(len(train_dataloader)), disable=cfg.disable_tqdm) tr_it = iter(train_dataloader) losses = [] @@ -272,19 +260,17 @@ def train(cfg): print("DATA FETCH ERROR") # continue - model.train() torch.set_grad_enabled(True) - batch = cfg.batch_to_device(data, device) if cfg.mixed_precision: - with autocast('cuda'): + with autocast("cuda"): output_dict = model(batch) else: if cfg.bf16: - with autocast('cuda',dtype=torch.bfloat16): + with autocast("cuda", dtype=torch.bfloat16): output_dict = model(batch) else: output_dict = model(batch) @@ -293,7 +279,7 @@ def train(cfg): losses.append(loss.item()) - if cfg.grad_accumulation >1: + if cfg.grad_accumulation > 1: loss /= cfg.grad_accumulation # Backward pass @@ -305,7 +291,7 @@ def train(cfg): if (cfg.track_grad_norm) or (cfg.clip_grad > 0): scaler.unscale_(optimizer) if cfg.track_grad_norm: - total_grad_norm = calc_grad_norm(model.parameters(), cfg.grad_norm_type) + total_grad_norm = calc_grad_norm(model.parameters(), cfg.grad_norm_type) if cfg.clip_grad > 0: torch.nn.utils.clip_grad_norm_(model.parameters(), cfg.clip_grad) if cfg.track_grad_norm: @@ -313,7 +299,7 @@ def train(cfg): scaler.step(optimizer) scaler.update() optimizer.zero_grad() - + else: loss.backward() @@ -339,7 +325,7 @@ def train(cfg): if cfg.local_rank == 0 and cfg.curr_step % cfg.batch_size == 0: - loss_names = [key for key in output_dict if 'loss' in key] + loss_names = [key for key in output_dict if "loss" in key] progress_bar.set_description(f"loss: {np.mean(losses[-10:]):.4f}") @@ -369,7 +355,7 @@ def train(cfg): val_score = run_eval(model, val_dataloader, cfg, pre="val", curr_epoch=epoch) else: val_score = 0 - + if cfg.train_val == True: if (epoch + 1) % cfg.eval_train_epochs == 0 or (epoch + 1) == cfg.epochs: if cfg.distributed and cfg.eval_ddp: @@ -379,7 +365,6 @@ def train(cfg): if cfg.local_rank == 0: _ = get_preds(model, train_val_dataloader, cfg, pre=cfg.pre_train_val) - if cfg.distributed: torch.distributed.barrier() @@ -387,16 +372,12 @@ def train(cfg): if not cfg.save_only_last_ckpt: checkpoint = create_checkpoint(cfg, model, optimizer, epoch, scheduler=scheduler, scaler=scaler) - torch.save( - checkpoint, f"{cfg.output_dir}/fold{cfg.fold}/checkpoint_last_seed{cfg.seed}.pth" - ) + torch.save(checkpoint, f"{cfg.output_dir}/fold{cfg.fold}/checkpoint_last_seed{cfg.seed}.pth") if (cfg.local_rank == 0) and (cfg.epochs > 0) and (cfg.save_checkpoint): checkpoint = create_checkpoint(cfg, model, optimizer, epoch, scheduler=scheduler, scaler=scaler) - torch.save( - checkpoint, f"{cfg.output_dir}/fold{cfg.fold}/checkpoint_last_seed{cfg.seed}.pth" - ) + torch.save(checkpoint, f"{cfg.output_dir}/fold{cfg.fold}/checkpoint_last_seed{cfg.seed}.pth") if cfg.test: run_eval(model, test_dataloader, test_df, cfg, pre="test") @@ -407,34 +388,30 @@ def train(cfg): if __name__ == "__main__": parser = argparse.ArgumentParser(description="") - + parser.add_argument("-C", "--config", help="config filename") parser_args, other_args = parser.parse_known_args(sys.argv) cfg = copy(importlib.import_module(parser_args.config).cfg) - - - # overwrite params in config with additional args if len(other_args) > 1: - other_args = {k.replace('-',''):v for k, v in zip(other_args[1::2], other_args[2::2])} + other_args = {k.replace("-", ""): v for k, v in zip(other_args[1::2], other_args[2::2])} for key in other_args: if key in cfg.__dict__: - print(f'overwriting cfg.{key}: {cfg.__dict__[key]} -> {other_args[key]}') + print(f"overwriting cfg.{key}: {cfg.__dict__[key]} -> {other_args[key]}") cfg_type = type(cfg.__dict__[key]) - if other_args[key] == 'None': + if other_args[key] == "None": cfg.__dict__[key] = None elif cfg_type == bool: - cfg.__dict__[key] = other_args[key] == 'True' + cfg.__dict__[key] = other_args[key] == "True" elif cfg_type == type(None): cfg.__dict__[key] = other_args[key] else: cfg.__dict__[key] = cfg_type(other_args[key]) - os.makedirs(str(cfg.output_dir + f"/fold{cfg.fold}/"), exist_ok=True) cfg.CustomDataset = importlib.import_module(cfg.dataset).CustomDataset @@ -444,6 +421,6 @@ def train(cfg): cfg.post_process_pipeline = importlib.import_module(cfg.post_process_pipeline).post_process_pipeline cfg.calc_metric = importlib.import_module(cfg.metric).calc_metric - + result = train(cfg) print(result) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py index 36b5190ab2..501d78a51c 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py @@ -14,7 +14,6 @@ import pickle - def get_linear_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps, last_epoch=-1): """ from https://github.com/huggingface/transformers/blob/main/src/transformers/optimization.py @@ -36,14 +35,12 @@ def get_linear_schedule_with_warmup(optimizer, num_warmup_steps, num_training_st def lr_lambda(current_step: int): if current_step < num_warmup_steps: return float(current_step) / float(max(1, num_warmup_steps)) - return max( - 0.0, float(num_training_steps - current_step) / float(max(1, num_training_steps - num_warmup_steps)) - ) + return max(0.0, float(num_training_steps - current_step) / float(max(1, num_training_steps - num_warmup_steps))) return LambdaLR(optimizer, lr_lambda, last_epoch) -def get_cosine_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps, num_cycles= 0.5, last_epoch= -1): +def get_cosine_schedule_with_warmup(optimizer, num_warmup_steps, num_training_steps, num_cycles=0.5, last_epoch=-1): """ from https://github.com/huggingface/transformers/blob/main/src/transformers/optimization.py Create a schedule with a learning rate that decreases following the values of the cosine function between the @@ -74,45 +71,46 @@ def lr_lambda(current_step): return LambdaLR(optimizer, lr_lambda, last_epoch) +def calc_grad_norm(parameters, norm_type=2.0): - -def calc_grad_norm(parameters,norm_type=2.): - if isinstance(parameters, torch.Tensor): parameters = [parameters] parameters = [p for p in parameters if p.grad is not None] norm_type = float(norm_type) if len(parameters) == 0: - return torch.tensor(0.) + return torch.tensor(0.0) device = parameters[0].grad.device - total_norm = torch.norm(torch.stack([torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]), norm_type) + total_norm = torch.norm( + torch.stack([torch.norm(p.grad.detach(), norm_type).to(device) for p in parameters]), norm_type + ) if torch.logical_or(total_norm.isnan(), total_norm.isinf()): total_norm = None - + return total_norm -def calc_weight_norm(parameters,norm_type=2.): + +def calc_weight_norm(parameters, norm_type=2.0): # l2_loss = 0 # for param in parameters : # l2_loss += 0.5 * torch.sum(param ** 2) # return l2_loss - + if isinstance(parameters, torch.Tensor): parameters = [parameters] parameters = [p for p in parameters if p.grad is not None] norm_type = float(norm_type) if len(parameters) == 0: - return torch.tensor(0.) + return torch.tensor(0.0) device = parameters[0].grad.device - total_norm = torch.stack([torch.norm(p.detach(), norm_type).to(device) for p in parameters]).mean() if torch.logical_or(total_norm.isnan(), total_norm.isinf()): total_norm = None - + return total_norm + class OrderedDistributedSampler(Sampler): def __init__(self, dataset, num_replicas=None, rank=None): if num_replicas is None: @@ -139,9 +137,7 @@ def __iter__(self): assert len(indices) == self.total_size # subsample - indices = indices[ - self.rank * self.num_samples : self.rank * self.num_samples + self.num_samples - ] + indices = indices[self.rank * self.num_samples : self.rank * self.num_samples + self.num_samples] print( "SAMPLES", self.rank * self.num_samples, @@ -182,36 +178,35 @@ def get_model(cfg, ds): if cfg.pretrained_weights is not None: if type(cfg.pretrained_weights) == list: cfg.pretrained_weights = cfg.pretrained_weights[cfg.fold] - print(f'{cfg.local_rank}: loading weights from',cfg.pretrained_weights) - state_dict = torch.load(cfg.pretrained_weights, map_location='cpu') + print(f"{cfg.local_rank}: loading weights from", cfg.pretrained_weights) + state_dict = torch.load(cfg.pretrained_weights, map_location="cpu") if "model" in state_dict.keys(): - state_dict = state_dict['model'] - state_dict = {key.replace('module.',''):val for key,val in state_dict.items()} + state_dict = state_dict["model"] + state_dict = {key.replace("module.", ""): val for key, val in state_dict.items()} if cfg.pop_weights is not None: - print(f'popping {cfg.pop_weights}') + print(f"popping {cfg.pop_weights}") to_pop = [] for key in state_dict: for item in cfg.pop_weights: if item in key: to_pop += [key] for key in to_pop: - print(f'popping {key}') + print(f"popping {key}") state_dict.pop(key) net.load_state_dict(state_dict, strict=cfg.pretrained_weights_strict) - print(f'{cfg.local_rank}: weights loaded from',cfg.pretrained_weights) - + print(f"{cfg.local_rank}: weights loaded from", cfg.pretrained_weights) + return net def create_checkpoint(cfg, model, optimizer, epoch, scheduler=None, scaler=None): - state_dict = model.state_dict() if cfg.save_weights_only: checkpoint = {"model": state_dict} return checkpoint - + checkpoint = { "model": state_dict, "optimizer": optimizer.state_dict(), @@ -225,44 +220,46 @@ def create_checkpoint(cfg, model, optimizer, epoch, scheduler=None, scaler=None) checkpoint["scaler"] = scaler.state_dict() return checkpoint + def load_checkpoint(cfg, model, optimizer, scheduler=None, scaler=None): - - print(f'loading ckpt {cfg.resume_from}') - checkpoint = torch.load(cfg.resume_from, map_location='cpu') - model.load_state_dict(checkpoint['model']) - optimizer.load_state_dict(checkpoint['optimizer']) - scheduler_dict = checkpoint['scheduler'] - if scaler is not None: - scaler.load_state_dict(checkpoint['scaler']) - - epoch = checkpoint['epoch'] + + print(f"loading ckpt {cfg.resume_from}") + checkpoint = torch.load(cfg.resume_from, map_location="cpu") + model.load_state_dict(checkpoint["model"]) + optimizer.load_state_dict(checkpoint["optimizer"]) + scheduler_dict = checkpoint["scheduler"] + if scaler is not None: + scaler.load_state_dict(checkpoint["scaler"]) + + epoch = checkpoint["epoch"] return model, optimizer, scheduler_dict, scaler, epoch -def get_dataset(df, cfg, mode='train'): - - #modes train, val, index +def get_dataset(df, cfg, mode="train"): + + # modes train, val, index print(f"Loading {mode} dataset") - if mode == 'train': + if mode == "train": dataset = get_train_dataset(df, cfg) -# elif mode == 'train_val': -# dataset = get_val_dataset(df, cfg) - elif mode == 'val': + # elif mode == 'train_val': + # dataset = get_val_dataset(df, cfg) + elif mode == "val": dataset = get_val_dataset(df, cfg) - elif mode == 'test': + elif mode == "test": dataset = get_test_dataset(df, cfg) else: pass return dataset -def get_dataloader(ds, cfg, mode='train'): - - if mode == 'train': + +def get_dataloader(ds, cfg, mode="train"): + + if mode == "train": dl = get_train_dataloader(ds, cfg) - elif mode =='val': + elif mode == "val": dl = get_val_dataloader(ds, cfg) - elif mode =='test': + elif mode == "test": dl = get_test_dataloader(ds, cfg) return dl @@ -275,8 +272,6 @@ def get_train_dataset(train_df, cfg): return train_dataset - - def get_train_dataloader(train_ds, cfg): if cfg.distributed: @@ -286,32 +281,30 @@ def get_train_dataloader(train_ds, cfg): else: try: if cfg.random_sampler_frac > 0: - + num_samples = int(len(train_ds) * cfg.random_sampler_frac) sample_weights = train_ds.sample_weights - sampler = WeightedRandomSampler(sample_weights, num_samples= num_samples ) + sampler = WeightedRandomSampler(sample_weights, num_samples=num_samples) else: sampler = None except: sampler = None - - + if cfg.use_custom_batch_sampler: sampler = RandomSampler(train_ds) - bsampler = CustomBatchSampler(sampler, batch_size =cfg.batch_size, drop_last=cfg.drop_last) + bsampler = CustomBatchSampler(sampler, batch_size=cfg.batch_size, drop_last=cfg.drop_last) train_dataloader = DataLoader( train_ds, batch_sampler=bsampler, -# shuffle=(sampler is None), -# batch_size=cfg.batch_size, + # shuffle=(sampler is None), + # batch_size=cfg.batch_size, num_workers=cfg.num_workers, pin_memory=cfg.pin_memory, collate_fn=cfg.tr_collate_fn, -# drop_last=cfg.drop_last, + # drop_last=cfg.drop_last, worker_init_fn=worker_init_fn, ) else: - train_dataloader = DataLoader( train_ds, @@ -336,9 +329,7 @@ def get_val_dataset(val_df, cfg, allowed_targets=None): def get_val_dataloader(val_ds, cfg): if cfg.distributed and cfg.eval_ddp: - sampler = OrderedDistributedSampler( - val_ds, num_replicas=cfg.world_size, rank=cfg.local_rank - ) + sampler = OrderedDistributedSampler(val_ds, num_replicas=cfg.world_size, rank=cfg.local_rank) else: sampler = SequentialSampler(val_ds) @@ -367,9 +358,7 @@ def get_test_dataset(test_df, cfg): def get_test_dataloader(test_ds, cfg): if cfg.distributed and cfg.eval_ddp: - sampler = OrderedDistributedSampler( - test_ds, num_replicas=cfg.world_size, rank=cfg.local_rank - ) + sampler = OrderedDistributedSampler(test_ds, num_replicas=cfg.world_size, rank=cfg.local_rank) else: sampler = SequentialSampler(test_ds) @@ -390,30 +379,33 @@ def get_test_dataloader(test_ds, cfg): return test_dataloader - def get_optimizer(model, cfg): params = model.parameters() if cfg.optimizer == "Adam": optimizer = optim.Adam(params, lr=cfg.lr, weight_decay=cfg.weight_decay) - + elif cfg.optimizer == "AdamW_plus": paras = list(model.named_parameters()) no_decay = ["bias", "LayerNorm.bias"] - params = [{"params": [param for name, param in paras if (not any(nd in name for nd in no_decay))], - "lr": cfg.lr, - "weight_decay":cfg.weight_decay}, - {"params": [param for name, param in paras if (any(nd in name for nd in no_decay))], - "lr": cfg.lr, - "weight_decay":0.}, - ] - optimizer = optim.AdamW(params, lr=cfg.lr) - - + params = [ + { + "params": [param for name, param in paras if (not any(nd in name for nd in no_decay))], + "lr": cfg.lr, + "weight_decay": cfg.weight_decay, + }, + { + "params": [param for name, param in paras if (any(nd in name for nd in no_decay))], + "lr": cfg.lr, + "weight_decay": 0.0, + }, + ] + optimizer = optim.AdamW(params, lr=cfg.lr) + elif cfg.optimizer == "AdamW": optimizer = optim.AdamW(params, lr=cfg.lr, weight_decay=cfg.weight_decay) - + elif cfg.optimizer == "SGD": optimizer = optim.SGD( params, @@ -426,7 +418,6 @@ def get_optimizer(model, cfg): return optimizer - def get_scheduler(cfg, optimizer, total_steps): if cfg.schedule == "steplr": @@ -440,7 +431,7 @@ def get_scheduler(cfg, optimizer, total_steps): optimizer, num_warmup_steps=cfg.warmup * (total_steps // cfg.batch_size) // cfg.world_size, num_training_steps=cfg.epochs * (total_steps // cfg.batch_size) // cfg.world_size, - num_cycles = cfg.num_cycles + num_cycles=cfg.num_cycles, ) elif cfg.schedule == "linear": scheduler = get_linear_schedule_with_warmup( @@ -448,34 +439,28 @@ def get_scheduler(cfg, optimizer, total_steps): num_warmup_steps=0, num_training_steps=cfg.epochs * (total_steps // cfg.batch_size) // cfg.world_size, ) - + elif cfg.schedule == "CosineAnnealingLR": - T_max = int(np.ceil(0.5*total_steps)) - scheduler = lr_scheduler.CosineAnnealingLR(optimizer, - T_max=T_max, - eta_min=1e-8) + T_max = int(np.ceil(0.5 * total_steps)) + scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=T_max, eta_min=1e-8) -# print("num_steps", (total_steps // cfg.batch_size) // cfg.world_size) + # print("num_steps", (total_steps // cfg.batch_size) // cfg.world_size) - - else: scheduler = None return scheduler - - - def read_df(fn): - if 'parquet' in fn: - df = pd.read_parquet(fn, engine = "fastparquet") + if "parquet" in fn: + df = pd.read_parquet(fn, engine="fastparquet") else: df = pd.read_csv(fn) return df + def get_data(cfg): # setup dataset @@ -488,75 +473,90 @@ def get_data(cfg): test_df = read_df(cfg.test_df) else: test_df = None - + if cfg.val_df: if type(cfg.val_df) == list: cfg.val_df = cfg.val_df[cfg.fold] val_df = read_df(cfg.val_df) if cfg.fold > -1: - if 'fold' in val_df.columns: - val_df = val_df[val_df["fold"] == cfg.fold] - train_df = df[df["fold"] != cfg.fold] + if "fold" in val_df.columns: + val_df = val_df[val_df["fold"] == cfg.fold] + train_df = df[df["fold"] != cfg.fold] else: - train_df = df + train_df = df else: train_df = df else: if cfg.fold == -1: val_df = df[df["fold"] == 0] else: - val_df = df[df["fold"] == cfg.fold] - + val_df = df[df["fold"] == cfg.fold] + train_df = df[df["fold"] != cfg.fold] - + return train_df, val_df, test_df def upload_s3(cfg): from boto3.session import Session import boto3 + BUCKET_NAME = cfg.s3_bucket_name ACCESS_KEY = cfg.s3_access_key SECRET_KEY = cfg.s3_secret_key - session = Session(aws_access_key_id=ACCESS_KEY, - aws_secret_access_key=SECRET_KEY) - s3 = session.resource('s3') + session = Session(aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY) + s3 = session.resource("s3") - s3.Bucket(BUCKET_NAME).upload_file(f"{cfg.output_dir}/fold{cfg.fold}/val_data_seed{cfg.seed}.pth", f"output/{cfg.name}/fold{cfg.fold}/val_data_seed{cfg.seed}.pth") - s3.Bucket(BUCKET_NAME).upload_file(f"{cfg.output_dir}/fold{cfg.fold}/test_data_seed{cfg.seed}.pth", f"output/{cfg.name}/fold{cfg.fold}/test_data_seed{cfg.seed}.pth") - s3.Bucket(BUCKET_NAME).upload_file(f"{cfg.output_dir}/fold{cfg.fold}/submission_seed{cfg.seed}.csv", f"output/{cfg.name}/fold{cfg.fold}/submission_seed{cfg.seed}.csv") + s3.Bucket(BUCKET_NAME).upload_file( + f"{cfg.output_dir}/fold{cfg.fold}/val_data_seed{cfg.seed}.pth", + f"output/{cfg.name}/fold{cfg.fold}/val_data_seed{cfg.seed}.pth", + ) + s3.Bucket(BUCKET_NAME).upload_file( + f"{cfg.output_dir}/fold{cfg.fold}/test_data_seed{cfg.seed}.pth", + f"output/{cfg.name}/fold{cfg.fold}/test_data_seed{cfg.seed}.pth", + ) + s3.Bucket(BUCKET_NAME).upload_file( + f"{cfg.output_dir}/fold{cfg.fold}/submission_seed{cfg.seed}.csv", + f"output/{cfg.name}/fold{cfg.fold}/submission_seed{cfg.seed}.csv", + ) def flatten(t): return [item for sublist in t for item in sublist] + def set_pandas_display(): - pd.set_option('display.max_columns', None) - pd.set_option('display.max_rows',10000) - pd.set_option('display.width', 10000) - pd.set_option('display.float_format', lambda x: '%.3f' % x) + pd.set_option("display.max_columns", None) + pd.set_option("display.max_rows", 10000) + pd.set_option("display.width", 10000) + pd.set_option("display.float_format", lambda x: "%.3f" % x) + def dumpobj(file, obj): - with open(file, 'wb') as handle: + with open(file, "wb") as handle: pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL) + def loadobj(file): - with open(file, 'rb') as handle: + with open(file, "rb") as handle: return pickle.load(handle) + def get_level(level_str): - ''' get level''' - l_names = {logging.getLevelName(lvl).lower(): lvl for lvl in [10, 20, 30, 40, 50]} # noqa + """get level""" + l_names = {logging.getLevelName(lvl).lower(): lvl for lvl in [10, 20, 30, 40, 50]} # noqa return l_names.get(level_str.lower(), logging.INFO) + def get_logger(name, level_str): - ''' get logger''' + """get logger""" logger = logging.getLogger(name) logger.setLevel(get_level(level_str)) handler = logging.StreamHandler() handler.setLevel(level_str) - handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) # pylint: disable=C0301 # noqa + handler.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ) # pylint: disable=C0301 # noqa logger.addHandler(handler) return logger - From 6b8f3f6a9b28e4b9d9fba82310ce09fbb8589105 Mon Sep 17 00:00:00 2001 From: ChristofHenkel Date: Wed, 26 Feb 2025 07:56:49 +0100 Subject: [PATCH 05/13] Update README.md DCO Remediation Commit for christofhenkel I, christofhenkel , hereby add my Signed-off-by to this commit: 1db03fdef0110f3b933bd424eb2626653809e302 I, christofhenkel , hereby add my Signed-off-by to this commit: 2ffe23d162a642acee2208f94a6e878023286717 I, christofhenkel , hereby add my Signed-off-by to this commit: df958e7c3adb667daf4540429d89f4b80b18b9e7 Signed-off-by: christofhenkel Signed-off-by: ChristofHenkel --- competitions/kaggle/Cryo-ET/1st_place_solution/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index c40853ce1d..41e5aab64c 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -60,7 +60,7 @@ For convenience we provide a file ```train_folded_v1.csv``` which contains the o We solve the competition with a 3D-segmentation approach leveraging [MONAI's FlexibleUNet](https://docs.monai.io/en/stable/networks.html#flexibleunet) architecture. Compared to the original implementation we adjusted the network to output more featuremap and enable deep-supervision. The following illustrates the resulting architecture at a high level: -![alt text](partly_Unet.png "Partly UNet") +![alt text](figures/partly_Unet.png "Partly UNet") We provide three different configurations which differ only in the used backbone and output feature maps. The configuration files are .py files and located under ```configs``` and share all other hyper-parameters. Each hyperparameter can be overwriten by adding a flag to the training command. To train a resnet34 version of our segmentation model simply run From 14bf96627b82ec7f98419170850164dcbadd4b35 Mon Sep 17 00:00:00 2001 From: ChristofHenkel Date: Wed, 26 Feb 2025 07:57:59 +0100 Subject: [PATCH 06/13] Add files via upload Signed-off-by: ChristofHenkel --- figures/partly_Unet.png | Bin 0 -> 145450 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 figures/partly_Unet.png diff --git a/figures/partly_Unet.png b/figures/partly_Unet.png new file mode 100644 index 0000000000000000000000000000000000000000..7f4f72cdbdf30b7b8d525cdd318333c2fb5d7543 GIT binary patch literal 145450 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

7+i2rfHv06owxCT7OzdNdS!v+=pTeRkRLO>T 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%0AaO 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 Date: Wed, 26 Feb 2025 08:05:02 +0100 Subject: [PATCH 07/13] Update README.md Signed-off-by: ChristofHenkel --- competitions/kaggle/Cryo-ET/1st_place_solution/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index 41e5aab64c..2e0d197492 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -60,7 +60,9 @@ For convenience we provide a file ```train_folded_v1.csv``` which contains the o We solve the competition with a 3D-segmentation approach leveraging [MONAI's FlexibleUNet](https://docs.monai.io/en/stable/networks.html#flexibleunet) architecture. Compared to the original implementation we adjusted the network to output more featuremap and enable deep-supervision. The following illustrates the resulting architecture at a high level: -![alt text](figures/partly_Unet.png "Partly UNet") +

+ figure of a Partly UNet We provide three different configurations which differ only in the used backbone and output feature maps. The configuration files are .py files and located under ```configs``` and share all other hyper-parameters. Each hyperparameter can be overwriten by adding a flag to the training command. To train a resnet34 version of our segmentation model simply run From fc2a5d9d1b44024b8943501b2590f0901746ed76 Mon Sep 17 00:00:00 2001 From: ChristofHenkel Date: Wed, 26 Feb 2025 08:05:16 +0100 Subject: [PATCH 08/13] Add files via upload Signed-off-by: ChristofHenkel --- .../Cryo-ET/1st_place_solution/partly_Unet.png | Bin 0 -> 145450 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 competitions/kaggle/Cryo-ET/1st_place_solution/partly_Unet.png diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/partly_Unet.png b/competitions/kaggle/Cryo-ET/1st_place_solution/partly_Unet.png new file mode 100644 index 0000000000000000000000000000000000000000..7f4f72cdbdf30b7b8d525cdd318333c2fb5d7543 GIT binary patch literal 145450 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

7+i2rfHv06owxCT7OzdNdS!v+=pTeRkRLO>T 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%0AaO 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 Date: Mon, 24 Mar 2025 14:53:23 +0100 Subject: [PATCH 09/13] Update README.md Signed-off-by: ChristofHenkel --- competitions/kaggle/Cryo-ET/1st_place_solution/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index 2e0d197492..e481bce757 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -45,7 +45,7 @@ cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ This tutorial is build upon the official Cryo ET competition data. It can be downloaded directly from kaggle: https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data -Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```) +Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```). If you decide to use the Kaggle API you need to create a Kaggle account and configure your token as described here. ```kaggle competitions download -c czii-cryo-et-object-identification``` From 4a317eb06ef773ae4091d8a1f80f0a220e5b8ba5 Mon Sep 17 00:00:00 2001 From: christofhenkel Date: Tue, 25 Mar 2025 12:40:31 +0000 Subject: [PATCH 10/13] added license --- .../Cryo-ET/1st_place_solution/README.md | 35 ++++++++++++------- .../configs/cfg_effnetb3.py | 12 +++++++ .../configs/cfg_resnet34.py | 11 ++++++ .../configs/cfg_resnet34_ds.py | 11 ++++++ .../configs/common_config.py | 12 +++++++ .../Cryo-ET/1st_place_solution/data/ds_1.py | 12 +++++++ .../1st_place_solution/metrics/metric_1.py | 11 ++++++ .../1st_place_solution/models/mdl_1.py | 11 ++++++ .../1st_place_solution/postprocess/pp_1.py | 11 ++++++ .../Cryo-ET/1st_place_solution/train.py | 12 +++++++ .../Cryo-ET/1st_place_solution/utils.py | 11 ++++++ 11 files changed, 136 insertions(+), 13 deletions(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index e481bce757..ea51fc067b 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -21,13 +21,32 @@ Investigating neurodegenerative diseases, cancer, and infectious diseases. Cryo-ET is particularly powerful because it enables direct imaging of biological systems without the need for staining or chemical fixation, preserving their native conformation. + + +## Required Data + +This tutorial is build upon the official Cryo ET competition data. +It can be downloaded to a local ```DATA_FOLDER``` directly from kaggle (You will also need to follow the competition url and click "join competition" to accept the terms and conditions): https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data . + +Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```). If you decide to use the Kaggle API you need to create a Kaggle account and configure your token as described [here](https://github.com/Kaggle/kaggle-api/blob/main/docs/README.md#api-credentials) and then be allowed to download the data with the following command: + +```kaggle competitions download -c czii-cryo-et-object-identification -p DATA_FOLDER``` + +Unzip the competition dataset to DATA_FOLDER + +``` +cd DATA_FOLDER +unzip czii-cryo-et-object-identification.zip -d czii-cryo-et-object-identification/ +``` + + ## Environment: -To have a common environment its suggested to use the basic pytorch docker container and add a few pip packages on top +To have a common environment its suggested to use the basic pytorch docker container and add a few pip packages on top. This tutorial is designed to use a docker container with DATA_FOLDER mounted under "/mount/cryo/data/" 1. This tutorial was tested with tag 24.08-py3, i.e. run the following command to pull/ start the container. -```docker run nvcr.io/nvidia/pytorch:24.08-py3``` +```docker run nvcr.io/nvidia/pytorch:24.08-py3 -v DATA_FOLDER:/mount/cryo/data/``` 2. Within the container clone this repository @@ -41,17 +60,7 @@ cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ ```pip install -r requirements.txt``` -## Required Data - -This tutorial is build upon the official Cryo ET competition data. It can be downloaded directly from kaggle: https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data - -Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```). If you decide to use the Kaggle API you need to create a Kaggle account and configure your token as described here. - -```kaggle competitions download -c czii-cryo-et-object-identification``` - -and adjust path to it in ```configs/common_config.py``` with ```cfg.data_folder```. - - +If you dont want to mount the DATA_FOLDER, or don't want to use docker, you have to adjust path to the data in ```configs/common_config.py``` with specifying```cfg.data_folder``` ## Training models diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py index 55151aba93..82d7ef8e0c 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_effnetb3.py @@ -1,3 +1,15 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from common_config import basic_cfg import os import pandas as pd diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py index 5f819efb00..470d69087d 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from common_config import basic_cfg import os import pandas as pd diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py index 226658f91e..6b177713a1 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/cfg_resnet34_ds.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from common_config import basic_cfg import os import pandas as pd diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py index 83e4de51d6..99aef862c7 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py @@ -1,3 +1,15 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from types import SimpleNamespace from monai import transforms as mt diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py index ac806e4d47..1c56f75634 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/data/ds_1.py @@ -1,3 +1,15 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import os import torch import numpy as np diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py index 68db35711c..1d539d8ad4 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/metrics/metric_1.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import numpy as np import torch from sklearn.metrics import roc_auc_score diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py index f181fa04ce..b8c871d4a9 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/models/mdl_1.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import torch import torch.nn.functional as F from torch import nn diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py index db3e7fe8fa..d0dee3e350 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/postprocess/pp_1.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import pandas as pd import torch from torch.nn import functional as F diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py index ca666e0f54..c2e3589e21 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/train.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/train.py @@ -1,3 +1,15 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import numpy as np import pandas as pd import importlib diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py index 501d78a51c..2f86c48e1c 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/utils.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import random import os import numpy as np From 2ea696eca5ec576afc6cf3979564b2b02b4d52aa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 12:43:16 +0000 Subject: [PATCH 11/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- competitions/kaggle/Cryo-ET/1st_place_solution/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index ea51fc067b..226b61be8c 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -25,7 +25,7 @@ Cryo-ET is particularly powerful because it enables direct imaging of biological ## Required Data -This tutorial is build upon the official Cryo ET competition data. +This tutorial is build upon the official Cryo ET competition data. It can be downloaded to a local ```DATA_FOLDER``` directly from kaggle (You will also need to follow the competition url and click "join competition" to accept the terms and conditions): https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data . Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```). If you decide to use the Kaggle API you need to create a Kaggle account and configure your token as described [here](https://github.com/Kaggle/kaggle-api/blob/main/docs/README.md#api-credentials) and then be allowed to download the data with the following command: @@ -60,7 +60,7 @@ cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ ```pip install -r requirements.txt``` -If you dont want to mount the DATA_FOLDER, or don't want to use docker, you have to adjust path to the data in ```configs/common_config.py``` with specifying```cfg.data_folder``` +If you dont want to mount the DATA_FOLDER, or don't want to use docker, you have to adjust path to the data in ```configs/common_config.py``` with specifying```cfg.data_folder``` ## Training models From 81776e4480c94fdc8def155dd62f9f1adce90510 Mon Sep 17 00:00:00 2001 From: "R. Garcia-Dias" Date: Fri, 4 Apr 2025 13:58:32 +0100 Subject: [PATCH 12/13] Suggestions to simplify the workflow Signed-off-by: R. Garcia-Dias --- .gitignore | 2 + .../Cryo-ET/1st_place_solution/README.md | 64 ++++++++++++------- .../configs/common_config.py | 2 +- .../1st_place_solution/requirements.txt | 1 + 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 84df49b9ce..da2eede422 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,5 @@ auto3dseg/notebooks/datalist.json *.png *.np* *.pt +competitions/kaggle/Cryo-ET/1st_place_solution/data/ +competitions/kaggle/Cryo-ET/1st_place_solution/results/ \ No newline at end of file diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md index 226b61be8c..4ea71eaeb0 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/README.md +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/README.md @@ -20,47 +20,61 @@ Understanding interactions between viruses and host cells. Investigating neurodegenerative diseases, cancer, and infectious diseases. Cryo-ET is particularly powerful because it enables direct imaging of biological systems without the need for staining or chemical fixation, preserving their native conformation. +## Requirements +- docker +- git +- kaggle API credentials +# Running the tutorial -## Required Data +1. Download the tutorial code from the ProjectMONAI repository. -This tutorial is build upon the official Cryo ET competition data. -It can be downloaded to a local ```DATA_FOLDER``` directly from kaggle (You will also need to follow the competition url and click "join competition" to accept the terms and conditions): https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data . +```bash +git clone https://github.com/Project-MONAI/tutorials.git +cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ +``` -Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```). If you decide to use the Kaggle API you need to create a Kaggle account and configure your token as described [here](https://github.com/Kaggle/kaggle-api/blob/main/docs/README.md#api-credentials) and then be allowed to download the data with the following command: +2. Run container to start the tutorial. -```kaggle competitions download -c czii-cryo-et-object-identification -p DATA_FOLDER``` +```bash +docker run --gpus all --ipc=host --ulimit memlock=-1 --ulimit stack=67108864 -v $PWD:/workspace/ -it nvcr.io/nvidia/pytorch:24.08-py3 /bin/bash +``` -Unzip the competition dataset to DATA_FOLDER +if you want to use the kaggle API to download the data, you need to mount your kaggle.json file into the container. You can do this by adding the following flag to the docker run command: -``` -cd DATA_FOLDER -unzip czii-cryo-et-object-identification.zip -d czii-cryo-et-object-identification/ +```bash +docker run --gpus all --ipc=host --ulimit memlock=-1 --ulimit stack=67108864 -v $PWD:/workspace/ -v $HOME/.config/kaggle/:/root/.kaggle -it nvcr.io/nvidia/pytorch:24.08-py3 /bin/bash ``` +3. Install necessary additional pip packages inside the container by running the following command on the prompt you get after running the previous command: -## Environment: -To have a common environment its suggested to use the basic pytorch docker container and add a few pip packages on top. This tutorial is designed to use a docker container with DATA_FOLDER mounted under "/mount/cryo/data/" +```bash +pip install -r requirements.txt +``` -1. This tutorial was tested with tag 24.08-py3, i.e. run the following command to pull/ start the container. +4. Download the data -```docker run nvcr.io/nvidia/pytorch:24.08-py3 -v DATA_FOLDER:/mount/cryo/data/``` +This tutorial is build upon the official Cryo ET competition data. +It can be downloaded to a local ```DATA_FOLDER``` directly from kaggle (You will also need to follow the competition url and click "join competition" to accept the terms and conditions): https://www.kaggle.com/competitions/czii-cryo-et-object-identification/data . -2. Within the container clone this repository +Alternativly it can be downloaded using the kaggle API (which can be installed via ```pip install kaggle```). If you decide to use the Kaggle API you need to create a Kaggle account and configure your token as described [here](https://github.com/Kaggle/kaggle-api/blob/main/docs/README.md#api-credentials) and then be allowed to download the data with the following command: -``` -git clone https://github.com/ProjectMONAI/tutorials -cd tutorials/competitions/kaggle/Cryo-ET/1st_place_solution/ +```bash +export DATA_FOLDER=$PWD/data +mkdir -p $DATA_FOLDER +kaggle competitions download -c czii-cryo-et-object-identification -p $DATA_FOLDER ``` +Unzip the competition dataset to DATA_FOLDER -3. And install necessary additional pip packages via - -```pip install -r requirements.txt``` +```bash +cd $DATA_FOLDER +unzip czii-cryo-et-object-identification.zip -d czii-cryo-et-object-identification/ +``` -If you dont want to mount the DATA_FOLDER, or don't want to use docker, you have to adjust path to the data in ```configs/common_config.py``` with specifying```cfg.data_folder``` +If you change the DATA_FOLDER location, have to adjust path to the `cfg.data_folder` data at `configs/common_config.py`. ## Training models @@ -75,9 +89,13 @@ We solve the competition with a 3D-segmentation approach leveraging [MONAI's Fle We provide three different configurations which differ only in the used backbone and output feature maps. The configuration files are .py files and located under ```configs``` and share all other hyper-parameters. Each hyperparameter can be overwriten by adding a flag to the training command. To train a resnet34 version of our segmentation model simply run -```python train.py -C cfg_resnet34 --output_dir WHATEVERISYOUROUTPUTDIR``` +```bash +export RESULTS=$PWD/results +mkdir -p $RESULTS +python train.py -C cfg_resnet34 --output_dir $RESULTS +``` -This will save checkpoints under the specified WHATEVERISYOUROUTPUTDIR when training is finished. +This will save checkpoints under the specified $RESULTS when training is finished. By default models are trained using bfloat16 which requires a GPU capable of that. Alternatively you can set ```cfg.bf16=False``` or overwrite as flag ```--bf16 False``` when running ```train.py ```. ### Replicating 1st place solution (segmentation part) diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py index 99aef862c7..efcddd574f 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/configs/common_config.py @@ -95,7 +95,7 @@ # paths -cfg.data_folder = "/mount/cryo/data/czii-cryo-et-object-identification/train/static/ExperimentRuns/" +cfg.data_folder = "/workspace/data/czii-cryo-et-object-identification/train/static/ExperimentRuns/" cfg.train_df = "train_folded_v1.csv" diff --git a/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt b/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt index 65331e9981..fcc88d67c8 100644 --- a/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt +++ b/competitions/kaggle/Cryo-ET/1st_place_solution/requirements.txt @@ -1,2 +1,3 @@ zarr monai==1.4.0 +kaggle==1.7.4.2 From 68e0dcf51b1b4eae1ff2947759c2a5797adc7ed0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:10:44 +0000 Subject: [PATCH 13/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index da2eede422..c9e20119f0 100644 --- a/.gitignore +++ b/.gitignore @@ -158,4 +158,4 @@ auto3dseg/notebooks/datalist.json *.np* *.pt competitions/kaggle/Cryo-ET/1st_place_solution/data/ -competitions/kaggle/Cryo-ET/1st_place_solution/results/ \ No newline at end of file +competitions/kaggle/Cryo-ET/1st_place_solution/results/