import sys, os.path
from optparse import OptionParser

# Global variables
is_ptex = False
is_subfont = False
cur_font = None
cur_dsize = 0
cur_ssize = 0
subfont_idx = 0
subfont_list = ['cyberb', 'outbtm', 'outbtb', 'outgtm', 'outgtb']

# DVI opcodes
SET_CHAR_0 = 0; SET_CHAR_127 = 127;
SET1 = 128; SET2 = 129; SET3 = 130; SET4 = 131;
SET_RULE = 132;
PUT1 = 133; PUT2 = 134; PUT3 = 135; PUT4 = 136;
PUT_RULE = 137;
NOP = 138;
BOP = 139; EOP = 140;
PUSH = 141; POP = 142;
RIGHT1 = 143; RIGHT2 = 144; RIGHT3 = 145; RIGHT4 = 146;
W0 = 147; W1 = 148; W2 = 149; W3 = 150; W4 = 151;
X0 = 152; X1 = 153; X2 = 154; X3 = 155; X4 = 156;
DOWN1 = 157; DOWN2 = 158; DOWN3 = 159; DOWN4 = 160;
Y0 = 161; Y1 = 162; Y2 = 163; Y3 = 164; Y4 = 165;
Z0 = 166; Z1 = 167; Z2 = 168; Z3 = 169; Z4 = 170;
FNT_NUM_0 = 171; FNT_NUM_63 = 234;
FNT1 = 235; FNT2 = 236; FNT3 = 237; FNT4 = 238;
XXX1 = 239; XXX2 = 240; XXX3 = 241; XXX4 = 242;
FNT_DEF1 = 243; FNT_DEF2 = 244; FNT_DEF3 = 245; FNT_DEF4 = 246;
PRE = 247; POST = 248; POST_POST = 249;
# DVIV opcodes
DIR = 255;
# DVI-IVD opcodes
BEGIN_REFLECT = 250; END_REFLECT = 251;
# XDV opcodes
NATIVE_FONT_DEF = 252;
GLYPHS = 253;
TEXT_GLYPHS = 254;
# XDV flags
XDV_FLAG_VERTICAL = 0x0100;
XDV_FLAG_COLORED = 0x0200;
XDV_FLAG_EXTEND = 0x1000;
XDV_FLAG_SLANT = 0x2000;
XDV_FLAG_EMBOLDEN = 0x4000;
# DVI identifications
DVI_ID = 2; DVIV_ID = 3; XDVI_ID = 6; XDV_ID = 7;
DVI_IDS = (DVI_ID, DVIV_ID, XDVI_ID, XDV_ID)

def warning(msg):
  sys.stderr.write('warning: %s\n' % msg)

def ValidID(dvi_id):
    if dvi_id not in DVI_IDS:
        return False
    return True

def BadDVI(msg):
  raise AttributeError('Bad DVI file: %s!' % msg)

def GetByte(fp): # { returns the next byte, unsigned }
  try: return ord(fp.read(1))
  except: return -1

def SignedByte(fp): # { returns the next byte, signed }
  try: b = ord(fp.read(1))
  except: return -1
  if b < 128: return b
  else: return b - 256

def Get2Bytes(fp): # { returns the next two bytes, unsigned }
  try: a, b = fp.read(2)
  except: BadDVI('Failed to Get2Bytes()')
  return (a << 8) + b

def SignedPair(fp): # {returns the next two bytes, signed }
  try: a, b = fp.read(2)
  except: BadDVI('Failed to SignedPair()')
  if a < 128: return (a << 8) + b
  else: return ((a - 256) << 8) + b

def Get3Bytes(fp): # { returns the next three bytes, unsigned }
  try: a, b, c = fp.read(3)
  except: BadDVI('Failed to Get3Bytes()')
  return (((a << 8) + b) << 8) + c

def SignedTrio(fp): # { returns the next three bytes, signed }
  try: a, b, c = fp.read(3)
  except: BadDVI('Failed to SignedTrio()')
  if a < 128: return (((a << 8) + b) << 8) + c
  else: return ((((a - 256) << 8) + b) << 8) + c

def Get4Bytes(fp): # { returns the next four bytes, unsigned }
  try: a, b, c, d = fp.read(4)
  except: BadDVI('Failed to Get4Bytes()')
  return (((((a << 8) + b) << 8) + c) << 8) + d

def SignedQuad(fp): # { returns the next four bytes, signed }
  try: a, b, c, d = fp.read(4)
  except: BadDVI('Failed to get SignedQuad()')
  if a < 128: return (((((a << 8) + b) << 8) + c) << 8) + d
  else: return ((((((a - 256) << 8) + b) << 8) + c) << 8) + d

def PutByte(q):
  return bytes.fromhex('%02x' % (q & 0xff))

def Put2Bytes(q):
  return PutByte(q>>8) + PutByte(q)

def Put3Bytes(q):
  return PutByte(q>>16) + PutByte(q>>8) + PutByte(q)

def PutSignedQuad(q):
  if q < 0: q += 0x100000000
  return PutByte(q>>24) + PutByte(q>>16) + PutByte(q>>8) + PutByte(q)

def PutUnsigned(q):
  if q >= 0x1000000: return (3, PutSignedQuad(q))
  if q >= 0x10000:   return (2, Put3Bytes(q))
  if q >= 0x100:     return (1, Put2Bytes(q))
  return (0, PutByte(q))

def PutSigned(q):
  if q < -0x800000 or q >= 0x800000:  return (3, PutSignedQuad(q))
  if q >= 0x8000:                     return (2, Put3Bytes(q))
  if q < -0x8000:     q += 0x1000000; return (2, Put3Bytes(q))
  if q >= 0x80:                       return (1, Put2Bytes(q))
  if q < -0x80:       q += 0x10000;   return (1, Put2Bytes(q))
  return (0, PutByte(q))

def PutGlyphs(width, glyphs):
  s = []
  length = len(glyphs)
  s.append(PutSignedQuad(width))
  s.append(Put2Bytes(length))
  for glyph in glyphs:
    s.append(PutSignedQuad(glyph["x"]))
    s.append(PutSignedQuad(glyph["y"]))
  for glyph in glyphs:
    s.append(Put2Bytes(glyph["id"]))

  return b''.join(s)

def PutTextGlyphs(text, width, glyphs):
  s = []
  length = len(text)
  s.append(Put2Bytes(length))
  for ch in text:
    s.append(Put2Bytes(ch))
  s.append(PutGlyphs(width, glyphs))

  return b''.join(s)

def GetInt(s):
  try: return int(s)
  except: return -1

def GetStrASCII(s): # used in Parse()
  if len(s) > 1 and ((s[0] == "'" and s[-1] == "'") or (s[0] == '"' and s[-1] == '"')):
    return [ord(c) for c in s[1:-1].decode('unicode_escape')]
  else: return ''

def UCS2toJIS(c):
  try:
    s = c.encode('iso2022-jp')
  except UnicodeEncodeError:
    s = c.encode('raw_unicode_escape')
  if len(s) == 1: return ord(s)
  else:           return (s[3] << 8) + s[4]

def GetStrUTF8(s): # used in Parse()
  if len(s) > 1 and ((s[0] == "'" and s[-1] == "'") or (s[0] == '"' and s[-1] == '"')):
    t = s[1:-1].encode('raw_unicode_escape').decode('unicode_escape')
    if is_ptex: return [UCS2toJIS(c) for c in t]
    else:       return [ord(c)       for c in t]
  else:         return ''

def PutStrASCII(t): # used in Dump()
  s = ''
  for o in t:
    if o == 92:         s += '\\\\'
    elif 32 <= o < 127: s += chr(o)
    elif o < 256:       s += ('\\x%02x' % o)
    elif o < 65536:     s += ('\\u%04x' % o)
    else:
      warning('Not support characters > 65535; may skip %d.\n' % o)
  return "'%s'" % s

def PutStrLatin1(t): # used in Dump()
  s = ''
  for o in t:
    if o == 92:                           s += '\\\\'
    elif 32 <= o < 127 or 161 <= o < 256: s += chr(o)
    elif o < 256:                         s += ('\\x%02x' % o)
    elif o < 65536:                       s += ('\\u%04x' % o)
    else:
      warning('Not support characters > 65535; may skip %d.\n' % o)
  return "'%s'" % s

