detect_byte_width()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 15
rs 9.4285
1
"""
2
A command-line tool for spliting POV-Ray density file (DF3)
3
to a series of separate images.
4
5
"""
6
7
from __future__ import print_function
8
import sys
9
import os
10
import argparse
11
import struct
12
13
from PIL import Image
14
15
from df3tools.exceptions import Df3Exception
16
17
18
def from_big_endian(bytestring):
19
    """ Convert big-endian bytestring to int """
20
    bytestring = bytestring.rjust(4, b'\x00')
21
    return struct.unpack(">L", bytestring)[0]
22
23
24
def split_by_n(seq, chunk_size):
25
    """ Generator splitting a sequence into chunks """
26
    while seq:
27
        yield seq[:chunk_size]
28
        seq = seq[chunk_size:]
29
30
31
def detect_size(df3_file, silent):
32
    """
33
    Detect image size and number of layers.
34
35
    :param df3_file: DF3 file descriptor.
36
    :param silent: suppress output (info messages, progress etc.)
37
38
    :returns: Tuple with sizes.
39
40
    """
41
    header = df3_file.read(6)
42
    sizes = [from_big_endian(v) for v in split_by_n(header, 2)]
43
    width, height, num_layers = sizes
44
    if not silent:
45
        print("Size: %dx%d, %d layers" % (width, height, num_layers))
46
47
    return (width, height, num_layers)
48
49
50
def detect_byte_width(data, num_voxels, silent):
51
    """
52
    Detect byte width.
53
54
    :param data: Byte string with DF3 body.
55
    :param num_voxels: Number of voxels in file.
56
    :param silent: suppress output (info messages, progress etc.)
57
58
    """
59
    byte_width = int(float(len(data)) / num_voxels)
60
    if not silent:
61
        plural = ' s'[byte_width > 1]
62
        print("Voxel resolution: %d byte%s" % (byte_width, plural))
63
64
    return byte_width
65
66
67
def df3split(filename, prefix="layer", img_format='tga', silent=True):
68
    """
69
    Split POV-Ray density file (DF3) to a series of separate images.
70
71
    :param filename: path to DF3 file to process
72
    :param prefix: output files prefix
73
    :param img_format: output files format (tga, png, etc.)
74
    :param silent: suppress output (info messages, progress etc.)
75
76
    """
77
    if not os.path.isfile(filename):
78
        raise Df3Exception("File not found: " + filename)
79
80
    with open(filename, "rb") as df3_file:
81
        width, height, num_layers = detect_size(df3_file, silent)
82
83
        data = df3_file.read()
84
        detect_byte_width(data, width * height * num_layers, silent)
85
86
        # parse data and save images
87
        for img_num, img_data in enumerate(split_by_n(data, width * height)):
88
            layer_num = str(img_num).zfill(len(str(num_layers)))
89
            img = Image.new("L", (width, height))
90
            img.putdata(img_data)
91
            img.save("%s%s.%s" % (prefix, layer_num, img_format.lower()))
92
            percentage = float(img_num + 1) / num_layers * 100
93
            if not silent:
94
                sys.stdout.write("Processing data [%.2f%%]\r" % percentage)
95
                sys.stdout.flush()
96
97
        if not silent:
98
            print("\nDone.")
99
100
101
def main():
102
    """ Main script execution """
103
    parser = argparse.ArgumentParser(description="""
104
    Split POV-Ray density file (DF3) to a series of separate images
105
    """)
106
    parser.add_argument("df3file", help="DF3 filename, including path")
107
    parser.add_argument("-t", "--format", type=str,
108
                        choices=["tga", "png"],
109
                        default="tga",
110
                        help="Output files format")
111
    parser.add_argument("-p", "--prefix", type=str,
112
                        default="layer",
113
                        help="Output files prefix")
114
    parser.add_argument("-s", "--silent", help="Suppress output",
115
                        default=False, action="store_true")
116
117
    args = parser.parse_args()
118
119
    df3split(args.df3file, args.prefix, args.format, args.silent)
120