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.
@@ -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