def DecodeISO2022JP(c):
  try:
    s = bytes.fromhex("1b 24 42 %02x %02x" % (c//256, c%256)).decode('iso2022-jp')
  except UnicodeDecodeError:
    s = chr(c)
  return s

def PutStrUTF8(t): # used in Dump()
  s = ''
  if is_subfont:
    for o in t:
      s += chr((subfont_idx << 8) + o)
  else: # not the case of subfont
    for o in t:
      if o == 92:         s += '\\\\'
      elif 32 <= o < 127: s += chr(o)
      elif o < 128:       s += ('\\x%02x' % o)
      elif is_ptex:
        s += DecodeISO2022JP(o)
      else:               s += chr(o)
  return "'%s'" % s

def IsFontChanged(f, z):
  global cur_font, cur_ssize, subfont_idx, is_subfont
  for n in subfont_list:
    if n == f[:-2]:
      is_subfont = True
      subfont_idx = int(f[-2:], 16)
      if cur_font == n and cur_ssize == z:
        return False
      else:
        cur_font = n; cur_ssize = z
        return True
  else:
    is_subfont = False
    cur_font = f; cur_ssize = z
    return True

############################################################
# DVI class
############################################################
class DVI(object):
  def __init__(self, unit='pt'):
    if   unit == 'sp': self.byconv = self.by_sp_conv
    elif unit == 'bp': self.byconv = self.by_bp_conv
    elif unit == 'mm': self.byconv = self.by_mm_conv
    elif unit == 'cm': self.byconv = self.by_cm_conv
    elif unit == 'in': self.byconv = self.by_in_conv
    else:              self.byconv = self.by_pt_conv
    self.Initialize()

  ##########################################################
  # Initialize: Required by __init__(), Load(), and Parse()
  ##########################################################
  def Initialize(self):
    self.id = DVI_ID
    self.numerator   = 25400000
    self.denominator = 473628672
    self.mag = 1000
    self.ComputeConversionFactors()
    self.comment = ''
    self.font_def = {}
    self.max_v = self.max_h = self.max_s = self.total_pages = 0
    self.pages = []

  ##########################################################
  # Load: DVI -> Internal Format
  ##########################################################
  def Load(self, fn):
    fp = open(fn, 'rb')
    self.LoadFromFile(fp)
    fp.close()

  def LoadFromFile(self, fp):
    self.Initialize()
    fp.seek(0, 2)
    if fp.tell() < 53: BadDVI('less than 53 bytes long')
    self.ProcessPreamble(fp)
    self.ProcessPostamble(fp)
    loc = self.first_backpointer
    while loc >= 0:
      fp.seek(loc)
      if GetByte(fp) != BOP: BadDVI('byte %d is not bop' % fp.tell())
      cnt = [SignedQuad(fp) for i in range(10)]
      loc = SignedQuad(fp)
      page = self.ProcessPage(fp)
      self.pages.insert(0, {'count':cnt, 'content':page})

  def ProcessPreamble(self, fp):
    fp.seek(0)
    if GetByte(fp) != PRE: BadDVI("First byte isn't start of preamble")
    id = GetByte(fp)
    if not ValidID(id):
      warning("ID byte is %d; use the default %d!" % (id, DVI_ID))
    self.id = id
    numerator = SignedQuad(fp)
    if numerator <= 0:
      warning('numerator is %d; use the default 25400000!' % numerator)
    else:
      self.numerator = numerator
    denominator = SignedQuad(fp)
    if denominator <= 0:
      warning('denominator is %d; use the default 473628672!' % denominator)
    else:
      self.denominator = denominator
    mag = SignedQuad(fp)
    if mag <= 0:
      warning('magnification is %d; use the default 1000!' % mag)
    else:
      self.mag = mag
    self.comment = fp.read(GetByte(fp)).decode('utf8')
    self.ComputeConversionFactors()

  def ProcessPostamble(self, fp):
    fp.seek(-5, 2) # at least four 223's
    while True:
      k = GetByte(fp)
      if   k < 0:    BadDVI('all 223s; is it a DVI file?') # found EOF
      elif k != 223: break
      fp.seek(-2, 1)
    if not ValidID(k):
      warning('ID byte is %d' % k)
    fp.seek(-5, 1)
    q = SignedQuad(fp)
    m = fp.tell() # id_byte
    if q < 0 or q > m - 33: BadDVI('post pointer %d at byte %d' % (q, m - 4))
    fp.seek(q) # move to post
    k = GetByte(fp)
    if k != POST: BadDVI('byte %d is not post' % k)
    self.post_loc = q
    self.first_backpointer = SignedQuad(fp)

    if SignedQuad(fp) != self.numerator:
      warning("numerator doesn't match the preamble!")
    if SignedQuad(fp) != self.denominator:
      warning("denominator doesn't match the preamble!")
    if SignedQuad(fp) != self.mag:
      warning("magnification doesn't match the preamble!")
    self.max_v = SignedQuad(fp)
    self.max_h = SignedQuad(fp)
    self.max_s = Get2Bytes(fp)
    self.total_pages = Get2Bytes(fp)
    while True:
      k = GetByte(fp)
      if   k == FNT_DEF1: p = GetByte(fp)
      elif k == FNT_DEF2: p = Get2Bytes(fp)
      elif k == FNT_DEF3: p = Get3Bytes(fp)
      elif k == FNT_DEF4: p = SignedQuad(fp)
      elif k == NATIVE_FONT_DEF: p = SignedQuad(fp)
      elif k != NOP: break
      if k == NATIVE_FONT_DEF: self.DefineNativeFont(p, fp)
      else: self.DefineFont(p, fp)
    if k != POST_POST:
      warning('byte %d is not postpost!' % (fp.tell() - 1))
    if SignedQuad(fp) != self.post_loc:
      warning('bad postamble pointer in byte %d!' % (fp.tell() - 4))
    m = GetByte(fp)
    if not ValidID(m):
      warning('identification in byte %d should be one of: %s!' % (fp.tell() - 1, DVI_IDS))
    if not self.id == m:
      if not (self.id == 2 and m == 3): # pTeX/upTeX with dir allowed
        warning('ID byte mismatch: preamble %d vs postamble %d!' % (self.id, m))

  def DefineFont(self, e, fp):
    c = SignedQuad(fp) # font_check_sum
    q = SignedQuad(fp) # font_scaled_size
    d = SignedQuad(fp) # font_design_size
    n = fp.read(GetByte(fp) + GetByte(fp)).decode('utf8')
    try:
      f = self.font_def[e]
    except KeyError:
      self.font_def[e] = {'name':n, 'checksum':c, 'scaled_size':q, 'design_size':d}
      if q <= 0 or q >= 0o1000000000:
        warning("%s---not loaded, bad scale (%d)!" % (n, q))
      elif d <= 0 or d >= 0o1000000000:
        warning("%s---not loaded, bad design size (%d)!" % (n, d))
    else:
      if f['checksum'] != c:
        warning("\t---check sum doesn't match previous definition!")
      if f['scaled_size'] != q:
        warning("\t---scaled size doesn't match previous definition!")
      if f['design_size'] != d:
        warning("\t---design size doesn't match previous definition!")
      if f['name'] != n:
        warning("\t---font name doesn't match previous definition!")

  def DefineNativeFont(self, e, fp):
    size = Get4Bytes(fp) # scaled size
    flags = Get2Bytes(fp)
    l = GetByte(fp) # name length
    fnt_name = fp.read(l).decode('utf8')
    index = Get4Bytes(fp) # face index
    ext = []
    embolden = 0
    if flags & XDV_FLAG_VERTICAL: ext.append("vertical")
    if flags & XDV_FLAG_COLORED: ext.append("color=%08X" % Get4Bytes(fp))
    if flags & XDV_FLAG_EXTEND: ext.append("extend=%d" % SignedQuad(fp))
    if flags & XDV_FLAG_SLANT: ext.append("slant=%d" % SignedQuad(fp))
    if flags & XDV_FLAG_EMBOLDEN: ext.append("embolden=%d" % SignedQuad(fp))
    try:
      f = self.font_def[e]
    except KeyError:
      if index > 0:
        fnt_name += "[%d]" % index
      name = '"%s"' % fnt_name
      if ext:
        name = '"%s:%s"' % (fnt_name, ";".join(ext))

      self.font_def[e] = {
        'name': name,
        'checksum': 0,
        'scaled_size': size,
        'design_size': 655360, # hardcoded
        }

  def ProcessPage(self, fp):
    s = []
    while True:
      o = GetByte(fp)
      p = self.Get1Arg(o, fp)
      if o < SET_CHAR_0 + 128 or o in (SET1, SET2, SET3, SET4):
        q = [p]
        while True:
          o = GetByte(fp)
          p = self.Get1Arg(o, fp)
          if o < SET_CHAR_0 + 128 or o in (SET1, SET2, SET3, SET4):
            q.append(p)
          else:
            break
        s.append([SET1, q])
      if o == SET_RULE:
        s.append([SET_RULE, [p, SignedQuad(fp)]])
      elif o in (PUT1, PUT2, PUT3, PUT4):
        s.append([PUT1, [p]])
      elif o == PUT_RULE:
        s.append([PUT_RULE, [p, SignedQuad(fp)]])
      elif o == NOP:
        continue
      elif o == BOP:
        warning('bop occurred before eop!')
        break
      elif o == EOP:
        break
      elif o == PUSH:
        s.append([PUSH])
      elif o == POP:
        s.append([POP])
      elif o in (RIGHT1, RIGHT2, RIGHT3, RIGHT4):
        s.append([RIGHT1, p])
      elif o == W0:
        s.append([W0])
      elif o in (W1, W2, W3, W4):
        s.append([W1, p])
      elif o == X0:
        s.append([X0])
      elif o in (X1, X2, X3, X4):
        s.append([X1, p])
      elif o in (DOWN1, DOWN2, DOWN3, DOWN4):
        s.append([DOWN1, p])
      elif o == Y0:
        s.append([Y0])
      elif o in (Y1, Y2, Y3, Y4):
        s.append([Y1, p])
      elif o == Z0:
        s.append([Z0])
      elif o in (Z1, Z2, Z3, Z4):
        s.append([Z1, p])
      elif o < FNT_NUM_0 + 64 or o in (FNT1, FNT2, FNT3, FNT4):
        s.append([FNT1, p])
      elif o in (XXX1, XXX2, XXX3, XXX4):
        q = fp.read(p)
        s.append([XXX1, q])
      elif o in (FNT_DEF1, FNT_DEF2, FNT_DEF3, FNT_DEF4):
        self.DefineFont(p, fp)
      elif o == NATIVE_FONT_DEF:
        self.DefineNativeFont(p, fp)
      elif o == GLYPHS:
        s.append([GLYPHS, self.GetGlyphs(fp)])
      elif o == TEXT_GLYPHS:
        s.append([TEXT_GLYPHS, self.GetTextGlyphs(fp)])
      elif o == DIR:
        s.append([DIR, p])
      elif o == BEGIN_REFLECT:
        s.append([BEGIN_REFLECT])
      elif o == END_REFLECT:
        s.append([END_REFLECT])
      elif o == PRE:
        warning('preamble command within a page!')
        break
      elif o in (POST, POST_POST):
        warning('postamble command %d!' % o)
        break
      else:
        warning('undefined command %d!' % o)
        break
    return s

  def Get1Arg(self, o, fp):
    if o < SET_CHAR_0 + 128:
      return o - SET_CHAR_0
    if o in (SET1, PUT1, FNT1, XXX1, FNT_DEF1, DIR):
      return GetByte(fp)
    if o in (SET2, PUT2, FNT2, XXX2, FNT_DEF2):
      return Get2Bytes(fp)
    if o in (SET3, PUT3, FNT3, XXX3, FNT_DEF3):
      return Get3Bytes(fp)
    if o in (RIGHT1, W1, X1, DOWN1, Y1, Z1):
      return SignedByte(fp)
    if o in (RIGHT2, W2, X2, DOWN2, Y2, Z2):
      return SignedPair(fp)
    if o in (RIGHT3, W3, X3, DOWN3, Y3, Z3):
      return SignedTrio(fp)
    if o in (SET4, SET_RULE, PUT4, PUT_RULE, RIGHT4, W4, X4, DOWN4, Y4, Z4, FNT4, XXX4, FNT_DEF4, NATIVE_FONT_DEF):
      return SignedQuad(fp)
    if o in (NOP, BOP, EOP, PUSH, POP, PRE, POST, POST_POST) or o > POST_POST:
      return 0
    if o in (W0, X0, Y0, Z0, BEGIN_REFLECT, END_REFLECT):
      return 0
    if o < FNT_NUM_0 + 64:
      return o - FNT_NUM_0

  def GetGlyphs(self, fp):
    width = SignedQuad(fp)
    length = Get2Bytes(fp)
    glyphs = {}
    for i in range(length):
      glyphs[i] = {}
      glyphs[i]["x"] = SignedQuad(fp)
      glyphs[i]["y"] = SignedQuad(fp)

    for i in range(length):
      glyphs[i]["id"] = Get2Bytes(fp)

    return (width, glyphs)

  def GetTextGlyphs(self, fp):
    length = Get2Bytes(fp)
    chars = []
    for i in range(length):
      chars.append(Get2Bytes(fp))
    width, glyphs = self.GetGlyphs(fp)

    return (chars, width, glyphs)

  def ReadGlyphs(self, val):
    import re
    glyphs = []
    w, g = val.split(" ", 1)
    for m in re.finditer(r"gid(?P<id>\d+?)\((?P<pos>.*?.)\)", g):
      gid = m.group("id")
      pos = m.group("pos")

      if "," in pos:
        x, y = pos.split(",")
      else:
        x, y = pos, "0sp"

      glyphs.append({"id": int(gid), 'x': self.ConvLen(x), 'y': self.ConvLen(y)})

    return (self.ConvLen(w), glyphs)

  def ReadTextGlyphs(self, val):
    _, text, glyphs = val.split(val[0])
    text = "'%s'" % text
    glyphs = glyphs.lstrip()
    chars = GetStr(text)
    w, glyphs = self.ReadGlyphs(glyphs)

    return (chars, w, glyphs)

  ##########################################################
  # Save: Internal Format -> DVI
  ##########################################################
  def Save(self, fn):
    fp = open(fn, 'wb')
    self.SaveToFile(fp)
    fp.close()

  def SaveToFile(self, fp):
    # WritePreamble
    fp.write(b''.join([bytes.fromhex('%02x' % PRE), PutByte(self.id), PutSignedQuad(self.numerator), PutSignedQuad(self.denominator), PutSignedQuad(self.mag), PutByte(len(self.comment)), self.comment.encode('utf8')]))
    # WriteFontDefinitions
    self.WriteFontDefinitions(fp)
    # WritePages
    if not self.pages:
      # dvistd0.pdf, Section A.1:
      # > A DVI file consists of a ``preamble,'' followed by a sequence of
      # > one or more "pages," followed by a ``postamble.''
      warning('one or more pages required!')
      self.pages.append({'count':[1,0,0,0,0,0,0,0,0,0], 'content':[]})
    stackdepth = 0; loc = -1
    for page in self.pages:
      w = x = y = z = 0; stack = []
      s = [bytes.fromhex('%02x' % BOP)]
      s.extend([PutSignedQuad(c) for c in page['count']])
      s.append(PutSignedQuad(loc))
      for cmd in page['content']:
        if cmd[0] == SET1:
          for o in cmd[1]:
            if o < 128: s.append(bytes.fromhex('%02x' % (SET_CHAR_0 + o)))
            else:       s.append(self.CmdPairU([SET1, o]))
        elif cmd[0] in (SET_RULE, PUT_RULE):
          s.append(bytes.fromhex('%02x' % cmd[0]) + PutSignedQuad(cmd[1][0]) + PutSignedQuad(cmd[1][1]))
        elif cmd[0] == PUT1:
          s.append(self.CmdPairU([PUT1, cmd[1][0]]))
        elif cmd[0] in (RIGHT1, DOWN1):
          s.append(self.CmdPair(cmd))
        elif cmd[0] in (W0, X0, Y0, Z0):
          s.append(bytes.fromhex('%02x' % cmd[0]))
        elif cmd[0] == PUSH:
          s.append(bytes.fromhex('%02x' % PUSH))
          stack.append((w, x, y, z))
          if len(stack) > stackdepth: stackdepth = len(stack)
        elif cmd[0] == POP:
          s.append(bytes.fromhex('%02x' % POP))
          w, x, y, z = stack.pop()
        elif cmd[0] == W1:
          w = cmd[1]; s.append(self.CmdPair(cmd))
        elif cmd[0] == X1:
          x = cmd[1]; s.append(self.CmdPair(cmd))
        elif cmd[0] == Y1:
          y = cmd[1]; s.append(self.CmdPair(cmd))
        elif cmd[0] == Z1:
          z = cmd[1]; s.append(self.CmdPair(cmd))
        elif cmd[0] == FNT1:
          if cmd[1] < 64: s.append(bytes.fromhex('%02x' % (FNT_NUM_0 + cmd[1])))
          else:           s.append(self.CmdPairU(cmd))
        elif cmd[0] == XXX1:
          if options.xxx_encoding == "none":
            l = len(cmd[1]) # leave encoding untouched
          else:
            cmd1 = cmd[1].encode(options.xxx_encoding)
            l = len(cmd1)
          if l < 256:
            s.append(bytes.fromhex('%02x' % XXX1) + bytes.fromhex('%02x' % l))
          else:
            s.append(bytes.fromhex('%02x' % XXX4) + PutSignedQuad(l))
          if options.xxx_encoding == "none":
            for o in cmd[1]:
              s.append(bytes.fromhex('%02x' % ord(o)))
          else:
              s.append(cmd1)
        elif cmd[0] == DIR:
          s.append(bytes.fromhex('%02x' % DIR) + bytes.fromhex('%02x' % cmd[1]))
        elif cmd[0] == BEGIN_REFLECT:
          s.append(bytes.fromhex('%02x' % BEGIN_REFLECT))
        elif cmd[0] == END_REFLECT:
          s.append(bytes.fromhex('%02x' % END_REFLECT))
        elif cmd[0] == GLYPHS:
          s.append(PutByte(GLYPHS))
          s.append(PutGlyphs(cmd[1], cmd[2]))
        elif cmd[0] == TEXT_GLYPHS:
          s.append(PutByte(TEXT_GLYPHS))
          s.append(PutTextGlyphs(cmd[1], cmd[2], cmd[3]))
        else:
          warning('invalid command %s!' % cmd[0])
      s.append(bytes.fromhex('%02x' % EOP))
      loc = fp.tell()
      fp.write(b''.join(s))
    # WritePostamble
    post_loc = fp.tell()
    fp.write(b''.join([bytes.fromhex('%02x' % POST), PutSignedQuad(loc), PutSignedQuad(self.numerator), PutSignedQuad(self.denominator), PutSignedQuad(self.mag), PutSignedQuad(self.max_v), PutSignedQuad(self.max_h), Put2Bytes(stackdepth), Put2Bytes(len(self.pages))]))
    # WriteFontDefinitions
    self.WriteFontDefinitions(fp)
    # WritePostPostamble
    fp.write(b''.join([bytes.fromhex('%02x' % POST_POST), PutSignedQuad(post_loc), PutByte(self.id_post), b'\xdf\xdf\xdf\xdf']))
    loc = fp.tell()
    while (loc % 4) != 0:
      fp.write(b'\xdf'); loc += 1

  def WriteFontDefinitions(self, fp):
    s = []
    for e in sorted(self.font_def.keys()):
      try:
        self.font_def[e]['native']
        flags = self.font_def[e]['flags']
        s.append(PutByte(NATIVE_FONT_DEF))
        s.append(PutSignedQuad(e))
        s.append(PutSignedQuad(self.font_def[e]['scaled_size']))
        s.append(Put2Bytes(flags))
        s.append(PutByte(len(self.font_def[e]['name'])))
        s.append(self.font_def[e]['name'].encode('utf8'))
        s.append(PutSignedQuad(self.font_def[e]['index']))
        if flags & XDV_FLAG_COLORED: s.append(PutSignedQuad(self.font_def[e]['color']))
        if flags & XDV_FLAG_EXTEND: s.append(PutSignedQuad(self.font_def[e]['extend']))
        if flags & XDV_FLAG_SLANT: s.append(PutSignedQuad(self.font_def[e]['slant']))
        if flags & XDV_FLAG_EMBOLDEN: s.append(PutSignedQuad(self.font_def[e]['embolden']))
      except KeyError:
        l, q = PutUnsigned(e)
        s.append(PutByte(FNT_DEF1 + l))
        s.append(q)
        s.append(PutSignedQuad(self.font_def[e]['checksum']))
        s.append(PutSignedQuad(self.font_def[e]['scaled_size']))
        s.append(PutSignedQuad(self.font_def[e]['design_size']))
        s.append(b'\x00')
        s.append(PutByte(len(self.font_def[e]['name'])))
        s.append(self.font_def[e]['name'].encode('utf8'))
    fp.write(b''.join(s))

  def CmdPairU(self, cmd):
    l, q = PutUnsigned(cmd[1])
    return bytes.fromhex('%02x' % (cmd[0] + l)) + q

  def CmdPair(self, cmd):
    l, q = PutSigned(cmd[1])
    return bytes.fromhex('%02x' % (cmd[0] + l)) + q

  ##########################################################
  # Parse: Text -> Internal Format
  ##########################################################
  def Parse(self, fn, encoding=''):
    fp = open(fn, 'r', encoding=encoding)
    s = fp.read()
    fp.close()
    self.ParseFromString(s, encoding=encoding)

  def ParseFromString(self, s, encoding=''):
    global GetStr, cur_font, cur_dsize, cur_ssize, subfont_idx
    if encoding == 'ascii': GetStr = GetStrASCII
    else:                   GetStr = GetStrUTF8
    self.Initialize()
    self.fnt_num = 0
    dir_used = 0
    for l in s.split('\n'):
      l = l.strip()
      if not l or l[0] == '%': continue
      try:
        key, val = l.split(':', 1)
        key = key.strip(); val = val.strip()
      except:
        if l[-1] == ']': v = l[:-1].split(' ')
        else: v = l.split(' ')
        if v[0] == "[page":
          self.cur_page = []
          count = [GetInt(c) for c in v[1:]]
          if len(count) < 10: count += ([0] * (10-len(count)))
          self.pages.append({'count':count, 'content':self.cur_page})
        continue
      # ParsePreamble
      if key == "id":
        self.id = GetInt(val)
        if not ValidID(self.id):
          warning('identification byte %d should be one of: %s!' % (self.id, DVI_IDS))
      elif key == "numerator":
        d = GetInt(val)
        if d <= 0:
          warning('non-positive numerator %d!' % d)
        else:
          self.numerator = d
          self.ComputeConversionFactors()
      elif key == "denominator":
        d = GetInt(val)
        if d <= 0:
          warning('non-positive denominator %d!' % d)
        else:
          self.denominator = d
          self.ComputeConversionFactors()
      elif key == "magnification":
        d = GetInt(val)
        if d <= 0:
          warning('non-positive magnification %d!' % d)
        else:
          self.mag = d
      elif key == "comment":
        self.comment = val[1:-1]
      # Parse Postamble
      elif key == "maxv":
        self.max_v = self.ConvLen(val)
      elif key == "maxh":
        self.max_h = self.ConvLen(val)
      elif key == "maxs":
        self.max_s = GetInt(val)
      elif key == "pages":
        self.total_pages = GetInt(val)
      # Parse Font Definitions
      elif key == "fntdef":
        self.font_def[self.fnt_num] = self.GetFntDef(val)
        self.fnt_num += 1
      # Parse Pages
      elif key == 'xxx':
        self.cur_page.append([XXX1, eval(val)])
      elif key == 'set':
        ol = GetStr(val)
        if is_subfont:
          subfont_idx = (ol[0] >> 8)
          self.AppendFNT1()
          nl = [ol[0] & 0xff]
          for o in ol[1:]:
            idx = (o >> 8)
            if idx != subfont_idx:
              self.cur_page.append([SET1, nl])
              subfont_idx = idx
              self.AppendFNT1()
              nl = [o & 0xff]
            else:
              nl.append(o & 0xff)
          self.cur_page.append([SET1, nl])
        else:
          self.cur_page.append([SET1, ol])
      elif key == 'put':
        ol = GetStr(val)
        if len(ol) != 1:
          warning('only one character is allowed for put!')
        self.cur_page.append([PUT1, ol])
      elif key == 'setrule':
        v = val.split(' ')
        if len(v) != 2:
          warning('two values are required for setrule!')
          continue
        self.cur_page.append([SET_RULE, [self.ConvLen(c) for c in v]])
      elif key == 'putrule':
        v = val.split(' ')
        if len(v) != 2:
          warning('two values are required for putrule!')
          continue
        self.cur_page.append([PUT_RULE, [self.ConvLen(c) for c in v]])
      elif key == 'fnt':
        f = self.GetFntDef(val)
        n = f['name']
        d = f['design_size']
        q = f['scaled_size']
        if n in subfont_list:
          is_subfont = True
          cur_font = n; cur_dsize = d; cur_ssize = q
        else:
          is_subfont = False
          try:
            e = list(self.font_def.keys())[list(self.font_def.values()).index(f)]
          except:
            e = self.fnt_num
            self.font_def[self.fnt_num] = f
            self.fnt_num += 1
          self.cur_page.append([FNT1, e])
      elif key == 'right':
        self.cur_page.append([RIGHT1, self.ConvLen(val)])
      elif key == 'down':
        self.cur_page.append([DOWN1, self.ConvLen(val)])
      elif key == 'w':
        self.cur_page.append([W1, self.ConvLen(val)])
      elif key == 'x':
        self.cur_page.append([X1, self.ConvLen(val)])
      elif key == 'y':
        self.cur_page.append([Y1, self.ConvLen(val)])
      elif key == 'z':
        self.cur_page.append([Z1, self.ConvLen(val)])
      elif key == 'push':
        self.cur_page.append([PUSH])
      elif key == 'pop':
        self.cur_page.append([POP])
      elif key == 'w0':
        self.cur_page.append([W0])
      elif key == 'x0':
        self.cur_page.append([X0])
      elif key == 'y0':
        self.cur_page.append([Y0])
      elif key == 'z0':
        self.cur_page.append([Z0])
      elif key == 'dir':
        self.cur_page.append([DIR, GetInt(val)])
        dir_used = 1
      elif key == 'begin_reflect':
        self.cur_page.append([BEGIN_REFLECT])
      elif key == 'end_reflect':
        self.cur_page.append([END_REFLECT])
      elif key == 'setglyphs':
        w, glyphs = self.ReadGlyphs(val)
        self.cur_page.append([GLYPHS, w, glyphs])
      elif key == 'settextglyphs':
        text, w, glyphs = self.ReadTextGlyphs(val)
        self.cur_page.append([TEXT_GLYPHS, text, w, glyphs])
      else:
        warning('invalid command %s!' % key)
    if self.id == 2 and dir_used == 1: # standard DVI with dir -> force pTeX/upTeX spec
      self.id_post = 3
    else:
      self.id_post = self.id

  def AppendFNT1(self):
    f = {'name':cur_font+"%02x"%subfont_idx, 'design_size':cur_dsize, 'scaled_size':cur_ssize, 'checksum':0}
    try:
      e = list(self.font_def.keys())[list(self.font_def.values()).index(f)]
    except:
      e = self.fnt_num
      self.font_def[e] = f
      self.fnt_num += 1
    self.cur_page.append([FNT1, e])

  ##########################################################
  # Dump: Internal Format -> Text
  ##########################################################
  def Dump(self, fn, tabsize=2, encoding=''):
    fp = open(fn, 'w', encoding=encoding)
    self.DumpToFile(fp, tabsize=tabsize, encoding=encoding)
    fp.close()

  def DumpToFile(self, fp, tabsize=2, encoding=''):
    global PutStr
    if   encoding == 'ascii':  PutStr = PutStrASCII
    elif encoding == 'latin1': PutStr = PutStrLatin1
    else:                      PutStr = PutStrUTF8
    # DumpPreamble
    fp.write("[preamble]\n")
    fp.write("id: %d\n" % self.id)
    fp.write("numerator: %d\n" % self.numerator)
    fp.write("denominator: %d\n" % self.denominator)
    fp.write("magnification: %d\n" % self.mag)
    fp.write("comment: %s\n" % repr(self.comment))
    # DumpPostamble
    fp.write("\n[postamble]\n")
    fp.write("maxv: %s\n" % self.byconv(self.max_v))
    fp.write("maxh: %s\n" % self.byconv(self.max_h))
    fp.write("maxs: %d\n" % self.max_s)
    fp.write("pages: %d\n" % self.total_pages)
    # DumpFontDefinitions
    fp.write("\n[font definitions]\n")
    for e in sorted(self.font_def.keys()):
      fp.write("fntdef: %s " % self.font_def[e]['name'])
      if self.font_def[e]['design_size'] != self.font_def[e]['scaled_size']:
        fp.write("(%s) " % self.byconv(self.font_def[e]['design_size']))
      fp.write("at %s\n" % self.byconv(self.font_def[e]['scaled_size']))
    # DumpPages
    if not self.pages:
      # dvistd0.pdf, Section A.1:
      # > A DVI file consists of a ``preamble,'' followed by a sequence of
      # > one or more "pages," followed by a ``postamble.''
      warning('one or more pages required!')
      self.pages.append({'count':[1,0,0,0,0,0,0,0,0,0], 'content':[]})
    for page in self.pages:
      fp.write("\n[page" + (" %d"*10 % tuple(page['count'])) + "]\n")
      indent = 0
      for cmd in page['content']:
        if cmd[0] == POP:
          indent -= tabsize
          fp.write("%spop:\n" % (' ' * indent))
          continue
        fp.write("%s" % (' ' * indent))
        if cmd[0] == PUSH:
          fp.write("push:\n")
          indent += tabsize
        elif cmd[0] == XXX1:
          if options.xxx_encoding == "none":
            fp.write("xxx: %s\n" % PutStrASCII(cmd[1])) # leave encoding untouched
          else:
            fp.write("xxx: '%s'\n" % cmd[1].decode(options.xxx_encoding))
        elif cmd[0] == DIR:
          fp.write("dir: %d\n" % cmd[1])
        elif cmd[0] == BEGIN_REFLECT:
          fp.write("begin_reflect:\n")
        elif cmd[0] == END_REFLECT:
          fp.write("end_reflect:\n")
        elif cmd[0] == SET_RULE:
          fp.write("setrule: %s %s\n" % (self.byconv(cmd[1][0]), self.byconv(cmd[1][1])))
        elif cmd[0] == PUT_RULE:
          fp.write("putrule: %s %s\n" % (self.byconv(cmd[1][0]), self.byconv(cmd[1][1])))
        elif cmd[0] == SET1:
          fp.write("set: %s\n" % PutStr(cmd[1]))
        elif cmd[0] == PUT1:
          fp.write("put: %s\n" % PutStr(cmd[1]))
        elif cmd[0] == FNT1:
          f = self.font_def[cmd[1]]['name']
          z = self.font_def[cmd[1]]['scaled_size']
          if IsFontChanged(f, z):
            fp.write("fnt: %s " % cur_font)
            if self.font_def[cmd[1]]['design_size'] != self.font_def[cmd[1]]['scaled_size']:
              fp.write("(%s) " % self.byconv(self.font_def[cmd[1]]['design_size']))
            fp.write("at %s\n" % self.byconv(cur_ssize))
        elif cmd[0] == GLYPHS:
          fp.write("setglyphs: %s\n" % self.DumpGlyphs(cmd[1][0], cmd[1][1]))
        elif cmd[0] == TEXT_GLYPHS:
          fp.write("settextglyphs: %s\n" % self.DumpTextGlyphs(cmd[1][0], cmd[1][1], cmd[1][2]))
        elif cmd[0] == RIGHT1:
          fp.write("right: %s\n" % self.byconv(cmd[1]))
        elif cmd[0] == DOWN1:
          fp.write("down: %s\n" % self.byconv(cmd[1]))
        elif cmd[0] == W1:
          fp.write("w: %s\n" % self.byconv(cmd[1]))
        elif cmd[0] == X1:
          fp.write("x: %s\n" % self.byconv(cmd[1]))
        elif cmd[0] == Y1:
          fp.write("y: %s\n" % self.byconv(cmd[1]))
        elif cmd[0] == Z1:
          fp.write("z: %s\n" % self.byconv(cmd[1]))
        elif cmd[0] == W0:
          fp.write("w0:\n")
        elif cmd[0] == X0:
          fp.write("x0:\n")
        elif cmd[0] == Y0:
          fp.write("y0:\n")
        elif cmd[0] == Z0:
          fp.write("z0:\n")

  def DumpGlyphs(self, w, g):
    yPresent = False
    for i in g:
      if g[i]["y"] != 0:
        yPresent = True

    glyphs = []
    for i in g:
      gid = "gid%s" % g[i]["id"]
      x = self.byconv(g[i]["x"])
      y = self.byconv(g[i]["y"])
      if yPresent:
        glyphs.append("%s(%s, %s)" % (gid, x, y))
      else:
        glyphs.append("%s(%s)" % (gid, x))

    return "%s %s" % (self.byconv(w), " ".join(glyphs))

  def DumpTextGlyphs(self, t, w, g):
    return "%s %s" % (PutStrUTF8(t), self.DumpGlyphs(w, g))

  ##########################################################
  # Misc Functions
  ##########################################################
  def ComputeConversionFactors(self):
    self.sp_conv = (self.numerator / 25400000.) * (473628672. / self.denominator)
    self.pt_conv = (self.numerator / 25400000.) * (7227. / self.denominator)
    self.bp_conv = (self.numerator / 254000.) * (72. / self.denominator)
    self.mm_conv = (self.numerator / 10000.) / self.denominator
    self.cm_conv = (self.numerator / 100000.) / self.denominator
    self.in_conv = (self.numerator / 254000.) * (1. / self.denominator)

  def ConvLen(self, s):
    try:    return int(s)
    except: pass
    try:    f = float(s[:-2])
    except: return 0
    m = s[-2:]
    if   m == "pt": return int(round(f / self.pt_conv))
    elif m == "in": return int(round(f / self.in_conv))
    elif m == "mm": return int(round(f / self.mm_conv))
    elif m == "cm": return int(round(f / self.cm_conv))
    elif m == "bp": return int(round(f / self.bp_conv))
    elif m == "sp": return int(round(f / self.sp_conv))
    else:
      try:    return int(round(f / self.pt_conv))
      except: return 0

  def GetFntDef(self, s):
    f = {}
    try:
      n, size = s.split('(', 1)
      d, q = size.split(')', 1)
    except:
      n, q = s.split(' ', 1)
    n = n.strip(); q = q.strip()
    if n.startswith('"') and n.endswith('"'):
      f['native'] = True
      n = n.strip('"')
      flags = 0
      color = 0
      extend = 0
      slant = 0
      embolden = 0
      try:
        name, ext = n.split(':')
      except:
        name, ext = n, ""

      try:
        name, index = name.split('[')
        index = index.split(']')[0]
      except:
        index = 0

      if ext:
        ext = ext.split(';')
        for opt in ext:
          try:
            key, value = opt.split('=')
          except:
            key, value = opt, ""
          if key == "color":
            flags |= XDV_FLAG_COLORED
            color = int(value, 16)
          if key == "vertical":
            flags |= XDV_FLAG_VERTICAL
          if key == "extend":
            flags |= XDV_FLAG_EXTEND
            extend = int(value)
          if key == "slant":
            flags |= XDV_FLAG_SLANT
            slant = int(value)
          if key == "embolden":
            flags |= XDV_FLAG_EMBOLDEN
            embolden = int(value)

      f['name'] = name
      f['index'] = int(index)
      f['flags'] = flags
      f['color'] = color
      f['extend'] = extend
      f['slant'] = slant
      f['embolden'] = embolden
    else:
      f['name'] = n

    if q[:2] == "at": q = q[2:]
    q = self.ConvLen(q.strip())
    try:    d = self.ConvLen(d.strip())
    except: d = q

    f['design_size'] = d
    f['scaled_size'] = q
    f['checksum'] = 0

    return f

  def by_sp_conv(self, a):
    v = self.sp_conv * a
    return "%dsp" % int(v)

  def by_pt_conv(self, a):
    v = self.pt_conv * a
    if v == int(v): return "%dpt" % int(v)
    else:           return "%fpt" % v

  def by_bp_conv(self, a):
    v = self.bp_conv * a
    if v == int(v): return "%dbp" % int(v)
    else:           return "%fbp" % v

  def by_mm_conv(self, a):
    v = self.mm_conv * a
    if v == int(v): return "%dmm" % int(v)
    else:           return "%fmm" % v

  def by_cm_conv(self, a):
    v = self.cm_conv * a
    if v == int(v): return "%dcm" % int(v)
    else:           return "%fcm" % v

  def by_in_conv(self, a):
    v = self.in_conv * a
    if v == int(v): return "%din" % int(v)
    else:           return "%fin" % v

############################################################
# Misc Functions for Main Routine
############################################################
def ProcessOptions():
  usage = """%prog [options] dvi_file|dvi_dump_file

DVIasm is a Python script to support changing or creating DVI files
via disassembling into text, editing, and then reassembling into
binary format. It is fully documented at
  https://tug.org/TUGboat/Articles/tb28-2/tb89cho.pdf
  http://ajt.ktug.kr/assets/2008/5/1/0201cho.pdf

Please report bugs to
  https://github.com/aminophen/dviasm"""

  version = """This is %prog-20230823

Copyright (C) 2007-2008 by Jin-Hwan Cho <chofchof@ktug.or.kr>
Copyright (C) 2011-2017 by Khaled Hosny <khaledhosny@eglug.org>
Copyright (C) 2019      by Arthur Reutenauer <arthur@reutenauer.eu>
Copyright (C) 2019-2022 by Hironobu Yamashita <h.y.acetaminophen@gmail.com>

This is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version."""

  parser = OptionParser(usage=usage, version=version)
  parser.add_option("-u", "--unit",
                    action="store", type="string", dest="unit",
                    metavar="STR",
                    help="unit (sp, pt, bp, mm, cm, in) [default=%default]")
  parser.add_option("-o", "--output",
                    action="store", type="string", dest="output",
                    metavar="FILE",
                    help="filename for output instead of stdout")
  parser.add_option("-e", "--encoding",
                    action="store", type="string", dest="encoding",
                    metavar="STR",
                    help="encoding for input/output [default=%default]")
  parser.add_option("-x", "--xxx-encoding",
                    action="store", type="string", dest="xxx_encoding",
                    metavar="STR",
                    help="encoding for interpreting xxx strings [default=%default]")
  parser.add_option("-t", "--tabsize",
                    action="store", type="int", dest="tabsize",
                    metavar="INT",
                    help="tab size for push/pop [default=%default]")
  parser.add_option("-p", "--ptex",
                    action="store_true", dest="ptex", default=False,
                    help="ISO-2022-JP-encoded DVI for Japanese pTeX")
  parser.add_option("-s", "--subfont",
                    action="append", type="string", dest="subfont",
                    metavar="STR",
                    help="the list of fonts with UCS2 subfont scheme (comma separated); disable internal subfont list if STR is empty")
  parser.set_defaults(unit='pt', encoding='utf8', xxx_encoding='none', tabsize=2)
  (options, args) = parser.parse_args()
  if not options.unit in ['sp', 'pt', 'bp', 'mm', 'cm', 'in']:
    parser.error("invalid unit name '%s'!" % options.unit)
  if options.tabsize < 0:
    parser.error("negative tabsize!")
  if not options.xxx_encoding in ['none', 'utf8', 'sjis', 'eucjp']:
    parser.error("invalid xxx-encoding '%s'!" % options.xxx_encoding)
  if not options.encoding in ['ascii', 'latin1', 'utf8', 'sjis', 'eucjp']:
    parser.error("invalid encoding '%s'!" % options.encoding)
  if options.ptex:
    global is_ptex
    is_ptex = True
    if not options.encoding in ['utf8', 'sjis', 'eucjp']:
      parser.error("invalid encoding '%s' for Japanese pTeX!" % options.encoding)
  if options.subfont:
    global subfont_list
    if not options.subfont[0]: # disable subfont
      subfont_list = []
    for l in options.subfont:
      subfont_list.extend([f.strip() for f in l.split(',')])
  if len(args) != 1:
    parser.error("try with the option --help!")
  return (options, args)

def IsDVI(fname):
  from os.path import splitext
  if splitext(fname)[1] not in ('.dvi', '.xdv'): return False
  try:
    fp = open(fname, 'rb')
    fp.seek(0)
    if GetByte(fp) != PRE: return False
    fp.seek(-4, 2)
    if GetByte(fp) != 223: return False
    fp.close()
  except:
    sys.stderr.write('Failed to read %s\n' % fname)
    return False
  return True

############################################################
# Main Routine
############################################################
if __name__ == '__main__':
  (options, args) = ProcessOptions()
  aDVI = DVI(unit=options.unit)
  if os.path.isfile(args[0]): fname = args[0]
  elif os.path.isfile(args[0] + '.xdv'): fname = args[0] + '.xdv'
  elif os.path.isfile(args[0] + '.dvi'): fname = args[0] + '.dvi'
  else:
    sys.stderr.write('File %s not found\n' % args[0])
    sys.exit(1)
  if IsDVI(fname): # dvi -> dump
    aDVI.Load(fname)
    if options.output: aDVI.Dump(options.output, tabsize=options.tabsize, encoding=options.encoding)
    else:              aDVI.DumpToFile(sys.stdout, tabsize=options.tabsize, encoding=options.encoding)
  else: # dump -> dvi
    aDVI.Parse(fname, encoding=options.encoding)
    if options.output: aDVI.Save(options.output)
    else:              aDVI.SaveToFile(sys.stdout.buffer)


from gluon import current
from gluon.storage import Storage
from common_utils import D, DQ, remove_fractions
from datetime import date, timedelta, datetime

def new(id_store, now, user):
    db = current.db

    return db.purchase.insert(
        id_store=id_store,
        created_on=now,
        modified_on=now,
        created_by=user.id
    )

def postprocess_stock_item(stock_item):
    stock_item.serial_numbers = stock_item.serial_numbers.replace('_', ',') if stock_item.serial_numbers else None
    # primitive serial number count verification
    # if stock_item.serial_numbers:
    #     stock_items_count = stock_item.serial_numbers.split(',')
    #     if not stock_items_count[-1]:
    #         stock_items_count.pop()
    #     print len(stock_items_count)
    # else:
    #     stock_item.serial_numbers = None

    # recalculate the taxes.
    total_tax = 1 if stock_item.id_item.taxes else 0
    for tax in stock_item.id_item.taxes or []:
        total_tax *= tax.percentage / 100.0
    stock_item.taxes = D(stock_item.price or 0) * D(total_tax)
    if not stock_item.id_item.allow_fractions:
        stock_item.purchase_qty = DQ(remove_fractions(stock_item.purchase_qty))
    return stock_item

def commit(purchase):
    """ Given a purchase Row, generates stock for every stock item in associated with the purchase """

    db = current.db

    # generate stocks for every purchase item
    stock_items = db(db.stock_item.id_purchase == purchase.id).iterselect()
    for stock_item in stock_items:
        stock_item = postprocess_stock_item(stock_item)
        stock_item.stock_qty = stock_item.purchase_qty
        stock_item.id_store = purchase.id_store.id
        # set the stock quantity to the purchased quantity
        stock_item.update_record()
        # update the item prices
        item = db.item(stock_item.id_item)
        # base price should not be 0
        if stock_item.base_price > 0:
            item.base_price = stock_item.base_price
            item.price2 = stock_item.price2
            item.price3 = stock_item.price3
        item.update_record()
    purchase.is_done = True
    purchase.update_record()

    # in this case purchase is being paid with creadit so we need to create an account payable
    if purchase.id_payment_opt.credit_days > 0:
        now = purchase.modified_on
        epd = date(now.year, now.month, now.day)
        epd += timedelta(days=purchase.id_payment_opt.credit_days)
        db.account_payable.insert(id_purchase=purchase.id, epd=epd)

    return purchase


from django import http
from django.db import models
from databrowse.datastructures import EasyModel
from databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
from django.utils.text import capfirst
from django.utils.encoding import smart_str, force_unicode, iri_to_uri
from django.utils.safestring import mark_safe
import urllib

class FieldChoicePlugin(DatabrowsePlugin):
    def __init__(self, field_filter=None):
        # If field_filter is given, it should be a callable that takes a
        # Django database Field instance and returns True if that field should
        # be included. If field_filter is None, that all fields will be used.
        self.field_filter = field_filter

    def field_dict(self, model):
        """
        Helper function that returns a dictionary of all fields in the given
        model. If self.field_filter is set, it only includes the fields that
        match the filter.
        """
        if self.field_filter:
            return dict([(f.name, f) for f in model._meta.fields if self.field_filter(f)])
        else:
            return dict([(f.name, f) for f in model._meta.fields if not f.rel and not f.primary_key and not f.unique and not isinstance(f, (models.AutoField, models.TextField))])

    def model_index_html(self, request, model, site):
        fields = self.field_dict(model)
        if not fields:
            return u''
        return mark_safe(u'
View by: %s
' % \
            u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))

    def urls(self, plugin_name, easy_instance_field):
        if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
            field_value = smart_str(easy_instance_field.raw_value)
            #if easy_instance_field.field.name!='url':
            if not isinstance(easy_instance_field.field, models.URLField):
                return [mark_safe(u'%s%s/%s/%s/' % (
                   easy_instance_field.model.url(),
                   plugin_name, easy_instance_field.field.name,
                   urllib.quote(field_value, safe='')))]
            else:
                return [iri_to_uri(field_value)]

    def model_view(self, request, model_databrowse, url):
        self.model, self.site = model_databrowse.model, model_databrowse.site
        self.fields = self.field_dict(self.model)

        # If the model has no fields with choices, there's no point in going
        # further.
        if not self.fields:
            raise http.Http404('The requested model has no fields.')

        if url is None:
            return self.homepage_view(request)
        url_bits = url.split('/', 1)
        if self.fields.has_key(url_bits[0]):
            return self.field_view(request, self.fields[url_bits[0]], *url_bits[1:])
        else:
            field = self.model._meta.get_field(url_bits[0])
            return self.field_view(request, field, *url_bits[1:])

        raise http.Http404('The requested page does not exist.')

    def homepage_view(self, request):
        easy_model = EasyModel(self.site, self.model)
        field_list = self.fields.values()
        field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
        return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list, 'request': request})

    def field_view(self, request, field, value=None):
        easy_model = EasyModel(self.site, self.model)
        easy_field = easy_model.field(field.name)
        if value is not None:
            obj_list = easy_model.objects(**{field.name: value})
            return render_to_response('databrowse/fieldchoice_detail.html', {'root_url': self.site.root_url, 'model': easy_model, 'field': easy_field, 'value': value, 'object_list': obj_list, 'request': request})
        obj_list = [v[field.name] for v in self.model._default_manager.distinct().order_by(field.name).values(field.name)]
        if '' in obj_list:
            obj_list.remove('')
        return render_to_response('databrowse/fieldchoice_list.html', {'root_url': self.site.root_url, 'model': easy_model, 'field': easy_field, 'object_list': obj_list, 'request': request})


# h의 처음 값과 마지막 값을
# if 문을 사용하여 하나씩 비교할 순 없을까

T = int(input())

for i in range(1, T + 1): # T번 반복

    h = input() # 일단 문장을 입력 받는다
    word = list(h) # h에 입력되는 데이터를 리스트로 만들어 word에 할당시킨다
    # print(word)
    rev = word[::-1] # rev에 word의 데이터 리스트를 역순환 시킨다
    # print(rev)
    if word == rev:
        print(f"#{i} {1}")
    else:
        print(f"#{i} {0}")

알고리즘/노션파이썬문제/20220721_실습/문제1989_초심자의_회문_검사.py

문제1989_초심자의_회문_검사.py


import sys

W = "\033[0m"  # white (normal)
R = "\033[31m"  # red
G = "\033[32m"  # green
O = "\033[33m"  # orange
B = "\033[34m"  # blue
P = "\033[35m"  # purple

def err_print(message):

    print()
    print(R + message + W, file=sys.stderr)
    print()


# -*- coding: utf-8 -*-
# @Time    : 2021/6/20 20:25
# @File    : 1600. 皇位继承顺序.py
from leetcode import *

class KingTree:
    def __init__(self, val, children=None):
        self.val = val
        self.children = children

class ThroneInheritance:

    def __init__(self, kingName: str):
        self.root = KingTree(kingName, [])
        self.deathList = set()
        self.familyList = {kingName: self.root}

    def birth(self, parentName: str, childName: str) -> None:
        # que = deque([self.root])
        # while que:
        #     tmp = que.popleft()
        #     if tmp.val == parentName:
        #         tmp.children.append(KingTree(childName, []))
        #         break
        #     for child in tmp.children:
        #         que.append(child)
        childTree = KingTree(childName, [])
        self.familyList[parentName].children.append(childTree)
        self.familyList[childName] = childTree

    def death(self, name: str) -> None:
        self.deathList.add(name)

    def getInheritanceOrder(self) -> List[str]:
        ans = []
        stack = [self.root]
        while stack:
            tmp = stack.pop()
            if tmp.val not in self.deathList:
                ans.append(tmp.val)
            for child in tmp.children[::-1]:
                stack.append(child)
        return ans

if __name__ == '__main__':
    test = ThroneInheritance("king")
    test.birth("king", "andy")
    test.birth("king", "bob")
    test.birth("king", "catherine")
    test.birth("andy", "matthew")
    test.birth("bob", "alex")
    test.birth("bob", "asha")
    print(test.getInheritanceOrder())
    test.death("bob")
    print(test.getInheritanceOrder())


from django.conf.urls.defaults import *
from django.conf import settings
from django.contrib import admin

#admin.autodiscover()

urlpatterns = patterns('ui.views',
    url(r'^$', 'index',
        name='index'),
    url(r'^login/$', 'login_user',
        name='login'),
    url(r'^logout/$', 'logout_user',
        name='logout'),
    url(r'^user/$', 'implicit_overview',
        name='resolve_user'),
    url(r'^user/overview/', 'global_user_overview',
        name='global_user_overview'),
    url(r'^user/id/(?P<uid>\d+)/$', 'overview_by_uid',
        name='user_by_id'),
    url(r'^user/name/(?P<username>[\w\-\.\d]+)/$', 'overview',
        name='user_by_name'),
    url(r'^user/(?P<username>[\w\-\.\d]+)/update_profile/$', 'update_profile',
        name='update_profile'),
    url(r'^user/(?P<username>[\w\-\.\d]+)/add_expertise/$', 'add_expertise',
        name='add_expertise'),
    url(r'^answer/(?P<domain_id>\d+)/next_question$', 'next_question',
        name='next_question'),
    url(r'^batches/$', 'user_batches',
        name='user_batches'),
    url(r'^batch/check_new/$', 'check_for_new_batches',
        name='check_for_new_batches'),
    url(r'^batch/(?P<batch_id>\d+)/overview/$', 'batch_overview',
        name='batch_overview'),
    url(r'^batch/(?P<batch_id>\d+)/allocate/$', 'batch_panel',
        name='allocate_batch'),
    url(r'^batch/get_allocation_suggestions/$', 'get_allocation_suggestions',
        name='get_allocation_suggestions'),
    url(r'^batch/commit_allocations/$', 'commit_allocations',
        name='commit_allocations'),
    url(r'^import/schema-map/questions/$', 'import_schema_map_questions',
        name='import_schema_map_questions'),
    url(r'^tamer/$', 'redirect_to_tamer',
        name='redirect_to_tamer'),
    url(r'^about/$', 'about',
        name='about'),
    url(r'^thanks/$', 'thanks_and_goodbye',
        name='thanks'),
    url(r'^domain/$', 'domain_overview',
        name='domain'),
    url(r'^domain/(?P<domain_id>\d+)/$', 'domain_details',
        name='domain_details'),
    url(r'^domain/(?P<domain_id>\d+)/avail-data$', 'avail_data',
        name='avail_data'),
    url(r'^domain/(?P<domain_id>\d+)/price-data$', 'price_data',
        name='price_data'),
    url(r'^denied', 'access_denied',
        name='access_denied'),
    url(r'^consent', 'consent',
        name='consent'),
    url(r'^noconsent', 'noconsent',
        name='noconsent'),
    url(r'^training', 'training',
        name='training'),
    url(r'^finished_training', 'finished_training',
        name='finished_training')
)

#urlpatterns += patterns('',
#    url(r'^admin/', include(admin.site.urls)),
#)

if settings.DEBUG:
    urlpatterns += patterns('',
        url(r'^static/(?P<path>.*)$', 'django.views.static.serve',
            {'document_root': settings.STATIC_DOC_ROOT}),
    )


""""
Team : FCC, IP2I, UCBLyon 1, France, 2022

Execute NNHProcessor.py program for all files lcio.
Entry condition: NNH_HOME and MARLIN_DLL exist like environment variables.
"""

#!/usr/bin/env python
import argparse
import os
import sys
import subprocess
import threading

from Transfer import TransferThread
import NNHProcessor
from NNHProcessor import NNHProcessorThread
from Merge import MergeThread

from Observer import Observer

class AnalysisFlow(Observer):

    mergedFiles = {}
    individualFiles = {}
    filesToDownload = {}
    failedFiles = {}

    lock = threading.Lock()

    def __init__(self):
        self.event = threading.Event()
        self.event.set()
        self.threadsToHandle = []

    def initAnalysisFlow(nThreadsTransfer, nThreadsProcess, nThreadsMerge):
        analysis = AnalysisFlow()

        for _ in range(nThreadsTransfer):
            thread = TransferThread()
            analysis.threadsToHandle.append(thread)
        for _ in range(nThreadsProcess):
            thread = NNHProcessorThread()
            analysis.threadsToHandle.append(thread)
        for _ in range(nThreadsMerge):
            thread = MergeThread()
            analysis.threadsToHandle.append(thread)

        for thread in analysis.threadsToHandle:
            thread.attachObserver(analysis)
            thread.start()

        return analysis

    def close():
        TransferThread.closeThreads()
        NNHProcessorThread.closeThreads()
        MergeThread.closeThreads()

    def addFile(self, targetMergedFileName, fileNamesToBeMerged, outputDirectory, remoteDirectory = None):

        self.event.clear()

        mergedFile = {
                'notProcessed': [],
                'processed': [],
                'failed': [],
                'outputDir': outputDirectory
        }

        with AnalysisFlow.lock:
            for i, file in enumerate(fileNamesToBeMerged):
                outputIndivName = targetMergedFileName.replace('.root', f'-{i}.root')

                AnalysisFlow.individualFiles[outputIndivName] = {
                        'mergeInto': targetMergedFileName,
                        'slcioFileToDelete': None
                }

                if remoteDirectory:
                    AnalysisFlow.filesToDownload[file] = {'rootFileName': outputIndivName}
                    AnalysisFlow.individualFiles[outputIndivName]['slcioFileToDelete'] = file

                mergedFile['notProcessed'].append(outputIndivName)

                if remoteDirectory:
                    self.launchTransfer(file, remoteDirectory, '.')
                else:
                    self.launchNNHProcessor(file, outputIndivName)

            AnalysisFlow.mergedFiles[targetMergedFileName] = mergedFile

    def launchTransfer(self, fileName, sourcePath, destinationPath):
        TransferThread.addTransfer(fileName, sourcePath, destinationPath)

    def launchNNHProcessor(self, inputFileName, outputFileName):
        params = NNHProcessor.Params()
        params.inputFileName = inputFileName
        params.outputFileName = outputFileName
        NNHProcessorThread.addParams(params)

    def launchMerge(self, outputFileName, fileNamesToMerge):
        MergeThread.addMergeTask(outputFileName, fileNamesToMerge)

    def update(self, fileName, msg):

        if msg.startswith('TRANSFER'):
            with AnalysisFlow.lock:
                file = AnalysisFlow.filesToDownload.pop(fileName, None)
                remainingFilesToDownload = len(AnalysisFlow.filesToDownload)

            if file:
                rootFileName = file['rootFileName']

                if msg.endswith('SUCCESS'):
                    self.launchNNHProcessor(fileName, rootFileName)

                elif msg.endswith('FAIL'):
                    print(f'ERROR : transfer of {fileName} failed')
                    os.system(f'rm -f {fileName}')
                    with AnalysisFlow.lock:
                        mergedFile = AnalysisFlow.individualFiles[rootFileName]['mergeInto']

                        AnalysisFlow.mergedFiles[mergedFile]['notProcessed'].remove(rootFileName)
                        AnalysisFlow.mergedFiles[mergedFile]['failed'].append(rootFileName)
                else:
                    print(f'ERROR : message unknown : {msg}')

                print(f'{remainingFilesToDownload} remaining files to download')

            else:
                print(f'ERROR : unknown file : {fileName}')

        elif msg.startswith('NNH'):

            with AnalysisFlow.lock:
                file = AnalysisFlow.individualFiles.pop(fileName, None)
                remainingFilesToAnalyse = len(AnalysisFlow.individualFiles)

            if file:
                if file["slcioFileToDelete"]:
                    os.system(f'rm -f {file["slcioFileToDelete"]}')

                mergeFile = file['mergeInto']
                with AnalysisFlow.lock:
                    AnalysisFlow.mergedFiles[mergeFile]['notProcessed'].remove(fileName)

                    if msg.endswith('SUCCESS'):
                        AnalysisFlow.mergedFiles[mergeFile]['processed'].append(fileName)

                    elif msg.endswith('FAIL'):
                        print(f'ERROR : analyse of {fileName} failed')
                        AnalysisFlow.mergedFiles[mergeFile]['failed'].append(fileName)

                    else:
                        print(f'ERROR : message unknown : {msg}')

                    if not AnalysisFlow.mergedFiles[mergeFile]['notProcessed']:
                        self.launchMerge(mergeFile, AnalysisFlow.mergedFiles[mergeFile]['processed'])

                print(f'{remainingFilesToAnalyse} remaining files to analyse')

            else:
                print(f'ERROR : unknown file :{fileName}')

        elif msg.startswith('MERGE'):
            with AnalysisFlow.lock:
                file = AnalysisFlow.mergedFiles.pop(fileName, None)
                remainingFilesToMerge = len(AnalysisFlow.mergedFiles)

            if file:

                for ifile in file['processed']:
                    os.system(f'rm -f {ifile}')
                for ifile in file['failed']:
                    os.system(f'rm -f {ifile}')

                if msg.endswith('SUCCESS'):
                    os.system(f'mv {fileName} {file["outputDir"]}')
                elif msg.endswith('FAIL'):
                    os.system(f'rm -f {fileName}')
                else:
                    print(f'ERROR : message unknown : {msg}')

                print(f'{remainingFilesToMerge} remaining files to merge')

            else:
                print(f'ERROR : unknown file :{fileName}')

        else:
            print(f'ERROR : unknown message : {msg}')

        with AnalysisFlow.lock:
            if not AnalysisFlow.mergedFiles:
                self.event.set()

    def launchAnalysis(self, processID, filesDirectory, outputDirectory, remote=False):

        filesDirectory = f'{filesDirectory}/{processID}'
        fileNameList = []

        if not remote:
            fileNameList = [f'{filesDirectory}/{file}' for file in os.listdir(filesDirectory) if f'{processID}' in file]
        else:
            listFiles = subprocess.Popen(
                    f'gfal-ls {filesDirectory}',
                    stdout = subprocess.PIPE,
                    stderr = subprocess.PIPE,
                    shell = True
            )

            stdout, stderr = listFiles.communicate()

            if stderr:
                print(f'ERROR for {processID} :')
                print(stderr.decode("utf-8"))
                return

            stdout = stdout.decode("utf-8")
            stdout = stdout.split('\n')

            for line in stdout:
                line = line.rstrip()
                if f'{processID}' in line:
                    fileNameList.append(line)

        fileNameList.sort()

        # fileNameList = fileNameList[0:1]

        print(f'{processID} : {len(fileNameList)} files to process')

        if not remote:
            self.addFile(
                    f'{processID}.root',
                    fileNameList,
                    outputDirectory)
        else:
            self.addFile(
                    f'{processID}.root',
                    fileNameList,
                    outputDirectory,
                    filesDirectory)

if __name__ == "__main__":

    # DEFAULT VALUE
    NB_CORES = 64
    INPUT_DIR = "/gridgroup/ilc/nnhAnalysisFiles/AHCAL"

    # PARAMETERS
    parser = argparse.ArgumentParser()

    parser.add_argument(
            '-n', '--ncores',
            help='Number of threads',
            required = False,
            default = NB_CORES)

    parser.add_argument(
            '-p', '--processes',
            help = 'ProcessIDs to analyse',
            required = False,
            nargs = '+')

    parser.add_argument(
            '-i', '--inputDirectory',
            help = 'Path of input files',
            required = False,
            default = INPUT_DIR)

    parser.add_argument(
            '-r', '--remote',
            help = 'indicate that files need to be downloaded',
            action = 'store_true',
            default = False)

    parser.add_argument(
            '-o', '--outputDirectory',
            help = 'output directory',
            required = True)

    args = vars(parser.parse_args())

    # ENVIRONMENT

    ## home variable
    if 'NNH_HOME' not in os.environ:
        print('ERROR : env variable NNH_HOME is not set')
        sys.exit(1)

    ## cores number
    nCores = int(args['ncores'])

    ## num processus list
    if args['processes']:
        processesID = []
        for p in args['processes']:
            processesID.append(p)
    else:
        try:
            ### Get all processus in local server
            processesID = os.listdir(INPUT_DIR)
        except:
            ### num processus list
            processesID = [
                402173, 402182, 402007, 402008, 402176, 402185, 402009, 402010,
                402011, 402012, 402001, 402002, 402013, 402014, 402003, 402004,
                402005, 402006, 500006, 500008, 500010, 500012, 500062, 500064,
                500066, 500068, 500070, 500072, 500074, 500076, 500078, 500080,
                500082, 500084, 500101, 500102, 500103, 500104, 500105, 500106,
                500107, 500108, 500110, 500112, 500086, 500088, 500090, 500092,
                500094, 500096, 500098, 500100, 500113, 500114, 500115, 500116,
                500117, 500118, 500119, 500120, 500122, 500124, 500125, 500126,
                500127, 500128
            ]
    processesID.sort()

    ## input directory
    filesDirectory = args['inputDirectory']
    remote = False
    if args['remote']:
        remote = True

    ## output directory
    outputDirectory = args['outputDirectory']
    if not os.path.exists(outputDirectory) and not os.path.isdir(outputDirectory):
        outputDirectory = join(os.getenv('NNH_HOME'), "/processor/RESULTS")

    os.system(f'mkdir -p {outputDirectory}')
    print("Output Directory:" + outputDirectory)

    ## Download files in distant server
    nDownloadThreads = 0
    if remote:
        nDownloadThreads = 1

    analysis = AnalysisFlow.initAnalysisFlow(nDownloadThreads, nCores, 1)

    # RUN
    for processID in processesID:
        analysis.launchAnalysis(
                processID,
                filesDirectory,
                outputDirectory,
                remote)

    analysis.event.wait()
    AnalysisFlow.close()

    ## output logs
    os.system(f'mv ./logs {outputDirectory}/logs')


# -*- coding: utf-8 -*-
"""
Created on Thu Sep 15 12:49:52 2016

@author: Amine Laghaout
"""

import pandas as pd
import module as md

#%% SETTINGS

trainDir = 'data/'              # Directory
trainFile = 'data1.csv'         # Data file
showSynopsis = True             # Show the data synopsis?
num_train_data = None           # Number of training data (None for all)
contamination = 1/17            # Assumed proportion of outliers (1/17, 1/10)
distance = 'Mahalanobis'        # Distance measure
plotTitle = '['+trainFile+']'   # Identify the data source in the plots.

#%% IMPORT DATA

trainData = pd.read_csv(trainDir+trainFile)[:num_train_data]
featureNames = list(trainData.columns)

#%% FEATURE ENGINEERING

# Extract the feature names
x = featureNames[2]             # variable1
y = featureNames[3]             # variable2
z = featureNames[0]             # equipment
#d = featureNames[1]            # date

# Remove incomplete records.
trainData = trainData.dropna()

# Remove data points at the origin as they are assumed to be irrelevant.
trainData = trainData.loc[abs(trainData[x]) + abs(trainData[y]) != 0]

#%% SYNOPSIS AND EXPLORATION

# Short synopsis of the raw data
md.dataSynopsis(trainData, showSynopsis)

# Reconstruct the Gaussian envelope.
dataFit = md.fit2Gaussian(trainData, x, y)

# Plot the data points and their Gaussian envelope.
md.plotScatter(dataFit, trainData, x, y, z, plotLegend = False,
               plotTitle = 'All data '+plotTitle, markerSize = 10,
               saveAs = 'All_'+plotTitle[1:-5]+'_-_scatter.pdf')

#%% OUTLIER DETECTION

# Measure the distance of each point from the inferred Gaussian envelope and
# update the data frame to include a distance feature.
trainData = md.measureDistance(dataFit, trainData, x, y)

# Sort the data points by their distance form the inferred Gaussian envelope.
md.plotDistance(trainData[distance], contamination, markerSize = 1,
                plotTitle = 'All data '+plotTitle,
                saveAs = 'All_'+plotTitle[1:-5]+'_-_distance.pdf')

# Retain only the data points that fall within the presumed boundary of the
# Gaussian envelope.
trainDataTrimmed = md.removeOutliers(trainData, x, y, contamination)

md.plotScatter(dataFit, trainDataTrimmed, x, y, z, plotLegend = False,
               plotTitle = 'Trimmed data '+plotTitle, markerSize = 10,
               saveAs = 'Trimmed_'+plotTitle[1:-5]+'.pdf')

# Produce a scatter plot of the linear models for each equipment type.
linearFitClusters = md.equipmentClusters(trainData, x, y, z)
md.plotData(linearFitClusters, 'intercept', 'slope', 'equipment',
            plotTitle = 'Linear models by equipment '+plotTitle,
            saveAs = 'Linear_models_by_equipment_'+plotTitle[1:-5]+'.pdf')


import random
from datetime import datetime

from dateutil.relativedelta import relativedelta

from function import global_value as g

def help(command):
    msg = f"```使い方："
    msg += f"\n\t{command} help          このメッセージ"
    msg += f"\n\t{command} results       成績出力"
    msg += f"\n\t{command} record        張り付け用集計済みデータ出力"
    msg += f"\n\t{command} graph         ポイント推移グラフを表示"
    msg += f"\n\t{command} ranking       ランキングを表示"
    msg += f"\n\t{command} member        登録されているメンバー"
    msg += f"\n\t{command} add | del     メンバーの追加/削除"
    msg += f"\n\t{command} load | save   メンバーリストの再読み込み/保存"
    msg += f"```"
    return(msg)

def invalid_argument():
    return(random.choice([
        f"えっ？",
        f"すみません、よくわかりません。",
        f"困らせないでください。",
    ]))

def invalid_score(user_id, score, pointsum):
    return(random.choice([
        f"<@{user_id}> {abs(pointsum - score) * 100}点合わないようです。",
        f"<@{user_id}> {abs(pointsum - score) * 100}点合いませんよ。",
        f"<@{user_id}> {abs(pointsum - score) * 100}点合いません。ご確認を。",
        f"<@{user_id}> {abs(pointsum - score) * 100}点合ってませんね。",
    ]))

def no_hits(starttime, endtime):
    keyword = g.config["search"].get("keyword", False)
    if keyword:
        return(
            "{} ～ {} に{}はありません。".format(
                starttime.strftime('%Y/%m/%d %H:%M'),
                endtime.strftime('%Y/%m/%d %H:%M'),
                keyword,
            )
        )
    else:
        return("見つかりません。")

def remarks(command_option, starttime):
    ret = ""
    remark = []

    if not command_option["playername_replace"]:
        remark.append("名前ブレ修正なし")
    if not command_option["guest_skip"]:
        remark.append("2ゲスト戦の結果を含む")
    if not command_option["unregistered_replace"]:
        remark.append("ゲスト置換なし(※：未登録プレイヤー)")
    if remark:
        ret = f"特記：" + "、".join(remark)

    retention_period = g.config["search"].getint("retention_period", 0)
    if not command_option["archive"] and retention_period != 0:
        limittime = datetime.now() - relativedelta(days = retention_period)

        if  starttime < limittime:
            if ret:
                ret += "\n"
            ret += f"注記：検索開始日がログの保存期間を越えています"

    return(ret)


import _flowunit as modelbox
import numpy as np
import cv2

class CustomResize(modelbox.FlowUnit):
    def __init__(self):
        super().__init__()

    def open(self, config):
        self.max_edge = config.get_int("max_edge", 320)
        return modelbox.Status.StatusCode.STATUS_SUCCESS

    def process(self, data_context):
        in_image_list = data_context.input("in_image")
        out_image_list = data_context.output("out_image")

        for buffer_img in in_image_list:
            width = buffer_img.get("width")
            height = buffer_img.get("height")
            channel = buffer_img.get("channel")

            img_data = np.array(buffer_img.as_object(), dtype=np.uint8)
            img_data = img_data.reshape(height, width, channel)

            im_size_min = np.min([height, width])
            im_size_max = np.max([height, width])
            resize = self.max_edge / float(im_size_min)
            if np.round(resize * im_size_max) > self.max_edge:
                resize = self.max_edge / float(im_size_max)

            resize_img = cv2.resize(img_data, None, None, fx=resize, fy=resize, interpolation=cv2.INTER_LINEAR)
            resize_img_height, resize_img_width, _ = resize_img.shape

            add_buffer = modelbox.Buffer(self.get_bind_device(), resize_img)
            add_buffer.copy_meta(buffer_img)
            add_buffer.set("width", resize_img_width)
            add_buffer.set("height", resize_img_height)
            add_buffer.set("width_stride", resize_img_width)
            add_buffer.set("height_stride", resize_img_height)
            out_image_list.push_back(add_buffer)

        return modelbox.Status.StatusCode.STATUS_SUCCESS

    def close(self):
        return modelbox.Status()

    def data_pre(self, data_context):
        return modelbox.Status()

    def data_post(self, data_context):
        return modelbox.Status()


import random
from graph.node import Node

class InputReader:
    def __init__(self):
        self.id_to_note_mapping = {}
        self.edge_ids = []
        self.color_set = None

    def read_input_graph_and_color_set(self, file_name):
        self.reset_input()

        with open(file_name, 'r') as graph_file:
            for line in graph_file:
                if self.color_set is None:
                    color_classes_amount = int(line.strip())

                    if color_classes_amount <= 0:
                        raise Exception('Invalid input format!')

                    self.color_set = range(0, color_classes_amount)
                    continue

                edge = line.strip().split(',')

                if len(edge) != 2:
                    raise Exception('Invalid input format!')

                if edge[0] not in self.id_to_note_mapping:
                    self.id_to_note_mapping[edge[0]] = Node(color=random.choice(self.color_set), node_id=edge[0])

                if edge[1] not in self.id_to_note_mapping:
                    self.id_to_note_mapping[edge[1]] = Node(color=random.choice(self.color_set), node_id=edge[1])

                self.edge_ids.append(edge)

        if len(self.id_to_note_mapping) == 0:
            raise Exception('Invalid input format!')

        for edge in self.edge_ids:
            self.id_to_note_mapping[edge[0]].add_edges([self.id_to_note_mapping[edge[1]]])

        return self.id_to_note_mapping.items()[0][1], self.color_set

    def reset_input(self):
        self.id_to_note_mapping = {}
        self.edge_ids = []

GIS/Project/Implementation/Tabu Search For Graph Coloring/input/input_reader.py


import sys
import re
import os

from arjuna.tpi.constant import *
from arjuna.core.constant import *

def ustr(input):
    return (str(input)).upper()

VNREGEX = r'^[a-z][_a-z0-9]{2,49}$'
VNREGEX_TEXT = '''
{} name must be a string of length 3-50 containing lower case letters, digits or _ (underscore).
It must begin with a letter.
'''

import os

def new_project_dir(input):
    input = input.rstrip("/").rstrip("\\")
    proj_name = os.path.basename(input)
    lname_check("Project", proj_name)
    if os.path.exists(input):
        print('Project path already exists: {}'.format(input))
        return input
    else:
        os.makedirs(input)
        return input

def absolute_file_path(input):
    if not os.path.isabs(input) or not os.path.isfile(input):
        print('Config file path does not exist or is not a file: {}'.format(input))
        print('Exiting...', file=sys.stderr)
        sys.exit(1)
    return input

def project_dir(input):
    if not os.path.exists(input):
        print('Project path does not exist: {}'.format(input))
        print('Current Working Directory: {}'.format(os.getcwd()))
        print('Path should be a correct absolute path or a correct path relative to current working directory.')
        print('Exiting...', file=sys.stderr)
        sys.exit(1)
    elif not os.path.isdir(input):
        print('Project path is not a directory: {}'.format(input))
        print('Exiting...', file=sys.stderr)
        sys.exit(1)
    else:
        if input == ".":
            proj_name = os.path.basename(os.getcwd())
        else:
            proj_name = os.path.basename(input)
        lname_check("Project", proj_name)
        return input

def lname_check(context, input):
    if not re.match(VNREGEX, input):
        print('Invalid {} name {} provided.'.format(context, input), file=sys.stderr)
        print(VNREGEX_TEXT.format(context), file=sys.stderr)
        print('Exiting...', file=sys.stderr)
        sys.exit(1)
    return input

# Argprse sends one argument at a time
def report_format(input):
    try:
        return ReportFormat[input.upper()].name
    except Exception as e:
        print(e)
        print('Invalid Report Format provided: {}. Allowed values: {}'.format(input, ", ".join([e.name for e in ReportFormat])))
        print('Exiting...', file=sys.stderr)
        sys.exit(1)

def dry_run_type(input):
    try:
        return DryRunType[input.upper()].name
    except Exception as e:
        allowed = ", ".join([e.name for e in DryRunType])
        print('Invalid Dry Run Type provided: {}. Allowed values: {}'.format(input, allowed))
        print('Exiting...', file=sys.stderr)
        sys.exit(1)

def int_or_notset(input):
    try:
        return int(input)
    except:
        if input.lower() == "not_set":
            return "not_set"
        else:
            print(e)
            print('Invalid input argument: {}. Expected an int.'.format(input))
            print('Exiting...', file=sys.stderr)
            sys.exit(1)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pylint: disable=E0401, W0614, W0401
"""Main sanic landing page"""
from users import BLUEPRINT as Users
from util import format_html

from sanic import Sanic, response
from sanic.exceptions import NotFound
from mongoengine import connect
import session

# Setup Sanic webserver
WEBSERVER = Sanic(name='login', load_env='CB_')

@WEBSERVER.listener('before_server_start')
async def init_webserver(*_):
    """Initialisatie webserver and sessions"""
    connect(host=WEBSERVER.config.MONGO_URI)
    session.SessionHandler(WEBSERVER)

@WEBSERVER.exception(NotFound)
async def error_404(request, _):
    """Process not found exceptions"""
    return await format_html('error.html', error_code=404, request=request)

@WEBSERVER.get('/')
async def index(_):
    """There is no index page so we redirect the users to the user page"""
    return response.redirect('/users/')

if __name__ == "__main__":
    WEBSERVER.static('/static', './static')
    WEBSERVER.blueprint(Users)
    WEBSERVER.run(
        host="0.0.0.0", port=8000, debug=True, access_log=False
    )


import torch
import os
import time

class bcolors:
    WARNING = '\033[93m'
    ENDC = '\033[0m'

#Check if MKL is enabled
print(bcolors.WARNING + "MKL Enabled : ", torch.backends.mkl.is_available(), bcolors.ENDC)

#Sets the number of threads used for intraop parallelism on CPU.
torch.set_num_threads(1)

#Problem size
n = 3000
reps = 10
DTYPE = torch.float32

@torch.jit.script
def naive(A,B,V,ret):
    for i in range(3):
        ret = A@B + torch.tensordot(V[i],torch.t(V[i]),dims=0)
    return ret

@torch.jit.script
def recommended(A,B,V,ret):
    tmp = A@B
    ret = torch.empty_like(A)
    for i in range(3):
        ret = tmp + torch.tensordot(V[i],torch.t(V[i]),dims=0)
    return ret

A = torch.randn([n, n], dtype=DTYPE)
B = torch.randn([n, n], dtype=DTYPE)
V = torch.randn([3, n], dtype=DTYPE)
ret = torch.randn([n, n], dtype=DTYPE)

for i in range(reps):
   start = time.perf_counter()
   ret1 = naive(A,B,V,ret)
   end = time.perf_counter()
   print("Naive : ", end-start)

   start = time.perf_counter()
   ret1 = recommended(A,B,V,ret)
   end = time.perf_counter()
   print("Recommended : ", end-start)

   print("\n")


def bellman(graph, start, end):
    distances = {}
    for vertex in graph:
        distances[vertex] = float('inf')
    distances[start] = 0

    for _ in range(len(graph)-1):
        for vertex in graph:
            for neighbor in graph[vertex]:
                distances[neighbor] = min(distances[neighbor],
                                          distances[vertex] + graph[vertex][neighbor])
    for vertex in graph:
        for neighbor in graph[vertex]:
            if distances[vertex] + graph[vertex][neighbor] < distances[neighbor]:
                distances[neighbor] = float('-inf')
    return distances[end]

graph = {
    's': {'a': 2, 'b': 1},
    'a': {'s': 3, 'b': 4, 'c': 8},
    'b': {'s': 4, 'a': 2, 'd': 2},
    'c': {'a': 2, 'd': 7, 't': 4},
    'd': {'b': 1, 'c': 11, 't': 5},
    't': {'c': 3, 'd': 5},
}

print(bellman(graph, 's', 't'))


from pkg_resources import yield_lines
import click
import sys; sys.path.extend(['.', 'src'])
from tqdm import tqdm

import argparse
import numpy as np
from sklearn.metrics.pairwise import polynomial_kernel
import torch
from util import load_data_for_worker

from fvd.fvd import get_fvd_logits, frechet_distance
from fvd.download import load_i3d_pretrained

def polynomial_mmd(X, Y):
    m = X.shape[0]
    n = Y.shape[0]

    # compute kernels
    K_XX = polynomial_kernel(X)
    K_YY = polynomial_kernel(Y)
    K_XY = polynomial_kernel(X, Y)

    # compute mmd distance
    K_XX_sum = (K_XX.sum() - np.diagonal(K_XX).sum()) / (m * (m - 1))
    K_YY_sum = (K_YY.sum() - np.diagonal(K_YY).sum()) / (n * (n - 1))
    K_XY_sum = K_XY.sum() / (m * n)

    mmd = K_XX_sum + K_YY_sum - 2 * K_XY_sum

    return mmd

def main(
    ):
    parser = argparse.ArgumentParser()
    parser.add_argument("--ref_batch", type=str, default="/data6/rld/data/UCF-101/train/ApplyEyeMakeup", help="path to reference batch npz file")
    parser.add_argument("--sample_batch", type=str, default="/data6/rld/data/UCF-101/train/ApplyEyeMakeup", help="path to sample batch npz file")
    parser.add_argument("--size", type=int, default=128, help="path to sample batch npz file")
    parser.add_argument("--frame_num",type=int, default=16, help="path to sample batch npz file")
    parser.add_argument("--sample_frame_gap",type=int, default=8, help="path to sample batch npz file")
    parser.add_argument("--sample_num", type=int, default=100)
    parser.add_argument("--batch_size", type=int, default=16)
    args = parser.parse_args()
    device = torch.device('cuda')
    #################### Load I3D ########################################
    i3d = load_i3d_pretrained(device)
    #################### Compute FVD ###############################

    fvds = []
    kvds = []
    fvd, kvd = eval_fvd(i3d, args, device)
    fvds.append(fvd)
    kvds.append(kvd)

    fvd_mean = np.mean(fvds)
    kvd_mean = np.mean(kvds)
    fvd_std = np.std(fvds)
    kvd_std = np.std(kvds)

    print(f"Final FVD {fvd_mean:.2f} +/- {fvd_std:.2f}")
    print(f"Final KVD {kvd_mean:.2f} +/- {kvd_std:.2f}")

def eval_fvd(i3d, args, device):
    sample_loader = load_data_for_worker(base_samples=args.sample_batch, image_size=args.size, \
        batch_size=args.batch_size, frame_num=args.frame_num)
    ref_loader = load_data_for_worker(base_samples=args.ref_batch, image_size=args.size, \
        batch_size = args.batch_size, frame_num=args.frame_num, frame_gap = args.sample_frame_gap)

    print("get real embeddings...")
    real_embeddings = []
    for id,ref in enumerate(ref_loader):
        if id >=args.sample_num:break
        real = ref # BCTHW -> BTHWC
        import pdb; pdb.set_trace()
        real_embeddings.append(get_fvd_logits(real, i3d=i3d, device=device))
    real_embeddings = torch.cat(real_embeddings)

    print("get fake embeddings...")
    fake_embeddings= []
    for id, sample in enumerate(tqdm(sample_loader)):
        if id >=args.sample_num:break

        # b t h w c
        fake_embeddings.append(get_fvd_logits(sample, i3d=i3d, device=device))
    fake_embeddings = torch.cat(fake_embeddings)

    fvd = frechet_distance(fake_embeddings.clone().detach(), real_embeddings.clone().detach())
    kvd = polynomial_mmd(fake_embeddings.clone().detach().cpu().numpy(), real_embeddings.detach().cpu().numpy())
    return fvd.item(), kvd.item()

if __name__ == '__main__':
    main()


from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, f1_score
from sklearn.ensemble import RandomForestClassifier
import numpy as np
from sklearn import svm

from matplotlib import pyplot as plt

import vectorize

def train_models(tfidf, list_personality):
    type_indicators = ["IE: Introversion (I) - Extroversion (E)", "NS: Intuition (N) – Sensing (S)",
                       "FT: Feeling (F) - Thinking (T)", "JP: Judging (J) – Perceiving (P)"]

    X = tfidf
    # kernals = ["poly", "rbf", "sigmoid", "linear"]
    xlabels = ["IE", "NS", "FT", "JP"]
    kernals = ["linear"]
    res = []
    for kernal in kernals:
        tmp = []
        for l in range(len(type_indicators)):
            print("%s ..." % (type_indicators[l]))

            Y = list_personality[:, l]

            seed = 3
            test_size = 0.33
            X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed)

            # define SVM model
            model = svm.SVC(decision_function_shape='ovr', kernel=kernal)
            # train the model
            model = model.fit(X_train, y_train)

            # 在测试集上进行预测
            y_pred = model.predict(X_test)
            predictions = [round(value) for value in y_pred]
            # evaluate predictions
            accuracy = accuracy_score(y_test, predictions)
            print("- %s Accuracy: %.2f%%" % (type_indicators[l], accuracy * 100.0))
        res.append(tmp)

