Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

#!/usr/bin/env python3 

 

from pprint import pprint 

 

def load_experiment(path): 

    import yaml, logging, re, fcsparser 

    from pathlib import Path 

 

    path = Path(path) 

    with path.open() as file: 

        documents = [x for x in yaml.load_all(file)] 

 

    if not documents: 

        raise UsageError("'{}' is empty.".format(path)) 

 

    # Find the *.fcs data files relevant to this experiment.  If there is a  

    # document with a mapping called "plates:", treat the values as paths to  

    # data directories and the keys as names that can refer to the directories  

    # in the rest of the file.  If there is a document with an assignment  

    # called "plate:", treat it as the path to the only data directory that  

    # will be used in the rest of the file.  If no data directory is specified  

    # by either of these two mechanisms, try to infer a path from the name of  

    # the YAML file itself. 

 

    inferred_path = path.parent / path.stem 

 

    def clean_up_plate_document(): 

        if len(documents[0]) > 1: 

            raise UsageError("Too many fields in 'plates' document.") 

        del documents[0] 

 

 

    if 'plates' in documents[0]: 

        plates = {k: Path(v) for k,v in documents[0]['plates'].items()} 

        clean_up_plate_document() 

    elif 'plate' in documents[0]: 

        plates = {None: Path(documents[0]['plate'])} 

        clean_up_plate_document() 

    elif inferred_path.is_dir(): 

        plates = {None: inferred_path} 

    else: 

        raise UsageError("No plates specified.") 

 

    # Construct and fill in an experiment data structure.  Cache parsed data in  

    # the 'wells' directory.  Parsing a well is expensive because there is a  

    # lot of data associated with each one.  Furthermore, experiments can use  

    # some wells over and over while not using others at all.  All of this  

    # makes on-the-fly caching worth the effort. 

 

    wells = {} 

    experiment = [] 

 

    def load_well(name): 

        if name in wells: 

            return wells[name] 

 

        # Find the *.fcs file referenced by the given name. 

 

        match = re.match('^(?:(.+)/)?([A-H][0-9]+)$', name) 

        if not match: 

            raise UsageError("Can't parse well name: '{}'".format(name)) 

 

        plate, well = match.groups() 

        if plate not in plates: 

            raise UsageError( 

                    "Plate '{}' not defined.".format(plate) 

                    if plate is not None else 

                    "No default plate defined.") 

 

        plate_path = plates[plate] 

        well_paths = list(plate_path.glob('**/*_{}_*.fcs'.format(well))) 

        if len(well_paths) == 0: 

            raise UsageError("No *.fcs files found for well '{}'".format(name)) 

        if len(well_paths) > 1: 

            raise UsageError("Multiple *.fcs files found for well '{}'".format(name)) 

        well_path = well_paths[0] 

 

        # Load the cell data for the given well. 

 

        logging.info('Loading {}'.format(well_path.name)) 

        meta, wells[name] = fcsparser.parse(str(well_path)) 

        return wells[name] 

 

 

    for document in documents: 

        comparison = Comparison() 

        experiment.append(comparison) 

 

        # Set the label for the comparison. 

 

        try: 

            comparison.label = document['label'] 

        except KeyError: 

            raise UsageError("The following comparison is missing a label:\n\n{}".format(yaml.dump(document))) 

 

        # Set the channel for the comparison. 

 

        try: 

            comparison.channel = document['channel'] 

        except KeyError: 

            raise UsageError("The following comparison is missing a channel:\n\n{}".format(yaml.dump(document))) 

 

        # Set the well data for the comparison.  This requires converting the  

        # well names we were given into paths and parsing those files. 

 

        if 'wells' not in document: 

            raise UsageError("The following comparison doesn't have any wells:\n\n{}".format(yaml.dump(document))) 

 

        for well_type, well_names in document['wells'].items(): 

            comparison.wells[well_type] = [load_well(x) for x in well_names] 

 

    return experiment 

 

 

 

channel_abbreviations = { 

        'red': 'PE-Texas Red-A', 

        'green': 'FITC-A', 

} 

default_control_channels = { 

        'PE-Texas Red-A': 'FITC-A', 

        'FITC-A': 'PE-Texas Red-A', 

} 

 

 

class Comparison: 

 

    def __init__(self, label='', channel='', wells={}): 

        self.label = label 

        self._channel = channel 

        self._control_channel = None 

        self.wells = wells 

 

    def __repr__(self): 

        return 'Comparison(label="{0.label}", channel="{0.channel}")'.format(self) 

 

    @property 

    def channel(self): 

        return channel_abbreviations.get(self._channel, self._channel) 

 

    @channel.setter 

    def channel(self, value): 

        self._channel = value 

 

    @property 

    def control_channel(self): 

        return default_control_channels.get( 

                self._control_channel, self._control_channel) 

 

    @control_channel.setter 

    def control_channel(self, value): 

        self._control_channel = value 

 

 

class UsageError (Exception): 

 

    def __init__(self, message): 

        super().__init__(message)