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