def draw_res(kernals, res, n, xlabels):
    x = np.arange(n)
    width = 0.1

    plt.figure(figsize=(10, 6))
    for i, kernal in enumerate(kernals):
        plt.bar(x + i * width, res[i], width=width, label=kernal)
        plt.legend()

    plt.xticks(x + width*1.5, xlabels)
    plt.show()

def main():
    tfidf, list_personality = vectorize.tfidf()
    train_models(tfidf, list_personality)

if __name__ == "__main__":
    main()


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import collections
import os
import zipfile
from tensor2tensor.data_generators import generator_utils
from tensor2tensor.data_generators import problem
from tensor2tensor.data_generators import text_encoder
from tensor2tensor.data_generators import text_problems
from tensor2tensor.utils import registry

import tensorflow.compat.v1 as tf

def _build_vocab(filename, vocab_dir, vocab_name):
  """Reads a file to build a vocabulary.

  Args:
    filename: file to read list of words from.
    vocab_dir: directory where to save the vocabulary.
    vocab_name: vocab file name.

  Returns:
    text encoder.
  """
  vocab_path = os.path.join(vocab_dir, vocab_name)
  if not tf.gfile.Exists(vocab_path):
    with tf.gfile.GFile(filename, "r") as f:
      data = f.read().split()
    counter = collections.Counter(data)
    count_pairs = sorted(counter.items(), key=lambda x: (-x[1], x[0]))
    words, _ = list(zip(*count_pairs))
    encoder = text_encoder.TokenTextEncoder(None, vocab_list=words)
    encoder.store_to_file(vocab_path)
  else:
    encoder = text_encoder.TokenTextEncoder(vocab_path)
  return encoder

