Test Failed
Push — master ( 3eb507...37f87e )
by Andrey
03:40
created

detect_byte_width()   A

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