Package bmrblib :: Module base_classes
[hide private]
[frames] | no frames]

Source Code for Module bmrblib.base_classes

  1  ############################################################################# 
  2  #                                                                           # 
  3  # The BMRB library.                                                         # 
  4  #                                                                           # 
  5  # Copyright (C) 2009-2012 Edward d'Auvergne                                 # 
  6  #                                                                           # 
  7  # This program is free software: you can redistribute it and/or modify      # 
  8  # it under the terms of the GNU General Public License as published by      # 
  9  # the Free Software Foundation, either version 3 of the License, or         # 
 10  # (at your option) any later version.                                       # 
 11  #                                                                           # 
 12  # This program is distributed in the hope that it will be useful,           # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of            # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             # 
 15  # GNU General Public License for more details.                              # 
 16  #                                                                           # 
 17  # You should have received a copy of the GNU General Public License         # 
 18  # along with this program.  If not, see <http://www.gnu.org/licenses/>.     # 
 19  #                                                                           # 
 20  ############################################################################# 
 21   
 22  # Module docstring. 
 23  """The TagCategory base class.""" 
 24   
 25  # Python module imports. 
 26  from numpy import float64, ndarray, zeros 
 27  from warnings import warn 
 28   
 29  # Bmrblib module imports. 
 30  from bmrblib.misc import no_missing, translate 
 31  from bmrblib.pystarlib.SaveFrame import SaveFrame 
 32  from bmrblib.pystarlib.TagTable import TagTable 
 33  from bmrblib.version import Star_version; version = Star_version() 
 34   
 35   