def _maybe_download_corpus(tmp_dir, vocab_type):
  """Download and unpack the corpus.

  Args:
    tmp_dir: directory containing dataset.
    vocab_type: which vocabulary are we using.

  Returns:
    The list of names of files.
  """
  if vocab_type == text_problems.VocabType.CHARACTER:

    dataset_url = ("https://s3.amazonaws.com/research.metamind.io/wikitext"
                   "/wikitext-103-raw-v1.zip")
    dir_name = "wikitext-103-raw"
  else:
    dataset_url = ("https://s3.amazonaws.com/research.metamind.io/wikitext"
                   "/wikitext-103-v1.zip")
    dir_name = "wikitext-103"

  fname = os.path.basename(dataset_url)
  compressed_filepath = generator_utils.maybe_download(tmp_dir, fname,
                                                       dataset_url)
  zip_ref = zipfile.ZipFile(compressed_filepath, "r")
  zip_ref.extractall(tmp_dir)
  zip_ref.close()

  files = os.path.join(tmp_dir, dir_name, "*")
  train_file, valid_file, test_file = None, None, None
  for f in tf.gfile.Glob(files):
    fname = os.path.basename(f)
    if "train" in fname:
      train_file = f
    elif "valid" in fname:
      valid_file = f
    elif "test" in fname:
      test_file = f

  assert train_file, "Training file not found"
  assert valid_file, "Validation file not found"
  assert test_file, "Testing file not found"

  return train_file, valid_file, test_file

