55Allows users to specify custom metadata applied via well mapping.
66"""
77
8+ import json
89import re
10+ import shutil
11+ import tempfile
912from pathlib import Path
10- from typing import Any , Dict , List , Optional , Union
13+ from typing import Any , Dict , Optional , Union
1114
12- # Support Python 3.7 by importing Literal from typing_extensions
13- try :
14- from typing import Literal # type: ignore
15- except ImportError :
16- from typing_extensions import Literal
17-
18- import json
1915import numpy as np
2016import pandas as pd
2117import py7zr
22- import shutil
23- import tempfile
24- import yaml
2518
26- from . import well_mapper , flow
19+ from . import flow
2720
2821
2922class YamlError (RuntimeError ):
3023 """Error raised when there is an issue with the provided .yaml file."""
3124
25+
3226class DataPathError (RuntimeError ):
3327 """Error raised when the path to the data is not specified correctly."""
3428
@@ -38,10 +32,10 @@ def load_ddpcr_metadata(unzipped_path: Path) -> Dict[Any, Any]:
3832 Load well metadata from an unzipped .ddpcr file.
3933
4034 Generates a metadata dict in the same format as the YAML well mapping,
41- i.e., key -> {well -> value}. The columns are a subset of the
42- metadata associated with each well in the BioRad software, namely
43- sample names (numbered 'Sample description' fields, returned as
44- numbered 'sample_description' keys) and targets for each channel/dye
35+ i.e., key -> {well -> value}. The columns are a subset of the
36+ metadata associated with each well in the BioRad software, namely
37+ sample names (numbered 'Sample description' fields, returned as
38+ numbered 'sample_description' keys) and targets for each channel/dye
4539 (returned as '[channel]_target' keys).
4640
4741 Parameters
@@ -54,36 +48,39 @@ def load_ddpcr_metadata(unzipped_path: Path) -> Dict[Any, Any]:
5448 A dictionary that contains a well mapping for metadata extracted from
5549 the .ddpcr experiment.
5650 """
57-
5851 filename_regex = r"^.*[\\/](?P<well>[A-P]\d+)\.dd.*json"
59-
60- # Create map of well index -> ID
52+
53+ # Create map of well index -> ID
6154 well_id_map = {}
62- for f in (unzipped_path / ' PeakMetaData' ).glob ("*.ddmetajson" ):
63- with open (f , 'r' ) as file :
55+ for f in (unzipped_path / " PeakMetaData" ).glob ("*.ddmetajson" ):
56+ with open (f , "r" ) as file :
6457 d = json .load (file )
65- well_id_map [d [' WellIndex' ]] = re .compile (filename_regex ).match (file .name ).group ("well" )
66-
58+ well_id_map [d [" WellIndex" ]] = re .compile (filename_regex ).match (file .name ).group ("well" )
59+
6760 # Get plate file name from last modified .ddplt file
68- plate_file = ''
61+ plate_file = ""
6962 last_mod_time = 0
7063 for f in unzipped_path .glob ("*.ddplt" ):
7164 mtime = f .stat ().st_mtime
7265 if mtime > last_mod_time :
7366 last_mod_time = mtime
7467 plate_file = f .name
75-
68+
7669 # Load metadata from plate file
7770 metadata_from_plt = {}
78- with open (unzipped_path / plate_file , 'r' ) as file :
71+ with open (unzipped_path / plate_file , "r" ) as file :
7972 f = json .load (file )
80- for w in f ['WellSamples' ]:
81- well = well_id_map [w ['WellIndex' ]]
82- condition_map = {f'sample_description_{ i + 1 } ' : val for i ,val in enumerate (w ['SampleIds' ])}
83- target_map = {p ['Dye' ]['DyeName' ]+ '_target' : p ['TargetName' ] for p in w ['Panel' ]['Targets' ]}
73+ for w in f ["WellSamples" ]:
74+ well = well_id_map [w ["WellIndex" ]]
75+ condition_map = {
76+ f"sample_description_{ i + 1 } " : val for i , val in enumerate (w ["SampleIds" ])
77+ }
78+ target_map = {
79+ p ["Dye" ]["DyeName" ] + "_target" : p ["TargetName" ] for p in w ["Panel" ]["Targets" ]
80+ }
8481 metadata_from_plt [well ] = condition_map | target_map
85-
86- metadata_map = pd .DataFrame .from_dict (metadata_from_plt , orient = ' index' ).to_dict ()
82+
83+ metadata_map = pd .DataFrame .from_dict (metadata_from_plt , orient = " index" ).to_dict ()
8784 return metadata_map
8885
8986
@@ -98,7 +95,7 @@ def load_ddpcr(
9895
9996 Generates a pandas DataFrame from a .ddpcr file, which is the
10097 file type for experiments on the BioRad QX100/QX200 machines.
101- Adds columns for metadata encoded by a given .yaml file.
98+ Adds columns for metadata encoded by a given .yaml file.
10299 Metadata is associated with the data based on well IDs extracted
103100 from the experiment data.
104101
@@ -111,9 +108,9 @@ def load_ddpcr(
111108 All metadata must be contained under the header 'metadata'.
112109 extract_metadata: bool, default True
113110 Whether to extract metadata from the .ddpcr file. If True,
114- adds a subset of the metadata associated with each well in the
111+ adds a subset of the metadata associated with each well in the
115112 BioRad software, namely sample names (numbered 'Sample description' fields,
116- returned as numbered 'condition' keys) and targets for each channel/dye
113+ returned as numbered 'condition' keys) and targets for each channel/dye
117114 (returned as '[channel]_target' keys).
118115
119116 Returns
@@ -123,12 +120,14 @@ def load_ddpcr(
123120 if not isinstance (data_path , Path ):
124121 data_path = Path (data_path )
125122
126- if data_path .suffix != ' .ddpcr' :
123+ if data_path .suffix != " .ddpcr" :
127124 raise DataPathError ("'data_path' must be a .ddpcr file." )
128-
125+
129126 # Unzip .ddpcr file
130127 tmp_data_path = Path (tempfile .mkdtemp ())
131- with py7zr .SevenZipFile (data_path , 'r' , password = '1b53402e-503a-4303-bf86-71af1f3178dd' ) as experiment :
128+ with py7zr .SevenZipFile (
129+ data_path , "r" , password = "1b53402e-503a-4303-bf86-71af1f3178dd"
130+ ) as experiment :
132131 experiment .extractall (path = tmp_data_path )
133132
134133 metadata_map = {}
@@ -139,26 +138,29 @@ def load_ddpcr(
139138 metadata_map = flow .load_well_metadata (yaml_path )
140139 except FileNotFoundError as err :
141140 raise YamlError ("Specified metadata YAML file does not exist!" ) from err
142-
141+
143142 # Load metadata from .ddpcr file
144143 if extract_metadata :
145144 metadata_map = metadata_map | load_ddpcr_metadata (tmp_data_path )
146145
147146 # Load data for each well
148147 data_list = []
149- for f in (tmp_data_path / ' PeakData' ).glob ("*.ddpeakjson" ):
150- with open (f , 'r' ) as file :
148+ for f in (tmp_data_path / " PeakData" ).glob ("*.ddpeakjson" ):
149+ with open (f , "r" ) as file :
151150 d = json .load (file )
152151
153152 # Ignore wells for which no data was collected
154- if not d ["DataAcquisitionInfo" ]['WasAcquired' ]: continue
153+ if not d ["DataAcquisitionInfo" ]["WasAcquired" ]:
154+ continue
155155
156156 # Extract raw data (channel amplitude) and channel names
157- channel_map = {c ['Channel' ]- 1 : c ['Dye' ] for c in d ["DataAcquisitionInfo" ]['ChannelMap' ]}
158- df = pd .DataFrame (np .transpose (d ['PeakInfo' ]['Amplitudes' ])).rename (columns = channel_map )
157+ channel_map = {
158+ c ["Channel" ] - 1 : c ["Dye" ] for c in d ["DataAcquisitionInfo" ]["ChannelMap" ]
159+ }
160+ df = pd .DataFrame (np .transpose (d ["PeakInfo" ]["Amplitudes" ])).rename (columns = channel_map )
159161
160162 well = f .stem
161- df .insert (0 , ' well' , [well ]* len (df ))
163+ df .insert (0 , " well" , [well ] * len (df ))
162164
163165 # Add metadata to DataFrame
164166 index = 0
@@ -170,10 +172,13 @@ def load_ddpcr(
170172 data_list .append (df )
171173
172174 # Fill empty values with <NA> and drop empty columns
173- data = pd .concat (data_list , ignore_index = True ).replace ([float ('nan' ), np .nan , '' ], pd .NA ).dropna (axis = 'columns' , how = 'all' )
175+ data = (
176+ pd .concat (data_list , ignore_index = True )
177+ .replace ([float ("nan" ), np .nan , "" ], pd .NA )
178+ .dropna (axis = "columns" , how = "all" )
179+ )
174180
175181 # Delete unzipped files
176182 shutil .rmtree (tmp_data_path )
177-
178- return data
179183
184+ return data
0 commit comments