36 -class BaseSaveframe:
37 """The base class for the saveframe classes.""" 38
39 - def __init__(self, datanodes):
40 """Initialise the class, placing the pystarlib data nodes into the namespace. 41 42 @param datanodes: The pystarlib data nodes object. 43 @type datanodes: list 44 """ 45 46 # Place the data nodes into the namespace. 47 self.datanodes = datanodes 48 49 # The saveframe counter. 50 self.count = 0 51 52 # Add the specific tag category objects. 53 self.tag_categories = CategoryList() 54 self.add_tag_categories()
55 56
57 - def add(self, **keywords):
58 """Add data to the saveframe. 59 60 If the keywords are within the tag dictionary structure as the variable name, then the data will be checked, translated and stored in that variable. If not, then a warning will be given. 61 62 @return: The saveframe count. 63 @rtype: int 64 """ 65 66 # Reset all data structures. 67 self.reset() 68 69 # First set default values. 70 for cat in self.tag_categories: 71 # Loop over the keys. 72 for key in cat._key_list: 73 # No variable or no default value. 74 if not cat[key].var_name or not cat[key].default: 75 continue 76 77 # Set the default. 78 setattr(self, cat[key].var_name, translate(cat[key].default)) 79 80 # Loop over the keywords. 81 for name, val in keywords.items(): 82 # Get the tag object. 83 info = self.tag_categories.get_tag(name) 84 85 # No corresponding tag, so set as a class instance variable and move to the next keyword. 86 if not info: 87 setattr(self, name, val) 88 continue 89 90 # Unpack. 91 cat_index, key, obj = info 92 93 # Check that a value has been supplied. 94 if not obj.missing: 95 no_missing(val, name) 96 97 # Check that the value is allowed. 98 if obj.allowed != None: 99 # List argument. 100 if not (isinstance(val, list) and not isinstance(val, ndarray)): 101 val_list = [val] 102 else: 103 val_list = val 104 105 # Loop over the list. 106 for i in range(len(val_list)): 107 if val_list[i] not in obj.allowed: 108 raise NameError("The %s keyword argument of '%s' must be one of %s." % (name, val_list[i], obj.allowed)) 109 110 # Length check of the non-free tag category elements (must be the same). 111 if (isinstance(val, list) or isinstance(val, ndarray)): 112 # Get the reference length. 113 N = self.tag_categories[cat_index]._N() 114 115 # No length yet. 116 if N == None: 117 pass 118 119 # Mismatch. 120 if N != None and len(val) != N: 121 raise NameError("The number of elements in the %s keyword argument should be N = %s." % (name, N)) 122 123 # Store the argument. 124 setattr(self, name, translate(val)) 125 126 # Saveframe counter updating. 127 self.count = self.count + 1 128 self.count_str = str(self.count) 129 130 # The data ID values. 131 for i in range(len(self.tag_categories)): 132 ids = self.tag_categories[i].generate_data_ids() 133 if ids: 134 self.data_ids = translate(ids) 135 136 # If needed, perform some saveframe specific operations. 137 self.pre_ops() 138 139 # Initialise the save frame. 140 self.frame = SaveFrame(title=self.create_title()) 141 142 # Create the tag categories. 143 for i in range(len(self.tag_categories)): 144 self.tag_categories[i].create() 145 146 # Add the saveframe to the data nodes. 147 self.datanodes.append(self.frame) 148 149 # Return the saveframe count. 150 return self.count
151 152
153 - def create_title(self):
154 """Create the saveframe title. 155 156 @return: The title. 157 @rtype: str 158 """ 159 160 # Build and return the title. 161 if hasattr(self, 'label'): 162 return self.label + '_' + self.count_str 163 else: 164 return self.sf_label + '_' + self.count_str
165 166
167 - def extract_data(self, datanode):
168 """Read all the tags from the datanodes. 169 170 @keyword datanode: The datanode. 171 @type datanode: Datanode instance 172 @return: The data. 173 @rtype: tuple 174 """ 175 176 # Find the mapping between the tag categories of the NMR-STAR file and the bmrblib class. 177 mapping = self.find_mapping(datanode) 178 179 # Loop over the mapping. 180 for i in range(len(mapping)): 181 # The tag category is not present in the file. 182 if mapping[i] == None: 183 continue 184 185 # Extract the data. 186 self.tag_categories[mapping[i]].extract_tag_data(datanode.tagtables[i]) 187 188 # Add the framecode for v2.1 files. 189 if version.major == 2: 190 self.sf_framecode = datanode.title
191 192
193 - def find_mapping(self, datanode):
194 """Determine the mapping between the tag categories of the NMR-STAR file and the bmrblib class. 195 196 @keyword datanode: The datanode. 197 @type datanode: Datanode instance 198 @return: The mapping structure. 199 @rtype: list 200 """ 201 202 # Init. 203 N = len(self.tag_categories) 204 M = len(datanode.tagtables) 205 counts = zeros((M, N), float64) 206 mapping = [] 207 208 # Count the tag name matches. 209 for table_ind in range(M): 210 for cat_ind in range(N): 211 # Alias. 212 cat = self.tag_categories[cat_ind] 213 table = datanode.tagtables[table_ind] 214 215 # Loop over the tags. 216 for key in cat.keys(): 217 for name in table.tagnames: 218 # Check for a match. 219 if name == cat[key].tag_name_full(): 220 counts[table_ind, cat_ind] += 1 221 222 # The index of the maximum count. 223 if not counts[table_ind].sum(): 224 index = None 225 else: 226 index = counts[table_ind].tolist().index(counts[table_ind].max()) 227 mapping.append(index) 228 229 # Return the mapping. 230 return mapping
231 232
233 - def loop(self):
234 """Loop over the saveframes, yielding the data. 235 236 @return: The saveframe data. 237 @rtype: tuple 238 """ 239 240 # Set up the tag information. 241 for i in range(len(self.tag_categories)): 242 self.tag_categories[i].tag_setup() 243 244 # Get the saveframe name. 245 sf_name = getattr(self, 'sf_label') 246 247 # Loop over all datanodes. 248 for datanode in self.datanodes: 249 # Find the saveframes via the SfCategory tag index. 250 found = False 251 for index in range(len(datanode.tagtables[0].tagnames)): 252 # First match the tag names. 253 if datanode.tagtables[0].tagnames[index] == self.tag_categories[0]['SfCategory'].tag_name_full(): 254 # Then the tag value. 255 if datanode.tagtables[0].tagvalues[index][0] == sf_name: 256 found = True 257 break 258 259 # Skip the datanode. 260 if not found: 261 continue 262 263 # Extract the information. 264 self.extract_data(datanode) 265 266 # Return the saveframe info. 267 yield self.read()
268 269
270 - def pre_ops(self):
271 """A dummy method for performing no saveframe specific operations prior to XML creation."""
272 273
274 - def read(self):
275 """Read all the data from the saveframe. 276 277 @return: A dictionary of all the data. 278 @rtype: dict 279 """ 280 281 # Init. 282 data = {} 283 284 # Loop over the tag categories. 285 for cat in self.tag_categories: 286 # Loop over the keys. 287 for key in cat._key_list: 288 # No variable. 289 if not cat[key].var_name: 290 continue 291 292 # No object. 293 if not hasattr(self, cat[key].var_name): 294 obj = None 295 296 # Get the object. 297 else: 298 obj = getattr(self, cat[key].var_name) 299 300 # Package the translated data. 301 data[cat[key].var_name] = translate(obj, format=cat[key].format, reverse=True) 302 303 # Return the data. 304 return data
305 306
307 - def reset(self):
308 """Reset all data structures to None.""" 309 310 # Loop over the tag categories. 311 for cat in self.tag_categories: 312 # Loop over the keys. 313 for key in cat._key_list: 314 # No variable. 315 if not cat[key].var_name or not hasattr(self, cat[key].var_name): 316 continue 317 318 # Skip special variables. 319 if cat[key].var_name in ['sf_label', 'count', 'count_str']: 320 continue 321 322 # Set to None. 323 setattr(self, cat[key].var_name, translate(None))
324 325
326 -class MissingSaveframe:
327 """Special class for when BMRB saveframes are non-existent in certain NMR-STAR versions.""" 328
329 - def __init__(self, name):
330 """Initialise the special class. 331 332 @param name: The name of the missing Saveframe. 333 @type name: str 334 """ 335 336 # Store the arg. 337 self.name = name
338 339
340 - def add(self, *args, **keywords):
341 """Special function for giving a warning.""" 342 343 # The warning. 344 warn(Warning("The %s saveframe does not exist in this NMR-STAR version." % self.name))
345 346
347 - def loop(self):
348 """Special function for giving a warning.""" 349 350 # The warning. 351 warn(Warning("The %s saveframe does not exist in this NMR-STAR version." % self.name)) 352 353 # Yield nothing. 354 yield None
355 356 357 358
359 -class CategoryList(list):
360 """A special lits object for holding the different saveframe tag categories.""" 361
362 - def get_tag(self, var_name):
363 """Return the tag object possessing the given variable name. 364 365 @param var_name: The variable name. 366 @type var_name: str 367 @return: The key and tag objects. 368 @rtype: str, TagObject instance 369 """ 370 371 # Loop over the categories. 372 for i in range(len(self)): 373 # Loop over the keys. 374 for key, obj in self[i].items(): 375 if var_name == obj.var_name: 376 return i, key, obj
377 378 379
380 -class TagTranslationTable(dict):
381 """A special dictionary object for creating the tag translation tables.""" 382
383 - def __init__(self):
384 """Set up the table.""" 385 386 # Initialise the baseclass. 387 super(TagTranslationTable, self).__init__() 388 389 # The length of the list variables. 390 self.N = None 391 392 # The key ordering. 393 self._key_list = []
394 395
396 - def add(self, key, var_name=None, tag_name=None, allowed=None, default=None, format='str', missing=True):
397 """Add an entry to the translation table. 398 399 @keyword key: The dictionary key. This is also the BMRB NMR-STAR database table name. 400 @type key: str 401 @keyword var_name: The saveframe variable name corresponding to the key. 402 @type var_name: None or str 403 @keyword tag_name: The BMRB NMR-STAR tag name corresponding to the key. 404 @type tag_name: None or str 405 @keyword allowed: A list of allowable values for the data. 406 @type allowed: None or list 407 @keyword default: The default value. 408 @type default: anything 409 @keyword format: The original python format of the data. 410 @type format: str 411 @keyword missing: A flag which if True will allow the data to be set to None. 412 @type missing: bool 413 """ 414 415 # The key already exists. 416 if key in self._key_list: 417 # Overwrite the variables. 418 self[key].allowed = allowed 419 self[key].missing = missing 420 self[key].tag_name = tag_name 421 self[key].var_name = var_name 422 self[key].default = default 423 self[key].format = format 424 425 # Change the key ordering. 426 self._key_list.remove(key) 427 self._key_list.append(key) 428 429 # Otherwise add a new object. 430 else: 431 # Add the tag object. 432 self[key] = TagObject(self, var_name=var_name, tag_name=tag_name, allowed=allowed, default=default, format=format, missing=missing) 433 434 # Add the key to the ordered list. 435 self._key_list.append(key)
436 437 438
439 -class TagObject(object):
440 """An object for filling the translation table.""" 441
442 - def __init__(self, category, var_name=None, tag_name=None, allowed=None, default=None, format='str', missing=True):
443 """Setup the internal variables. 444 445 This stores the variable name, BMRB NMR-STAR tag name, a list of allowable values, the missing flag, and any other tag specific information corresponding to the key. 446 447 @param category: The parent tag category class object. 448 @type category: TagTranslationTable instance 449 @keyword var_name: The saveframe variable name corresponding to the key. 450 @type var_name: None or str 451 @keyword tag_name: The BMRB NMR-STAR tag name corresponding to the key. 452 @type tag_name: None or str 453 @keyword allowed: A list of allowable values for the data. 454 @type allowed: None or list 455 @keyword default: The default value. 456 @type default: anything 457 @keyword format: The original python format of the data. 458 @type format: str 459 @keyword missing: A flag which if True will allow the data to be set to None. 460 @type missing: bool 461 """ 462 463 # Store the tag category object. 464 self.category = category 465 466 # The default values. 467 self.allowed = allowed 468 self.missing = missing 469 self.tag_name = tag_name 470 self.var_name = var_name 471 self.default = default 472 self.format = format
473 474
475 - def tag_name_full(self):
476 """Add the prefix to the tag name and return the full tag name. 477 478 @return: The full tag name with prefix. 479 @rtype: str 480 """ 481 482 # Return the name. 483 if not self.tag_name: 484 return None 485 else: 486 return self.category.tag_prefix + self.tag_name
487 488 489
490 -class TagCategory(TagTranslationTable):
491 """The base class for tag category classes.""" 492 493 # The category name. 494 tag_category_label = None 495 496 # The base category is not free. 497 free = False 498
499 - def __init__(self, sf):
500 """Initialise the tag category object, placing the saveframe into its namespace. 501 502 @param sf: The saveframe object. 503 @type sf: saveframe instance 504 """ 505 506 # Initialise the baseclass. 507 super(TagCategory, self).__init__() 508 509 # Place the saveframe and tag info into the namespace. 510 self.sf = sf 511 512 # The tag category name. 513 self.tag_category_label = None
514 515
516 - def _N(self):
517 """Determine the length of the variables. 518 519 @return: The length. 520 @rtype: int 521 """ 522 523 # Loop over the objects until a variable is found in self. 524 N = None 525 for key in self.keys(): 526 # The variable exists. 527 if self[key].var_name and hasattr(self.sf, self[key].var_name): 528 # Get the object. 529 obj = getattr(self.sf, self[key].var_name) 530 531 # Is it a list? 532 if not isinstance(obj, list) and not isinstance(obj, ndarray): 533 continue 534 535 # The length. 536 N = len(obj) 537 break 538 539 # Set the length in this class. 540 if N: 541 self.N = N 542 543 # Return N. 544 return N
545 546
547 - def create(self):
548 """Create the tag category.""" 549 550 # Init. 551 self.tag_setup() 552 tag_names = [] 553 tag_values = [] 554 555 # Empty tag category. 556 if self.is_empty(): 557 return 558 559 # Loop over the keys of the class dictionary. 560 for key in self._key_list: 561 # The tag names and values (skipping entries with no corresponding tag name or variable). 562 if self[key].tag_name != None and self[key].var_name != None: 563 # The name (adding the tag prefix). 564 tag_names.append(self[key].tag_name_full()) 565 566 # The value. 567 if hasattr(self.sf, self[key].var_name): 568 val = getattr(self.sf, self[key].var_name) 569 else: 570 val = translate(None) 571 572 # Convert to a list, if necessary. 573 if not isinstance(val, list): 574 val = [val] 575 576 # Append the value list. 577 tag_values.append(val) 578 579 # No data, so don't add the table. 580 if not len(tag_names): 581 return 582 583 # Check the input data to avoid cryptic pystarlib error messages. 584 N = len(tag_values[0]) 585 for i in range(len(tag_values)): 586 if len(tag_values[i]) > N: 587 # Mismatch. 588 if N != 1: 589 raise NameError("The tag values are not all the same length '%s'." % tag_values) 590 591 # First element was single. 592 N = len(tag_values[i]) 593 594 # Fix the single values if the other data are lists. 595 for i in range(len(tag_values)): 596 if len(tag_values[i]) == 1: 597 tag_values[i] = tag_values[i] * N 598 599 # Create the tagtable. 600 table = TagTable(free=self.free, tagnames=tag_names, tagvalues=tag_values) 601 602 # Add the tagtable to the save frame. 603 self.sf.frame.tagtables.append(table)
604 605
606 - def extract_tag_data(self, tagtable):
607 """Extract all of the tag data from the tagtable, placing it into the designated variable names. 608 609 @param tagtable: The tagtable. 610 @type tagtable: Tagtable instance 611 """ 612 613 # Loop over the variables. 614 for key in self._key_list: 615 # No corresponding tag in the tagtable. 616 if self[key].tag_name_full() not in tagtable.tagnames: 617 continue 618 619 # Currently no corresponding variable in the tag category. 620 if self[key].var_name == None: 621 continue 622 623 # Find the index of the tag. 624 index = tagtable.tagnames.index(self[key].tag_name_full()) 625 626 # The data. 627 data = tagtable.tagvalues[index] 628 629 # Free tagtable data (collapse the list). 630 if self.free: 631 data = data[0] 632 633 # Set the data. 634 setattr(self.sf, self[key].var_name, data)
635 636
637 - def generate_data_ids(self):
638 """Generate the data ID structure. 639 640 @keyword N: The number of data points. 641 @type N: int 642 """ 643 644 # The length. 645 N = self._N() 646 647 # If not 648 if not N: 649 return 650 651 # The data ID values. 652 return list(range(1, N+1))
653 654
655 - def is_empty(self):
656 """Dummy method for check if the tag category is empty. 657 658 @return: The state of emptiness (False). 659 @rtype: bool 660 """ 661 662 # Not empty. 663 return False
664 665
666 - def tag_setup(self, tag_category_label=None, sep=None):
667 """Setup the tag names. 668 669 @keyword tag_category_label: The tag name prefix specific for the tag category. 670 @type tag_category_label: None or str 671 @keyword sep: The string separating the tag name prefix and suffix. 672 @type sep: str 673 """ 674 675 # Place the args into the class namespace. 676 if tag_category_label: 677 self.tag_category_label = tag_category_label 678 if sep: 679 self.sep = sep 680 else: 681 self.sep = '.' 682 683 # Create the full tag label. 684 self.tag_prefix = '_' 685 if self.tag_category_label: 686 self.tag_prefix = self.tag_prefix + self.tag_category_label + self.sep
687 688 689
690 -class TagCategoryFree(TagCategory):
691 """The free version of the TagCategory class.""" 692 693 # The base category is free. 694 free = True 695
696 - def __init__(self, sf):
697 """Setup the TagCategoryFree tag category. 698 699 @param sf: The saveframe object. 700 @type sf: saveframe instance 701 """ 702 703 # Initialise the baseclass. 704 super(TagCategoryFree, self).__init__(sf) 705 706 # Add some generic saveframe category tag. 707 self.add(key='SfCategory', var_name='sf_label', tag_name='Saveframe_category') 708 self.add(key='SfFramecode', var_name='sf_framecode', tag_name=None)
709