@registry.register_problem
class LanguagemodelWikitext103(text_problems.Text2SelfProblem):
  """Wikitext103 dataset token-level."""

  @property
  def dataset_splits(self):
    return [{
        "split": problem.DatasetSplit.TRAIN,
        "shards": 10,
    }, {
        "split": problem.DatasetSplit.EVAL,
        "shards": 1,
    }, {
        "split": problem.DatasetSplit.TEST,
        "shards": 1,
    }]

  @property
  def is_generate_per_split(self):
    return True

  @property
  def vocab_type(self):
    return text_problems.VocabType.TOKEN

  def generate_samples(self, data_dir, tmp_dir, dataset_split):
    train_file, valid_file, test_file = _maybe_download_corpus(
        tmp_dir, self.vocab_type)

    if dataset_split == problem.DatasetSplit.TRAIN:
      filepath = train_file
      if self.vocab_type == text_problems.VocabType.TOKEN:
        _build_vocab(train_file, data_dir, self.vocab_filename)

    elif dataset_split == problem.DatasetSplit.EVAL:
      filepath = valid_file

    elif dataset_split == problem.DatasetSplit.TEST:
      filepath = test_file

    def _generate_samples():
      with tf.gfile.GFile(filepath, "r") as f:
        for line in f:
          line = " ".join(line.strip().split())
          if line:
            yield {"targets": line}

    return _generate_samples()

