easymode 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: easymode
3
+ Version: 0.0.1
4
+ Summary: Easymode - a collection of pretrained general networks for segmenting common eukaryotic features in cryoET
5
+ Home-page:
6
+ Author: mgflast
7
+ Author-email: mgflast@gmail.com
8
+ License: GPL v3
9
+ Requires-Dist: mrcfile
10
+ Requires-Dist: numpy
11
+ Requires-Dist: scipy
12
+ Requires-Dist: huggingface_hub
13
+ Requires-Dist: requests
14
+ Requires-Dist: tifffile
15
+ Requires-Dist: psutil
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: license
19
+ Dynamic: requires-dist
20
+ Dynamic: summary
File without changes
File without changes
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+ import random
3
+ from scipy.ndimage import rotate, gaussian_filter, median_filter
4
+
5
+ from easymode.membrain_fourier_augmentations.fourier_augmentations import MissingWedgeMaskAndFourierAmplitudeMatchingCombined
6
+
7
+ ROT_XZ_YZ_MAX_ANGLE = 10.0
8
+ ROT_XY_MAX_ANGLE = 10.0
9
+
10
+ def rotate_90_xy(img, label):
11
+ k = random.randint(0, 3)
12
+ img = np.rot90(img, k=k, axes=(1, 2))
13
+ label = np.rot90(label, k=k, axes=(1, 2))
14
+ return img, label
15
+
16
+ def rotate_90_xz(img, label):
17
+ k = random.randint(0, 1) * 2
18
+ img = np.rot90(img, k=k, axes=(0, 2))
19
+ label = np.rot90(label, k=k, axes=(0, 2))
20
+ return img, label
21
+
22
+ def flip(img, label):
23
+ k = random.choice([None, 0, 1, 2])
24
+ if k is not None:
25
+ img = np.flip(img, axis=k)
26
+ label = np.flip(label, axis=k)
27
+ return img, label
28
+
29
+ def rotate_continuous_xz_or_yz(img, label):
30
+ plane = random.choice([(0, 2), (0, 1)])
31
+ angle = np.random.uniform(-ROT_XZ_YZ_MAX_ANGLE, ROT_XZ_YZ_MAX_ANGLE)
32
+
33
+ img = rotate(img, angle, axes=plane, order=1, mode='reflect', prefilter=False, reshape=False)
34
+ label = rotate(label, angle, axes=plane, order=0, mode='constant', cval=2, reshape=False)
35
+
36
+ return img, label
37
+
38
+ def rotate_continuous_xy(img, label):
39
+ angle = np.random.uniform(-ROT_XY_MAX_ANGLE, ROT_XY_MAX_ANGLE)
40
+
41
+ img = rotate(img, angle, axes=(1, 2), order=1, mode='reflect', prefilter=False, reshape=False)
42
+ label = rotate(label, angle, axes=(1, 2), order=0, mode='constant', cval=2, reshape=False)
43
+
44
+ return img, label
45
+
46
+ def remove_wedge(img, label):
47
+ membrain_fourier_trickery_machine = MissingWedgeMaskAndFourierAmplitudeMatchingCombined()
48
+ img = membrain_fourier_trickery_machine(img)
49
+ return img, label
50
+
51
+ def filter_gaussian(img, label):
52
+ img = gaussian_filter(img, sigma=random.uniform(0.3, 1.5))
53
+ return img, label
@@ -0,0 +1,49 @@
1
+ import os, shutil, json
2
+
3
+ version = "0.0.1"
4
+ license = "GNU GPL v3"
5
+
6
+ root = os.path.dirname(os.path.dirname(__file__))
7
+ settings_path = os.path.join(os.path.expanduser("~"), "easymode", "settings.txt")
8
+
9
+
10
+ def parse_settings():
11
+ # If settings file not found, copy the one from core to the right location.
12
+ if not os.path.exists(settings_path):
13
+ os.makedirs(os.path.dirname(settings_path), exist_ok=True)
14
+ shutil.copy(os.path.join(root, "core", "settings.txt"), settings_path)
15
+
16
+ try:
17
+ with open(settings_path, 'r') as f:
18
+ sdict = json.load(f)
19
+ except Exception as e:
20
+ shutil.copy(os.path.join(root, "core", "settings.txt"), settings_path)
21
+ parse_settings()
22
+ return
23
+
24
+ # Read settings - if any parameters are missing, insert them.
25
+ with open(os.path.join(root, "core", "settings.txt"), 'r') as f:
26
+ default_settings = json.load(f)
27
+
28
+ for key in default_settings:
29
+ if key not in sdict:
30
+ sdict[key] = default_settings[key]
31
+
32
+ if sdict["MODEL_DIRECTORY"] == "" or not os.path.exists(sdict["MODEL_DIRECTORY"]):
33
+ sdict["MODEL_DIRECTORY"] = os.path.join(os.path.expanduser("~"), "easymode")
34
+
35
+ with open(settings_path, 'w') as f:
36
+ json.dump(sdict, f, indent=2)
37
+
38
+ return sdict
39
+
40
+
41
+ settings = parse_settings()
42
+
43
+
44
+ def edit_setting(key, value):
45
+ global settings
46
+ settings[key] = value
47
+ with open(settings_path, 'w') as f:
48
+ json.dump(settings, f, indent=2)
49
+ print(key, value)
@@ -0,0 +1,206 @@
1
+ import os
2
+ import requests
3
+ import json
4
+ from pathlib import Path
5
+ from huggingface_hub import hf_hub_download, HfApi
6
+ from easymode.core.model import create
7
+ import tensorflow as tf
8
+ import easymode.core.config as cfg
9
+
10
+ # Configuration
11
+ HF_REPO_ID = "mgflast/easymode" # Single repo for all models
12
+ MODEL_CACHE_DIR = cfg.settings["MODEL_DIRECTORY"]
13
+ VERSION_FILE = "model_info.json"
14
+
15
+
16
+ def get_model_info(model_title):
17
+ """Get model repository and filename info."""
18
+ filename = f"{model_title}_3d.h5"
19
+
20
+ return {
21
+ 'repo_id': HF_REPO_ID,
22
+ 'filename': filename,
23
+ 'local_path': os.path.join(MODEL_CACHE_DIR, filename),
24
+ 'version_path': os.path.join(MODEL_CACHE_DIR, f"{model_title}_3d_info.json")
25
+ }
26
+
27
+
28
+ def is_online():
29
+ """Check if internet connection is available."""
30
+ try:
31
+ response = requests.get("https://huggingface.co", timeout=5)
32
+ return response.status_code == 200
33
+ except:
34
+ return False
35
+
36
+
37
+ def get_remote_version(repo_id):
38
+ """Get latest version info from Hugging Face."""
39
+ try:
40
+ api = HfApi()
41
+ repo_info = api.repo_info(repo_id)
42
+
43
+ # Use last modified time as version identifier
44
+ last_modified = repo_info.last_modified
45
+ return {
46
+ 'version': last_modified.isoformat() if last_modified else "unknown",
47
+ 'commit_hash': repo_info.sha[:8] if repo_info.sha else "unknown"
48
+ }
49
+ except Exception as e:
50
+ print(f"Warning: Could not get remote version info: {e}")
51
+ return None
52
+
53
+
54
+ def get_local_version(version_path):
55
+ """Get local version info."""
56
+ if not os.path.exists(version_path):
57
+ return None
58
+
59
+ try:
60
+ with open(version_path, 'r') as f:
61
+ return json.load(f)
62
+ except:
63
+ return None
64
+
65
+
66
+ def save_version_info(version_path, version_info):
67
+ """Save version info locally."""
68
+ os.makedirs(os.path.dirname(version_path), exist_ok=True)
69
+ with open(version_path, 'w') as f:
70
+ json.dump(version_info, f, indent=2)
71
+
72
+
73
+ def download_model(repo_id, filename, local_path, version_path):
74
+ """Download model from Hugging Face."""
75
+ print(f"Downloading {repo_id}/{filename}...")
76
+
77
+ try:
78
+ # Ensure cache directory exists
79
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
80
+
81
+ # Download file
82
+ downloaded_path = hf_hub_download(
83
+ repo_id=repo_id,
84
+ filename=filename,
85
+ cache_dir=MODEL_CACHE_DIR,
86
+ local_dir=os.path.dirname(local_path)
87
+ )
88
+
89
+ # Get and save version info
90
+ remote_version = get_remote_version(repo_id)
91
+ if remote_version:
92
+ save_version_info(version_path, remote_version)
93
+
94
+ print(f"\nDownloaded successfully to {local_path}")
95
+ return local_path
96
+
97
+ except Exception as e:
98
+ raise RuntimeError(f"Failed to download {repo_id}: {e}")
99
+
100
+
101
+ def load_model_weights(weights_path):
102
+ model = create()
103
+ dummy_input = tf.zeros((1, 160, 160, 160, 1))
104
+ _ = model(dummy_input)
105
+ model.load_weights(weights_path)
106
+ return model
107
+
108
+ def cache_model(model_title, force_download=False, silent=False):
109
+ info = get_model_info(model_title)
110
+ online = is_online()
111
+
112
+ # Check if local file exists
113
+ local_exists = os.path.exists(info['local_path'])
114
+
115
+ if force_download or not local_exists:
116
+ # Need to download
117
+ if not online:
118
+ if local_exists:
119
+ print("Local model found. There may be updates available, but we cannot check without an internet connection.")
120
+ else:
121
+ print(f"The required network weights are not available in the local cache {MODEL_CACHE_DIR} and there is no internet connection available to download them - aborting...")
122
+ exit()
123
+ else:
124
+ print(f"The required network weights for {model_title} are not available in the local cache. Downloading now...")
125
+ download_model(info['repo_id'], info['filename'],
126
+ info['local_path'], info['version_path'])
127
+
128
+ elif local_exists and online:
129
+ # Check if we need to update
130
+ local_version = get_local_version(info['version_path'])
131
+ remote_version = get_remote_version(info['repo_id'])
132
+
133
+ if remote_version and local_version:
134
+ if remote_version['version'] != local_version['version']:
135
+ if not silent:
136
+ print(f"New version available for {model_title}, updating...")
137
+ download_model(info['repo_id'], info['filename'], info['local_path'], info['version_path'])
138
+ elif remote_version and not local_version:
139
+ if not silent:
140
+ print("No local version info, checking for updates...")
141
+ save_version_info(info['version_path'], remote_version)
142
+
143
+ return info['local_path']
144
+
145
+ def load_model(local_path):
146
+ return load_model_weights(local_path)
147
+
148
+ def clear_model_cache(model_title=None):
149
+ """Clear local model cache."""
150
+ if model_title:
151
+ # Clear specific model
152
+ info = get_model_info(model_title)
153
+ files_to_remove = [info['local_path'], info['version_path']]
154
+
155
+ for file_path in files_to_remove:
156
+ if os.path.exists(file_path):
157
+ os.remove(file_path)
158
+ print(f"Removed {file_path}")
159
+ else:
160
+ # Clear all models
161
+ import shutil
162
+ if os.path.exists(MODEL_CACHE_DIR):
163
+ shutil.rmtree(MODEL_CACHE_DIR)
164
+ print(f"Cleared model cache: {MODEL_CACHE_DIR}")
165
+
166
+ def list_remote_models():
167
+ """List all available models in the Hugging Face repository."""
168
+ if not is_online():
169
+ print("Cannot list remote models: No internet connection")
170
+ return []
171
+
172
+ try:
173
+ api = HfApi()
174
+ repo_files = api.list_repo_files(HF_REPO_ID)
175
+
176
+ # Filter for .h5 model files
177
+ model_files = [f for f in repo_files if f.endswith('.h5')]
178
+
179
+ if not model_files:
180
+ print("No model files found in repository")
181
+ return []
182
+
183
+ print()
184
+ print(f"Easymode can currently segment the following features:")
185
+ print()
186
+ models = []
187
+
188
+ for model_file in sorted(model_files):
189
+ model_name = model_file.replace('.h5', '')
190
+ if '_' in model_name:
191
+ title, dim = model_name.rsplit('_', 1)
192
+ models.append({'title': title, 'filename': model_file})
193
+
194
+ # Check if we have it locally
195
+ local_path = os.path.join(MODEL_CACHE_DIR, model_file)
196
+ local_status = "weights local" if os.path.exists(local_path) else "weights available for download"
197
+
198
+ print(f" > {title} - [{local_status}]")
199
+ else:
200
+ pass
201
+
202
+ return models
203
+
204
+ except Exception as e:
205
+ print(f"Error listing remote models: {e}")
206
+ return []
@@ -0,0 +1,257 @@
1
+ import os, glob, time, multiprocessing, psutil
2
+ import tensorflow as tf
3
+ import gc
4
+ from tensorflow.keras import mixed_precision
5
+ import mrcfile
6
+ import numpy as np
7
+ from easymode.core.distribution import cache_model, load_model
8
+
9
+ TILE_SIZE = 160
10
+ OVERLAP = 32
11
+ MAX_CHUNK_SIZE = 64
12
+
13
+ def tile_volume(volume, patch_size=TILE_SIZE, overlap=OVERLAP):
14
+ d, h, w = volume.shape
15
+ stride = patch_size - 2 * overlap
16
+
17
+ z_boxes = max(1, (d + stride - 1) // stride)
18
+ y_boxes = max(1, (h + stride - 1) // stride)
19
+ x_boxes = max(1, (w + stride - 1) // stride)
20
+
21
+ tiles = []
22
+ positions = []
23
+
24
+ for z_idx in range(z_boxes):
25
+ for y_idx in range(y_boxes):
26
+ for x_idx in range(x_boxes):
27
+ z_start = z_idx * stride - overlap
28
+ y_start = y_idx * stride - overlap
29
+ x_start = x_idx * stride - overlap
30
+
31
+ vol_z_start = max(0, z_start)
32
+ vol_y_start = max(0, y_start)
33
+ vol_x_start = max(0, x_start)
34
+
35
+ vol_z_end = min(d, z_start + patch_size)
36
+ vol_y_end = min(h, y_start + patch_size)
37
+ vol_x_end = min(w, x_start + patch_size)
38
+
39
+ extracted = volume[vol_z_start:vol_z_end, vol_y_start:vol_y_end, vol_x_start:vol_x_end]
40
+
41
+ tile = np.zeros((patch_size, patch_size, patch_size), dtype=volume.dtype)
42
+
43
+ tile_z_start = vol_z_start - z_start
44
+ tile_y_start = vol_y_start - y_start
45
+ tile_x_start = vol_x_start - x_start
46
+
47
+ tile[tile_z_start:tile_z_start + extracted.shape[0],
48
+ tile_y_start:tile_y_start + extracted.shape[1],
49
+ tile_x_start:tile_x_start + extracted.shape[2]] = extracted
50
+
51
+ tiles.append(tile)
52
+ positions.append((z_idx * stride, y_idx * stride, x_idx * stride))
53
+
54
+ tiles = np.array(tiles)
55
+ tiles = np.expand_dims(tiles, axis=-1)
56
+
57
+ return tiles, positions, volume.shape
58
+
59
+
60
+ def detile_volume(segmented_tiles, positions, original_shape, patch_size=TILE_SIZE, overlap=OVERLAP):
61
+ d, h, w = original_shape
62
+ output_volume = np.zeros((d, h, w), dtype=np.float32)
63
+ stride = patch_size - 2 * overlap
64
+
65
+ if segmented_tiles.ndim == 5:
66
+ segmented_tiles = segmented_tiles.squeeze(-1)
67
+
68
+ for tile, (z_pos, y_pos, x_pos) in zip(segmented_tiles, positions):
69
+ center_region = tile[overlap:overlap + stride, overlap:overlap + stride, overlap:overlap + stride]
70
+
71
+ z_end = min(z_pos + stride, d)
72
+ y_end = min(y_pos + stride, h)
73
+ x_end = min(x_pos + stride, w)
74
+
75
+ actual_z = z_end - z_pos
76
+ actual_y = y_end - y_pos
77
+ actual_x = x_end - x_pos
78
+
79
+ output_volume[z_pos:z_end, y_pos:y_end, x_pos:x_end] = center_region[:actual_z, :actual_y, :actual_x]
80
+
81
+ return output_volume
82
+
83
+
84
+ def create_weight_matrix(patch_size, border_size=OVERLAP):
85
+ """Create weight matrix that reduces border artifacts."""
86
+ weights = np.ones((patch_size, patch_size, patch_size), dtype=np.float32)
87
+ fade_size = 16
88
+
89
+ for dim in range(3):
90
+ profile = np.ones(patch_size)
91
+
92
+ for i in range(border_size + fade_size):
93
+ if i < border_size:
94
+ weight = 0.1
95
+ else:
96
+ weight = 0.1 + 0.9 * (i - border_size) / fade_size
97
+
98
+ profile[i] = weight
99
+ profile[-(i + 1)] = weight
100
+
101
+ if dim == 0:
102
+ weights *= profile[:, np.newaxis, np.newaxis]
103
+ elif dim == 1:
104
+ weights *= profile[np.newaxis, :, np.newaxis]
105
+ else:
106
+ weights *= profile[np.newaxis, np.newaxis, :]
107
+
108
+ return weights
109
+
110
+
111
+ def _segment_tile_list(tiles, model, batch_size=8, max_chunk_size=MAX_CHUNK_SIZE):
112
+ num_tiles = len(tiles)
113
+ segmented_tiles = []
114
+ for i in range(0, num_tiles, max_chunk_size):
115
+ chunk_end = min(i + max_chunk_size, num_tiles)
116
+ chunk = tiles[i:chunk_end]
117
+
118
+ try:
119
+ chunk_result = model.predict(chunk, verbose=0, batch_size=batch_size)
120
+ segmented_tiles.extend(chunk_result)
121
+
122
+ except tf.errors.ResourceExhaustedError:
123
+ print(f"Memory error with chunk size {len(chunk)}, falling back to smaller chunks")
124
+ fallback_chunk_size = max(1, len(chunk) // 4)
125
+ for j in range(i, chunk_end, fallback_chunk_size):
126
+ small_chunk = tiles[j:min(j + fallback_chunk_size, chunk_end)]
127
+ small_result = model.predict(small_chunk, verbose=0, batch_size=batch_size)
128
+ segmented_tiles.extend(small_result)
129
+
130
+ return segmented_tiles
131
+
132
+
133
+ def _segment_tomogram_instance(volume, model, batch_size):
134
+ tiles, positions, original_shape = tile_volume(volume)
135
+ segmented_tiles = _segment_tile_list(tiles, model, batch_size=batch_size, max_chunk_size=MAX_CHUNK_SIZE)
136
+ segmented_tiles = np.array(segmented_tiles)
137
+ segmented_volume = detile_volume(segmented_tiles, positions, original_shape)
138
+
139
+ tf.keras.backend.clear_session()
140
+ gc.collect()
141
+ return segmented_volume.astype(np.float32)
142
+
143
+
144
+ def segment_tomogram(model, tomogram_path, tta=1, batch_size=2):
145
+ volume = mrcfile.read(tomogram_path)
146
+ volume -= np.mean(volume)
147
+ volume /= np.std(volume) + 1e-8
148
+
149
+ segmented_volume = np.zeros_like(volume)
150
+ k_xy = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
151
+ k_fx = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]
152
+ k_yz = [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]
153
+ for j in range(tta):
154
+ tta_vol = volume.copy()
155
+ tta_vol = np.rot90(tta_vol, k=k_xy[j], axes=(1, 2))
156
+ tta_vol = tta_vol if not k_fx[j] else np.flip(tta_vol, axis=1)
157
+ tta_vol = np.rot90(tta_vol, k=2 * k_yz[j], axes=(0, 1))
158
+ segmented_tta_vol = _segment_tomogram_instance(tta_vol, model, batch_size)
159
+ segmented_tta_vol = np.rot90(segmented_tta_vol, k=-2 * k_yz[j], axes=(0, 1))
160
+ segmented_tta_vol = segmented_tta_vol if not k_fx[j] else np.flip(segmented_tta_vol, axis=1)
161
+ segmented_tta_vol = np.rot90(segmented_tta_vol, k=-k_xy[j], axes=(1, 2))
162
+ segmented_volume += segmented_tta_vol
163
+ segmented_volume /= tta
164
+
165
+ segmented_volume[:32, :, :] = 0
166
+ segmented_volume[-32:, :, :] = 0
167
+ segmented_volume[:, :32, :] = 0
168
+ segmented_volume[:, -32:, :] = 0
169
+ segmented_volume[:, :, :32] = 0
170
+ segmented_volume[:, :, -32:] = 0
171
+ return segmented_volume
172
+
173
+
174
+ def save_mrc(pxd, path, data_format, voxel_size=10.0):
175
+ if data_format == 'float32':
176
+ pxd = pxd.astype(np.float32)
177
+ elif data_format == 'uint16':
178
+ pxd = (pxd * 255).astype(np.uint16) # scaling to [0, 255] because that's what we're used to in Ais
179
+ elif data_format == 'int8':
180
+ pxd = (pxd * 127).astype(np.int8)
181
+ with mrcfile.new(path, overwrite=True) as m:
182
+ m.set_data(pxd)
183
+ m.voxel_size = voxel_size
184
+
185
+ def segmentation_thread(tomogram_list, model_path, feature, output_dir, gpu, batch_size, tta, overwrite, data_format):
186
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)
187
+ os.environ['TF_DISABLE_MKL'] = '1'
188
+ os.environ['TF_XLA_FLAGS'] = '--tf_xla_enable_xla_devices=false'
189
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
190
+ os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
191
+ for device in tf.config.list_physical_devices('GPU'):
192
+ tf.config.experimental.set_memory_growth(device, True)
193
+ mixed_precision.set_global_policy('mixed_float16')
194
+
195
+ process_start_time = psutil.Process().create_time()
196
+
197
+ model = load_model(model_path)
198
+
199
+ for j, tomogram_path in enumerate(tomogram_list, 1):
200
+ tomo_name = os.path.splitext(os.path.basename(tomogram_path))[0]
201
+ output_file = os.path.join(output_dir, f"{tomo_name}__{feature}.mrc")
202
+ wrote_temporary = False
203
+ try:
204
+ if os.path.exists(output_file):
205
+ file_age = os.path.getmtime(output_file)
206
+ if not overwrite or file_age > process_start_time - 60:
207
+ continue
208
+
209
+ with mrcfile.new(output_file, overwrite=True) as m:
210
+ m.set_data(-1.0 * np.ones((10, 10, 10), dtype=np.float32))
211
+ wrote_temporary = True
212
+ segmented_volume = segment_tomogram(model, tomogram_path, tta, batch_size)
213
+
214
+ save_mrc(segmented_volume, output_file, data_format)
215
+ print(f"{j}/{len(tomogram_list)} (on GPU {gpu}) - {feature} - {os.path.basename(output_file)}")
216
+ except Exception as e:
217
+ if wrote_temporary:
218
+ os.remove(output_file)
219
+ print(f"{j}/{len(tomogram_list)} (on GPU {gpu}) - {feature} - {os.path.basename(output_file)} - ERROR: {e}")
220
+
221
+ def dispatch_segment(feature, data_directory, output_directory, tta=1, gpus=(0,1,2,3), batch_size=8, overwrite=False, data_format='int8'):
222
+ if output_directory is None:
223
+ output_directory = data_directory
224
+ gpus = [int(n) for n in gpus.split(',')]
225
+
226
+ print(f'easymode segment\n'
227
+ f'feature: {feature}\n'
228
+ f'data_directory: {data_directory}\n'
229
+ f'output_directory: {output_directory}\n'
230
+ f'output_format: {data_format}\n'
231
+ f'gpus: {gpus}\n'
232
+ f'tta: {tta}\n'
233
+ f'overwrite: {overwrite}\n'
234
+ f'batch_size: {batch_size}\n')
235
+
236
+ tomograms = sorted(glob.glob(os.path.join(data_directory, '*.mrc')))
237
+
238
+ model_path = cache_model(feature)
239
+
240
+ os.makedirs(output_directory, exist_ok=True)
241
+
242
+ processes = []
243
+ for gpu in gpus:
244
+ p = multiprocessing.Process(target=segmentation_thread,
245
+ args=(tomograms, model_path, feature, output_directory, gpu, batch_size, tta, overwrite, data_format))
246
+ processes.append(p)
247
+ p.start()
248
+ time.sleep(2)
249
+
250
+ for p in processes:
251
+ p.join()
252
+
253
+
254
+
255
+
256
+
257
+