macsima2mc 1.2.3__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.
- macsima2mc-1.2.3/LICENSE +28 -0
- macsima2mc-1.2.3/PKG-INFO +64 -0
- macsima2mc-1.2.3/README.md +40 -0
- macsima2mc-1.2.3/macsima2mc/CLI.py +84 -0
- macsima2mc-1.2.3/macsima2mc/__init__.py +0 -0
- macsima2mc-1.2.3/macsima2mc/exceptions.py +92 -0
- macsima2mc-1.2.3/macsima2mc/illumination_corr.py +64 -0
- macsima2mc-1.2.3/macsima2mc/macsima2mc.py +50 -0
- macsima2mc-1.2.3/macsima2mc/mc_tools.py +110 -0
- macsima2mc-1.2.3/macsima2mc/ome_schema.py +179 -0
- macsima2mc-1.2.3/macsima2mc/ome_writer.py +30 -0
- macsima2mc-1.2.3/macsima2mc/qc.py +77 -0
- macsima2mc-1.2.3/macsima2mc/templates.py +65 -0
- macsima2mc-1.2.3/macsima2mc/tools.py +406 -0
- macsima2mc-1.2.3/macsima2mc.egg-info/PKG-INFO +64 -0
- macsima2mc-1.2.3/macsima2mc.egg-info/SOURCES.txt +20 -0
- macsima2mc-1.2.3/macsima2mc.egg-info/dependency_links.txt +1 -0
- macsima2mc-1.2.3/macsima2mc.egg-info/entry_points.txt +2 -0
- macsima2mc-1.2.3/macsima2mc.egg-info/requires.txt +7 -0
- macsima2mc-1.2.3/macsima2mc.egg-info/top_level.txt +1 -0
- macsima2mc-1.2.3/pyproject.toml +48 -0
- macsima2mc-1.2.3/setup.cfg +4 -0
macsima2mc-1.2.3/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Schapiro Lab
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: macsima2mc
|
|
3
|
+
Version: 1.2.3
|
|
4
|
+
Summary: stages MACSima tiles for registration with ASHLAR in MCMICRO.
|
|
5
|
+
Author-email: Victor Perez <hhabraxas@gmail.com>, "Miguel A. Ibarra-Arellano" <c180l058j@mozmail.com>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/SchapiroLabor/macsima2mc
|
|
8
|
+
Project-URL: Issues, https://github.com/SchapiroLabor/macsima2mc/issues
|
|
9
|
+
Keywords: mcmicro,multiplex imaging,proteomics,macsima,staging
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: ome-types==0.5.2
|
|
18
|
+
Requires-Dist: numpy
|
|
19
|
+
Requires-Dist: tifffile
|
|
20
|
+
Requires-Dist: pandas
|
|
21
|
+
Requires-Dist: beautifulsoup4
|
|
22
|
+
Requires-Dist: scikit-image
|
|
23
|
+
Requires-Dist: BaSiCPy
|
|
24
|
+
|
|
25
|
+
# Staging module for Miltenyi - MACSIMA to MCMICRO
|
|
26
|
+
|
|
27
|
+
The macsima2mc.py script stages MACSima data sets for being registerd with ASHLAR in MCMICRO.
|
|
28
|
+
|
|
29
|
+
The script takes as main input the path to the cycle folder that cointains the raw tiles generated by the Miltenyi-MACSima platform. macsima2mc.py reads these tiles and distributes them into acquisition groups. i.e tiles with common rack, well, ROI and exposure levels will be written into an ome.tiff file with the necessary metadata for registration. To be more precise two such ome.tiff files will be generated per cycle, one corresponds to the background signal and the other to the markers signal.
|
|
30
|
+
|
|
31
|
+
## CLI
|
|
32
|
+
### Required arguments
|
|
33
|
+
| Argument|Long name|Type|Description|Default value|
|
|
34
|
+
|---------|---------|----|-----------|-------------|
|
|
35
|
+
| -i | string/path | --input | Absolute path to the the parent folder of the raw tiles, i.e. the cycle folder whose name follows the pattern X_Cycle_N,where N represents the cycle number | NA |
|
|
36
|
+
| -o | string/path | --output | Absolute path to the directory in which the outputs will be saved. If the output directory doesn't exist it will be created. | NA |
|
|
37
|
+
|
|
38
|
+
### Optional arguments
|
|
39
|
+
| Argument|Type|Long name| Description | Default value |
|
|
40
|
+
|---------|----|---------|-------------|---------------|
|
|
41
|
+
|-rm|string | --reference_marker | Name of the reference marker for registration|'DAPI'|
|
|
42
|
+
|-osd|string | --output_subdir | String specifying the name of the subfolder in which the staged images will be saved.|'raw'|
|
|
43
|
+
|-ic|boolean flag | --illumination_correction |Give this flag to apply illumination correction to all tiles, the illumination profiles are calculated with basicpy | FALSE |
|
|
44
|
+
|-he|boolean flag | --hi_exposure_only |Give this flag to extract only the set of images with the highest exposure time|FALSE|
|
|
45
|
+
|-rr|boolean flag | --remove_reference_marker |mark the removal of the reference marker ,e.g. DAPI, for all cycles except the first one |FALSE|
|
|
46
|
+
|-qc|boolean flag | --qc_metrics | measure features of contrast, intensity and sharpness of each tile in the cycle and appends them to a table |FALSE|
|
|
47
|
+
|-wt|boolean flag | --write_table | writes a table with the acquisition parameters, metadata and,if enabled, qc metrics of each tile. Table will be saved in --output/cycle_info |FALSE|
|
|
48
|
+
## Container usage
|
|
49
|
+
### download container:
|
|
50
|
+
- Docker
|
|
51
|
+
```
|
|
52
|
+
docker pull ghcr.io/schapirolabor/macsima2mc:v1.1.1
|
|
53
|
+
```
|
|
54
|
+
- Singularity
|
|
55
|
+
```
|
|
56
|
+
singularity pull docker://ghcr.io/schapirolabor/macsima2mc:v1.1.1
|
|
57
|
+
```
|
|
58
|
+
### Script execution
|
|
59
|
+
|
|
60
|
+
- Singularity
|
|
61
|
+
``` bash
|
|
62
|
+
singularity exec --bind $path_to_your_local_cycle_folder:/mnt,$path_to_your_local_output_folder:/media --no-home $path_to_container python staging/macsima2mc/macsima2mc.py -i /mnt/$path_to_your_local_cycle_folder -o /media/$path_to_your_local_output_folder
|
|
63
|
+
```
|
|
64
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Staging module for Miltenyi - MACSIMA to MCMICRO
|
|
2
|
+
|
|
3
|
+
The macsima2mc.py script stages MACSima data sets for being registerd with ASHLAR in MCMICRO.
|
|
4
|
+
|
|
5
|
+
The script takes as main input the path to the cycle folder that cointains the raw tiles generated by the Miltenyi-MACSima platform. macsima2mc.py reads these tiles and distributes them into acquisition groups. i.e tiles with common rack, well, ROI and exposure levels will be written into an ome.tiff file with the necessary metadata for registration. To be more precise two such ome.tiff files will be generated per cycle, one corresponds to the background signal and the other to the markers signal.
|
|
6
|
+
|
|
7
|
+
## CLI
|
|
8
|
+
### Required arguments
|
|
9
|
+
| Argument|Long name|Type|Description|Default value|
|
|
10
|
+
|---------|---------|----|-----------|-------------|
|
|
11
|
+
| -i | string/path | --input | Absolute path to the the parent folder of the raw tiles, i.e. the cycle folder whose name follows the pattern X_Cycle_N,where N represents the cycle number | NA |
|
|
12
|
+
| -o | string/path | --output | Absolute path to the directory in which the outputs will be saved. If the output directory doesn't exist it will be created. | NA |
|
|
13
|
+
|
|
14
|
+
### Optional arguments
|
|
15
|
+
| Argument|Type|Long name| Description | Default value |
|
|
16
|
+
|---------|----|---------|-------------|---------------|
|
|
17
|
+
|-rm|string | --reference_marker | Name of the reference marker for registration|'DAPI'|
|
|
18
|
+
|-osd|string | --output_subdir | String specifying the name of the subfolder in which the staged images will be saved.|'raw'|
|
|
19
|
+
|-ic|boolean flag | --illumination_correction |Give this flag to apply illumination correction to all tiles, the illumination profiles are calculated with basicpy | FALSE |
|
|
20
|
+
|-he|boolean flag | --hi_exposure_only |Give this flag to extract only the set of images with the highest exposure time|FALSE|
|
|
21
|
+
|-rr|boolean flag | --remove_reference_marker |mark the removal of the reference marker ,e.g. DAPI, for all cycles except the first one |FALSE|
|
|
22
|
+
|-qc|boolean flag | --qc_metrics | measure features of contrast, intensity and sharpness of each tile in the cycle and appends them to a table |FALSE|
|
|
23
|
+
|-wt|boolean flag | --write_table | writes a table with the acquisition parameters, metadata and,if enabled, qc metrics of each tile. Table will be saved in --output/cycle_info |FALSE|
|
|
24
|
+
## Container usage
|
|
25
|
+
### download container:
|
|
26
|
+
- Docker
|
|
27
|
+
```
|
|
28
|
+
docker pull ghcr.io/schapirolabor/macsima2mc:v1.1.1
|
|
29
|
+
```
|
|
30
|
+
- Singularity
|
|
31
|
+
```
|
|
32
|
+
singularity pull docker://ghcr.io/schapirolabor/macsima2mc:v1.1.1
|
|
33
|
+
```
|
|
34
|
+
### Script execution
|
|
35
|
+
|
|
36
|
+
- Singularity
|
|
37
|
+
``` bash
|
|
38
|
+
singularity exec --bind $path_to_your_local_cycle_folder:/mnt,$path_to_your_local_output_folder:/media --no-home $path_to_container python staging/macsima2mc/macsima2mc.py -i /mnt/$path_to_your_local_cycle_folder -o /media/$path_to_your_local_output_folder
|
|
39
|
+
```
|
|
40
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import pathlib
|
|
3
|
+
|
|
4
|
+
#---CLI-BLOCK---#
|
|
5
|
+
def get_args():
|
|
6
|
+
"""
|
|
7
|
+
This function parses the command line arguments and returns them as a namespace object.
|
|
8
|
+
|
|
9
|
+
returns: namespace object with the arguments.
|
|
10
|
+
"""
|
|
11
|
+
parser=argparse.ArgumentParser()
|
|
12
|
+
|
|
13
|
+
#Mandatory arguments
|
|
14
|
+
parser.add_argument('-i',
|
|
15
|
+
'--input',
|
|
16
|
+
required=True,
|
|
17
|
+
type=pathlib.Path,
|
|
18
|
+
help='Path to the cycle folder'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
parser.add_argument('-o',
|
|
22
|
+
'--output',
|
|
23
|
+
required=True,
|
|
24
|
+
type=pathlib.Path,
|
|
25
|
+
help='Path where the stacks will be saved. If directory does not exist it will be created.'
|
|
26
|
+
)
|
|
27
|
+
#Optional arguments
|
|
28
|
+
parser.add_argument('-rm',
|
|
29
|
+
'--reference_marker',
|
|
30
|
+
default='DAPI',
|
|
31
|
+
help='string specifying the name of the reference marker'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
parser.add_argument('-osd',
|
|
35
|
+
'--output_subdir',
|
|
36
|
+
default='raw',
|
|
37
|
+
help='string specifying the name of the subfolder in which the staged images will be saved'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
parser.add_argument('-ic',
|
|
41
|
+
'--illumination_correction',
|
|
42
|
+
action='store_true',
|
|
43
|
+
help='Applies illumination correction to all tiles, the illumination profiles are created with basicpy'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
parser.add_argument('-he',
|
|
48
|
+
'--hi_exposure_only',
|
|
49
|
+
action='store_true',
|
|
50
|
+
help='Activate this flag to extract only the set of images with the highest exposure time.'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
parser.add_argument('-rr',
|
|
54
|
+
'--remove_reference_marker',
|
|
55
|
+
action='store_true',
|
|
56
|
+
help='It will mark the removal of the reference markers in the markers.csv file except for the first cycle. Use this when you \
|
|
57
|
+
dont want to keep e.g. the DAPI images of the other cycles.'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
parser.add_argument('-qc',
|
|
61
|
+
'--qc_metrics',
|
|
62
|
+
action='store_true',
|
|
63
|
+
help='measure features of contrast, intensity and sharpness of each image'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
parser.add_argument('-oqc',
|
|
67
|
+
'--only_qc_file',
|
|
68
|
+
action='store_true',
|
|
69
|
+
help='skips the stacking of the tiles and only calculates the qc table. Still flag -wt is required to write the table. '
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
parser.add_argument('-wt',
|
|
73
|
+
'--write_table',
|
|
74
|
+
action='store_true',
|
|
75
|
+
help='writes a table in --output/cycle_info. Content of table is acquisition parameters, metadata and, if enabled, qc metrics of each tile'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
args=parser.parse_args()
|
|
79
|
+
|
|
80
|
+
return args
|
|
81
|
+
#---END_CLI-BLOCK---#
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def any_ref(mf_tuple,
|
|
6
|
+
ref_marker='DAPI'):
|
|
7
|
+
"""
|
|
8
|
+
This function checks if the reference marker is present in the mf_tuple.
|
|
9
|
+
Args:
|
|
10
|
+
mf_tuple (tuple): tuple with the markers and filters
|
|
11
|
+
ref_marker (str): reference marker used for registration
|
|
12
|
+
Returns:
|
|
13
|
+
bool: True if the reference marker is present in the mf_tuple, False otherwise.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
exist_ref = False
|
|
17
|
+
for m in mf_tuple:
|
|
18
|
+
if m[0] == ref_marker:
|
|
19
|
+
exist_ref = True
|
|
20
|
+
break
|
|
21
|
+
return exist_ref
|
|
22
|
+
|
|
23
|
+
def miss_channel(chann_index,chans_meta,conformed_markers):
|
|
24
|
+
|
|
25
|
+
if chans_meta[ chann_index ].size:
|
|
26
|
+
pass
|
|
27
|
+
else:
|
|
28
|
+
|
|
29
|
+
ref_index=np.argwhere([meta.size > 0 for meta in chans_meta ]).flatten()[0]
|
|
30
|
+
chann_meta=chans_meta[ ref_index ]
|
|
31
|
+
edit_feats={'marker':conformed_markers[ chann_index ][0],
|
|
32
|
+
'filter':conformed_markers[ chann_index ][1]}
|
|
33
|
+
#'exposure?':}
|
|
34
|
+
#how are conformed markers formed?
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def at_roi(grouped,dimensions,ref_marker):
|
|
40
|
+
#for dimensions is expected that the last element of the list is the exposure_level
|
|
41
|
+
rows=[]
|
|
42
|
+
for index,frame in grouped:
|
|
43
|
+
marker_filter_map = list( frame.groupby(["marker","filter"]).indices.keys() )
|
|
44
|
+
ref_exists=any_ref(marker_filter_map,ref_marker)
|
|
45
|
+
subst_exp_level=index[-1]
|
|
46
|
+
rows.append( list(index) + [ ref_exists, subst_exp_level ] )
|
|
47
|
+
|
|
48
|
+
df=pd.DataFrame(rows,columns=dimensions+['ref_marker','aux_exp_level'])
|
|
49
|
+
|
|
50
|
+
frames=[]
|
|
51
|
+
for index,frame in df.groupby(dimensions[:-1]):
|
|
52
|
+
substitute=frame.loc[ frame['ref_marker']==True,'exposure_level'].max()
|
|
53
|
+
frame['aux_exp_level']=frame['aux_exp_level'].where(frame['ref_marker'],substitute)
|
|
54
|
+
frames.append(frame)
|
|
55
|
+
|
|
56
|
+
return pd.concat(frames)
|
|
57
|
+
|
|
58
|
+
def at_tile(group,marker_filter_map):
|
|
59
|
+
rows=[]
|
|
60
|
+
for tile_id,frame in group.groupby(['tile']):
|
|
61
|
+
aux_array = [ frame.loc[ (frame['marker']==marker) & (frame['filter']==filt)].size
|
|
62
|
+
for marker, filt in marker_filter_map
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
aux_array =np.array(aux_array)==0
|
|
66
|
+
missing_channel=np.any(aux_array)
|
|
67
|
+
|
|
68
|
+
if missing_channel:
|
|
69
|
+
channel_indices=np.argwhere(aux_array).flatten()
|
|
70
|
+
#Each missing (marker,filter) pair will be separated by a colon ":", i.e. marker1,filter1:marker2,filter2
|
|
71
|
+
channel= ':'.join( [ ','.join(marker_filter_map[i]) for i in channel_indices] )
|
|
72
|
+
else:
|
|
73
|
+
channel=''
|
|
74
|
+
|
|
75
|
+
qc_data=[tile_id[0],missing_channel,channel,tile_id[0]]
|
|
76
|
+
cols=['tile','missing_ch','channels','aux_tile']
|
|
77
|
+
rows.append( qc_data )
|
|
78
|
+
|
|
79
|
+
df=pd.DataFrame(rows,columns=cols)
|
|
80
|
+
auxiliary_tile_index=df.loc[df['missing_ch']==False ][['tile']].values[0]
|
|
81
|
+
df.loc[ df['missing_ch']==True, ['aux_tile']]=auxiliary_tile_index
|
|
82
|
+
|
|
83
|
+
return df
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from basicpy import BaSiC
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def indices_per_channel(total_imgs, no_of_channels):
|
|
5
|
+
"""
|
|
6
|
+
This function creates a list of lists with the indices of the images in the stack per channel.
|
|
7
|
+
Args:
|
|
8
|
+
total_imgs (int): total number of images in the stack.
|
|
9
|
+
no_of_channels (int): number of channels.
|
|
10
|
+
Returns:
|
|
11
|
+
list: list of lists with the indices of the images in the stack per channel.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
#total_imgs in the stack
|
|
15
|
+
img_indices = [list(range(ch,total_imgs,no_of_channels)) for ch in range(0, no_of_channels)]
|
|
16
|
+
return img_indices
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def extract_channel_imgs (stack,indices):
|
|
20
|
+
"""
|
|
21
|
+
This function extracts the images in the stack per channel.
|
|
22
|
+
Args:
|
|
23
|
+
stack (np.array): stack of images.
|
|
24
|
+
indices (list): list of indices of the images in the stack per channel.
|
|
25
|
+
Returns:
|
|
26
|
+
np.array: stack of images per channel.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
return stack[indices,:,:]
|
|
30
|
+
|
|
31
|
+
def filter_out_blanks(channel_tiles):
|
|
32
|
+
|
|
33
|
+
signal_indices=[index for index in range(0,channel_tiles.shape[0]) if np.sum(channel_tiles[index,:,:])>0 ]
|
|
34
|
+
filtered_tiles=channel_tiles[signal_indices,:,:]
|
|
35
|
+
return filtered_tiles
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def apply_corr(uncorr_stack,no_of_channels):
|
|
40
|
+
"""
|
|
41
|
+
This function applies the BaSiC algorithm to correct the illumination in the images.
|
|
42
|
+
Args:
|
|
43
|
+
uncorr_stack (np.array): stack of uncorrected images.
|
|
44
|
+
no_of_channels (int): number of channels.
|
|
45
|
+
Returns:
|
|
46
|
+
np.array: stack of corrected images.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
corr_stack = np.zeros( uncorr_stack.shape,dtype=uncorr_stack.dtype )
|
|
50
|
+
total_imgs = uncorr_stack.shape[0]
|
|
51
|
+
indices = indices_per_channel(total_imgs, no_of_channels)
|
|
52
|
+
basic = BaSiC(get_darkfield=False, smoothness_flatfield=1.0, fitting_mode="approximate", sort_intensity=True)
|
|
53
|
+
|
|
54
|
+
for ind_list in indices:
|
|
55
|
+
uncorr_imgs = extract_channel_imgs(uncorr_stack, ind_list)
|
|
56
|
+
imgs_for_ffp=filter_out_blanks(uncorr_imgs)
|
|
57
|
+
#basic.fit(uncorr_imgs)
|
|
58
|
+
basic.fit(imgs_for_ffp)
|
|
59
|
+
ffp = basic.flatfield
|
|
60
|
+
corr_stack[ind_list,:,:] = np.uint16(np.clip(uncorr_imgs.astype(float) / ffp, 0, 65535))
|
|
61
|
+
|
|
62
|
+
return corr_stack
|
|
63
|
+
|
|
64
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import tools
|
|
2
|
+
from templates import macsima_pattern
|
|
3
|
+
import CLI
|
|
4
|
+
import mc_tools
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
# Get arguments
|
|
8
|
+
args = CLI.get_args()
|
|
9
|
+
|
|
10
|
+
# Assign arguments to variables
|
|
11
|
+
input = args.input
|
|
12
|
+
output = args.output
|
|
13
|
+
ref = args.reference_marker
|
|
14
|
+
basicpy_corr = args.illumination_correction
|
|
15
|
+
out_folder_name = args.output_subdir
|
|
16
|
+
|
|
17
|
+
# Extract acquisition info of the cycle from file name,e.g. rack,well,markers,filters, etc.
|
|
18
|
+
cycle_info = tools.cycle_info(input, macsima_pattern(version=2), ref_marker= ref)
|
|
19
|
+
|
|
20
|
+
# Extract and append ome metadata info contained in each file
|
|
21
|
+
cycle_info = tools.append_metadata( cycle_info )
|
|
22
|
+
|
|
23
|
+
# Create stack
|
|
24
|
+
cycle_number=int(cycle_info['cycle'].unique()[0])
|
|
25
|
+
if args.only_qc_file:
|
|
26
|
+
pass
|
|
27
|
+
else:
|
|
28
|
+
output_dirs = tools.create_stack(cycle_info,
|
|
29
|
+
output,
|
|
30
|
+
ref_marker=ref,
|
|
31
|
+
hi_exp=args.hi_exposure_only,
|
|
32
|
+
ill_corr=basicpy_corr,
|
|
33
|
+
out_folder=out_folder_name)
|
|
34
|
+
# Save markers file in each output directory
|
|
35
|
+
for path in output_dirs:
|
|
36
|
+
mc_tools.write_markers_file(path,args.remove_reference_marker)
|
|
37
|
+
|
|
38
|
+
# Calculate and append ome metadata info contained in each file
|
|
39
|
+
if (args.qc_metrics or args.only_qc_file) :
|
|
40
|
+
import qc
|
|
41
|
+
cycle_info=qc.append_qc(cycle_info)
|
|
42
|
+
|
|
43
|
+
if args.write_table:
|
|
44
|
+
qc_output_dir=output / "cycle_info"
|
|
45
|
+
qc_output_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
cycle_info.to_csv( qc_output_dir / 'cycle_{c}.csv'.format( c=f'{ cycle_number:03d}' ), index=False )
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
main()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from tools import extract_values
|
|
3
|
+
import re
|
|
4
|
+
import numpy as np
|
|
5
|
+
from ome_types import from_tiff
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def flatten_list(lol):
|
|
11
|
+
"""
|
|
12
|
+
This function flattens a list of lists.
|
|
13
|
+
Args:
|
|
14
|
+
lol (list): list of lists.
|
|
15
|
+
Returns:
|
|
16
|
+
list: flattened list.
|
|
17
|
+
"""
|
|
18
|
+
return [x for xs in lol for x in xs]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def markers_file():
|
|
22
|
+
"""
|
|
23
|
+
This function creates a dictionary with the columns of the markers.csv file.
|
|
24
|
+
Returns:
|
|
25
|
+
dict: dictionary with the columns of the markers.csv file.
|
|
26
|
+
"""
|
|
27
|
+
columns = {'channel_number': [],
|
|
28
|
+
'cycle_number': [],
|
|
29
|
+
'marker_name':[],
|
|
30
|
+
'Filter':[],
|
|
31
|
+
'background':[],
|
|
32
|
+
'exposure':[],
|
|
33
|
+
'remove':[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return columns
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_patterns():
|
|
40
|
+
"""
|
|
41
|
+
Dictionary of tuples where first element indicates the pattern to search and
|
|
42
|
+
second element is a boolean that indicates whether to transform the search output into a value or not
|
|
43
|
+
the keys of this dictionary should be a subset of the keys in the markers_file function.
|
|
44
|
+
Returns:
|
|
45
|
+
dict: dictionary with regular expressions to extract metadata from macsima filenames.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
patterns = {
|
|
49
|
+
'cycle_number': (r"cycle-(.*?)-", True),
|
|
50
|
+
'marker_name' : (r"markers-(.*?)-filters",False),
|
|
51
|
+
'Filter' : (r"-filters-(.*?).ome",False),
|
|
52
|
+
'background' : (r"-src-(.*?)-" ,False)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return patterns
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def write_markers_file( data_path, rm_ref_marker,ref_marker='DAPI'):
|
|
59
|
+
"""
|
|
60
|
+
This function writes the markers.csv file.
|
|
61
|
+
Args:
|
|
62
|
+
data_path (Path): path to the folder containing the images.
|
|
63
|
+
rm_ref_marker(Boolean): mark reference markers for removal except for the first one
|
|
64
|
+
ref_marker (str): reference marker.
|
|
65
|
+
Returns:
|
|
66
|
+
dict: dictionary with the columns of the markers.csv file.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
img_paths = list(sorted( data_path.glob('*.tif*')))
|
|
70
|
+
mks_file = markers_file()
|
|
71
|
+
patt = get_patterns()
|
|
72
|
+
|
|
73
|
+
for img in img_paths:
|
|
74
|
+
img_name = [img.stem]
|
|
75
|
+
cycle_no = extract_values(patt['cycle_number'][0], img_name, number_cast=patt['cycle_number'][1])
|
|
76
|
+
background = extract_values(patt['background'][0], img_name, number_cast=patt['background'][1])
|
|
77
|
+
markers = extract_values(patt['marker_name'][0], img_name, number_cast=patt['marker_name'][1])[0].split('__')
|
|
78
|
+
filters = extract_values(patt['Filter'][0], img_name, number_cast=patt['Filter'][1])[0].split('__')
|
|
79
|
+
ome = from_tiff(img)
|
|
80
|
+
|
|
81
|
+
if background[0]=='B':
|
|
82
|
+
remove = len(markers)*['TRUE']
|
|
83
|
+
markers = ['bg_{c}_{m}-{f}'.format( c= f'{cycle_no[0]:03d}' , m=x , f=y ) for x,y in zip(markers,filters) ]
|
|
84
|
+
fmt_background = len(markers)*['']
|
|
85
|
+
else:
|
|
86
|
+
fmt_background = []
|
|
87
|
+
remove = len(markers)*['']
|
|
88
|
+
for x,y in zip(markers,filters):
|
|
89
|
+
if x == ref_marker:
|
|
90
|
+
fmt_background.append('')
|
|
91
|
+
else:
|
|
92
|
+
fmt_background.append(f'bg_{cycle_no[0]:03d}_{x}-{y}')
|
|
93
|
+
|
|
94
|
+
mks_file['cycle_number'].extend(len(markers)*cycle_no)
|
|
95
|
+
mks_file['marker_name'].extend(markers)
|
|
96
|
+
mks_file['Filter'].extend(filters)
|
|
97
|
+
mks_file['background'].extend(fmt_background)
|
|
98
|
+
mks_file['exposure'].extend([ome.images[0].pixels.planes[ch].exposure_time for ch,_ in enumerate(markers)])
|
|
99
|
+
mks_file['remove'].extend(remove)
|
|
100
|
+
|
|
101
|
+
mks_file['channel_number'] = list(range(1, 1 + len(mks_file['marker_name'])))
|
|
102
|
+
mks_file_df = DataFrame(mks_file)
|
|
103
|
+
if rm_ref_marker:
|
|
104
|
+
earliest_cycle=mks_file_df['cycle_number'].min()
|
|
105
|
+
condition=( mks_file_df['marker_name']== ref_marker ) & ( mks_file_df['cycle_number']>earliest_cycle )
|
|
106
|
+
mks_file_df.loc[ condition , ['remove'] ]='TRUE'
|
|
107
|
+
|
|
108
|
+
mks_file_df.to_csv( data_path.parent.absolute() / 'markers.csv' , index=False )
|
|
109
|
+
|
|
110
|
+
return mks_file
|