@registry.register_problem
class LanguagemodelWikitext103Characters(LanguagemodelWikitext103):
  """Wikitext-103, character-level."""

  @property
  def vocab_type(self):
    return text_problems.VocabType.CHARACTER

@registry.register_problem
class LanguagemodelWikitext103L4k(LanguagemodelWikitext103):
  """Wikitext-103, token-level, with examples up to 4,096 tokens long."""

  def generate_samples(self, data_dir, tmp_dir, dataset_split):
    samples_by_line = super(LanguagemodelWikitext103L4k,
                            self).generate_samples(data_dir, tmp_dir,
                                                   dataset_split)

    def _generate_samples():
      tokens = []
      for sample in samples_by_line:
        sample_tokens = sample["targets"].split()
        if len(tokens) + len(sample_tokens) < self.sequence_length:
          tokens.extend(sample_tokens)
        else:
          yield {"targets": " ".join(tokens)}
          tokens = sample_tokens

    return _generate_samples()

  def max_length(self, model_hparams):
    return model_hparams.split_to_length or self.sequence_length

  @property
  def sequence_length(self):
    """Length of each example (in tokens)."""
    return 4096

@registry.register_problem
class LanguagemodelWikitext103L16k(LanguagemodelWikitext103L4k):
  """Wikitext-103, token-level, with examples up to 16,384 tokens long."""

  @property
  def sequence_length(self):
    """Length of each example (in tokens)."""
    return 16384


#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#Description: CNN visual #visual heat map
#Date: 2020/12/22
#Author: Steven Huang, Auckland, NZ
#Reference: https://keras.io/examples/vision/grad_cam/

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

import numpy as np
import tensorflow as tf
from tensorflow.keras import models
from tensorflow import keras
from mnistCnn import prepareMnistData
from cnnTf2Kernel import createModel
import matplotlib.pyplot as plt
import matplotlib.cm as cm

def make_gradcam_heatmap(img_array, model, last_conv_layer_name, classifier_layer_names):
    # First, we create a model that maps the input image to the activations
    # of the last conv layer
    last_conv_layer = model.get_layer(last_conv_layer_name)
    last_conv_layer_model = keras.Model(model.inputs, last_conv_layer.output)

    # Second, we create a model that maps the activations of the last conv
    # layer to the final class predictions
    classifier_input = keras.Input(shape=last_conv_layer.output.shape[1:])
    x = classifier_input
    for layer_name in classifier_layer_names:
        x = model.get_layer(layer_name)(x)
    classifier_model = keras.Model(classifier_input, x)

    # Then, we compute the gradient of the top predicted class for our input image
    # with respect to the activations of the last conv layer
    with tf.GradientTape() as tape:
        # Compute activations of the last conv layer and make the tape watch it
        last_conv_layer_output = last_conv_layer_model(img_array)
        tape.watch(last_conv_layer_output)
        # Compute class predictions
        preds = classifier_model(last_conv_layer_output)
        top_pred_index = tf.argmax(preds[0])
        top_class_channel = preds[:, top_pred_index]

    # This is the gradient of the top predicted class with regard to
    # the output feature map of the last conv layer
    grads = tape.gradient(top_class_channel, last_conv_layer_output)

    # This is a vector where each entry is the mean intensity of the gradient
    # over a specific feature map channel
    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))

    # We multiply each channel in the feature map array
    # by "how important this channel is" with regard to the top predicted class
    last_conv_layer_output = last_conv_layer_output.numpy()[0]
    pooled_grads = pooled_grads.numpy()
    for i in range(pooled_grads.shape[-1]):
        last_conv_layer_output[:, :, i] *= pooled_grads[i]

    # The channel-wise mean of the resulting feature map
    # is our heatmap of class activation
    heatmap = np.mean(last_conv_layer_output, axis=-1)

    # For visualization purpose, we will also normalize the heatmap between 0 & 1
    heatmap = np.maximum(heatmap, 0) / np.max(heatmap)
    return heatmap

def getCombineImg(img, heatmap, saveFile=None):
      # We rescale heatmap to a range 0-255
    heatmap = np.uint8(255 * heatmap)

    # We use jet colormap to colorize heatmap
    jet = cm.get_cmap("jet")

    # We use RGB values of the colormap
    jet_colors = jet(np.arange(256))[:, :3]
    jet_heatmap = jet_colors[heatmap]

    # We create an image with RGB colorized heatmap
    jet_heatmap = keras.preprocessing.image.array_to_img(jet_heatmap)
    jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))
    jet_heatmap = keras.preprocessing.image.img_to_array(jet_heatmap)

    # Superimpose the heatmap on original image
    superimposed_img = jet_heatmap * 0.4 + img
    superimposed_img = keras.preprocessing.image.array_to_img(superimposed_img)

    # Save the superimposed image
    if saveFile:
        superimposed_img.save(saveFile)

def getHeatMap(model, img):
    """Visualizing class activation map(CAM)"""
    img = img.reshape(-1, 28, 28, 1)
    preds = model.predict(img)
    label = np.argmax(preds[0])
    print('label,preds, preds.shape:', label, preds, preds.shape)

    last_conv_layer_name = "conv2d_1"
    classifier_layer_names = [
        "max_pooling2d_1",
        "flatten",
        "dense"
    ]
    # Generate class activation heatmap
    heatmap = make_gradcam_heatmap(img, model, last_conv_layer_name, classifier_layer_names)
    return heatmap

def main():
    x_train, y_train, x_test, y_test, input_shape = prepareMnistData(0.1) #input_shape 28*28*1
    model = createModel(input_shape, classes=10)

    file = r'./weights/cnnTf2Kernel_2.h5' #r'./weights/cnnTf2Kernel.h5'
    model.load_weights(file)

    test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
    print('\nTest accuracy:', test_acc,'loss=',test_loss)

    testImg = x_test[6]
    testLabel = y_test[6]
    print('testLabel:', testLabel)

    heatmap = getHeatMap(model, testImg)
    # Display heatmap
    plt.matshow(heatmap)
    plt.show()

    getCombineImg(testImg,heatmap, r'./res/7.png')

if __name__=="__main__":
    main()